Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uniform, consistent error responses from ASP.Net Web API 2

I'm developing a Web API 2 application and I'm currently trying to format error resposnes in a uniform way (so that the consumer will also know what data object/structure they can inspect to get more info about the errors). This is what I've got so far:

{   
    "Errors":
    [
        {
            "ErrorType":5003,
            "Message":"Error summary here",
            "DeveloperAction":"Some more detail for API consumers (in some cases)",
            "HelpUrl":"link to the docs etc."
        }
    ]
}

This works fine for exceptions thrown by the application itself (i.e inside controllers). However, if the user requests a bad URI (and gets a 404) or uses the wrong verb (and gets a 405) etc, Web Api 2 spits out a default error message e.g.

{
     Message: "No HTTP resource was found that matches the request URI 'http://localhost/abc'."
}

Is there any way of trapping these kinds of errors (404, 405 etc.) and formatting them out into the error response in the first example above?

So far I've tried:

  • Custom ExceptionAttribute inherting ExceptionFilterAttribute
  • Custom ControllerActionInvoker inherting ApiControllerActionInvoker
  • IExceptionHandler (new Global Error Handling feature from Web API 2.1)

However, none of these approaches are able to catch these kinds of errors (404, 405 etc). Any ideas on how/if this can be achieved?

...or, am I going about this the wrong way? Should I only format error responses in my particular style for application/user level errors and rely on the default error responses for things like 404?

like image 408
harman_kardon Avatar asked Jun 13 '14 14:06

harman_kardon


People also ask

How does ASP net handle Web API errors?

Using HttpError in ASP.NET Web API You can use the CreateErrorResponse extension method in your Web API controller method to return meaningful error codes and error messages. Note that the CreateErrorResponse method creates an HttpError object and then wraps it inside an HttpResponseMessage object.

What are the different ways to handle errors in Web API?

You can customize how Web API handles exceptions by writing an exception filter. An exception filter is executed when a controller method throws any unhandled exception that is not an HttpResponseException exception.

How does Web API handle 404 error?

A simple solution is to check for the HTTP status code 404 in the response. If found, you can redirect the control to a page that exists. The following code snippet illustrates how you can write the necessary code in the Configure method of the Startup class to redirect to the home page if a 404 error has occurred.

What is IHttpActionResult C#?

IHttpActionResult contains a single method, ExecuteAsync, which asynchronously creates an HttpResponseMessage instance. C# Copy. public interface IHttpActionResult { Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken); }


2 Answers

You can override the DelegatingHandler abstract class and intercept the response to the client. This will give you the ability to return what you want.

Here's some info on it. http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler(v=vs.118).aspx

Here's a poster of the Web Api pipeline that shows what can be overriden. http://www.asp.net/posters/web-api/asp.net-web-api-poster.pdf

Create a Handler class like this to override the response

public class MessageHandler1 : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Process request");
        // Call the inner handler.
        var response = base.SendAsync(request, cancellationToken);

        Debug.WriteLine("Process response");
        if (response.Result.StatusCode == HttpStatusCode.NotFound)
        {
            //Create new HttpResponseMessage message
        }
        ;
        return response;
    }
}

In your WebApiConfig.cs class add the handler.

config.MessageHandlers.Add(new MessageHandler1());

UPDATE As Kiran mentions in the comments you can use the OwinMiddleware to intercept the response going back to the client. This would work for MVC and Web Api running on any host.

Here's an example of how to get the response and change it as it goes to the client.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use(typeof(MyMiddleware)); 
    }
}

public class MyMiddleware : OwinMiddleware
{
    public MyMiddleware(OwinMiddleware next) : base(next) { }

    public override async Task Invoke(IOwinContext context)
    {
        await Next.Invoke(context);
        if(context.Response.StatusCode== 404)
        {
            context.Response.StatusCode = 403;
            context.Response.ReasonPhrase = "Blah";
        }
    }
}
like image 162
Dan H Avatar answered Oct 21 '22 03:10

Dan H


I have done in same way as @Dan H mentioned

 public class ApiGatewayHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            var response = await base.SendAsync(request, cancellationToken);
            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                var objectContent = response.Content as ObjectContent;
                return await Task.FromResult(new ApiResult(HttpStatusCode.NotFound, VmsStatusCodes.RouteNotFound, "", objectContent == null ? null : objectContent.Value).Response());
            }
            return response;
        }
        catch (System.Exception ex)
        {
            return await Task.FromResult(new ApiResult(HttpStatusCode.BadRequest, VmsStatusCodes.UnHandledError, ex.Message, "").Response());
        }

    }
}

Added routing like below and now it hits the try catch for invalid url

  config.Routes.MapHttpRoute(name: "DefaultApi",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional });
        config.Routes.MapHttpRoute(name: "NotFound", routeTemplate: "api/{*paths}", defaults: new { controller = "ApiError", action = "NotFound" });
like image 27
Ullas Avatar answered Oct 21 '22 01:10

Ullas