How to create a custom Value Provider for MVC

by Donovan Brown 23. September 2011 16:22

Problem:

I need to test an MVC Controller Action that needs the Identity of the current user without mocking the HTTPContext.

Solution:

Create a custom Value Provider that allows access to the current user via an Action Parameter.

Explanation:

One of the goals of the MVC pattern is making the application easier to test.  Simply using the pattern does not grantee your application will be easy to test.  The application must be designed with testing in mind.  This does not require that you use Test Driven Development (TDD) but I do believe you must be what I like to call "Test Aware".  You must constantly be thinking during your design how are we going to test this? Does this design lend itself to easy testing?  Testing can no longer be an afterthought.


While recently working on a MVC project I ran into an issue that was going to be very difficult to test because it relied on the read only User property of the Controller base class.  My goal was to find a way to allow my action to be tested without having to Mock the HTTPContext.  The action I was testing required Authorization and used the User.Identity property to query information from the data store.  Because the User property of the Controller base class is ready only I was unable to set it in my unit test which would require me to mock the HTTPContext to test that action.


MVC Controllers have several mechanisms for receiving data.  You can access the data from context objects, invoke the framework's model binding feature or have the data passed as parameters using a value provider.  I prefer my action methods to have data passed as parameters.  When your action depends only on the parameters, without accessing context data directly they become much easier to understand and test.  Actions that only depend on their parameter are called "Pure". When your action method accepts parameters the framework employs the help of a collection of Value Providers and Model Binders to locate suitable values.  Like many components in the MVC framework Value Providers and Model Binders are pluggable.  This flexibility in the MVC framework allows developers to replace or extend the framework.  My solution was to simply create a custom Value Provider that would allow me to access the current user as an input parameter to my action method.

Below is an example of the way the action would be written if you access the User property of the Controller base class.

[Authorize]
public ActionResult Index()
{
   Models.Userdetails clerk = context.Userdetails.Where(u => u.Username == User.Identity.Name).Single();
   return View(context.Citations.Where(c => c.ID == clerk.ID &&
                                            string.IsNullOrEmpty(c.UploadedBy)));
}

However, to test this method would be very difficult.  Instead I decided to write my action method so that it accepts a GenericPrincipal object as a parameter.

[Authorize]
public ActionResult Index(GenericPrincipal currentUser)
{
   Models.Userdetails clerk = context.Userdetails.Where(u => u.Username == currentUser.Identity.Name).Single();
   return View(context.Citations.Where(c => c.ID == clerk.ID &&
                                            string.IsNullOrEmpty(c.UploadedBy)));
}

By writing the method this way I was able to create my test method without having to mock anything.

[TestMethod]
public void Index()
{
   // Arrange
   HomeController controller = new HomeController();

   // Act
   GenericIdentity id = new GenericIdentity("userName");
   GenericPrincipal user = new GenericPrincipal(id, null);
   ViewResult result = controller.Index(user) as ViewResult;

   // Assert
   ViewDataDictionary viewData = result.ViewData;
   Assert.IsTrue(viewData.Model is IEnumerable<Models.Citations>);
}

To achieve this design I had to extend the Value Providers with a custom class.
Writing a custom Value Provider is very simple.  You simply create a class that implements the IValueProvider interface.  The IValueProvider interface only has two methods:
•    ContainsPrefix - This is your value providers way of letting the MVC framework know you can handle this item.
•    GetValue - If you return true from ContainsPrefix this method will be called to provide the value.

To allow the MVC framework to access your value provider you must provide a ValueProviderFactory class as well.  This class derives from ValueProviderFactory and simply returns an instance of your Value Provider. The code below shows both the Value Provider Factory and the Value Provider.

public class CurrentUserValueProviderFactory : ValueProviderFactory
{
   public override IValueProvider GetValueProvider(ControllerContext controllerContext)
   {
      return new CurrentUserValueProvider();
   }

   private class CurrentUserValueProvider : IValueProvider
   {
      public bool ContainsPrefix(string prefix)
      {
         return "user".Equals(prefix, StringComparison.OrdinalIgnoreCase);
      }

      public ValueProviderResult GetValue(string key)
      {
         return ContainsPrefix(key)
            ? new ValueProviderResult(HttpContext.Current.User, null, CultureInfo.CurrentCulture)
            : null;
      }
   }
}

The final step is to register your Value Provider Factory with the MVC framework.  Add the highlighted code to the Application_Start method inside Global.asax.cs


protected void Application_Start()
{
   AreaRegistration.RegisterAllAreas();

   RegisterRoutes(RouteTable.Routes);

   ValueProviderFactories.Factories.Add(new Classes.CurrentUserValueProviderFactory());
}

This adds your custom Value Provider to the MVC framework and allows you to gain access to the current user in any action method by simply having a GenericPrincipal user parameter.

Tags: ,

Work

About the author

My name is Donovan Brown and I am a Technology Specialist for DevTools with Microsoft with a background in application development.  I also run one of the Nation’s fastest growing online registration sites for motorsports events DLBRacing.com.  When I am not writing software I race cars for fun.  DLBRacing.com has given me the opportunity to combine my two passions writing software and racing cars.

AdSense

Month List

AdSense