Skip to content

Commit

Permalink
#6 - refactor serviceinvoker, split message parsing into a new class
Browse files Browse the repository at this point in the history
  • Loading branch information
rpierry committed Jun 23, 2015
1 parent 7566025 commit 259d5ae
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 175 deletions.
2 changes: 2 additions & 0 deletions CenturyLinkCloudSDK/CenturyLinkCloudSDK.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
<Compile Include="Client.cs" />
<Compile Include="Configuration.cs" />
<Compile Include="Runtime\Constants.cs" />
<Compile Include="Runtime\DefaultHttpMessageFormatter.cs" />
<Compile Include="Runtime\DefaultServiceResolver.cs" />
<Compile Include="Runtime\IHttpMessageFormatter.cs" />
<Compile Include="Runtime\IServiceInvoker.cs" />
<Compile Include="Runtime\IServiceResolver.cs" />
<Compile Include="Runtime\ServiceBase.cs" />
Expand Down
3 changes: 3 additions & 0 deletions CenturyLinkCloudSDK/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@ public static IServiceInvoker ServiceInvoker

private static IServiceResolver serviceResolver = new DefaultServiceResolver();
internal static IServiceResolver ServiceResolver { get { return serviceResolver; } }

private static IHttpMessageFormatter httpMessageFormatter = new DefaultHttpMessageFormatter();
internal static IHttpMessageFormatter HttpMessageFormatter { get { return httpMessageFormatter; } }
}
}
70 changes: 70 additions & 0 deletions CenturyLinkCloudSDK/Runtime/DefaultHttpMessageFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace CenturyLinkCloudSDK.Runtime
{
internal class DefaultHttpMessageFormatter : IHttpMessageFormatter
{
public HttpRequestMessage CreateHttpRequestMessage(HttpMethod httpMethod, string serviceUri, ServiceModels.Authentication authentication, object content)
{
var httpRequestMessage = new HttpRequestMessage(httpMethod, serviceUri);
httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants.ServiceUris.JsonMediaType));

if (authentication != null)
{
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authentication.BearerToken);
}

if (content != null)
{
var serializedContent = JsonConvert.SerializeObject(content);
httpRequestMessage.Content = new StringContent(serializedContent, new UTF8Encoding(), Constants.ServiceUris.JsonMediaType);
}

return httpRequestMessage;
}

public Task<TResponse> DeserializeResponse<TResponse>(HttpResponseMessage response)
{
return response.Content.ReadAsAsync<TResponse>();
}

public async Task<string> DeserializeErrorResponse(HttpResponseMessage response)
{
if (response.IsSuccessStatusCode) return string.Empty;

var apiError = default(ServiceModels.ApiError);
try
{
apiError = await response.Content.ReadAsAsync<ServiceModels.ApiError>().ConfigureAwait(false);
}catch
{
//ignore - it'd be nice if we had a TryReadAsAsync
}

if (apiError == default(ServiceModels.ApiError)) return string.Empty;

var errorMessages = new StringBuilder();
if (!string.IsNullOrEmpty(apiError.Message))
{
errorMessages.AppendLine(apiError.Message);
}

if(apiError.ModelState != null)
{
foreach(var error in apiError.ModelState.SelectMany(prop => prop.Value))
{
errorMessages.AppendLine(error);
}
}

return errorMessages.ToString();
}
}
}
149 changes: 43 additions & 106 deletions CenturyLinkCloudSDK/Runtime/DefaultServiceInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,30 @@ namespace CenturyLinkCloudSDK.Runtime
{
internal class DefaultServiceInvoker : IServiceInvoker
{
CenturyLinkCloudServiceException CreateDetailedException(HttpRequestMessage request, HttpResponseMessage response, Exception innerOrNull)
{
var message = Constants.ExceptionMessages.DefaultServiceExceptionMessage;
var ex =
innerOrNull == null ?
new CenturyLinkCloudServiceException(message) :
new CenturyLinkCloudServiceException(message, innerOrNull);

ex.HttpRequestMessage = request;
ex.HttpResponseMessage = response;

if (request.Content != null)
{
ex.RequestContent = request.Content.ReadAsStringAsync().Result;
}

if (response.Content != null)
{
ex.ResponseContent = response.Content.ReadAsStringAsync().Result;
}

return ex;
}

/// <summary>
/// This is the main method through which all api requests are made.
/// </summary>
Expand All @@ -26,131 +50,44 @@ internal class DefaultServiceInvoker : IServiceInvoker
public async Task<TResponse> Invoke<TResponse>(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
where TResponse : class
{
var messageFormatter = Configuration.HttpMessageFormatter;

using (HttpClient httpClient = new HttpClient(new NativeMessageHandler()))
{
httpClient.BaseAddress = new Uri(Configuration.BaseUri);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants.ServiceUris.JsonMediaType));


HttpResponseMessage httpResponseMessage = null;

try
{
httpResponseMessage = await httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
var response = await DeserializeResponse<TResponse>(httpResponseMessage).ConfigureAwait(false);

return response;
}
catch (CenturyLinkCloudServiceException ex)
{
ex.HttpRequestMessage = httpRequestMessage;
ex.HttpResponseMessage = httpResponseMessage;

if (httpRequestMessage.Content != null)
if (httpResponseMessage.IsSuccessStatusCode)
{
ex.RequestContent = httpRequestMessage.Content.ReadAsStringAsync().Result;
}

if (httpResponseMessage.Content != null)
{
ex.ResponseContent = httpResponseMessage.Content.ReadAsStringAsync().Result;
}

throw ex;
}
catch (Exception ex)
return await messageFormatter.DeserializeResponse<TResponse>(httpResponseMessage).ConfigureAwait(false);
}
}catch(Exception ex)
{
var serviceException = new CenturyLinkCloudServiceException(Constants.ExceptionMessages.DefaultServiceExceptionMessage, ex);
serviceException.HttpRequestMessage = httpRequestMessage;
serviceException.HttpResponseMessage = httpResponseMessage;

if (httpRequestMessage.Content != null)
{
serviceException.RequestContent = httpRequestMessage.Content.ReadAsStringAsync().Result;
}

if (httpResponseMessage.Content != null)
{
serviceException.ResponseContent = httpResponseMessage.Content.ReadAsStringAsync().Result;
}

throw serviceException;
//sending failed or parsing the response did
throw CreateDetailedException(httpRequestMessage, httpResponseMessage, ex);
}
}
}


