WebAPi - unify error messages format from ApiController and OAuthAuthorizationServerProvider - c#

In my WebAPI project I'm using Owin.Security.OAuth to add JWT authentication.
Inside GrantResourceOwnerCredentials of my OAuthProvider I'm setting errors using below line:
context.SetError("invalid_grant", "Account locked.");
this is returned to client as:
{
"error": "invalid_grant",
"error_description": "Account locked."
}
after user gets authenticated and he tries to do "normal" request to one of my controllers he gets below response when model is invalid (using FluentValidation):
{
"message": "The request is invalid.",
"modelState": {
"client.Email": [
"Email is not valid."
],
"client.Password": [
"Password is required."
]
}
}
Both requests are returning 400 Bad Request, but sometimes You must look for error_description field and sometimes for message
I was able to create custom response message, but this only applies to results I'm returning.
My question is: is it possible to replace message with error in response that is returned by ModelValidatorProviders and in other places?
I've read about ExceptionFilterAttribute but I don't know if this is a good place to start. FluentValidation shouldn't be a problem, because all it does is adding errors to ModelState.
EDIT:
Next thing I'm trying to fix is inconsistent naming convention in returned data across WebApi - when returning error from OAuthProvider we have error_details, but when returning BadRequest with ModelState (from ApiController) we have modelState. As You can see first uses snake_case and second camelCase.

