Practical .NET

Adding Custom Processing to Requests in ASP.NET

When a request comes in to your ASP.NET site, it's routed through a series of message handlers (in ASP.NET Web API) or modules (in ASP.NET MVC), each of which performs some operation on the request. After a request is processed (presumably, by one of your Controllers), the response from your request goes through those handlers or modules again on its way back to the client.

Putting code in a handler or module allows you to perform some operation on every inbound request or outbound response. So, for example, if you want to customize security for your site, a good way to do that is to add your own module or handler to this chain. Alternatively, if you wanted to check data leaving your Web API site for "sensitive" information, a module or handler would be a good choice for that task, also.

The Limitations of ASP.NET Modules
Of the two technologies, ASP.NET MVC's HttpModules are the most limited. In many ways, modules are legacy technology dating from the beginnings of ASP.NET. However, this is the only option if you really do want to process every request to your site (including, for example, requests for image files and CSS files). You can even use HttpModules in Web API application (I've used them to handle Web API authentication, for example).

If, however, you're only interested in processing requests for ASP.NET controllers, you'll probably be better off with the technologies I discussed in an earlier column. The main limitation of modules is that you're severely restricted in what you can do.

The problem here is that the methods in a module are passed an HttpApplication object that has Context, Request and Response properties. These properties give you access to information about the request being made to your site and the response your site is returning. These are the same objects you have access to in your Controllers and, as in your Controllers, most of the properties on these objects are read-only. So, in an HttpModule you're limited to reading the incoming request or response or adding/removing headers on them.

Creating an HttpModule
Creating a module is a bit of a pain, also. First, you need to declare a class that implements the IHttpModule interface and give it a property called ModuleName that returns the name of your class as a string. You'll also need a Dispose method. Here's the start of a typical module:

public class GenericHttpModule : IHttpModule
{
  public String ModuleName
  {
    get 
    { 
      return "GenericHttpModule"; 
    }
  }
  public void Dispose() { }

Your next step is to add the Init method, which returns nothing but accepts an HttpApplication object. In this method, to process incoming requests, you need to wire up a method of your own to the HttpApplication object's BeginRequest method; if you want to process the outbound Response, you'll wire up your method to the object's EndRequest method. Here's an Init method that does both:

public void Init(HttpApplication application)
{
  application.BeginRequest += (new EventHandler(Inbound));
  application.EndRequest += (new EventHandler(Outbound));
}

The signatures of both the BeginRequest and EndRequest methods are the same: The methods are passed two parameters, one of type object and one of type EventArgs. The first parameter is the interesting one because it holds a reference to the HttpApplication object that holds the Context, Request and Response properties. The two methods I would need to work with the code in my Init method would look like this:

private void Inbound(Object source, EventArgs e)
{
  HttpApplication application = (HttpApplication)source;
  // ... process inbound request ... 
}
private void Outbound(Object source, EventArgs e)
{
  HttpApplication application = (HttpApplication)source;
  // ... process outbound request ...             
}

You have one final thing to do: To have ASP.NET actually use your module, you need to tell your application about it. You do that in your web.config file with an add element, inside its modules element (IIS 7.0) or its httpModules element (IIS 6.0/IIS 7.0 running in Classic mode). The add element must reference both the name of your class and its type. This element would tie my sample module into the chain in IIS 7.0 (and would do the same in the httpModules element):

<modules> <add name="GenericHttpModule" type="HttpModulesAPI.GenericHttpModule"/>

The Basics of Handlers
Creating an ASP.NET Web API handler is, comparatively speaking, much simpler.

First, you must create a class that inherits from DelegatingHandler. Once you've done that, you override your class's SendAsync method. When a request hits your site, your SendAsync method will be passed the incoming request as an HttpRequestMessage. You have more flexibility here than you do with modules: you can add or remove headers or replace the message's content.

When you've done whatever you want with the incoming request, you call the base class's SendAsync method, passing the request message (there's also a cancellation token involved but I'll ignore it for simplicity's sake). Calling the base SendAsync method passes the request on to the next handler in the chain and, eventually, to your controller.

After your Controller has processed your request, the response message will be returned back through the chain of handlers as an HttpResponseMessage object. That means that your call to the base SendAsync method will, eventually, return the response from your Controller to your handler. Again, you can add or remove headers or replace the message's Content before returning the message to the ASP.NET process that called your delegating handler in the first place. Eventually, that response message will be delivered to the client that made the original request.

Here's the skeleton of a typical handler:

public class GenericMessageHandler : DelegatingHandler
{
  protected async override System.Threading.Tasks.Task<HttpResponseMessage>
          SendAsync(HttpRequestMessage request,
          System.Threading.CancellationToken cancellationToken)
  {
    //...work with HttpRequestMessage...
    HttpResponseMessage resp = await base.SendAsync(request, cancellationToken);
    //...work with HttpReqponseMessage
    return resp;
  }
}

And, in fact, you don't have to call the base SendAsync method at all -- there's nothing stopping you from creating an HttpResponseMessage in your SendAsync method and returning that.

Adding Your Handler to the Pipeline
To have your application use your handler, go to your App_Start folder, open the WebApiConfig file and add your new handler class to the config parameter's MessageHandlers class. This code, for example, adds my handler to the pipeline:

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

But I have to be honest here: My experience has been that (outside of security) there are very few operations that I want to perform on every request to my ASP.NET Web API site. As a result, my typical handler begins with a bunch of If statements that check to see if this is a request that my handler should work with. In those scenarios, ASP.NET Web API gives me alternative: I can add the relevant handler just to the specific routes where it's needed.

To do that, I go to ASP.NET Web API's WebApiConfig file in the App_Start folder and add a fifth parameter to the MapHttpRoute method used to define routes (to use this parameter, you must provide a value for the constraints parameter on the method, even if all you provide is null). The handler parameter allows me to specify a DelegatingHandler to be used in processing requests and responses in that route. Here's an example with my GenericMessageHandler added to a route that grabs requests for my Customer controller (as this code shows, to tie my handler into the processing pipeline I also have to set its InnerHandler property to HttpControllerDispatcher):

config.Routes.MapHttpRoute(
                name: "CustomerApi",
                routeTemplate: "api/Customer/{id}",
                defaults: new { id = RouteParameter.Optional },
                constraints: null,		
                handler: new GenericMessageHandler(){InnerHandler = new HttpControllerDispatcher(config) }
            );

I can, of course, selectively add this handler to multiple routes.

So if you want to add processing to every request that your site gets (or even just some of them), then you don't have to add code to every Controller or Action method in your project. You can, instead, bundle that code into either a module (for ASP.NET MVC) or a handler (for ASP.NET Web API).

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

Subscribe on YouTube