Nov 30, 2011

Custome TempData Provider

Life span of TempData object is only till the subsequent request, or until the item is removed explicitly. This is useful when you want to pass data to another view that you will be redirecting to, rather than rendering to. By default, TempData is stored in the session using SessionStateTempDataProvider. So if you want to switch away from the default Session-State Mode, and use State Server Mode or SQL Server Mode, you need to store TempData objects which can be serializable.

You can also specify a custome temp data provider by setting TempDataProvider property of the controller.
public EmployeeController()
        {
            TempDataProvider = new CustomeTempDataProvider();
        }
 

CustomeTempDataProvider just need to implement ITempDataProvider. Refer to the sample code. In this example I am saving/loadion temp data to/from HttpContext.Current.Cache.

public class CustomeTempDataProvider : ITempDataProvider

    {
        internal string TempDataSessionStateKey = "__ControllerTempData";

        public IDictionary LoadTempData(ControllerContext controllerContext)
        {
            string userIdentifier = controllerContext.HttpContext.Request.UserHostAddress;

            HttpContext current = HttpContext.Current;
            if (current != null)
            {
                Dictionary dictionary = HttpContext.Current.Cache[TempDataSessionStateKey + userIdentifier] as Dictionary;
                if (dictionary != null)
                {
                    HttpContext.Current.Cache.Remove(TempDataSessionStateKey + userIdentifier);
                    return dictionary;
                }
            }
            return new Dictionary(StringComparer.OrdinalIgnoreCase);
        }

        public void SaveTempData(ControllerContext controllerContext, IDictionary values)
        {
            string userIdentifier = controllerContext.HttpContext.Request.UserHostAddress;

            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            HttpContext current = HttpContext.Current;
            bool flag = values != null && values.Count > 0;
            if (current == null)
            {
                if (flag)
                {
                    throw new InvalidOperationException("SessionStateTempDataProvider_SessionStateDisabled");
                }
            }
            else
            {
                if (flag)
                {
                    HttpContext.Current.Cache[TempDataSessionStateKey + userIdentifier] = values;
                    return;
                }
                if (HttpContext.Current.Cache[TempDataSessionStateKey + userIdentifier] != null)
                {
                    HttpContext.Current.Cache.Remove(TempDataSessionStateKey + userIdentifier);
                }
            }
        }
    }
 

How Default Model Binder works under the hood

If you have a requirement to write a custom model binding by implementing interface IModelBinder, then it's better to have a peek on DefaultModelBinder which is implemented in System.Web.Mvc. There is a lot which happens in this class and for most of the cases I prefer to create my custom model binder by deriving from DefaultModelBinder. In this way I can override whatever I need to and rest I can get for free.

There is a very less documentation around DefaultModelBinder so to find out what happens under the hood I decompiled System.Web.Mvc.DefaultModelBinder. Here are some of the important methods

  • BindModel - Only Member of IModelBinder interface. This binds simple as well as complex model.
  • BindComplexModel - This Creates Model by calling CreateModel and then call BindComplexElementalModel which calls BindProperties.
  • CreateModel - This create an instance of Model Type which you specify as a parameter type in the controller. So in the particular case CreateModel will return an object of type Employee.
    [HttpPost]
        public ActionResult EmployeeList(Employee employee)
       
    one case in which I had to override this was when I had a requirement to support for Interface type parameter. Refer to my posting on custom model binding
  • BindProperties - Binds all the properties of the model class by using controller context and binding context. This is the place where lot of magic happens. In the above case this will loop through all the properties of Employee and then bind from the controller context. This is one of the best part of default model binder which saves lot of development time. So for example it will bind the value of the property from HttpContext.Request.Form, which is collection of form variables. By default there are five ValueProviders:ChildActionValueProvider, FormValueProvider, RouteDataValueProvider, QueryStringValueProvider, HttpFileCollectionValueProvider.
  • BindProperty Calls GetPropertyValue which call BindModel which calls BindSimpleModel which gets the value.

In case of filters, DefaultModelBinder creates the Model object and in the filter you can access this model object using filterContext.ActionParameters[key] and populates the model object. In this case no binding happens in the DefaultModelBinder. Put a break point at the following location CreateModel, BindModel in the DefaultModelBinder and in your filter actionexecuting. You will notice that BindModel is called first and then CreateModel which creates the model object and then the filter action will be called. In filter action you can access the model object which CreateModel created using filterContext.ActionParameters[key]


The ModelBindingContext object is created and populated by whoever calls into the BindModel() method. If the model is coming in as an argument to your action method, this is done by ControllerActionInvoker.GetParameterValue(), and the ModelName property will be set to the name of the parameter (unless overridden by [Bind(Prefix = ... )]).

ModelBindingContext bindingContext = new ModelBindingContext() {
  FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified 
  ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
  ModelName = parameterName, 
  ModelState = controllerContext.Controller.ViewData.ModelState, 
  PropertyFilter = propertyFilter,
  ValueProvider = controllerContext.Controller.ValueProvider 
 };
 

If the model is being updated via UpdateModel(), the ModelBindingContext object is created by the UpdateModel() method itself. The ModelName parameter will be the prefix argument passed to UpdateModel().

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.

Nov 14, 2011

Custom Model Binder on Interface Type

Its very common to use Default Model Binder which maps a browser request to a data object, something like this.

public ActionResult EmployeeList(Employee employee)
{
    return View();
}


One down side of this is that during unit test you have to construct an object from employee class. It will be nice if we can have interface parameter type something like this. In this case, unit test doesn't have to bother on the concrete implementation of Employee class.

public ActionResult EmployeeList(IEmployee employee)
{
    return View();
}

But unfortunately this will throw exception "Cannot create an instance of an interface". The reason for this is that during binding it try to create an instance of model type which in this case is an interface and hence it throws exception. You can easily decompile System.Web.Mvc.DefaultModelBinder and look at method CreateModel where this is failing.

To get away with the issue I created an InterfaceTypeModelBinder. The constructor of the InterfaceTypeModelBinder takes ModelType as aparameter.

public class InterfaceTypeModelBinder : DefaultModelBinder
{
        private Type ModelType { get; set; }

        public InterfaceTypeModelBinder(Type modelType)
        {
            ModelType = modelType;
        }

        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            Type type = GetModelType(bindingContext.ModelType);
            Debug.WriteLine(Environment.StackTrace);
            return Activator.CreateInstance(type);
        }

        private  Type GetModelType( Type modelType)
        {
            Type type = modelType;

            if (ModelType != null)
                type = ModelType;

            if (modelType.IsGenericType)
            {
                Type genericTypeDefinition = type.GetGenericTypeDefinition();
                if (genericTypeDefinition == typeof(IDictionary<,>))
                {
                    type = typeof(Dictionary<,>).MakeGenericType(type.GetGenericArguments());
                }
                else
                {
                    if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IList<>))
                    {
                        type = typeof(List<>).MakeGenericType(type.GetGenericArguments());
                    }
                }
            }
            return type;
        }       
}
        
Now in my Global.asax.cs file I can register my binder like this
protected void Application_Start()
{
     AreaRegistration.RegisterAllAreas();

     RegisterGlobalFilters(GlobalFilters.Filters);
     RegisterRoutes(RouteTable.Routes);
     ModelBinders.Binders[typeof(IEmployee)] = new InterfaceTypeModelBinder(typeof(Employee));
     ModelBinders.Binders[typeof(ICustomer)] = new InterfaceTypeModelBinder(typeof(Customer));
}


By doing this I can use IEmployee or ICustomer or any type as a parameter as long as it is registered.