UPDATED ANSWER (Use Middleware)
Since the Web API original delegating handler idea meant that it would not be early enough in the pipeline as the OAuth middleware then a custom middleware needs to be created...
public static class ErrorMessageFormatter {
public static IAppBuilder UseCommonErrorResponse(this IAppBuilder app) {
app.Use<JsonErrorFormatter>();
return app;
}
public class JsonErrorFormatter : OwinMiddleware {
public JsonErrorFormatter(OwinMiddleware next)
: base(next) {
}
public override async Task Invoke(IOwinContext context) {
var owinRequest = context.Request;
var owinResponse = context.Response;
//buffer the response stream for later
var owinResponseStream = owinResponse.Body;
//buffer the response stream in order to intercept downstream writes
using (var responseBuffer = new MemoryStream()) {
//assign the buffer to the resonse body
owinResponse.Body = responseBuffer;
await Next.Invoke(context);
//reset body
owinResponse.Body = owinResponseStream;
if (responseBuffer.CanSeek && responseBuffer.Length > 0 && responseBuffer.Position > 0) {
//reset buffer to read its content
responseBuffer.Seek(0, SeekOrigin.Begin);
}
if (!IsSuccessStatusCode(owinResponse.StatusCode) && responseBuffer.Length > 0) {
//NOTE: perform your own content negotiation if desired but for this, using JSON
var body = await CreateCommonApiResponse(owinResponse, responseBuffer);
var content = JsonConvert.SerializeObject(body);
var mediaType = MediaTypeHeaderValue.Parse(owinResponse.ContentType);
using (var customResponseBody = new StringContent(content, Encoding.UTF8, mediaType.MediaType)) {
var customResponseStream = await customResponseBody.ReadAsStreamAsync();
await customResponseStream.CopyToAsync(owinResponseStream, (int)customResponseStream.Length, owinRequest.CallCancelled);
owinResponse.ContentLength = customResponseStream.Length;
}
} else {
//copy buffer to response stream this will push it down to client
await responseBuffer.CopyToAsync(owinResponseStream, (int)responseBuffer.Length, owinRequest.CallCancelled);
owinResponse.ContentLength = responseBuffer.Length;
}
}
}
async Task<object> CreateCommonApiResponse(IOwinResponse response, Stream stream) {
var json = await new StreamReader(stream).ReadToEndAsync();
var statusCode = ((HttpStatusCode)response.StatusCode).ToString();
var responseReason = response.ReasonPhrase ?? statusCode;
//Is this a HttpError
var httpError = JsonConvert.DeserializeObject<HttpError>(json);
if (httpError != null) {
return new {
error = httpError.Message ?? responseReason,
error_description = (object)httpError.MessageDetail
?? (object)httpError.ModelState
?? (object)httpError.ExceptionMessage
};
}
//Is this an OAuth Error
var oAuthError = Newtonsoft.Json.Linq.JObject.Parse(json);
if (oAuthError["error"] != null && oAuthError["error_description"] != null) {
dynamic obj = oAuthError;
return new {
error = (string)obj.error,
error_description = (object)obj.error_description
};
}
//Is this some other unknown error (Just wrap in common model)
var error = JsonConvert.DeserializeObject(json);
return new {
error = responseReason,
error_description = error
};
}
bool IsSuccessStatusCode(int statusCode) {
return statusCode >= 200 && statusCode <= 299;
}
}
}
...and registered early in the pipeline before the the authentication middlewares and web api handlers are added.
public class Startup {
public void Configuration(IAppBuilder app) {
app.UseResponseEncrypterMiddleware();
app.UseRequestLogger();
//...(after logging middle ware)
app.UseCommonErrorResponse();
//... (before auth middle ware)
//...code removed for brevity
}
}
This example is just a basic start. It should be simple enough able to extend this starting point.
Though in this example the common model looks like what is returned from OAuthProvider, any common object model can be used.
Tested it with a few In-memory Unit Tests and through TDD was able to get it working.
[TestClass]
public class UnifiedErrorMessageTests {
[TestMethod]
public async Task _OWIN_Response_Should_Pass_When_Ok() {
//Arrange
var message = "\"Hello World\"";
var expectedResponse = "\"I am working\"";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(message, Encoding.UTF8, "application/json");
//Act
var response = await client.PostAsync("/api/Foo", content);
//Assert
Assert.IsTrue(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.AreEqual(expectedResponse, result);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_BadRequest() {
//Arrange
var expectedResponse = "invalid_grant";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(expectedResponse, Encoding.UTF8, "application/json");
//Act
var response = await client.PostAsync("/api/Foo", content);
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error_description);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_MethodNotAllowed() {
//Arrange
var expectedResponse = "Method Not Allowed";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Act
var response = await client.GetAsync("/api/Foo");
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_NotFound() {
//Arrange
var expectedResponse = "Not Found";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Act
var response = await client.GetAsync("/api/Bar");
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error);
}
}
public class WebApiTestStartup {
public void Configuration(IAppBuilder app) {
app.UseCommonErrorMessageMiddleware();
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
app.UseWebApi(config);
}
}
public class FooController : ApiController {
public FooController() {
}
[HttpPost]
public IHttpActionResult Bar([FromBody]string input) {
if (input == "Hello World")
return Ok("I am working");
return BadRequest("invalid_grant");
}
}
}
ORIGINAL ANSWER (Use DelegatingHandler)
Consider using a DelegatingHandler
Quoting from an article found online.
Delegating handlers are extremely useful for cross cutting concerns.
They hook into the very early and very late stages of the
request-response pipeline making them ideal for manipulating the
response right before it is sent back to the client.
This example is a simplified attempt at the unified error message for HttpError responses
public class HttpErrorHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var response = await base.SendAsync(request, cancellationToken);
return NormalizeResponse(request, response);
}
private HttpResponseMessage NormalizeResponse(HttpRequestMessage request, HttpResponseMessage response) {
object content;
if (!response.IsSuccessStatusCode && response.TryGetContentValue(out content)) {
var error = content as HttpError;
if (error != null) {
var unifiedModel = new {
error = error.Message,
error_description = (object)error.MessageDetail ?? error.ModelState
};
var newResponse = request.CreateResponse(response.StatusCode, unifiedModel);
foreach (var header in response.Headers) {
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
}
return response;
}
}
Though this example is very basic, it is trivial to extend it to suit your custom needs.
Now it is just a matter of adding the handler to the pipeline
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.MessageHandlers.Add(new HttpErrorHandler());
// Other code not shown...
}
}
Message handlers are called in the same order that they appear in
MessageHandlers collection. Because they are nested, the response message travels in the other direction. That is, the last handler is
the first to get the response message.
Source: HTTP Message Handlers in ASP.NET Web API

is it possible to replace message with error in response that is
returned by ModelValidatorProviders
We may use overloaded SetError to do it otherwise, replace error with message.
BaseValidatingContext<TOptions>.SetError Method (String)
Marks this context as not validated by the application and assigns various error information properties. HasError becomes true and IsValidated becomes false as a result of calling.
string msg = "{\"message\": \"Account locked.\"}";
context.SetError(msg);
Response.StatusCode = 400;
context.Response.Write(msg);

