Reading an HttpError result from HttpResponseMessage without using exceptions - c#

I'm trying to pull an HttpError out of an HttpResponseMessage message which may or not be there. If the Api throws an exception it will be serialised as an HttpError however errors such as 404's will not be in this format.
I've managed to fix this bug in the code below by catching to exception thrown if we fail to deserialize the HttpError.
The issue is now I'm using exception driven development.
Idealy I want something like this.
var httpError = await response.Content.TryReadAsAsync<HttpError>(formatters);
if (httpError == null)
{
// Definetly not an HttpError and no exception thrown
}
Surely the must be an easy way of telling the type of the content in the HttpContent?
public static async Task<ApiResponseMessage<T>> GetApiResponseAsync<T>(this HttpResponseMessage response, IEnumerable<MediaTypeFormatter> formatters) where T : class
{
if (!response.IsSuccessStatusCode)
{
HttpError httpError;
// Exception driven programming
try
{
// Could use string?
var contentString = response.Content.ReadAsStringAsync();
// This doesn't work. Throws exception if not correct type
var contentObject = await response.Content.ReadAsAsync<object>();
var alwaysNull = contentObject as HttpError;
httpError = await response.Content.ReadAsAsync<HttpError>(formatters);
}
catch (Exception)
{
httpError = null;
}
return new ApiResponseMessage<T>
{
IsSuccess = false,
HttpError = httpError,
Response = response
};
}
return new ApiResponseMessage<T>
{
IsSuccess = true,
Result = await response.Content.ReadAsAsync<T>(formatters),
Response = response
};
}

Cleaned up the code so it at least compiles.
public class ReadAsyncResult<T>
{
public ReadAsyncResult()
{
}
public ReadAsyncResult(T result)
{
Result = result;
IsSuccess = result != null;
}
public T Result { get; set; }
public bool IsSuccess { get; set; }
public static async Task<ReadAsyncResult<T>> TryReadAsAsync<T>(HttpContent content)
{
return await TryReadAsAsync<T>(content, CancellationToken.None);
}
public static async Task<ReadAsyncResult<T>> TryReadAsAsync<T>(HttpContent content,
CancellationToken cancellationToken)
{
if (content == null)
return new ReadAsyncResult<T>();
var type = typeof(T);
var objectContent = content as ObjectContent;
if (objectContent?.Value != null && type.IsInstanceOfType(objectContent.Value))
{
return new ReadAsyncResult<T>((T) objectContent.Value);
}
var mediaType = content.Headers.ContentType;
var reader =
new MediaTypeFormatterCollection(new MediaTypeFormatterCollection()).FindReader(type, mediaType);
if (reader == null) return new ReadAsyncResult<T>();
var value = await ReadAsAsyncCore<T>(content, type, reader, cancellationToken);
return new ReadAsyncResult<T>(value);
}
private static async Task<T> ReadAsAsyncCore<T>(HttpContent content, Type type, MediaTypeFormatter formatter,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var stream = await content.ReadAsStreamAsync();
var result = await formatter.ReadFromStreamAsync(type, stream, content, null, cancellationToken);
return (T) result;
}
}

It is of course, blindingly simple.
var message = new HttpResponseMessage();
HttpError httpError;
message.TryGetContentValue(out httpError);
if (httpError != null)
{
// Do stuff
}
Edit:
This didn't fix my issue as the content type was not of type ObjectResult. I was expecting the TryGetContentValue to work in the same way as HttpContent.ReadAsAsync.
After digging through the source code for ReadAsAsync I have created a working solution.
public class ReadAsyncResult<T>
{
public ReadAsyncResult()
{
}
public ReadAsyncResult(T result)
{
Result = result;
IsSuccess = result != null;
}
public T Result { get; set; }
public bool IsSuccess { get; set; }
}
public static async Task<ReadAsyncResult<T>> TryReadAsAsync<T>(this HttpContent content)
{
return await TryReadAsAsync<T>(content, CancellationToken.None);
}
public static async Task<ReadAsyncResult<T>> TryReadAsAsync<T>(this HttpContent content, CancellationToken cancellationToken)
{
if (content == null)
return new ReadAsyncResult<T>();
var type = typeof(T);
var objectContent = content as ObjectContent;
if (objectContent?.Value != null && type.IsInstanceOfType(objectContent.Value))
{
return new ReadAsyncResult<T>((T)objectContent.Value);
}
var mediaType = content.Headers.ContentType;
var reader = new MediaTypeFormatterCollection(new MediaTypeFormatterCollection()).FindReader(type, mediaType);
if (reader == null) return new ReadAsyncResult<T>();
var value = await ReadAsAsyncCore<T>(content, type, reader, cancellationToken);
return new ReadAsyncResult<T>(value);
}
private static async Task<T> ReadAsAsyncCore<T>(HttpContent content, Type type, MediaTypeFormatter formatter, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var stream = await content.ReadAsStreamAsync();
var result = await formatter.ReadFromStreamAsync(type, stream, content, null, cancellationToken);
return (T) result;
}

Related

Unit Test case for Generic HttpGet methods

