Anton's thoughts on consulting, project management and application development.

Update: You may also be interested in my post about creating a dashboard to review Mvc-Mini-Profiler logs. Source code available on Google Code.

I have recently integrated the mvc-mini-profiler tool into JobSeriously. One thing that JobSeriously does is make good use of ActionFilters to handle cross-cutting concerns. The more I thought about it the more I recognized a need to see the performance of those custom filters as part of the Mvc-mini-profiler stream. After all, what good is it to see an action that takes a long time only to find out that most of the time is spent in your new spiffy ActionFilter?

So, I set out on a path to automagically profile all of the ActionFilters (and ResultFilters) throughout my ASP.NET MVC application. The first logical step, is to come up with a wrapper class that can wrap an ActionFilterAttribute and attach profiling to it (without altering the implementation or imposing overhead while not profiling).

Due to some under-the-hood implementation choices by the MVC team, we have to wrap ActionFilterAttributes based on the interfaces they support. In this case, it’s IActionFilter and IResultFilter.

    public class ProfiledFilterWrapper : IActionFilter, IResultFilter
    {
        private readonly IActionFilter actionFilter;
        private readonly IResultFilter resultFilter;

        public ProfiledFilterWrapper(IActionFilter actionFilter)
        {
            this.actionFilter = actionFilter;
        }

        public ProfiledFilterWrapper(IResultFilter resultFilter)
        {
            this.resultFilter = resultFilter;
        }

        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
            using (MiniProfiler.StepStatic("Attribute: " + actionFilter.GetType().Name + ".OnActionExecuted"))
            {
                actionFilter.OnActionExecuted(filterContext);
            }
        }

        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            using (MiniProfiler.StepStatic("Attribute: " + actionFilter.GetType().Name + ".OnActionExecuting"))
            {
                actionFilter.OnActionExecuting(filterContext);
            }
        }

        public void OnResultExecuted(ResultExecutedContext filterContext)
        {
            using (MiniProfiler.StepStatic("Attribute: " + resultFilter.GetType().Name + ".OnResultExecuted"))
            {
                resultFilter.OnResultExecuted(filterContext);
            }
        }

        public void OnResultExecuting(ResultExecutingContext filterContext)
        {
            using (MiniProfiler.StepStatic("Attribute: " + resultFilter.GetType().Name + ".OnResultExecuting"))
            {
                resultFilter.OnResultExecuting(filterContext);
            }
        }
    }

Now that we have the AttributeWrapper, we need a way to wire it up. Fortunately, the MVC team made this relatively easy by providing the ability to customize the ControllerActionInvoker. Our next step is to create an ActionInvoker that will wrap all ActionFilterAttributes with our wrapper class.
Note: We certainly don’t want to wrap things more than once or wrap the actual ProfilingActionFilter that you may be using from the mvc-mini-profiler. That’s what those LINQ operations in this class are doing.

    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Mvc;

    using MvcMiniProfiler.MVCHelpers;

    /// <summary>
    ///	Custom ControllerActionInvoker that wraps attributes for profiling with MvcMiniProfiler
    /// </summary>
    public class ProfiledActionInvoker : ControllerActionInvoker
    {
        protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            FilterInfo baseFilters = base.GetFilters(controllerContext, actionDescriptor);

            WrapFilters(baseFilters.ActionFilters);
            WrapFilters(baseFilters.ResultFilters);

            return baseFilters;
        }

        private void WrapFilters(IList<IActionFilter> filters)
        {
            // Not tested with attribute ordering (may not honor the ordering)
            IList<IActionFilter> originalFilters = filters.ToList();

            // Avoid wrapping the ProfilingActionFilter (sometimes injected by MVC Mini Profiler) and Attributes that are already wrapped.
            IEnumerable<IActionFilter> wrappedFilters = originalFilters
                       .Where(t => t.GetType() != typeof(ProfiledFilterWrapper)
                              && t.GetType() != typeof(ProfilingActionFilter))
                       .Select(item => new ProfiledFilterWrapper(item));

            IEnumerable<IActionFilter> unwrappedFilters = originalFilters
                       .Where(t => t.GetType() == typeof(ProfiledFilterWrapper)
                              || t.GetType() == typeof(ProfilingActionFilter));

            filters.Clear();

            foreach (IActionFilter actionFilter in wrappedFilters)
            {
                filters.Add(actionFilter);
            }

            foreach (IActionFilter actionFilter in unwrappedFilters)
            {
                filters.Add(actionFilter);
            }
        }

        private void WrapFilters(IList<IResultFilter> filters)
        {
            // Not tested with attribute ordering (may not honor the ordering)
            IList<IResultFilter> originalFilters = filters.ToList();

            // Avoid wrapping the ProfilingActionFilter (sometimes injected by MVC Mini Profiler) and Attributes that are already wrapped.
            IEnumerable<IResultFilter> wrappedFilters = originalFilters
                       .Where(t => t.GetType() != typeof(ProfiledFilterWrapper)
                              && t.GetType() != typeof(ProfilingActionFilter))
                       .Select(item => new ProfiledFilterWrapper(item));

            IEnumerable<IResultFilter> unwrappedFilters = originalFilters
                       .Where(t => t.GetType() == typeof(ProfiledFilterWrapper)
                              || t.GetType() == typeof(ProfilingActionFilter));

            filters.Clear();

            foreach (IResultFilter actionFilter in wrappedFilters)
            {
                filters.Add(actionFilter);
            }

            foreach (IResultFilter actionFilter in unwrappedFilters)
            {
                filters.Add(actionFilter);
            }
        }
    }

Now that we have a way to wrap all of the attributes, we need some way to hook our ActionInvoker into the MVC pipeline. To do that, we need to create a custom ControllerFactory that will create use our ActionInvoker instead of the default one. The implementation below wraps the existing controller factory and swaps out the ActionInvoker. The reason I chose to create a wrapper rather than a concrete implementation is that I want to be able to use a controller factory from my IoC provider (Ninject). This pass-through approach gives us the best of both worlds.

    using System;
    using System.Web.Mvc;
    using System.Web.Routing;
    using System.Web.SessionState;

    /// <summary>
    /// A wrapper ControllerFactory which can be used to profile the performance of attributes in MVC.
    /// </summary>
    public class PerformanceControllerFactory : IControllerFactory
    {
        readonly IControllerFactory controllerFactory;

        public PerformanceControllerFactory(IControllerFactory controllerFactory)
        {
            this.controllerFactory = controllerFactory;
        }

        public IController CreateController(RequestContext requestContext, string controllerName)
        {
            var controller = controllerFactory.CreateController(requestContext, controllerName);

            var normalController = controller as Controller;
            if (normalController != null)
            {
                normalController.ActionInvoker = new ProfiledActionInvoker();
            }

            return controller;
        }

        public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
        {
            return controllerFactory.GetControllerSessionBehavior(requestContext, controllerName);
        }

        public void ReleaseController(IController controller)
        {
            controllerFactory.ReleaseController(controller);
        }
    }

With everything nicely wrapped and ready to go, we just need to add 2 lines of code to the App_Start procedure of the ASP.NET MVC application to hook in our custom ControllerFactory. I would suggest you place this code at the end of your App_Start to ensure any other code you have is able to hook in what it needs first.

IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();

ControllerBuilder.Current.SetControllerFactory(new PerformanceControllerFactory(factory));
About these ads

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: