Unit Testing WebApi with custom ModelBinders

Today I started working on a new WebApi application that uses Windows Authentication. I need the current user to check group access and load features that are available for the current user.

While working on this I used ModelBinders to improve the testability of my code. While searching for a solution I also encountered some differences between model binders in WebApi and MVC. In this blog I want to quickly show how to use a ModelBinder in WebApi to improve testability.

The basic way

Off course you can always do something like this:

   1:  public List<Application> GetApplications()
   2:  {
   3:      IPrincipal currentUser = User;
   4:      ....
   5:  }


The base ApiController exposes a User property that returns the current IPrincipal. This Principal can be used to check the current user and the roles they are in.

Let’s say you want to test this action method. You want to check for certain combinations of Users/Groups and make sure the correct features are loaded. So we need to find a way to change the User property of the ApiController base class. This property doesn’t expose a setter so we can’t change it directly.

Luckily for us we have access to the source code of the ApiController:
   1:  public abstract class ApiController : IHttpController, IDisposable
   2:  {
   3:       .....
   4:   
   5:       public IPrincipal User
   6:       {
   7:            get { return Thread.CurrentPrincipal; }
   8:       }
   9:   
  10:       ....
  11:  }

So the only thing the User property is doing, is checking the Thread.CurrentPrincipal. And Thread.CurrentPrincipal does have a setter property!
In our test code we could switch the current user with the following code:

   1:  var principal = new GenericPrincipal(new GenericIdentity("foo"), 
   2:                                       new string[] { });
   3:  Thread.CurrentPrincipal = principal;


After that we can call our ApiController and execute tests against the principal we just setup.

But is this really the way to go? Is this nice testable code? Will it be clear for you one year from now? Will it be clear to your colleague?

Refactoring for testability

Code should ask for what it needs. All access to ‘global data’ should be avoided.

Why? Because that way it’s much easier to see what a function needs and on which data it executes.

Things like statics and singletons are considered global data. A user of your class has no way of knowing which data you access and what actions you start executing when you use statics or singletons.

In our case, the Thread.CurrentPrincipal is such a problem. What if we could change your action method to the following:

   1:  public List<Application> GetApplications(IPrincipal user)
   2:  {
   3:      ....
   4:  }

The method now clearly states what it needs. There are no hidden surprises and users of your class can easily see what they need to test it.

(See also the following blog posts: Unit Testing, hell or heaven? and TDDing in Visual Studio 2012)
To make this method overload work in ASP.NET WebApi we can use a functionality called ModelBinding.

A ModelBinder for IPrincipal

ModelBinding is the process in which the ASP.NET framework checks how it can populate the parameters of your method. It looks trough things like Form fields, JSON post data, Route data, Querystring parameters and even posted files.
The frameworks model bindings capabilities can be easily extended with your own ModelBinders which can vastly improve your code.
After looking around on the internet I found the following blog post by Scott Hanselman: IPrincipal (User) ModelBinder in ASP.NET MVC for easier testing.
ASP.NET WebApi however needs some changes to this code:
   1:  using System;
   2:  using System.Web.Http;
   3:  using System.Web.Http.Controllers;
   4:  using System.Web.Http.ModelBinding;
   5:   
   6:  namespace MyApp.ModelBinder
   7:  {
   8:      public class PrincipalModelBinder : IModelBinder
   9:      {
  10:          public bool BindModel(HttpActionContext actionContext, 
  11:                                ModelBindingContext bindingContext)
  12:          {
  13:              if (actionContext == null)
  14:              {
  15:                  throw new ArgumentNullException("actionContext");
  16:              }
  17:              if (bindingContext == null)
  18:              {
  19:                  throw new ArgumentNullException("bindingContext");
  20:              }
  21:   
  22:              var apiController = actionContext.ControllerContext
  23:                                    .Controller as ApiController;
  24:   
  25:              if (apiController == null)
  26:              {
  27:                  throw new InvalidOperationException(
  28:                      "Cannot determine current  user. 
  29:                       Controller is not of type ApiController"); 
  30:              }
  31:   
  32:              bindingContext.Model = apiController.User;
  33:              return true;
  34:          }
  35:      }
  36:  }
 
The Controller is cast to an ApiController and then bindingContext.Model is set to the current user.

If you then register your new ModelBinder in your WepApiConfig everything is good to go:

   1:  config.BindParameter(typeof(IPrincipal), new PrincipalModelBinder());

From your unit test you can now easily pass the correct principal to your method. And when running your application in the browser, WebApi will use your custom model binder to get a value for IPrincipal and pass that to your method.

ModelBinding is one of the extension points of both MVC and WebApi that can really help you in creating better testable code.
For what reasons have you used ModelBinding? Or have you found another way to make code like this more testable? Or maybe you have a totally different opinion? Please leave a comment!

Wouter de Kort works as a lead architect and consultant. He helps organizations stay on the cutting edge of software development. Wouter focuses on DevOps. He loves solving complex problems and helping other developers to grow. Wouter authored the book DevOps on the Microsoft stack and a couple of other books. Wouter is a Microsoft MVP and an ALM Ranger. You can find him on Twitter (@wouterdekort), on his blog at wouterdekort.com and at the various conferences where Wouter speaks.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

Post comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.