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)
{
}
}
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:
- Method should not return
ActionResult
. With this, cyclic behaviour of interception can be avoided.
- Method should have only two parameters, viz.
InterceptorParasDictionary<string,object>
and
object
types.
- 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)
{
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.
[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;
}
}
[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.