Health monitoring action filter for ASP.NET MVC

The ASP.NET MVC Framework lets Controller actions perform the actual logic for the coordination of work between the (domain) model and the views of the web application. The actions themselves can be filtered if you want to. There are a couple of sample implementations out there that show you how you can limit actions to particular HTTP verbs or use Role Based Security to only allow particular roles to execute an action.

Yet, filtering is not the only useful work that an action filter can do. Logging is one of them. This made me think of the health monitoring (more on that in my slidedeck on Health Monitoring in ASP.NET) and how it would be nice to see some WebEvent come along for every controller action that is called. I whipped up a small piece of code that I put into my Developer Days demo pack and will explain in this post.

First, a bit of background in case you are not familiar with action filters. Action filters get a chance to intercept the call to an action before or after the action has executed, – or -before or after an action result is executed. The first two moments are always available, the second two only when your controller action returns an ActionResult. The implementation of an action filter is performed in a special attribute class. The attribute can then be applied to all relevant actions or the controller class. In the latter case all actions will have that particular action filter automatically. Here’s an example for the HealthMonitoringActionFilter that we will create:

[HealthMonitoringActionFilter]

public ActionResult ShowJoke(int jokeID)

{

  Joke joke = repository.GetJokeByID(jokeID);

  if (joke == null)

    throw new ArgumentException(“Unknown ID for Joke”, “jokeID”);

 

  return RenderView(“ShowJoke”, joke);

}

An action filter is a ActionFilterAttribute derived class that can override a couple of methods corresponding to the four moments of interception:

  • OnActionExecuting (ActionExecutingContext)
  • OnActionExecuted (ActionExecutedContext)
  • OnResultExecuting (ResultExecutingContext)
  • OnResultExecuted (ResultExecutedContext)

Above you can see the argument type shown in italics. All context classes derive from ControllerContext and give access to the RouteData, Controller and HttpContext because of that. They also present you with properties for the ActionResult that comes from this controller action.

You can pick the moment of filter execution you desire and do your thing there. The _ing methods allow you to cancel the actual execution that would follow. The _ed methods are after the fact but offer additional information such as the exception (Exception property) that may have occurred and whether is was handled by the filter (ExceptionHandled property).

In our case we want to send out a health monitoring event whenever a controller action fires. To start, let’s create that event class, by deriving from System.Web.Management.WebManagementEvent and overriding the necessary methods.

public class MvcActionEvent : WebManagementEvent

{

  public string Action { get; set; }

  public Type Controller { get; set; }

 

  public const int MvcActionEventCode = WebEventCodes.WebExtendedBase + 1;

 

  public MvcActionEvent(string action, Type controller, string message, object source)

    : base(message, source, MvcActionEventCode)

  {

    Action = action;

    Controller = controller;

  }

 

  protected override void IncrementPerfCounters()

  {

    // Increase performance counters for actions

  }

 

  public override void FormatCustomEventDetails(WebEventFormatter formatter)

  {

    base.FormatCustomEventDetails(formatter);

    formatter.AppendLine(String.Format(“Controller: {0}”, Controller.Name));

    formatter.AppendLine(String.Format(“Action: {0}”, Action));

  }

}

The MvcActionEvent class has two additional properties for the current Action and controller type. I haven’t implemented the IncrementPerfCounters for real, but just imagine that some nifty counters get a bump of one. The extra properties are appended to the output of events details via the FormatCustomEventDetails method.

The last bit of code that we’ll need is the actual ActionFilter attribute. Since we want to raise the Mvc action event after the action has executed. Therefore, an override to the OnActionExecuted method is needed where the MvcActionEvent is raised.

public class HealthMonitoringActionFilterAttribute : ActionFilterAttribute

{

  public override void OnActionExecuted(ActionExecutedContext filterContext)

  {

    base.OnActionExecuted(filterContext);

 

    WebBaseEvent.Raise(new MvcActionEvent(filterContext.ActionMethod.Name,

      filterContext.Controller.GetType(),

      String.Format(“MVC action ‘{0}’ has executed”, filterContext.ActionMethod.Name),

      filterContext.Controller));

  }

}

At this point we do not care about the ActionResult being executed. If you would care about it, create another event class (e.g. MvcActionResultEvent), override the OnResultExecuted and raise the event. Simple as that.

There you go. Just apply this action filter attribute to whatever controller or action(s) you want. Remember to change your web.config to register the new event class and to create a new rule that uses it.

<system.web>

  <healthMonitoring heartbeatInterval=10 enabled=true>

 

    <eventMappings>

      <add name=MvcActionEvent type=HumorWebsite.MvcActionEvent, HumorWebsite startEventCode=0 endEventCode=2147483647 />

    </eventMappings>

 

    <providers>

      <remove name=SqlWebEventProvider/>

      <add name=SqlWebEventProvider connectionStringName=LocalSqlServer

    maxEventDetailsLength=1073741823 buffer=false bufferMode=Notification

    type=System.Web.Management.SqlWebEventProvider,System.Web,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a/>

    </providers>

 

    <rules>

      <clear/>

      <!–<add name=”Heartbeat Default” eventName=”Heartbeats” provider=”SqlWebEventProvider”/>–>

      <add name=Controller action events eventName=MvcActionEvent provider=SqlWebEventProvider />

    </rules>

 

  </healthMonitoring>

  …

</system.web>

I chose to use the SqlWebEventProvider, but any other event provider (read: sink) could have been used, obviously.

Full source code is available as part of the demo code of my Developer Days 2008 talk. If you are interested in some more Health monitoring, check out the slidedeck and accompanying demo code.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s