Using Dependency Injection with StructureMap to decouple business logic from WebApi OData Controllers

Today’s post title is rather lengthly so I keep the introduction short and come right to the point: when using OData (or any web services therefore) it can become quite hard to keep the actual business logic separate and not to mix things where they do not belong. At least for me, as I have to admit that this happened to us in one of our projects. And when we wanted to upgrade from ODATA v3 to ODATA v4 we realised that we have to re-factor quite a few things. So there had to be a better approach.

With StructureMap it is very easy to use IoC with WebApi via the WebApi.StructureMap package. So all we have to do is to define a constructor that accepts our business logic and inject it.

For this to work we need a common interface that we can define on our controllers. In our case we dsigned our system to have a set of entities which all derive from a common base entity:

public class BaseEntity
{
  long Id { get; set; }
  string Name { get; set; }
}

public class Order : BaseEntity
{
  DateTimeOffset DeliveryDate { get; set; }
}

public class Item : BaseEntity
{
  Guid TrackingId { get; set; }
}

Our business logic would be handled on an entity basis via a Manager and all of our managers should support the following basic CRUD operations:

  • C New
  • R Get
  • R GetAll
  • U Update
  • D Remove

For the naming we decided to have the following convention in place:

  1. every Manager would be called #Entity#Manager, e.g. OrderManager, ItemManager
  2. every Controller would be called #Entity#sController, e.g. OrdersController, ItemsController (following ODATA naming conventions)

We then defined an interface for our managers:

public interface IEntityManager<T>
  where T : BaseEntity
{
  IEnumerable<T> Get();

  T Get(long id);

  T New(T entityToBeCreated);

  T Update(T modifiedEntity, T originalEntity);

  T Update(T entityToBeUpdated, DictionaryParameters delta);

  void Remove(T entityToBeDeleted);
}

With this we could define our controllers like this:

public class OrdersController : OdataController
{
  private readonly IEntityManager<Order> entityManager;

  public OrdersController(IEntityManager<Order> entityManager)
  {
    Contract.Requires(null != entityManager);

    this.entityManager = entityManager;
  }

  // ...
}

public class ItemsController : OdataController
{
  private readonly IEntityManager<Item> entityManager;

  public ItemsController(IEntityManager<Item> entityManager)
  {
    Contract.Requires(null != entityManager);

    this.entityManager = entityManager;
  }

  // ...
}

We then needed a special registration convention for our controllers:

public class ControllerRegistrationConvention : IRegistrationConvention
{
  public void ScanTypes(TypeSet types, Registry registry)
  {
    Contract.Requires(null != types);
    Contract.Requires(null != registry);

    types
      .FindTypes(TypeClassification.Concretes | TypeClassification.Closed)
      .Where(e => e.FullName.EndsWith("Controller")
        && typeof(OdataController).IsAssignableFrom(e))
      .ForEach(controllerType =>
      {
        Register(controllerType, registry);
      }
  }

  public void Register(Type controllerType, Registry registry)
  {
    // ...
  }
}

With this convention we could tell WebApi to use this for our ODATA controllers:

public static class IoC
{
  public static Action<ConfigurationExpression> DefaultConfiguration
  {
    get
    {
      return map =>
      {
        map.Scan(scanner =>
        {
          scanner.TheCallingAssembly();
          scanner.WithDefaultConventions();
          scanner.Convention<ControllerRegistrationConvention>();
        });
      };
    }
  }
}

public class WebApiApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    GlobalConfiguration.Configuration.UseStructureMap(IoC.IoC.DefaultConfiguration);
  }
}

Now the only things that was left was to do the mapping from the generic entity manager to the concrete manager type with a little bit of reflection:

private void Register(Type controllerType, Registry registry)
{
  controllerType.GetConstructors().Any(ctor =>
  {
    // 1. we are only interested in constructors with a single parameter
    var parameters = ctor.GetParameters();
    if (1 != parameters.Length)
    {
      return false;
    }

    // 2. and the parameter must be a generic
    var parameterType = parameters.First().ParameterType;
    if (!parameterType.IsGenericType)
    {
      return false;
    }

    // 3. that generic must have only a single argument
    var genericArguments = parameterType.GetGenericArguments();
    if (1 != genericArguments.Length)
    {
      return false;
    }
    var genericArgument = genericArguments.First();

    // 4. the base type of that generic type must derive from BaseEntity
    if (genericArgument.BaseType != typeof(BaseEntity))
    {
      return false;
    }

    // 5. now resolve the actual manager
    // we *know* the manager resides in the same assembly as IEntityManager<>
    var managerType = typeof(IEntityManager<>).Assembly.DefinedTypes
      .FirstOrDefault(e => e.IsGenericType
        && 1 == e.GenericTypeParameters.Length
        && e.GenericTypeParameters.First()
          .GetGenericParameterConstraints().First().Name == genericArgument.Name
        && e.Name.StartsWith(string.Concat(genericArgument.Name, "Manager"))
        );
    Contract.Assert(null != managerType, controllerType.FullName);

    // 6. now we create the generic type to be injected
    var genericManagerType = managerType.MakeGenericType(genericArgument);

    // 7. and register the mapping
    registry.For(parameterType).Use(genericManagerType);

    // it's as easy as that
    return true;
  });
}

We can verify this with a simple unit test:

[TestMethod]
public void DefaultConfigurationIsValid()
{
  var sut = new Container(WebApi.IoC.IoC.DefaultConfiguration);

  Trace.WriteLine(sut.WhatDoIHave());
  sut.AssertConfigurationIsValid();
}

The output of WhatDoIHave will then look similar to this:

==================================================================================
PluginType            Namespace                  Description                      
----------------------------------------------------------------------------------
Func<TResult>         System                     Open Generic Template for Func<> 
----------------------------------------------------------------------------------
Func<T, TResult>      System                     Open Generic Template for Func<,>
----------------------------------------------------------------------------------
IContainer            StructureMap               Object:  StructureMap.Container  
----------------------------------------------------------------------------------
IEntityManager<Order> Net.Appclusive.Core.Domain OrderManager<Order>              
----------------------------------------------------------------------------------
IEntityManager<Item>  Net.Appclusive.Core.Domain ItemManager<Item>                
----------------------------------------------------------------------------------
Lazy<T>               System                     Open Generic Template for Func<> 
==================================================================================

With this in place we completely decoupled the business logic from the transport protocol and could upgrade to ODATA v4 (or any other means).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: