Introduction
There are situations in which we have an implementation that will be reused in many places that are not confined to a single place or method. This is fulfilled by the Filters in MVC. This is a very good concept introduced in MVC. The implementation that is said above is called cross-cutting concerns. Thus, in simple terms, this adds extra logic to be implemented into the request being processed. Some of the examples of cross-cutting concerns are Authorization & Output Caching. Let's discuss the various types of filters and how to create them and use them.
Getting started
Let's first see a glimpse at how the filters are used.
namespace WebSecurityDemoTest.Filters
{
public class WebSecurityAuthorize : AuthorizeAttribute
{
protected bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
{
return false;
}
if (HttpContext.Current.Session["SessionHelper"] == null)
{
return false;
}
return true;
}
protected void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult("/");
base.HandleUnauthorizedRequest(filterContext);
}
}
}
The preceding snippet is an example of a CustomAuthorize attribute. This attribute is a kind of Filter only. Since this is defined here once and is used as an MVC attribute in many places, this needs to be checked at every controller level in every application where authorization is required. Now let's see if this would not have been the case, how explicitly we would have checked at every action level.
namespace WebDemo.Controllers
{
public class DemoController : Controller
{
//..instances or constructor if any read only variables used
public ActionResult Index()
{
if (!Request.IsAuthenticated)
{
FormsAuthenticationn.RedirectToLoginPage(); //MVC WebSecurity
}
//else rest implementation..
}
}
}
In the preceding snippet, as you can see, the Request.IsAuthenticated is checked at each action level since the authentication needs to be checked before letting the anonymous user access the action. Thus redundancy can be normalized using Filters by just using [Authorize] or [CustomAuthorize] at the controller/action level. Thus we can say Filters are attributes that help in adding an extra check at the routing level since this needs to be checked during the request processing only. Attributes are special classes derived from the namespace System. Attribute. Basically, there are four types of filters that the MVC framework supports. They are.
Filter Type |
Inheritance |
What this does |
Authorization |
IAuthorizationFilter |
Runs first during the request processing before any other filters or the action execution |
Action |
IActionFilter |
Runs before the action execution(Action level) |
Result |
IResultFilter |
Runs before or after the action execution(Action level) |
Exception |
IExceptionFilter |
Runs only when any action method or any other filter throws an exception |
Before the invocation of any action method, the framework first looks for any definition of any filter attributes, then moves into the action method execution that in turn gives an extra level of security to the application without writing redundant code. Let's look into each briefly.
Authorization Filter
This as the name suggests is required to provide an authorization level in the application. The default attribute is [Authorize]. Its constructor also can accept parameters like Roles [Authorize(Roles="Admin")] which is very handy as the roles-based application is very common nowadays. Authorization filters as specified in the table above run first before any filter's execution or even before the action method execution. The following snippet shows how the Interface from which it extends looks as in.
namespace System.Web.MVC
{
public interface IAuthorizationFilter
{
void OnAuthorization(AuthorizationContext filterCOntext);
}
}
The best thing is we can customize the Authorize attribute by extending our custom authorize class from the Authorize attribute class and manipulating our logic there as I have said in the very first snippet. A nice tip here is to keep the Custom Authorize attribute very simple. Built-in Authorize attribute also accepts the Users parameter like [Authorize(Users="Suraj", "Simon", "Mike")], which filters the action based on the user's access level. The use is also very simple. Either we can use it at the controller level or the action level. Just place [CustomAuthorize] before the controller class or the action methods. Action Filter The action filter can be used for any purpose, why as the interface it extends supports two methods to be invoked. As mentioned in the table, this filter may be executed before the Action execution but not first. Let's look at the interface.
namespace System.Web.MVC
{
public interface IActionFilter
{
void OnActionExecuting(ActionExecutingContext filterContext);
void OnActionExecuted(ActionExecutedContext filterContext);
}
}
As you can see in the preceding snippet, the interface has two methods to be defined, As the method names suggest, the first one OnActionExecuting tells us that before the action method has been invoked we can use this method, similarly the second one says that once the action method is executed this can be invoked. Now let's see each of them one by one.
OnActionExecuting Method
As has already been said, this method is invoked before the action begins execution. The filterContext of type ActionExecutingContext is ed since the parameter contains the following properties:
Name |
Description |
ActionDescriptor |
This provides the details of an action method |
Result |
This gives the result for the action method, the filter can be manipulated to cancel the request also |
Let's look into the implementation of this method, in a custom action filter attribute.
namespace Filters.WebDemoInfra
{
public class CustomActionFilter : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.IsLocal)
{
filterContext.Result = new HttpNotFoundResult();
}
}
}
}
Thus when we try and understand what we have written in the snippet above, we find that, before the action is executed, this method is invoked, and the method OnActionExecuting returns a not null result. In other words, HttpNotFoundResult will land the user on a 404 not found error page, even if the action has a view defined. This is the power of this filter. Now let's look into its sister's implementation.
OnActionExecuted
This method as defined previously, can be invoked to span the execution of the action method being processed based on the request. Let's check using an example that will measure the time taken for the action to complete the full execution.
namespace Filters.WebDemoInfra
{
public class CustomActionFilter : FilterAttribute, IActionFilter
{
private Stopwatch timer; // System.Diagnostics
public void OnActionExecuting(ActionExecutingContext filterContext)
{
timer = Stopwatch.StartNew();
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
timer.Stop();
if (filterContext.Exception == null)
{
filterContext.HttpContext.Response.Write(string.Format("Total Time taken: {0}", timer.Elapsed.TotalSeconds));
}
}
}
}
The preceding snippet is a very simple snippet to understand. The private variable is of type StopWatch which is a built-in class in the namespace System. Diagnostics. In the executing method, we are initializing the timer and after execution of the action method since the executed method is invoked, the httpContext that contains both the request and response as well, we assign the time elapsed in seconds to the response. Let's look at the properties provided by the OnActionExecuted method.
Name |
Description |
ActionDescriptor |
This provides the details of an action method |
Cancelled |
If in case the action has been canceled by any other filter this property returns true |
Exception |
If any filter or any action throws an exception, it is returned by this property |
ExceptionHandled |
If any filter or any action throws an exception and it has been handled, this property returns true |
Result |
This gives the result for the action method, the filter can be manipulated to cancel the request also |
Thus, this was all about the Action filters. Result Filter These are quite similar to the Action Filters. These are the filters that are invoked or operate on the results being produced by the Action Result methods. This is implemented from the IResultFiler interface which also looks quite similar to the IActionFilter. Let's see how.
namespace System.Web.MVC
{
public interface IResultFilter
{
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}
}
OnResultExecuting
The OnResultExecuting method is invoked when a result has been returned by the Action method but before the action is executed fully. This also has the same properties as the OnActionExecuting method as described in the table.
OnResultExecuted
The OnResultExecuted method is invoked after the action result has been executed. Here also the parameter filterContext contains the same properties as the OnActionExecuted method as described in the table above. The demonstration can be the same Timer as described in the Action filter section. The following snippet will show you the built-in Action and Result Filter classes.
public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
{
public virtual void OnActionExecuting(ActionExecutingContext filterContext)
{
}
public virtual void OnActionExecuted(ActionExecutedContext filterContext)
{
}
public virtual void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public virtual void OnResultExecuted(ActionExecutedContext filterContext)
{
}
}
Using Global Filters
This is a very nice feature that can be implemented in the MVC application. Global.asax as we all know is the heart of the MVC web application. The Application_Start method is called from here whenever the application starts. We have a FilterConfig.cs file inside our App_start folder, where we can find a static method called RegisterGlobalFilters. In this method, we register our custom filter that needs to be invoked throughout the application that is applied to all controllers and actions in the application. Let's see how we register.
namespace Filters
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new CustomAllAttribute());
}
}
}
Thus the second filter added/registered as the global filter is our custom attribute. This custom attribute implementation needs to be thought upon and then written since this would be used throughout the application, so this should also be very simple so that it can be manipulated and maintained at any point in time.
Filter Ordering
In the MVC framework, the order in which the filter is invoked (if there is more than one action), does not matter much. But even if you wish to add order based on the business logic we have, then we can use the Order keyword that accepts an int value to set the order of the Filter invocation/execution. Let's use an example and understand.
[ProfileA(Message = "First")]
[ProfileB(Message = "Second")]
public ActionResult Index()
{
//Implementation
}
In the preceding snippet, we have the two custom filters ProfileA and ProfileB that take a message as a parameter and print that using the context response write method. Thus, this would write "First" (For OnActionExecuting) "Second" (For OnActionExecuting) "Second" (For OnActionExecuted) "First" (For OnActionExecuted). Thus if we wish to modify the order like the "Second" would come first then the "First", then we need to use Order.
[ProfileA(Message = "First"), Order = 2]
[ProfileB(Message = "Second"), Order = 1]
public ActionResult Index()
{
//Implementation
}
Thus, here the output as in the previous will change based on the order specified in the preceding snippet. Thus based on the requirements we can specify as many filters, it be built-in or custom, and set the order of execution also. This is how flexible the MVC framework is. :)
Conclusion
Thus here I tried to explain the filters that are being used in the MVC framework. Here I have discussed Authorize, Action & Result filters. I will try and explain the Exception Filters in the next section. Thanks for your patience. Corrections and suggestions are humbly accepted. :)
References
- Adam Freeman: Pro ASP.NET MVC