Thursday, March 26, 2015

Web API 2 Message Handlers and Filters

Message Handlers

Custom Message Handlers


Custom message handlers are used for cross cutting concerns like logging or authorization.

Lets look at the Message Handler Pipeline we will be building:



HttpServer gets the initial message from the network. FooHandler and LogEverythingHandler are custom handlers created for our cross cutting concerns. HttpRoutingDispatcher functions as the last handler in the list responsible for sending the message to the HttpControllerDispatcher. The HttpControllerDispatcher then finds the appropriate controller and sends the message to it.

Let’s take a look at some code:

public class FooHandler : DelegatingHandler
{
    private readonly IBarProvider _provider;

    public FooHandler(IBarProvider provider)
    {
        _provider = provider;
    }

    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        System.Threading.CancellationToken cancellationToken)
    {           
        if (_provider.DoesRequestContainBar(request))
        {
            return await base.SendAsync(request, cancellationToken);
        }

        var response = request.CreateErrorResponse(HttpStatusCode.BadRequest, 
            "Request does not contain bar.");

        return response;
    }
}

The following 3 rules need to be followed to create a custom message handler:
  1. Your class must inherit from the DelegatingHandler class.
  2. You must implement the SendAsync class.
  3. You must call base.SendAsynch to pass the message to the next custom message handler.

If we wanted to we could create another class to log every request to our API.

In order to tell Web API that we want these custom message handlers added to the pipeline we need to add some code the WebApiConfig class.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new FooHandler());
        config.MessageHandlers.Add(new LogEverything());
    }
}

Execution Order

With our custom message handlers added to the pipeline the following becomes the order of execution:
  • HttpServer
  • FooHandler
  • LogEverything
  • HttpRoutingDispatcher
  • HttpControllerDispatcher

This illustrates how custom message handlers give us the ability to apply general rules to all the messages coming into our API’s.

Filters

This quote beautifully illustrates the difference between Handlers and Filters:

“The major difference between their two is their focus. Message Handlers are applied to all HTTP requests. They perform the function of an HTTP intermediary. Filters apply only to requests that are dispatched to the particular controller/action where the filter is applied.”

Filters get applied to the individual controllers using attribute tags above a class or method.

Global Exception Filters

We can add exception filters to controller methods.

Let’s take a look at the following Filter and corresponding Controller.

public class UnauthorizedFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        if (context.Exception is UnauthorizedException)
        {
            context.Response =
                new HttpResponseMessage(HttpStatusCode.Unauthorized);
        }
    }
}

public class ProductsController : ApiController
{
    [UnauthorizedFilterAttribute]
    public Baz GetBaz(int id)
    {
        throw new UnauthorizedException("This method is not authorized.");
    }
}

In the above example UnauthorizedFilterAttribute is decorating ProductsController’s GetBaz method.

This gives us a clean separation between exception handling and controller logic. This also allows us to reuse the exception filter.

However, we can also make our exception filters global.

public sealed class GlobalExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(
        HttpActionExecutedContext httpActionExecutedContext)
    {
        if (httpActionExecutedContext != null)
        {
            if (httpActionExecutedContext.Exception is FooException)
            {
                var fooException = 
                    httpActionExecutedContext.Exception as FooException;
                httpActionExecutedContext.Response = 
                    new HttpResponseMessage(fooException.Code)
                {
                    Content = new StringContent(fooException.Message)
                };
            }
            else
            {
                httpActionExecutedContext.Response =
                    new HttpResponseMessage(
                        HttpStatusCode.InternalServerError)
                {
                    Content = new StringContent("There was a problem!")
                };
            }
        }
    }
}

Our GlobalExceptionFilterAttribute inherits from the ExceptionFilterAttribute. It also has only one overridden method named OnException. This class handles the response that should be sent back to the client.

public sealed class GlobalLogFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(
        HttpActionExecutedContext httpActionExecutedContext)
    {
        if (httpActionExecutedContext != null)
        {
            Logger logger = new Logger();
            logger.Error("There was an exception: {0}",
                httpActionExecutedContext.Exception.Message);
        }
    }
}

In the case of the GlobalLogFilterAttribute we can handle logging errors.

We now have a mechanism to:
  • Log exceptions at the controller level
  • Handle Errors that arise from the controller level

If all of our controller methods have the same logging and client exception response needs we've saved lots of coding. We've also ensured that fixes to the logging and client exception response don’t get partially implemented across only some controllers.

How do these “Global” exceptions get applied?

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();

        config.Filters.Add(new GlobalLogFilterAttribute());
        config.Filters.Add(new GlobalExceptionFilterAttribute());
    }
}

Inside the WebApiConfig class the HttpConfiguration has a global filter list that can have custom Filters added to it.

2 comments:

  1. Great things you’ve always with us. Just keep writing this kind of posts.The time which was wasted in traveling for tuition now it can be used for studies.Thanks
    video and blog marketing

    ReplyDelete
  2. Good stuff, got little type "You must implement the SendAsync class", SendAsync is a method not a class.

    ReplyDelete