ASP.NET MVC 3–Global Action Filters

23 Jul
2011

In one of my previous blog post I mentioned about ASP.NET MVC action filters.

Action filters provides us to ability create an aspect oriented approach in ASP.NET MVC application. It’s not the only way off course, IoC containers provide this ability too. Specially in enterprise level applications you can use IoC containers to create wrappers around your objects with creating interceptors. Action filter is to easy way to have a cross cutting aspect approach for your controllers in ASP.NET MVC.

ASP.NET MVC 3 has a new action filter type called Global Action Filters. You don’t need to decorate your all controllers, action methods with attributes with Global Action Filters. ASP.NET MVC pipeline invokes your action filter before/after events after you defined your Global Action Filter you just need to register it in one location. So you can create your aspect oriented approach around your controllers/action methods.

To create an Global Action Filters you just need to define a new class which inherits from ActionFilterAttribute

public class MvcProfilerGlobalAttribute : ActionFilterAttribute
{
//...
}

And registration point for your Global Action Filter is Application_Start() method in your Global.asax.cs file or your custom HttpModule implementation.

protected void Application_Start()
{
    //...
    GlobalFilters.Filters.Add(new MvcProfilerGlobalAttribute());
}

I needed to profile and trace all request and find requests which take longest time, requests which run highest cost sql queries and that kind of information in one of my previous enterprise scale service oriented project. I needed this because big team of developers were working on project and it was really hard to review each sql queries because it was a project in production so I found this profiling option is the best way to find service endpoints that needs refactoring and optimization improvements. It was years ago and it was a WCF, Remoting supported framework and I ended up with an interceptor implementation in service side but in this blog post I want to implement with Global Action Filter to demonstrate its capacity. This example is not profiling sql queries but it is open for improvements, this is just an example about Global Action Filters.

I wanted to create a custom profiler that can log request and response information about requests with a Global Action Filter and so you can trace all request to your ASP.NET MVC controllers and get some statistical information about request. I will try to profile requested url with query string information, execution time, returned http status code, request and response headers, and client ip for each request. For this sample I will just use a in-memory repository to to save profile information. 

Let’s create an action filter for this purpose.

  public class MvcProfilerGlobalAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var httpContext = filterContext.RequestContext.HttpContext;
            var httpRequest = httpContext.Request;
            var inputStream = httpRequest.InputStream;
            var requestBody = new StreamReader(inputStream).ReadToEnd();
            inputStream.Seek(0, SeekOrigin.Begin);
            
            var profileData = ProfilerContext.Current;
            profileData.RequestData = requestBody;
            profileData.RequestHeaders = httpRequest.Headers.ToString();
            profileData.QueryStringData = httpRequest.QueryString.ToString();
            profileData.ClientIp = httpRequest.UserHostAddress;
            if (httpRequest.Url != null)
                profileData.Url = httpRequest.Url.ToString();

            base.OnActionExecuting(filterContext);
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var httpContext = filterContext.RequestContext.HttpContext;
            var httpResponse = httpContext.Response;
            
            var profileData = ProfilerContext.Current;
            profileData.ResponseHeaders = httpResponse.Headers.ToString();
            profileData .EndTime = DateTime.UtcNow;
            ProfilerContext.Current.Persist();
            base.OnActionExecuted(filterContext);
        }

    }

ProfileData and ProfilerContext classes as helpers, I don’t need to explain basic C#/ASP.NET code here.

 public class ProfileData
    {
        public string Url { get; set; }
        public string RequestData { get; set; }
        public string ResponseHttpStatusCode { get; set; }
        public string RequestHeaders { get; set; }
        public string ResponseHeaders { get; set; }
        public string QueryStringData { get; set; }
        public string ClientIp { get; set; }
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }
  public class ProfilerContext : IDisposable
    {
        private readonly IProfilerLogRepository repository;
        private const string ProfilerItemKey = "DEVELOQPROFILER";
        private readonly Guid profilerContextIdentifier;
        private readonly DateTime started;

        public ProfileData ProfileData { get; private set; }

        public ProfilerContext() : this(new InMemoryProfilerLogRepository()) { }

        public ProfilerContext(IProfilerLogRepository repository)
        {
            this.repository = repository;
            profilerContextIdentifier = Guid.NewGuid();
            started = DateTime.UtcNow;
            ProfileData = new ProfileData();
        }

        public void Persist()
        {
            repository.Add(ProfileData);
        }

        public void Dispose()
        {
            Persist();
        }

        public static ProfilerContext Current
        {
            get
            {
                var context = HttpContext.Current;
                if (context == null) return null;
                InitCurrentProfiler();
                return context.Items[ProfilerItemKey] as ProfilerContext;
            }
            private set
            {
                var context = HttpContext.Current;
                if (context == null) return;

                context.Items[ProfilerItemKey] = value;
            }
        }

        private static void InitCurrentProfiler()
        {
            var context = HttpContext.Current;
            if (context == null) return;
            if (context.Items[ProfilerItemKey] as ProfilerContext != null) return;

            Current = new ProfilerContext();
        }


    }

And I created a basic repository to provide persistency but it just adds ProfileData objects in memory by using a static generic list. Writing a repository that can persists objects in your datastore is one of the step that you need to complete if you want to extend and use this sample in your projects.

public interface IProfilerLogRepository
    {
        void Add(ProfileData profileData);
        List GetAll();
    }
public class InMemoryProfilerLogRepository : IProfilerLogRepository
    {
        private static List profileLogs;

        public InMemoryProfilerLogRepository()
        {
            if (profileLogs == null)
                profileLogs = new List();
        }

        public void Add(ProfileData profileData)
        {
            profileLogs.Add(profileData);
        }

        public List GetAll()
        {
            return profileLogs;
        }
    }

After you created this files in your project you just need to update your Global.asax.cs file to register Global Action Filter as I showed in the beginning of this post.

I need to explain two points in this code snippets, Global Action Filter provides two methods us (but not limited, for more information read my previous post) as OnActionExecuting, OnActionExecuted. We can override them and access current request related information.

In this sample I created a profiler and haven’t changed anything about current request. But one of the most popular usages of Global Action Filters is doing some custom authentication and authorization settings. If you are interested about doing an security check with your Global Action Filters, I strongly recommend to check FluentSecurity project before you start to implement your own.



1 Response to ASP.NET MVC 3–Global Action Filters

Avatar

Berke Sökhan

April 2nd, 2012 at 10:52 am

Was looking for a global filters ref and the found this post from Google, kudos for the Gatherer link! :) )

I didnt know that you were a Planeswalker, Cengiz. Pity that we didn’t make any duels when we work together.

Comment Form

top