Related

How to return the same status code from a second API call

I have an ASP.NET Core API calling a second API.
I throw an exception in my services layer, if there is an error from the second API:
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{
//return HTTP response with StatusCode = X, if response.StatusCode == X
throw new HttpRequestException(await response.Content.ReadAsStringAsync());
//this always returns 400
}
How can I throw an exception that will return a response with the same status code from the second API call?
If I use HttpRequestException it will always return 400, even if the response object had StatusCode = 500.
EDIT:
The first API endpoint looks like this:
public async Task<ActionResult<HttpResponseMessage>> CreateTenancy([FromBody]TenancyRequest tenancy)
{
//Make some calls...
return Created(string.Empty, new { TenancyID = newTenancyExternalId });
}
The second API endpoint looks like this:
[HttpPost]
public IHttpActionResult CreateTenancy([FromBody]TenancyDTO tenancyDTO)
{
var tenancy = GetTenancy();
return Created(string.Empty, tenancy);
}
I've tried using throw new HttpResponseException(response); but this removes the descriptive Exception message, the payload ends up like this:
{
"Code": 500,
"CorrelationId": "2df08016-e5e3-434a-9136-6824495ed907",
"DateUtc": "2020-01-30T02:02:48.4428978Z",
"ErrorMessage": "Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.",
"ErrorType": "InternalServerError"
}
I'd like to keep the ErrorMessage value in the original payload:
{
"Code": 400,
"CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
"DateUtc": "2020-01-30T03:05:13.2397543Z",
"ErrorMessage": "\"Specified cast is not valid.\"",
"ErrorType": "BadRequest"
}
The end goal is to have this returned:
{
"Code": 500,
"CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
"DateUtc": "2020-01-30T03:05:13.2397543Z",
"ErrorMessage": "\"Specified cast is not valid.\"",
"ErrorType": "InternalServerError"
}
I tried something simple as changing the return type of the API endpoint and returning the object as it when there is an error. Otherwise, build your own HttpResponseMessage and return that. This snippet below uses text but you can use a serializer to serialize other content if you have.
public async Task<HttpResponseMessage> Test(string str)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"myAPI that returns different errors 400, 404, 500 etc based on str");
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return response;
// do something else
return new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent("Your Text here") };
}
Other approach of using Filters
The other approach of using IHttpActionResult as your return type, you can use Filters to conform all your HttpResponseMessages to IHttpActionResult.
Filter: Create a separate cs file and use this filter definition.
public class CustomObjectResponse : IHttpActionResult
{
private readonly object _obj;
public CustomObjectResponse(object obj)
{
_obj = obj;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = _obj as HttpResponseMessage;
return Task.FromResult(response);
}
}
and in your API, you would use your filter like so,
public async Task<IHttpActionResult> Test(string str)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"http://localhost:4500/api/capacity/update-mnemonics/?mnemonic_to_update={str}");
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return new CustomObjectResponse(response);
// Other Code here
// Return Other objects
KeyValuePair<string, string> testClass = new KeyValuePair<string, string>("Sheldon", "Cooper" );
return new OkWithObjectResult(testClass);
// Or Return Standard HttpResponseMessage
return Ok();
}
You could simply make your API call and copy its response code into something compatible with IStatusCodeActionResult.
An alternative s to throw a custom exception. Create something like
public class ApiCallException : Exception
{
public APiCallException(int statusCode, ...)
{
ApiStatusCode = statusCode;
}
int ApiStatusCode { get; }
...
}
and copy over the status code from your API result, and then throw the exception.
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new ApiCallException(500, content);
}
You can then register an exception filter to deal with the result when calling AddMvc.
services.AddMvc(options => options.Filters.Add<ExceptionFilter>());
where ExceptionFilter could be something like
public class ExceptionFilter : IExceptionFilter
{
// ...
public void OnException(ExceptionContext context)
{
if (context.Exception is ApiCallException ace)
{
var returnObject = CreateReturnObjectSomehow();
context.Result = new ObjectResult(returnObject) { StatusCode = ace.StatusCode };
}
else
{
// do something else
}
}
}
Thanks Jawad and Kit for providing great answers which helped me work out the solution below:
Turns out there was some middleware handling the exception:
public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception exception)
{
if (httpContext.Response.HasStarted) throw;
var statusCode = ConvertExceptionToHttpStatusCode(exception);
httpContext.Response.Clear();
httpContext.Response.StatusCode = (int)statusCode;
httpContext.Response.ContentType = "application/json";
if (statusCode != HttpStatusCode.BadRequest)
{
_logger.Error(exception, "API Error");
}
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(new Error(statusCode, httpContext.Request.CorrelationId(), exception.Message, statusCode.ToString())));
}
}
The Error class looks like this:
public class Error
{
public int Code { get; }
public Guid? CorrelationId { get; }
public DateTime DateUtc { get; }
public string ErrorMessage { get; }
public string ErrorType { get; }
public Error(HttpStatusCode code, Guid? correlationId, string errorMessage, string errorType)
{
Code = (int)code;
CorrelationId = correlationId;
DateUtc = DateTime.UtcNow;
ErrorMessage = errorMessage;
ErrorType = errorType;
}
}
I created this class:
public class ApiCallException : Exception
{
public int StatusCode { get; }
public override string Message { get; }
public ApiCallException(int statusCode, string message)
{
StatusCode = statusCode;
Message = message;
}
}
Then updated my original code to have this:
if (!response.IsSuccessStatusCode)
{
throw new ApiCallException((int)response.StatusCode, await response.Content.ReadAsStringAsync());
}

