Click here to Skip to main content
15,881,413 members
Articles / Web Development / ASP.NET

ASP.NET MVC controller action with Interceptor pattern

Rate me:
Please Sign up or sign in to vote.
4.40/5 (4 votes)
2 Oct 2012CPOL8 min read 67.6K   924   24  
This article is to demonstrate interceptor pattern with MVC controller action, and so action can be intercepted in controller classes without using action filters.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Web.Mvc;
using MvcCallInterceptors.Controllers;

namespace MvcCallInterceptors.Interceptors
{
    /*
     * InterceptorsRegistry is a class which takes care of registration of interceptor methods, 
     * and get interceptors methods for a method call in the controller.
     */
    public sealed class InterceptorsRegistry
    {
        static Dictionary<string, List<ActionInterceptor>> Interceptors = new Dictionary<string, List<ActionInterceptor>>();

        /// <summary>
        /// This method registers all interceptor methods in the given class type which are decorated with ActionInterceptorAttribute.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public static void RegisterInterceptors<T>() where T : IInterceptorMvcController
        {
            //Challenge to stop recursive call if they are there

            var interceptedByViewControllerType = typeof(T);
            var members = interceptedByViewControllerType.FindMembers(MemberTypes.Method,
            BindingFlags.Instance | BindingFlags.Public, new MemberFilter(SearchForInterceptorAttribute),
            typeof(ActionInterceptorAttribute));

            foreach (var member in members)
            {
                //Register either with 
                var attribute = member.GetCustomAttributes(typeof(ActionInterceptorAttribute), false)[0] as ActionInterceptorAttribute;
                var methodInfo = member as MethodInfo;
                // To restrict recursive calling this is the simplest way I found and ActionResult return type is kind of 
                // overhead to pass result to subsequent interceptors and convert to object again.
                if(methodInfo.ReturnType==typeof(ActionResult) || methodInfo.ReturnType.IsSubclassOf(typeof(ActionResult)))
                {
                    throw new Exception("ActionResult methods cannot be act as interceptor");
                }

                if (methodInfo.GetParameters().Length !=2 || methodInfo.GetParameters()[0].ParameterType!=typeof(InterceptorParasDictionary<string,object>) ||
                    methodInfo.GetParameters()[1].ParameterType != typeof(object))
                {
                    throw new Exception("Interceptor method should have only two parameters namely InterceptorParasDictionary<string,object> and object types");
                }
                var key = ((attribute.InterceptionForViewControllerType == null ? attribute.ViewName.ToString() : attribute.InterceptionForViewControllerType.FullName)
                        + ";" + attribute.ActionName).ToLower();

                // In one interceptor (or interceptor controller) class if somebody tries to register more than one methods as interceptors for one action method
                // of other controller then it's not possible, such kind of scenario should not be happened and can be restricted by registering interceptor methods 
                // by using one thing only i.e. either ViewName or InterceptionForViewController but not both.
                // And below condtion would take care of this.

                
                var actionInterceptor = new ActionInterceptor(interceptedByViewControllerType, attribute, member as MethodInfo);
                if (Interceptors.ContainsKey(key) && Interceptors[key].All((inc)=>inc.Equals(actionInterceptor)))
                {
                    throw new Exception(string.Format("Duplicate interceptor entry for action {0} in {1}.",attribute.ActionName,
                        attribute.InterceptionForViewControllerType==null?"View Name "+  attribute.ViewName : "Controller "+  attribute.InterceptionForViewControllerType.FullName ));
                }

                if (!Interceptors.ContainsKey(key) || Interceptors[key] == null)
                {
                    Interceptors[key] = new List<ActionInterceptor>();
                }

                Interceptors[key].Add(actionInterceptor);
            }
        }

        public static IEnumerable<ActionInterceptor> GetInterceptors(BaseMvcController controller, ActionDescriptor actionDescriptor)
        {
            var key1 = (controller.GetType().FullName + ";" + actionDescriptor.ActionName).ToLower();
            var key2 = (controller.ViewName + ";" + actionDescriptor.ActionName).ToLower();
            var result1 = new List<ActionInterceptor>();
            var result2 = new List<ActionInterceptor>();
            if (InterceptorsRegistry.Interceptors.ContainsKey(key1))
            {
                result1 = InterceptorsRegistry.Interceptors[key1];
            }
            if (InterceptorsRegistry.Interceptors.ContainsKey(key2))
            {
                result2 = InterceptorsRegistry.Interceptors[key2];
            }
            var commonCount = result1.Intersect(result2, new ActionInterceptorComparer()).Count();
            if (commonCount > 0)
            {
                throw new Exception("An Interceptor registered multiple times");
            }
            var result = result1.Union(result2, new ActionInterceptorComparer());

            return result;
        }

        private static bool SearchForInterceptorAttribute(MemberInfo objMemberInfo, Object objSearch)
        {
            // Compare the name of the member function with the filter criteria. 
            return objMemberInfo.GetCustomAttributes(typeof(ActionInterceptorAttribute), false).Length > 0;
        }

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
India India
Like to dream on long drive and falling asleep on short drive..!

Comments and Discussions