How can I write test cases for Generic Http methods. My methods are below.
public async Task<T> HttpGetAsync<T>(string urlSuffix)
{
var url = Common.FormatUrl(urlSuffix, _instanceUrl, _apiVersion);
return await HttpGetAsync<T>(url);
}
public async Task<T> HttpGetAsync<T>(Uri uri)
{
try
{
var response = await HttpGetAsync(uri);
var jToken = JToken.Parse(response);
if (jToken.Type == JTokenType.Array)
{
var jArray = JArray.Parse(response);
return JsonConvert.DeserializeObject<T>(jArray.ToString());
}
// else
try
{
var jObject = JObject.Parse(response);
return JsonConvert.DeserializeObject<T>(jObject.ToString());
}
catch
{
return JsonConvert.DeserializeObject<T>(response);
}
}
catch (BaseHttpClientException e)
{
throw ParseForceException(e.Message);
}
}
protected async Task<string> HttpGetAsync(Uri uri)
{
var responseMessage = await _httpClient.GetAsync(uri).ConfigureAwait(false);
if (responseMessage.StatusCode == HttpStatusCode.NoContent)
{
return string.Empty;
}
var response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
if (responseMessage.IsSuccessStatusCode)
{
return response;
}
throw new BaseHttpClientException(response, responseMessage.StatusCode);
}

How to determine object type without raising an exception?

I'm refactoring some older code that uses HttpClient by inheriting from WebRequestHandler and doing:
public class ClientRequestHandler : WebRequestHandler
{
public ClientRequestHandler()
{ }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await base.SendAsync(request, cancellationToken)
.ContinueWith<HttpResponseMessage>(async task =>
{
var response = task.Result;
if (response.StatusCode == HttpStatusCode.NotFound)
{
var nex = await response.Content.ReadAsAsync<NotFoundException>();
throw nex;
}
else if (response.StatusCode == HttpStatusCode.Conflict)
{
var ex1 = await response.Content.ReadAsAsync<SomeException>();
throw ex1;
var ex2 = response.Content.ReadAsAsync<OtherException>().Result;
throw ex2;
}
return response;
}).ConfigureAwait(false);
}
}
The issue is the following block of code:
var ex1 = await response.Content.ReadAsAsync<SomeException>();
throw ex1;
var ex2 = response.Content.ReadAsAsync<OtherException>().Result;
throw ex2;
I can wrap individual reads into try catch or first the content as a string, cast object and then check the type with either typeof or is. So it will look something like this:
var o = response.Content.ReadAsStringAsync().Result as object;
if (o is SomeException)
throw (SomeException)o;
else if (o is OtherException)
throw (OtherException)o;
Is there a cleaner way to determine what the content is and just switch to the correct instance?

Intercept Json before returning from web.api

I am looking to intercept the returning model from my web.api and run the returning data through a translation service, i.e. Google or Azure for languages other than English. Is there way to add an attribute or additional config to intercept the model, perform the translation, and then return back to the controller?
try this code also add this code
//add this line global.asax file
GlobalConfiguration.Configuration.MessageHandlers.Add(new CustomLogHandler());
public class CustomLogHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var logMetadata = await BuildRequestMetadata(request);
var response = await base.SendAsync(request, cancellationToken);
logMetadata = await BuildResponseMetadata(logMetadata, response);
await SendToLog(logMetadata);
return response;
}
private async Task<LogMetadata> BuildRequestMetadata(HttpRequestMessage request)
{
LogMetadata log = new LogMetadata
{
RequestMethod = request.Method.Method,
RequestTimestamp = DateTime.Now,
RequestUri = request.RequestUri.ToString(),
RequestContent = await request.Content.ReadAsStringAsync(),
};
return log;
}
private async Task<LogMetadata> BuildResponseMetadata(LogMetadata logMetadata, HttpResponseMessage response)
{
logMetadata.ResponseStatusCode = response.StatusCode;
logMetadata.ResponseTimestamp = DateTime.Now;
logMetadata.ResponseContentType = response.Content == null ? string.Empty : response.Content.Headers.ContentType.MediaType;
logMetadata.Response = await response.Content.ReadAsStringAsync();
return logMetadata;
}
private async Task<bool> SendToLog(LogMetadata logMetadata)
{
try
{
//write this code
}
catch
{
return false;
}
return true;
}
}

Mock returns null value when ReturnResult is a custom object but works as expected when it is a primitive type bool