A method was called at an unexpected time from IHttpFilter.SendRequestAsync

I'm developing an UWP app that calls a web service. For that I use a HttpClient object from Windows.Web.Http namespace and I pass a IHttpFilter object to its constructor. This filter is responsible for the authentication process. I based my solution following this link and the authentication logic is based on this
I don't know what I'm doing wrong but I got this exception: A method was called at an unexpected time. (Exception from HRESULT: 0x8000000E)
The scenario that I'm testing is when the token is invalid despite it is assumed it is valid. In this case the (now > TokenManager.Token.ExpiresOn) condition is false, then an authorization header is added (with an invalid token), then a request is sent, then the http response code and www-authenticate header is inspected and if it is neccessary, a new access token must be requested by means of refresh token in order to do a retry. It is when I reach this line when the exception is thrown (the second time): response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress);
I have no idea what I'm doing wrong. I have seen another questions where people got this error and usually it's because they try to get the task's result without waiting for the task's completion but I'm using the await keyword in all asynchronous methods.
public class AuthFilter : HttpFilter
{
public override IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
{
return AsyncInfo.Run<HttpResponseMessage, HttpProgress>(async (cancellationToken, progress) =>
{
var retry = true;
if (TokenManager.TokenExists)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (now > TokenManager.Token.ExpiresOn)
{
retry = false;
await RefreshTokenAsync();
}
request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken);
}
HttpResponseMessage response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress);
cancellationToken.ThrowIfCancellationRequested();
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
var authHeader = response.Headers.WwwAuthenticate.SingleOrDefault(x => x.Scheme == "Bearer");
if (authHeader != null)
{
var challenge = ParseChallenge(authHeader.Parameters);
if (challenge.Error == "token_expired" && retry)
{
var success = await RefreshTokenAsync();
if (success)
{
request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken);
response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress);
}
}
}
}
return response;
});
}
private async Task<bool> RefreshTokenAsync()
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Accept.Add(new HttpMediaTypeWithQualityHeaderValue("application/json"));
var content = new HttpStringContent(JsonConvert.SerializeObject(new { RefreshToken = TokenManager.Token.RefreshToken }), UnicodeEncoding.Utf8, "application/json");
var response = await httpClient.PostAsync(SettingsService.Instance.WebApiUri.Append("api/login/refresh-token"), content);
if (response.IsSuccessStatusCode)
TokenManager.Token = JsonConvert.DeserializeObject<Token>(await response.Content.ReadAsStringAsync());
return response.IsSuccessStatusCode;
}
}
private (string Realm, string Error, string ErrorDescription) ParseChallenge(IEnumerable<HttpNameValueHeaderValue> input)
{
var realm = input.SingleOrDefault(x => x.Name == "realm")?.Value ?? string.Empty;
var error = input.SingleOrDefault(x => x.Name == "error")?.Value ?? string.Empty;
var errorDescription = input.SingleOrDefault(x => x.Name == "error_description")?.Value ?? string.Empty;
return (realm, error, errorDescription);
}
public override void Dispose()
{
InnerFilter.Dispose();
GC.SuppressFinalize(this);
}
}
public abstract class HttpFilter : IHttpFilter
{
public IHttpFilter InnerFilter { get; set; }
public HttpFilter()
{
InnerFilter = new HttpBaseProtocolFilter();
}
public abstract IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request);
public abstract void Dispose();
}
EDIT:
According to these links (link, link), it seems to be that I cannot send the same request twice. But I need to re-send the same request but with different authentication header. How can I achieve that?
Ok, after struggling with this for a long time, I ended up doing this:
public override IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
{
return AsyncInfo.Run<HttpResponseMessage, HttpProgress>(async (cancellationToken, progress) =>
{
var retry = true;
if (TokenManager.TokenExists)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (now > TokenManager.Token.ExpiresOn)
{
retry = false;
await RefreshTokenAsync();
}
request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken);
}
HttpResponseMessage response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress);
cancellationToken.ThrowIfCancellationRequested();
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
var authHeader = response.Headers.WwwAuthenticate.SingleOrDefault(x => x.Scheme == "Bearer");
if (authHeader != null)
{
var challenge = ParseChallenge(authHeader.Parameters);
if (challenge.Error == "token_expired" && retry)
{
var secondRequest = request.Clone();
var success = await RefreshTokenAsync();
if (success)
{
secondRequest.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken);
response = await InnerFilter.SendRequestAsync(secondRequest).AsTask(cancellationToken, progress);
}
}
}
}
return response;
});
}
public static HttpRequestMessage Clone(this HttpRequestMessage request)
{
var clone = new HttpRequestMessage(request.Method, request.RequestUri)
{
Content = request.Content
};
foreach (KeyValuePair<string, object> prop in request.Properties.ToList())
{
clone.Properties.Add(prop);
}
foreach (KeyValuePair<string, string> header in request.Headers.ToList())
{
clone.Headers.Add(header.Key, header.Value);
}
return clone;
}
Because I needed to re-send the request, I made a second request cloning the first one.

