Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET MVC controller action with Interceptor pattern

0.00/5 (No votes)
2 Oct 2012 1  
This article is to demonstrate interceptor pattern with MVC controller action, and so action can be intercepted in controller classes without using action filters.

Introduction

In software systems, the ever increasing demands require them to perform beyond the original scope of the specification, so designing systems which can cope up with future diverse requirements is always preferable and a good design technique.

In this article, I will demonstrate a mechanism to intercept an MVC controller action without using action filters and how it’s useful in some scenarios, especially for applications following a pluggable architecture and satisfying diverse software requirements.

As we know there are action filters in ASP.NET MVC, and from version 3.0, Global Action Filters are available. With the action filter, pre-action and post-action logic can be handled. Such action filters can be implemented by decorating a filter attribute at the action method level or the controller class level.

A classic example of this is to use HandleErrorAttribute for handling errors thrown by action methods.   

[HandleError]
public class HomeController : Controller { }

Now consider a scenario, when an XYZ plug-in is added in the application and you want to divert AccountController LogOn action execution to your XYZ plug-in controller action, how can you do that without modifying the base implementation of the AccountController class? This can be done by using the interception mechanism.

I will describe this, but before that some background of action filters is necessary. 

Background

There are several filter attributes like AuthorizeAttribute, OutputCacheAttribute, etc. Apart from this, you can create your own action filter attribute by inheriting the ActionFilterAttribute class in your custom attribute class. 

You can register these attributes at a global level using System.Web.Mvc.GlobalFilterCollection, so these can be called for all action methods. 

void Application_Start()
{
    GlobalFilters.Filters.Add(new HandleErrorAttribute());
}

In the action filter attribute class, the IActionFilter interface is implemented. With this you can hook the action call in the virtual OnActionExecuting and OnActionExecuted methods of the action filter class and there you can put your pre-action and post-action logic, respectively. 

In ASP.NET MVC, additional flexibility to apply action filters can be achieved by using a filter provider. GlobalFilterCollection is nothing but a filter provider which holds entries for all global filters, and there are two other filter providers viz. FilterAttributeFilterProvider and ControllerInstanceFilterProvider. The same way you can have your own custom filter provider, with which you can use conditional filtering to apply conditional filters for an action or all controller actions.  

public class ConditionalFilterProvider : IFilterProvider
{
    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, 
            ActionDescriptor actionDescriptor)
    {
        //place here your logic for application of conditional filters.
    }
}

Then you can register this provider as:

var provider = new ConditionalFilterProvider(); 
FilterProviders.Providers.Add(provider);  

and you can also remove an existing provider.

var oldProvider = FilterProviders.Providers.Single(
                f => f is FilterAttributeFilterProvider
);
FilterProviders.Providers.Remove(oldProvider);

These filters get collected by using the method call FilterProviders.Providers.GetFilters from ControllerActionInvoker

Here is a very good article about filters and here you can check for conditional filters.

From all these filter descriptions, you will come to know that you can intercept an action call before and after by using a filter, but then why is there an interception mechanism? 

Interception mechanism 

As you know, it is always preferable to design a loosely coupled system considering future extensibility and when pluggable architecture is considered, such a design always has very high importance. As we discussed about filters, it gives a good amount of extensibility, however we need to do more changes (say manipulation) in the base classes every time a new filter needs to be introduced. 

I can figure out some limitations with using filters as given below (especially in module driven development (loosely coupled) and pluggable architecture):

  • You cannot intercept an action call in one controller from other controllers; to intercept, you need to define a filter attribute and decorate it for the action or controller
  • or

    you need to place some logic in your custom filter provider to apply such filters

    or

    you need to use some dependency injection mechanism by exposing some contract which can be consumed while intercepting.

    In all these scenarios, you need to modify the existing base (original) implementation of the controller and/or filter provider. 

  • You need to always place a pre-action or post-action logic in the OnActionExecuted or OnActionExecuting methods, there is no other easy way to do this.

To overcome these limitations, an easy way to intercept the action method call is the Interceptor pattern with ASP.NET MVC controllers.

Let’s talk about some basic things about the Interceptor pattern. The interception mechanism is based around three basic concepts: Matching rules, Call handlers, and Interceptors. 

  • Matching rule: Simple and flexible objects that determine which methods should have extra handling applied. Here, InterceptorsRegistry and ActionInterceptorAttribute classes are designed to define matching rules.
  • Interceptor calls: The main role is to dispatch calls to interceptors before and after execution of any method (in our case, action). For this, create a proper execution context with which changes in parameters and the result can be shared along the chain execution of interceptors. Here, BaseControllerInterceptorInvoker is an execution point from where the intercept dispatcher and execution context get prepared. InterceptMethodDispatcher is designed to play a role as a dispatcher. InterceptorExecutionContext is designed to hold the execution context.  
  • Interceptors: Actual methods which hook the call.

Below is a good article on the Interceptor pattern: http://www.edwardcurry.org/web_publications/curry_DEBS_04.pdf.

Implementation