I am using Moq in .net core(1.1) and having a bit of torrid time understanding this behavior as all the examples on interweb points to the fact the this should work with no issues.
I have already tried with:
Returns(Task.FromResult(...)
Returns(Task.FromResult(...)
ReturnsAsync(...)
Mocking a IHttpClient interface to wrap PostAsync, PutAsync and GetAsync. All of these return an ApiResponse object.
var mockClient = new Mock<IHttpClient>();
Does not work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(new ApiResponse()));
PostSync definition:
public async Task<ApiResponse> PostAsync(string url, string body, string authToken = null)
Does work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(bool));
PostSync definition:
public async Task<bool> PostAsync(string url, string body, string authToken = null)
Usage:
var api = new ApiService(mockClient.Object);
var response = api.LoginAsync(body.Username, body.Password);
UPDATE
[Fact]
public async void TestLogin()
{
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null)).Returns(Task.FromResult(new ApiResponse()));
var api = new ApiService(mockClient.Object);
var response = await api.LoginAsync(body.Username, body.Password);
Assert.IsTrue(response);
}
Return Type:
public class ApiResponse
{
public string Content { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Reason { get; set; }
}
LoginAsync:
public async Task<bool> LoginAsync(string user, string password)
{
var body = new { Username = user, Password = password };
try
{
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response .State == 1;
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
}
PostAsync:
public async Task<object> PostAsync(string url, string body, string authToken = null)
{
var client = new HttpClient();
var content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), content);
var resp = await response.Result.Content.ReadAsStringAsync();
return new ApiResponse
{
Content = resp,
StatusCode = response.Result.StatusCode,
Reason = response.Result.ReasonPhrase
};
}
Assuming a simple method under test like this based on minimal example provided above.
public class ApiService {
private IHttpClient _http;
private string _login_url;
public ApiService(IHttpClient httpClient) {
this._http = httpClient;
}
public async Task<bool> LoginAsync(string user, string password) {
var body = new { Username = user, Password = password };
try {
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response.StatusCode == HttpStatusCode.OK;
} catch (Exception ex) {
//Logger.Error(ex);
return false;
}
}
}
The following test works when configured correctly
[Fact]
public async Task Login_Should_Return_True() { //<-- note the Task and not void
//Arrange
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<string>(), null))
.ReturnsAsync(new ApiResponse() { StatusCode = HttpStatusCode.OK });
var api = new ApiService(mockClient.Object);
//Act
var response = await api.LoginAsync("", "");
//Assert
Assert.IsTrue(response);
}
The above is just for demonstrative purposes only to show that it can work provided the test is configured properly and exercised based on the expected behavior.
Take some time and review the Moq quick start to get a better understanding of how to use the framework.

Validating Request Content-Type

I'm trying to validate the value of Content-Type in POST, PUT and PATCH requests, but the current code is only working when I forget the content-type clause or when I use a content-type like: "Content-Type: Foo".
When I send "Content-Type: text/css" I get this:
500 Internal Server Error
No MediaTypeFormatter is available to read an object of type 'MyClassDto' from content with media type 'text/css'.
This is my code:
public class ContentTypeFilter : IActionFilter
{
private readonly List<MediaTypeHeaderValue> _suport;
/// <summary />
public ContentTypeFilterAttribute()
{
_suport = new List<MediaTypeHeaderValue>();
foreach (var formatter in GlobalConfiguration.Configuration.Formatters.ToArray())
{
_suport.AddRange(formatter.SupportedMediaTypes);
}
}
public bool AllowMultiple { get { return false; } }
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
var metodos = new List<string> { "POST", "PUT", "PATCH" };
if (actionContext.Request.Content != null)
{
if (metodos.Contains(actionContext.Request.Method.Method.ToUpperInvariant()))
{
MediaTypeHeaderValue contentType = actionContext.Request.Content.Headers.ContentType;
if (contentType == null || !_suport.Any(x => x.MediaType.Equals(contentType.MediaType)))
{
return CreateResponse(actionContext.Request, "Invalid Content-Type");
}
}
}
return continuation();
}
private static Task<HttpResponseMessage> CreateResponse(HttpRequestMessage request, string mensagem)
{
var tsc = new TaskCompletionSource<HttpResponseMessage>();
var response = request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
response.ReasonPhrase = mensagem;
response.Content = new StringContent(mensagem);
tsc.SetResult(response);
return tsc.Task;
}
Is there another way to validate content-type and return error 415 if the content isn't XML or JSON?
I've found a good solution here.
With some changes to get what I want:
public class ContentTypeFilter : DelegatingHandler
{
private readonly List<MediaTypeHeaderValue> _suport;
/// <summary />
public ContentTypeFilter()
{
_suport = new List<MediaTypeHeaderValue>();
foreach (var formatter in GlobalConfiguration.Configuration.Formatters.ToArray())
{
_suport.AddRange(formatter.SupportedMediaTypes);
}
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var metodos = new List<string> { "POST", "PUT", "PATCH" };
if (request.Content != null)
{
if (metodos.Contains(request.Method.Method.ToUpperInvariant()))
{
MediaTypeHeaderValue contentType = request.Content.Headers.ContentType;
// Nas configurações não possui o Charset aceito.
if (contentType == null || !_suport.Any(x => x.MediaType.Equals(contentType.MediaType)))
{
return Task<HttpResponseMessage>.Factory.StartNew(() => CreateResponse(request, "Suported content-types: " + string.Join(", ", _suport.Select(x => x.ToString()))));
}
}
}
return base.SendAsync(request, cancellationToken);
}
private static HttpResponseMessage CreateResponse(HttpRequestMessage request, string mensagem)
{
var response = request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
response.ReasonPhrase = mensagem;
response.Content = new StringContent(mensagem);
return response;
}
}

Categories