Wrapping all responses

I want to wrap all my http responses.
For example we have an action which returns some JSON data:
public IActionResult Get()
{
var res = new
{
MessageBody = "Test",
SomeData = 1
};
return Ok(res);
}
I want my response looks like:
{
"StatusCode":200,
"Result":
{
"MessageBody ":"Test",
"SomeData":1
}
}
If there is error then response must contain ErrorMessage field in a response.
In the mvc 5 I used the DelegationHandler, but in the asp.net core this class is not implemented. Now, we have to use middlewares.
This is code for mvc 5:
public class WrappingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
return BuildApiResponse(request, response);
}
private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
{
object content;
string errorMessage = null;
if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
{
HttpError error = content as HttpError;
if (error != null)
{
content = null;
errorMessage = error.Message;
#if DEBUG
errorMessage = string.Concat(errorMessage, error.ExceptionMessage, error.StackTrace);
#endif
}
}
var newResponse = request.CreateResponse(response.StatusCode, new ApiResponse(response.StatusCode, content, errorMessage));
foreach (var header in response.Headers)
{
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
}
and, a middleware for asp.net core. There are no TryGetContentValue, HttpError and other stuff in asp.net core. So, I am trying to read response body first:
public class FormatApiResponseMiddleware
{
private readonly RequestDelegate _next;
public FormatApiResponseMiddleware(RequestDelegate next)
{
_next = next;
}
private bool IsSuccessStatusCode(int statusCode)
{
return (statusCode >= 200) && (statusCode <= 299);
}
public async Task Invoke(HttpContext context)
{
object content = null;
string errorMessage = null;
if (!IsSuccessStatusCode(context.Response.StatusCode))
{
content = null;
//how to get error
}
var body= context.Response.Body;
}
}
But, Body stream has CanRead equal false and I get error that stream cannot be read. How to properly wrap response?
I suggest using ExceptionHandlerMiddleware as a template/sample on how your middleware should be implemented.
For example, you should be aware about case, when response has already started
// We can't do anything if the response has already started, just abort.
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the error handler will not be executed.");
throw;
}
or don't forget to clear current response, if you want to replace it:
context.Response.Clear();
Moreover, maybe you will find useful just to reuse it, and implement your own error handler instead of a full middleware. That way you can send a custom JSON error to the client. For that, define a class that will represent your custom error:
public class ErrorDto
{
public int Code { get; set; }
public string Message { get; set; }
// other fields
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Then register an exception handler middleware in the Configure method. Pay attention to the order in which the middleware is registered, and make sure it’s registered before MVC for example:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500; // or another Status
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorDto()
{
Code = 1, //<your custom code based on Exception Type>,
Message = ex.Message // or your custom message
// … other custom data
}.ToString(), Encoding.UTF8);
}
});
});

