How to create a custom session value provider
Value Providers
Value Providers are the components that feeds data to model binders. The framework contains a bunch of built-in value providers like FormValueProvider, RouteDataValueProvider, QueryStringValueProvider and HttpFileCollectionValueProvider that fetches data from Request.Form, Request.QueryString, Request.Files and RouteData.Values. These Value Providers are called in the order they are registered and so the one that registered earlier gets the first chance. We can easily restrict the model to bind with data from a particular Value Provider.
The interesting thing is we can even create own custom Value Provider to feed data to models. In this article we see how to create a custom value provider that feed data from session to model binders.
Custom Session Value Provider
All the built-in value providers should implement the interface IValueProvider.
public interface IValueProvider { bool ContainsPrefix(string prefix); ValueProviderResult GetValue(string key); }
The ContainsPrefix method is called by the model binder to determine whether the value provider can resolve the data for a given prefix. The GetValue method returns a value for a given data key or returns null if the provider doesn't have any suitable data.
Implementing our custom session value provider is quite simple. In the ContainsPrefix method we have to check whether the passed parameter is stored in session. In the GetValue method we have to return the value from the session for the passed key.
public class SessionValueProvider: IValueProvider { public bool ContainsPrefix(string prefix) { return HttpContext.Current.Session[prefix] != null; } public ValueProviderResult GetValue(string key) { if(HttpContext.Current.Session[key] == null) return null; return new ValueProviderResult(HttpContext.Current.Session[key], HttpContext.Current.Session[key].ToString(), CultureInfo.CurrentCulture); } }
How we can use our SessionValueProvider? The answer is we need a factory for that. The value providers has to be registered through factories to make them feed data to model binders. We have to create a factory to register our SessionValueProvider by deriving from the abstract class ValueProviderFactory. The factory contains a single method GetValueProvider where we should instantiate our custom value proivder and return it.
public class SessionValueProviderFactory : ValueProviderFactory { public override IValueProvider GetValueProvider (ControllerContext controllerContext) { return new SessionValueProvider(); } }
Finally we have to register our factory to the ValueProviderFactories.Factories collection in the Application_Start event of Global.asax.cs.
ValueProviderFactories.Factories.Add(new SessionValueProviderFactory());
Example
Suppose you have a model called UserModel with a property AccountNo that is stored in session.
public class UserModel { public string AccountNo { get; set; } ... }
Session["AccountNo"] = "X8w237jd923"
Let's say we have an action that expects this model as below,
public ViewResult SomeAction(UserModel userModel, …) { ... }
At the time of model binding the DefaultModelBinder checks with the value providers could they return value for the parameter AccountNo by calling the ContainsPrefix method. If none of the value providers registered eariler could return the value our SessionValueProvider checks with session whether such a parameter is stored and if yes it return the value.
Summary
Value providers helps us to avoid dealing with heavy objects like Request directly in controller actions and that helps lot in unit testing. Usually value providers looks for data in Request, in our example we tried differently by looking data in session. Dealing directly with session in controller actions make them hard to unit test and I think that our SessionValueProvider will do a decent job in those cases.