Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to gracefully return exception from a WebApi Message Handler

I have a global ExceptionFilter in my Mvc\WebApi application registered as:

public virtual void RegisterHttpFilters(HttpConfiguration config)
{
    config.Filters.Add(new MyExceptionFilter(_exceptionHandler));
}

where MyExceptionFilter is:

public class MyExceptionFilter : ExceptionFilterAttribute
{
    private readonly IMyExceptionHandler m_exceptionHandler;

    public MyExceptionFilter(IMyExceptionHandler exceptionHandler)
    {
        m_exceptionHandler = exceptionHandler;
    }

    public override void OnException(HttpActionExecutedContext context)
    {
        Exception ex = context.Exception;
        if (ex != null)
        {
            object response = null;
            HttpStatusCode statusCode = m_exceptionHandler != null
                ? m_exceptionHandler.HandleException(ex, out response)
                : HttpStatusCode.InternalServerError;
            context.Response = context.Request.CreateResponse(statusCode, response ?? ex);
        }

        base.OnException(context);
    }
}

This filter returns all exceptions as json-objects and allows some IMyExceptionHandler-implementation to customize the object being returned.

All this works well. Till I have an exception in some of my message handlers:

public class FooMessageHandler : DelegatingHandler
{
    private readonly Func<IBar> _barFactory;
    public FooMessageHandler(Func<IBar> barFactory)
    {
        _barFactory = varFactory;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Properties["MY_BAR"] = _barFactory();
        return base.SendAsync(request, cancellationToken);
    }
}

As you can see this handler creates some component and put it into the current http request message. When an exception happens in FuncOfIBar then I get the Yellow Screen of Death. My ExceptionFilter isn't called.

I tried to specifically catch the exception in the message handler and return HttpResponseException but it doesn't change anything - still getting YSOD:

public class XApplicationInitializerMessageHandler : DelegatingHandler
{
    private readonly Func<IXCoreApplication> _appFactory;
    private readonly IXExceptionHandler m_exceptionHandler;

    public XApplicationInitializerMessageHandler(Func<IXCoreApplication> appFactory, IXExceptionHandler exceptionHandler)
    {
        ArgumentValidator.EnsureArgumentNotNull(appFactory, "appFactory");
        _appFactory = appFactory;
        m_exceptionHandler = exceptionHandler;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            request.SetApplication(_appFactory());
        }
        catch (Exception ex)
        {
            object resultObject = null;
            HttpStatusCode statusCode = m_exceptionHandler != null
                ? m_exceptionHandler.HandleException(ex, out resultObject)
                : HttpStatusCode.InternalServerError;
            HttpResponseMessage responseMessage = request.CreateResponse(statusCode, resultObject ?? ex);

            throw new HttpResponseException(responseMessage);
        }
        return base.SendAsync(request, cancellationToken);
    }
}

}

I want behavior of my app to be same regardless where an exception happens: in a ApiController or a message handler.

How to do this?

I know about Application_Error but I'd like to keep HttpApplication customization untouchable.

like image 945
Shrike Avatar asked Aug 08 '12 13:08

Shrike


4 Answers

Instead of throwing HttpResponseException I should just return an HttpResponseMessage:

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    try
    {
        request.SetApplication(_appFactory());
    }
    catch (Exception ex)
    {
        object resultObject = null;
        HttpStatusCode statusCode = m_exceptionHandler != null
            ? m_exceptionHandler.HandleException(ex, out resultObject)
            : HttpStatusCode.InternalServerError;
        HttpResponseMessage responseMessage = request.CreateResponse(statusCode, resultObject ?? ex);

        return Task<HttpResponseMessage>.Factory.StartNew(() => responseMessage);
    }
    return base.SendAsync(request, cancellationToken);
}
like image 78
Shrike Avatar answered Oct 21 '22 08:10

Shrike


What I have done with succes is using an attribute-based approach. You place an custom attribute on each of the actions that should used exception handling:

[ExceptionHandling] 
public IEnumerable<string> Get()

Your custom exception handling attribute is implemented like this:

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {}

As you can see you, the custom attribute is inherited from ExceptionFilterAttribute. You then just override the OnException method. What you then can do is look at the type of the exception and handle it according to business rules or what ever fits your needs. In some cases you might just swallow the exception on the server. If you want to notify the client, you can throw an HttpResponseException, which can hold all relevant information, such as message, call stack, inner exception, etc.

For more inspiration take a look at this great blog post: ASP.NET Web API Exception Handling

like image 44
Mads Tjørnelund Toustrup Avatar answered Oct 21 '22 08:10

Mads Tjørnelund Toustrup


I had the same issue with global exception filters not running. The solution was that Web API exception filters must be configured in a different global collection than ASP.NET MVC exception filters.

So to configure ASP.NET MVC error handling you would run:

System.Web.Mvc.GlobalFilters.Filters.Add(new MyMvcExceptionFilter());

But for Web API error handling you must run:

System.Web.Http.GlobalConfiguration.Configuration.Filters.Add(new MyWebApiExceptionFilter());

From my limited experience with Web API this type of situation is typical: superficially the patterns used will be very similar to ASP.NET MVC and so instantly familiar. This gives the illusion that writing one piece of code will have an effect on both frameworks, but actually their implementations underneath it all are largely or completely separate and you'll need to write two versions of the code that are very similar to make it all work.

Excellent reference on Web API exception handling: http://www.asp.net/web-api/overview/web-api-routing-and-actions/exception-handling

like image 5
Alex Avatar answered Oct 21 '22 08:10

Alex


In startup.cs:

config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());

I found that using GlobalExceptionHandler can catch the exception in delegating handler and return custom JSON objects.

like image 2
LMF Avatar answered Oct 21 '22 09:10

LMF