private static async Task<TResponse> DeserializeResponse<TResponse>(HttpResponseMessage httpResponseMessage)
{
var apiMessage = new StringBuilder();

if (httpResponseMessage.IsSuccessStatusCode)
{
return await httpResponseMessage.Content.ReadAsAsync<TResponse>().ConfigureAwait(false);
}

if (!httpResponseMessage.IsSuccessStatusCode)
{
//request failed

//This logic is necessary to prevent exceptions from being thrown when a resource is not found due to bad data in the system.
if(httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NotFound)
if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return default(TResponse);
}

var serviceException = new CenturyLinkCloudServiceException(Constants.ExceptionMessages.DefaultServiceExceptionMessage);
var apiError = await DeserializeApiErrorMessage(httpResponseMessage).ConfigureAwait(false);

if(apiError != null)
{
if(!string.IsNullOrEmpty(apiError.Message))
{
apiMessage.Append(apiError.Message);
}

if(apiError.ModelState != null)
{
if(apiError.ModelState.Count > 0)
{
foreach(var modelProperty in apiError.ModelState)
{
foreach(var errorMessage in modelProperty.Value)
{
apiMessage.AppendLine(errorMessage);
}
}
}
}

}

if (apiMessage.Length == 0)
{
apiMessage.Append(Constants.ExceptionMessages.DefaultServiceExceptionMessage);
}

serviceException.ApiMessage = apiMessage.ToString();

throw serviceException;
var detailedEx = CreateDetailedException(httpRequestMessage, httpResponseMessage, null);
var errorMessageOrEmpty = await Configuration.HttpMessageFormatter.DeserializeErrorResponse(httpResponseMessage).ConfigureAwait(false);
detailedEx.ApiMessage =
string.IsNullOrEmpty(errorMessageOrEmpty) ?
Constants.ExceptionMessages.DefaultServiceExceptionMessage :
errorMessageOrEmpty;
throw detailedEx;
}

return default(TResponse);
}

private static async Task<ApiError> DeserializeApiErrorMessage(HttpResponseMessage httpResponseMessage)
{
if (!httpResponseMessage.IsSuccessStatusCode)
{
var content = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
JObject jsonObject = content.TryParseJson();

if (jsonObject != null)
{
return await httpResponseMessage.Content.ReadAsAsync<ApiError>().ConfigureAwait(false);
}
}

return null;
}
}
}
}
17 changes: 17 additions & 0 deletions CenturyLinkCloudSDK/Runtime/IHttpMessageFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using CenturyLinkCloudSDK.ServiceModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace CenturyLinkCloudSDK.Runtime
{
public interface IHttpMessageFormatter
{
HttpRequestMessage CreateHttpRequestMessage(HttpMethod httpMethod, string serviceUri, Authentication authentication, object content);
Task<TResponse> DeserializeResponse<TResponse>(HttpResponseMessage response);
Task<string> DeserializeErrorResponse(HttpResponseMessage response);
}
}
34 changes: 7 additions & 27 deletions CenturyLinkCloudSDK/Runtime/ServiceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ protected ServiceBase(Authentication authentication, IServiceInvoker serviceInvo
/// <param name="httpMethod"></param>
/// <param name="serviceUri"></param>
/// <returns></returns>
protected HttpRequestMessage CreateHttpRequestMessage(HttpMethod httpMethod, string serviceUri)
protected HttpRequestMessage CreateAuthorizedHttpRequestMessage(HttpMethod httpMethod, string serviceUri)
{
return CreateHttpRequestMessage<Object>(httpMethod, serviceUri, null);
return CreateAuthorizedHttpRequestMessage(httpMethod, serviceUri, null);
}

/// <summary>
Expand All @@ -38,32 +38,12 @@ protected HttpRequestMessage CreateHttpRequestMessage(HttpMethod httpMethod, str
/// <param name="serviceUri"></param>
/// <param name="content"></param>
/// <returns></returns>
protected HttpRequestMessage CreateHttpRequestMessage<TContent>(HttpMethod httpMethod, string serviceUri, TContent content)
protected HttpRequestMessage CreateAuthorizedHttpRequestMessage(HttpMethod httpMethod, string serviceUri, object content)
{
var httpRequestMessage = new HttpRequestMessage(httpMethod, serviceUri);
httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants.ServiceUris.JsonMediaType));

try
{
if (content != null)
{
var serializedContent = JsonConvert.SerializeObject(content);
httpRequestMessage.Content = new StringContent(serializedContent, new UTF8Encoding(), Constants.ServiceUris.JsonMediaType);
}

if (authentication != null)
{
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authentication.BearerToken);
}
}
catch(Exception ex)
{
var serviceException = new CenturyLinkCloudServiceException(Constants.ExceptionMessages.DefaultServiceExceptionMessage, ex);
serviceException.HttpRequestMessage = httpRequestMessage;
throw serviceException;
}