Error handling (Sending ex.Message to the client)

I have an ASP.NET Core 1.0 Web API application and trying to figure out how to pass the exception message to the client if a function that my controller is calling errors out.
I have tried so many things, but nothing implements IActionResult.
I don't understand why this isn't a common thing that people need. If there truthfully is no solution can someone tell me why?
I do see some documentation out there using HttpResponseException(HttpResponseMessage), but in order to use this, I have to install the compat shim. Is there a new way of doing these things in Core 1.0?
Here is something I have been trying with the shim but it isn't working:
// GET: api/customers/{id}
[HttpGet("{id}", Name = "GetCustomer")]
public IActionResult GetById(int id)
{
Customer c = _customersService.GetCustomerById(id);
if (c == null)
{
var response = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent("Customer doesn't exist", System.Text.Encoding.UTF8, "text/plain"),
StatusCode = HttpStatusCode.NotFound
};
throw new HttpResponseException(response);
//return NotFound();
}
return new ObjectResult(c);
}
When the HttpResponseException is thrown, I look on the client and can't find the message I am sending anything in the content.
Here is an simple error DTO class
public class ErrorDto
{
public int Code {get;set;}
public string Message { get; set; }
// other fields
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
And then using the ExceptionHandler middleware:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500; // or another Status accordingly to Exception Type
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorDto()
{
Code = <your custom code based on Exception Type>,
Message = ex.Message // or your custom message
// other custom data
}.ToString(), Encoding.UTF8);
}
});
});
Yes it is possible to change the status code to whatever you need:
In your CustomExceptionFilterAttribute.cs file modify the code as follows:
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
context.Result = new ContentResult
{
Content = $"Error: {exception.Message}",
ContentType = "text/plain",
// change to whatever status code you want to send out
StatusCode = (int?)HttpStatusCode.BadRequest
};
}
}
That's pretty much it.
If you have custom exceptions, then you can also check for them when grabbing the thrown exception from the context. Following on from that you can then send out different HTTP Status Codes depdending on what has happened in your code.
Hope that helps.
You can create a custom Exception Filter like below
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
context.Result = new JsonResult(exception.Message);
}
}
Then apply the above attribute to your controller.
[Route("api/[controller]")]
[CustomExceptionFilter]
public class ValuesController : Controller
{
// GET: api/values
[HttpGet]
public IEnumerable<string> Get()
{
throw new Exception("Suckers");
return new string[] { "value1", "value2" };
}
}
Rather than raising and catching an exception, how about you simplify your action to:
// GET: api/customers/{id}
[HttpGet("{id}", Name = "GetCustomer")]
public IActionResult GetById(int id)
{
var customer = _customersService.GetCustomerById(id);
if (customer == null)
{
return NotFound("Customer doesn't exist");
}
return Ok(customer);
}
I wrote a blog post with some more options such as returning a JSON object instead of text.
Maybe that is helpful. You can return just object and sent for example a BadRequest (HTTP CODE: 400) with your custom object as actual parameter (I just used an interpolated string here) but you can put in anything.
In your client side you can catch that error situation for example with an AJAX error handler.
// GET: api/TruckFahrerGeoData
[HttpGet]
public object GetTruckFahrerGeoData()
{
var truckFahrerGeoDataItems = new List<TruckFahrerGeoDataViewModel>();
var geodataItems = _context.TruckFahrerGeoData;
foreach (var truckFahrerGeoData in geodataItems)
{
GeoTelemetryData geoTelemetryData = JsonConvert.DeserializeObject<GeoTelemetryData>(truckFahrerGeoData.TelemetryData);
if (geoTelemetryData == null)
{
return BadRequest($"geoTelemetryData null for id: {truckFahrerGeoData.Id}");
}
TruckFahrerGeoDataViewModel truckFahrerGeoDataViewModel = new TruckFahrerGeoDataViewModel
{
Speed = geoTelemetryData.Speed,
Accuracy = geoTelemetryData.Accuracy,
TruckAppId = geoTelemetryData.Activity.TruckAppId,
TruckAuftragStatusId = geoTelemetryData.Activity.TruckAuftragStatusId,
ClId = geoTelemetryData.Activity.ClId,
TruckAuftragLaufStatusId = geoTelemetryData.Activity.TruckAuftragLaufStatusId,
TaskId = geoTelemetryData.Activity.TaskId,
TruckAuftragWorkflowStatusId = geoTelemetryData.Activity.TruckAuftragWorkflowStatusId
};
truckFahrerGeoDataItems.Add(truckFahrerGeoDataViewModel);
}
return truckFahrerGeoDataItems;
}
Or an even more cleaner way with IActionResult like that way:
// GET: api/TruckFahrerGeoData
[HttpGet]
public IActionResult GetTruckFahrerGeoData()
{
var truckFahrerGeoDataItems = new List<TruckFahrerGeoDataViewModel>();
var geodataItems = _context.TruckFahrerGeoData;
foreach (var truckFahrerGeoData in geodataItems)
{
GeoTelemetryData geoTelemetryData = JsonConvert.DeserializeObject<GeoTelemetryData>(truckFahrerGeoData.TelemetryData);
if (geoTelemetryData == null)
{
return BadRequest($"geoTelemetryData null for id: {truckFahrerGeoData.Id}");
}
TruckFahrerGeoDataViewModel truckFahrerGeoDataViewModel = new TruckFahrerGeoDataViewModel
{
Speed = geoTelemetryData.Speed,
Accuracy = geoTelemetryData.Accuracy,
TruckAppId = geoTelemetryData.Activity.TruckAppId,
TruckAuftragStatusId = geoTelemetryData.Activity.TruckAuftragStatusId,
ClId = geoTelemetryData.Activity.ClId,
TruckAuftragLaufStatusId = geoTelemetryData.Activity.TruckAuftragLaufStatusId,
TaskId = geoTelemetryData.Activity.TaskId,
TruckAuftragWorkflowStatusId = geoTelemetryData.Activity.TruckAuftragWorkflowStatusId
};
truckFahrerGeoDataItems.Add(truckFahrerGeoDataViewModel);
}
return Ok(truckFahrerGeoDataItems);
}
Late to the party but refining the answer .
Define your error response class with minimum below attributes
using Microsoft.AspNetCore.Http;
public class ErrorResponse
{
private readonly RequestDelegate next;
public ErrorResponse(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context )
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError;
string result = string.Empty;
object data = new object();
if (ex is ForbiddenException)
{
code = HttpStatusCode.Forbidden;
result = JsonConvert.SerializeObject(new Response<object>(Status.Forbidden(ex.Message), data));
}
else if(ex is BadRequestException){
code = HttpStatusCode.BadRequest;
result = JsonConvert.SerializeObject(new Response<object>(Status.BadRequest(ex.Message), data));
}
else if (ex is NotFoundException)
{
code = HttpStatusCode.NotFound;
result = JsonConvert.SerializeObject(new Response<object>(Status.NotFound(ex.Message), data));
}
else if (ex is UnauthorizedException)
{
code = HttpStatusCode.Unauthorized;
result = JsonConvert.SerializeObject(new Response<object>(Status.Unauthorized(ex.Message), data));
}
else
{
result = JsonConvert.SerializeObject(new Response<object>(Status.InternalServerError(ex.Message), data));
}
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
Next use this class as middleware in startup.cs class
app.UseHttpsRedirection();
app.UseMiddleware(typeof(ErrorResponse));
Now each request and response will go through this class,if an error occurs then error code will be set to true with error code. A sample response like below
data: {}
status: {
code: 404
error: true
message: "No employee data found"
type: "Not Found"
}
I had the same problem and after some research, I found out I could use HttpClient to call my API and read the response easily. HttpClient does not throw any error when the HTTP response contains an error code, but it sets the IsSuccessStatusCode property to false.
This is my function using the HttpClient. I call this from my controller.
public static async Task<HttpResponseMessage> HttpClientPost(string header, string postdata, string url)
{
string uri = apiUrl + url;
using (var client = new HttpClient())
{
//client.BaseAddress = new Uri(uri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", header);
HttpResponseMessage response = await client.PostAsync(uri, new StringContent(postdata));
return response;
}
}
This is my controller code, where I call the function and read the response and determine whether I have an error or not and respond accordingly. Note that I am checking the IsSuccessStatusCode.
HttpResponseMessage response;
string url = $"Setup/AddDonor";
var postdata = JsonConvert.SerializeObject(donor);
response = await ApiHandler.HttpClientPost(HttpContext.Session.GetString(tokenName), postdata, url);
//var headers = response.Headers.Concat(response.Content.Headers);
var responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
tnxresult = JsonConvert.DeserializeObject<TnxResult>(AppFunctions.CleanResponse(responseBody));
return Json(new
{
ok = true,
message = tnxresult.Message,
statusCode = tnxresult.StatusCode
});
}
else
{
ApiError rs = JsonConvert.DeserializeObject<ApiError>(AppFunctions.CleanResponse(responseBody));
return Json(new
{
ok = false,
message = rs.Message,
statusCode = rs.StatusCode
});
}
My API returns error messages in JSON. If the call is successful, I am packing the response in JSON too.
The crucial line of code is this one...
var responseBody = await response.Content.ReadAsStringAsync();
It serializes the HTTP content to a string as an asynchronous operation.
After that I can convert my JSON string to an object and access the error/success message and the Status Code too.

