Uniform, consistent error responses from ASP.Net W

2020-02-25 07:24发布

问题:

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?

回答1:

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";
        }
    }
}


回答2:

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" });