Sunday, November 24, 2013

Creating an application context for a .NET MVC and Web API application

When I worked with SharePoint there was a useful object that would hold contextual data for the current request that was being processed, the SPContext.Current object. It would contain all sorts of useful SharePoint specific contextual info. Recently I was building a new ASP.NET MVC website, including a Web API portion, and wanted to create a similar concept in this custom website. The application being built receives certain information from request headers on each request, looks up data in a database based on this info and makes it easily available to all the code running during that request. I wanted to solve the 'easily available' requirement through just such a contextual object. I also wanted to make this work equally for the MVC as well as the API controllers which process the HTTP request a little differently. I thought it would have been done before, but it took some digging and experimentation to come up with a clean solution, so I thought I'd share it.

In brief, I will create a class that will hold the context data, and have one static property called Current. The Current property actually stores its data in the HttpContext.Current.Items collection (see Hanselman's discussion on why). Any code in that request can then use AppContext.Current.Data to get at this common data.

Step 1 : Create the AppContext


public class AppContext
{
    private const string APP_CONTEXT = "AppContext";

    // TODO: Update this with as many fields as needed
    public string Data { get; private set; }
    public AppContext(string Data)
    {
        this.Data = Data;
    }

    public static AppContext Current
    {
        get { return (AppContext)HttpContext.Current.Items[APP_CONTEXT] ; }
        set { HttpContext.Current.Items[APP_CONTEXT] = value; }
    }
}

The AppContext class holds the common data that needs to be accessible easily and quickly to any code running durring this request. This example only has one property, Data, but you can expand the AppContext to have as much complexity as needed. Notice the private setter on the property, the idea is that an AppContext is created once and not changed.

Step 2 : Create the Action filters


using System.Web.Mvc;

namespace JoeApp.ActionFilters.Web
{
    public class AppContextFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            AppContext.Current = new AppContext(filterContext.RequestContext.HttpContext.Request.Headers["MyData"]);
        }
    }
}

The first action filter is meant to be used by the MVC code. This filter inherits from the base class in the System.Web.Mvc namespace. The next action filter is meant for the Web API, and inherits form the base class in System.Web.Http.Filters. Each of these filters is reading a value from the Request headers, and each is doing so a little differently, according to the pipeline they execute in. The important thing to notice is that they both store the value in AppContext.Current by creating a new instance of the AppContext. In the real app I worked on, the code in these filters called a common helper function that did some database work and some other logic before creating the AppContext, so this can me much more complex than saving a string. Notice that the filters have the same class name, but live in a different namespace. I chose this to stay consistent with how Microsoft implemented the base classes.
using System.Web.Http.Filters;

namespace JoeApp.ActionFilters.Http
{
    public class AppContextFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            AppContext.Current = new AppContext(actionContext.Request.Headers.GetValues("MyData").FirstOrDefault());
        }
    }
}

Step 3 : Register the filters


In order for the filters to be used on every request, they need to be globally registered. The easy way to do so is in the global.asax, or rather in the config classes it calls. For the web filter it's the FilterConfig class:
 public class FilterConfig
 {
     public static void RegisterGlobalFilters(GlobalFilterCollection filters)
     {
         filters.Add(new HandleErrorAttribute());
         filters.Add(new AppContextFilter());
     }
 }

For the api filter it's the WebApiConfig class:
public static void Register(HttpConfiguration config)
{
    // Web API configuration and services

    // Web API routes
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    config.Filters.Add(new AppContextFilter());
}

In each of these, I only added the one line that adds the filter to the filters collection.
Last step : Use the context
Now you can use the context in any of your code, and since your action filter exeutes on each request, the context will be properly populated each time. In the following example, the AppContext.Data contained the same data I passed in the request header called "MyData":
The controller action method:
        public ActionResult Index()
        {
            ViewBag.Data = AppContext.Current.Data;
            return View();
        }

The view:
<h2>Header was: @ViewBag.Data</h2>

The result

In not so many lines of code and a few simple classes, you can create a very handy construct to carry request wide data throughout your app. It's a concept used by Microsoft in a number of places (HttpContext, SPContext, etc) and so will be easily recognized by other developers.

Happy coding!