MVC request to Web Api

I'm trying to build an MVC that requests through a PCL to a WebApi. I am sending a get requests and getting stuck on the awaiting for the response. Postman returns the correct values. I also don t get exceptions on send. The 3 projects are both on the same solution.
PCL
HttpResponseMessage httpResponse = null;
try
{
httpResponse = await _http.GetAsync( "http://localhost:43818/api/values" );
}
catch (Exception e)
{
var meessage = e.Message;
var stack = e.StackTrace;
}
if (httpResponse.StatusCode == HttpStatusCode.OK)
{
string json = await httpResponse.Content.ReadAsStringAsync( );
}
So the issue is that in the PCL, it's doesn pass the await, it gets stuck.
MVC
var result = apiClient.GetIndex( );
Web Api
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Also, how do i wait in my MVC for the response before rendering the controller view
In your Class library (PCL), Create method GetIndex as below,
public async Task GetIndexAsync()
{
HttpResponseMessage httpResponse = null;
try
{
_http.BaseAddress = new Uri("http://localhost:43818/");
httpResponse = await _http.GetAsync("api/values");
}
catch (Exception e)
{
var meessage = e.Message;
var stack = e.StackTrace;
}
if (httpResponse.StatusCode == HttpStatusCode.OK)
{
string json = await httpResponse.Content.ReadAsStringAsync();
}
}
And In MVC calling method as below,
var result = apiClient.GetIndexAsync().Wait();
which solved both your problems.
Ok so i found the best sollution. Blocking threads is not a very good idea.
This is the fix
PCL
public async Task<HttpResponseMessage> Register()
{
HttpRequestMessage request = new HttpRequestMessage
{
RequestUri = new Uri( _http.BaseAddress, "account/register/" ),
Method = HttpMethod.Post,
Content = new StringContent( "{\"Email\": \"email#yahoo.com\",\"Password\": \"Password!1\",\"ConfirmPassword\": \"Password!1\"}",
Encoding.UTF8,
_contentType
),
};
HttpResponseMessage response = new HttpResponseMessage();
try
{
response = await _http.SendAsync( request, CancellationToken.None );
}
catch (Exception e)
{
Debugger.Break();
}
return response;
}
MVC Client
public async Task<ViewResult> Index( )
{
var thisTask = await Api.Register( );
return View();
}

Categories

Resources