In my last blog post, I introduced you to the new Global Filters feature that is part of ASP.NET MVC 3 (Preview 1). Such filters run for every single request in the system.
The registration API makes it obvious that you're using a single instance of an object, so your filters should not be stateful (that is, since the single instance is shared through all requests, you will not be able to store any pre-request data inside the filter itself).
What you might not have realized is that, even with filter attributes, trying to keep per-request state on the filter itself might fail, because the CLR may choose to optimize the creation of your filter attribute so that there is only a single attribute instance per application, rather than creating a new instance every time it's asked for.
So, how do you store state for your filters?
Per-request state should probably go into HttpContext.Items. This collection is initialized to empty for every request, so anything you put into the collection will be eligible for garbage collection once the request is finished.
Per-user state should probably go into the user's session if it's transient for the session, or into a long-term store (like a database) if the data should be preserved across sessions.
There are plenty of examples of using session and databases out there, so I've cooked up an example filter with uses per-request state via HttpContext.Items. This simple filter can be used to add timing information for the execution of actions and rendering. It stores two items in HttpContext.Items: one stopwatch for timing the action, and one for timing the response rendering. Although it attaches the result to the end of HTML rendering just for the purposes of this demo, you could also have it write to the Debug console or render as an HTML comment so that it doesn't affect the layout of the page.
using System; using System.Diagnostics; using System.Web.Mvc; public class RequestTimingFilter : IActionFilter, IResultFilter { Stopwatch GetTimer(ControllerContext context, string name) { string key = "__timer__" + name; if (context.HttpContext.Items.Contains(key)) { return (Stopwatch)context.HttpContext.Items[key]; } var result = new Stopwatch(); context.HttpContext.Items[key] = result; return result; } public void OnActionExecuting(ActionExecutingContext filterContext) { GetTimer(filterContext, "action").Start(); } public void OnActionExecuted(ActionExecutedContext filterContext) { GetTimer(filterContext, "action").Stop(); } public void OnResultExecuting(ResultExecutingContext filterContext) { GetTimer(filterContext, "render").Start(); } public void OnResultExecuted(ResultExecutedContext filterContext) { var renderTimer = GetTimer(filterContext, "render"); renderTimer.Stop(); var actionTimer = GetTimer(filterContext, "action"); var response = filterContext.HttpContext.Response; if (response.ContentType == "text/html") { response.Write( String.Format( "<p>Action '{0} :: {1}', Execute: {2}ms, Render: {3}ms.</p>", filterContext.RouteData.Values["controller"], filterContext.RouteData.Values["action"], actionTimer.ElapsedMilliseconds, renderTimer.ElapsedMilliseconds ) ); } } }