Nov 16, 2011

How to persist Model State from a controller action using Action Filter

To persist ModelState from a controller action I created a filter called StateManagementFilter

    public class StateManagementFilter : ActionFilterAttribute
    {
        #region Constructor

        /// 
        /// 
        /// 
        /// This will reset state before action is executed.         /// This will save action state after it's executed        /// User can access/store state by the type        public StateManagementFilter(bool resetState = false, bool saveState = true, string stateType= "")
        {
            StateType = stateType;
            ShouldResetState = resetState;
            ShouldSaveState = saveState;

            //To avoid sending a user someone else’s state, we’ll need a way of uniquely identifying users.
            //Here I am using SessionID which may not be perfect always so we can use any shot of identification (like UserID) which uniquely identifies an User.
            HttpContext current = HttpContext.Current;
            UserIdentifier = "UserIdentifier" + current.Session.SessionID;
            
            if (resetState)
                ResetUserState();
        }

        #endregion

        #region ActionFilterAttribute Override

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext.Controller.ViewData.Model != null && ShouldSaveState)
                SetUserState(filterContext.Controller);
            base.OnActionExecuted(filterContext);
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (!ShouldResetState)
                filterContext.Controller.ViewData.Model = GetUserState(filterContext.Controller);
            base.OnActionExecuting(filterContext);
        }

        #endregion

        #region Private Property

        private string StateType { get; set; }
        private string UserIdentifier { get; set; }
        private bool ShouldResetState { get; set; }
        private bool ShouldSaveState { get; set; }

        #endregion

        #region Private Method

        private void ResetUserState()
        {
            HttpContext.Current.Cache.Remove(StateType + UserIdentifier);
        }


        /// 
        /// This stores the model object in the cache. For simplicity I am using web caching mechanism but we can use any caching framework 
        /// 
        ///         private void SetUserState(ControllerBase controller)
        {
            //If model object need to persisted only till the subsequent requests then TempData can be used
            //controller.TempData["__UserStateData"] = controller.ViewData.Model;
            System.Web.HttpContext.Current.Cache[StateType + UserIdentifier] = controller.ViewData.Model;
        }

        /// 
        /// This retrieves state from the cache
        /// 
        ///         /// 
        private object GetUserState(ControllerBase controller)
        {
            //return controller.TempData["__UserStateData"];
            return HttpContext.Current.Cache[StateType + UserIdentifier];
        }

        #endregion

    }


In the controller I can use this filter like like this

[StateManagementFilter(true, true, "EmployeeList")]
        public ActionResult EmployeeList()
        {
            return View(new Employee());
        }

        [HttpPost, StateManagementFilter(false, true, "EmployeeList")]
        public ActionResult EmployeeList(Employee employee)
        {
            Employee employee1 = (Employee) ViewData.Model;
            return View(employee);
        }

In the first controller action I am saving the state which will mean that the next controller action will get the View Data Model from the first controller action. So in the above example the new Employee object which is getting created in the first controller action can be available in the second controller action.

Also by specifying the type of state, controller can specify which state it will be interest in.

This will be mostly useful when you perform some costly business logic to create a model object in one controller action and would like to persist. So by using this filter you can save this in cache.

This can also be used for redirect call. For example you perform some business logic and then you decide based on some condition that this need to be redirected to different action. Although for this particular case it will be better to use TempData as the life span of TempData object is onli till the subsiquest request.Refer to the commented code in SetState/GetState. For using temp data in web farm you may need to implement custome temp data provider.

No comments:

Post a Comment