Let’s go step by step to understand and implement the interception mechanism with an MVC controller. 

  • Matching rule: 
  • ActionInterceptorAttribute is an attribute class like FilterAttribute, the difference is, you have to put FilterAttribute before the action or controller on which it needs to be applied. However you have to decorate the ActionInterceptorAttribute attribute before the method which would act as the interceptor method. This is an inversion of control ‘Don’t call us, we’ll call you.’ After decorating any method with this attribute, you need to register the class in InterceptorsRegistry.

    There are the below rules to define the interceptor methods:

    1. Method should not return ActionResult. With this, cyclic behaviour of interception can be avoided.
    2. Method should have only two parameters, viz. InterceptorParasDictionary<string,object> and object types.
    3. There should be only one before and one after interceptor method in a class for one action.

    There are two ways to intercept an action with this attribute. By using the controller type for which this interceptor is defined:

    [ActionInterceptor(InterceptionOrder.Before, typeof(AccountController), "LogOn")]
    public void SubmitLogOn(InterceptorParasDictionary<string, object> paras, object result)

    and by using a unique view name or model name in the system:

    [ActionInterceptor(InterceptionOrder.After, "Account", "LogOn")]
    public object MyLogOn(InterceptorParasDictionary<string, object> paras, object result)

    Though there is a mechanism placed to get unique registered interceptors in the GetInterceptors method of InterceptorsRegistry. I would say, it would be better if you can go with only one way because going with one way (either by using controller type or view name) would prevent chances of duplication in the early stage of registration. 

    Here, InterceptionOrder is an enum which is used to specify the interceptor execution order, i.e., before or after an action. If not specified, then after is the default. 

    In ActionInterceptorAttribute, there is an optional parameter breakExecutionOnException, if it is set to true (default is always true) then on exception, it would terminate the execution from that point and all the following chain execution of interceptors including the action would be terminated.

    The ActionInterceptor class is used to hold interceptor information and along with that it is used to get registered in InterceptorsRegistry

  • Interceptors call:
  • Here is the class diagram, the basic thing with an MVC controller is that you can intercept any action through IActionInvoker, and that’s why the new BaseControllerInterceptorInvoker class is derived from the base ControllerActionInvoker.

    In this invoker, InvokeActionMethod is overridden to intercept an action. For the class which will be having interceptor, methods must be derived either from BaseMvcController or implemented with IInterceptorMvcController, and the controller class should be derived from BaseMvcController

    [ActionInterceptor(InterceptionOrder.Before, "Account", "LogOn")]
    public void SubmitLogOn(InterceptorParasDictionary<string, object> paras, object result)
    {
        //some logic
        this.InterceptorExecutionContext.CancelAllExecutions = true;
    }
  • Demo:
  • After creating the ASP.NET MVC 3 Web Application, reference to MvcCallInterceptors is added in the project.  

    To use this component and to intercept the HomeController Index method, the base class of HomeController is set to BaseMvcController, and the View property is overridden to specify the model or view this controller serves. 

    protected override string View
    {
        get { return "Home"; }
    }

One new class MyHomeAccountController is derived from BaseMvcController, below the interceptor is added for the HomeController Index method. 

public class MyHomeAccountController : BaseMvcController
{
    [ActionInterceptor("Home", "Index")]
    public object Index(InterceptorParasDictionary<string,object> paras, object result)
    {
        (result as ViewResult).ViewBag.Message = 
          (result as ViewResult).ViewBag.Message + " Hey, You have been intercepted.";
        return result;
    }
}

And the final step is to register the MyHomeAccountController class.

MvcCallInterceptors.Interceptors.InterceptorsRegistry.RegisterInterceptors<MyHomeAccountController>(); 

The same way, there are two interceptors created for the AccountController LogOn action in the same MyHomeAccountController class.

//Before : change in parameter value.
[ActionInterceptor(InterceptionOrder.Before, "Account", "LogOn")]
public void SubmitLogOn(InterceptorParasDictionary<string, object> paras, object result)
{
    if (paras.Count > 0)
    {
        (paras["model"] as LogOnModel).UserName = "***" + (paras["model"] as LogOnModel).UserName;
    }            
            
}

//After : Redirected to different view
[ActionInterceptor(InterceptionOrder.After, "Account", "LogOn")]
public object MyLogOn(InterceptorParasDictionary<string, object> paras, object result)
{
    if (paras.Count == 0)
    {
        return View("MyLogOn");
    }
    else
    {
        return result;
    }
}

With the interception mechanism, to verify whether filters are getting executed in their normal manner or not, there is commented code in the MyHomeAccountController Index method, uncomment it and check whether the call is getting passed to the HandleErrorAttribute filter for custom error handling. 

Along with the benefits of the Interceptor pattern, there is a drawback, the interceptor pattern increases the complexity in design. The more interceptors can hook into the system the more bloated its interface. The inherent openness of the pattern also introduces potential vulnerabilities into systems. With such an open design, malicious interceptors or simply erroneous ones may be introduced, resulting in system corruption or errors. 

I have some interesting thoughts to enhance this mechanism. 

  • We can place a lock in the InteceptorsRegistry class while registering any class for thread safety and which is necessary in a web application.  
  • Bypass duplicate registration of the interceptor class. 
  • Provide a Dependency Injection mechanism in the interceptor methods. (It’s a thought.)
  • To intercept all the methods in the controller by designing a controller level attribute.

I will try to implement the above things in the next revision of this article.

Point of interest

I learnt and got a chance to go more deeply in some of the below interesting concepts:

  • ASP.NET MVC framework.
  • Filter by using Dependency Injection with MVC Unity framework.
  • Interceptor pattern, Inversion of Control.
  • Reflection using expression trees.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here