return httpRequestMessage;
return
Configuration
.HttpMessageFormatter
.CreateHttpRequestMessage(httpMethod, serviceUri, authentication, content);
}
}
}
2 changes: 1 addition & 1 deletion CenturyLinkCloudSDK/Services/AccountService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public async Task<IEnumerable<Activity>> GetRecentActivity(IEnumerable<string> a
{
var requestModel = new GetRecentActivityRequest() { Accounts = accounts, Limit = recordCountLimit };

var httpRequestMessage = CreateHttpRequestMessage(HttpMethod.Post, string.Format(Constants.ServiceUris.Account.GetRecentActivity, Configuration.BaseUri), requestModel);
var httpRequestMessage = CreateAuthorizedHttpRequestMessage(HttpMethod.Post, string.Format(Constants.ServiceUris.Account.GetRecentActivity, Configuration.BaseUri), requestModel);
var result = await serviceInvoker.Invoke<IEnumerable<Activity>>(httpRequestMessage, cancellationToken).ConfigureAwait(false);

return result;
Expand Down
4 changes: 2 additions & 2 deletions CenturyLinkCloudSDK/Services/AlertService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task<AlertPolicies> GetAlertPolicies()
/// <returns></returns>
public async Task<AlertPolicies> GetAlertPolicies(CancellationToken cancellationToken)
{
var httpRequestMessage = CreateHttpRequestMessage(HttpMethod.Get, string.Format(Constants.ServiceUris.Alerts.GetAlertPoliciesForAccount, Configuration.BaseUri, authentication.AccountAlias));
var httpRequestMessage = CreateAuthorizedHttpRequestMessage(HttpMethod.Get, string.Format(Constants.ServiceUris.Alerts.GetAlertPoliciesForAccount, Configuration.BaseUri, authentication.AccountAlias));
var result = await serviceInvoker.Invoke<AlertPolicies>(httpRequestMessage, cancellationToken).ConfigureAwait(false);

return result;
Expand Down Expand Up @@ -149,7 +149,7 @@ internal async Task<IEnumerable<AlertTrigger>> GetTriggersByAlertPolicyLink(stri
/// <returns></returns>
internal async Task<IEnumerable<AlertTrigger>> GetTriggersByAlertPolicyLink(string uri, CancellationToken cancellationToken)
{
var httpRequestMessage = CreateHttpRequestMessage(HttpMethod.Get, uri);
var httpRequestMessage = CreateAuthorizedHttpRequestMessage(HttpMethod.Get, uri);
var alertPolicy = await serviceInvoker.Invoke<AlertPolicy>(httpRequestMessage, cancellationToken).ConfigureAwait(false);

return alertPolicy.Triggers;
Expand Down
2 changes: 1 addition & 1 deletion CenturyLinkCloudSDK/Services/AuthenticationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task<UserInfo> Login(string username, string password, Cancellation
{
var requestModel = new LoginRequest() { UserName = username, Password = password };

var httpRequestMessage = CreateHttpRequestMessage(HttpMethod.Post, string.Format(Constants.ServiceUris.Authentication.Login, Configuration.BaseUri), requestModel);
var httpRequestMessage = CreateAuthorizedHttpRequestMessage(HttpMethod.Post, string.Format(Constants.ServiceUris.Authentication.Login, Configuration.BaseUri), requestModel);
var result = await serviceInvoker.Invoke<UserInfo>(httpRequestMessage, cancellationToken).ConfigureAwait(false);

return result;
Expand Down
Loading

0 comments on commit 259d5ae

Please sign in to comment.