Session State on Action

In my previous post I have mentioned that Session State is only at controller level. But if someone what at both level  i.e controller and action like at controller level session state behavior is “Read-Only” and at action level we want “Default”. Now what?  By end of this post you will learn how to achieve this.

We will be using “OVERIDDING” feature to implement this.

Steps:

  1.  Create custom Attribute
  2.  Override the “GetControllerSessionBehavior” method present in class DefaultControllerFactory.
  3.  Register it in

1. Creating custom Attribute

public sealed class ActionSessionStateAttribute : Attribute
{
        public SessionStateBehavior SessionBehavior { get; private set; }          
        public ActionSessionStateAttribute(SessionStateBehavior sessionBehavior)
        {
            SessionBehavior = sessioBehavior;
        }
}

2. Overriding of method

public class SessionControllerFactory : DefaultControllerFactory
{       
        protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
        {
            if (controllerType == null)
                return SessionStateBehavior.Default;
           
            var actionName = requestContext.RouteData.Values["action"].ToString();
            Type typeOfRequest=requestContext.HttpContext.Request.RequestType.ToLower() =="get"?typeof(HttpGetAttribute):typeof(HttpPostAttribute);
            // [Line1]
            var cntMethods = controllerType.GetMethods()
                   .Where(m => 
                    m.Name == actionName &&
                    (  (  typeOfRequest == typeof(HttpPostAttribute) && 
                          m.CustomAttributes.Where(a => a.AttributeType == typeOfRequest).Count()>0
                       )
                       ||
                       (  typeOfRequest == typeof(HttpGetAttribute) &&
                          m.CustomAttributes.Where(a => a.AttributeType == typeof(HttpPostAttribute)).Count() == 0
                       )
                    )
                );
            MethodInfo actionMethodInfo = actionMethodInfo = cntMethods != null && cntMethods.Count() == 1 ? cntMethods.ElementAt(0):null;
            if (actionMethodInfo != null)
            {
                var sessionStateAttr = actionMethodInfo.GetCustomAttributes(typeof(ActionSessionStateAttribute), false)
                                    .OfType<ActionSessionStateAttribute>()
                                    .FirstOrDefault();

                if (sessionStateAttr != null)
                {
                    return sessionStateAttr.Behavior;
                }
            }
            return base.GetControllerSessionBehavior(requestContext, controllerType);
 }

3. Register class in Global.asax

public class MvcApplication : System.Web.HttpApplication
 {
        protected void Application_Start()
        {
            // --- other code ---
            ControllerBuilder.Current.SetControllerFactory(typeof(SessionControllerFactory));
        }
}

4. Example :

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class TestController : CustomController
{
       [ActionSessionState(SessionStateBehavior.Default)]
        public ActionResult TestSession()
        {
            HttpContext.Session["test"]="India";
            return View();
        }
}

Common Mistakes and Code Explanation

Some people uses below code in place of line 1

MethodInfo actionMethodInfo = controllerType.GetMethod(actionName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

But this will give you “System.Reflection.AmbiguousMatchException” exception. As many controller has two action with same name but one with HttpGet and other with HttpPost. Because of this when GetMethod is called it find two method with same that’s why it throws the exception.

To resolve this issue  use GetMethods and LINQ.

Note: In controller writing [HttpGet] on action is not compulsory for get methods i.e. on action if HttpGet is not written then by default .net will consider it as [HttpGet]. But for post action it is compulsory. Will use this property for finding the correct method.

Steps to 

1. Find Type of request

var actionName =requestContext.RouteData.Values["action"].ToString();
Type typeOfRequest = filterContext.HttpContext.Request.RequestType.ToLower() =="get"?typeof(HttpGetAttribute):typeof(HttpPostAttribute);

2. Find the action using reflection

var cntMethods = controllerType.GetMethods()
                 .Where(m => 
                    m.Name == actionName &&
                    (  (  typeOfRequest == typeof(HttpPostAttribute) && 
                          m.CustomAttributes.Where(a => a.AttributeType == typeOfRequest).Count()>0
                       )
                       ||
                       (  typeOfRequest == typeof(HttpGetAttribute) &&
                          m.CustomAttributes.Where(a => a.AttributeType == typeof(HttpPostAttribute)).Count() == 0
                       )
                    )
                );
            MethodInfo actionMethodInfo = actionMethodInfo = cntMethods != null && cntMethods.Count() == 1 ? cntMethods.ElementAt(0):null;