Simplifying OData Controllers by using generic controller classes

Writing Odata Controllers usually involves a lot of boiler-plate code (and scaffolders are of no real help here either). However, when decoupling and unifying the business logic it is possible to also generalise the associated controllers and reduce their coding to a minimum.

A typical Odata controller using IEntityManager (as described in Using Dependency Injection with StructureMap to decouple business logic from WebApi OData Controllers might look like this (only Get and Delete methods are shown here):

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

  public OrdersController(IEntityManager<Order> entityManager)
  {
    this.entityManager = entityManager;
  }

  public async Task<IHttpActionResult> GetOrder([FromODataUri] long key, ODataQueryOptions<Order> queryOptions)
  {
    return Ok(entityManager.Get(key);
  }

  public async Task<IHttpActionResult> GetOrders(ODataQueryOptions<Order> queryOptions)
  {
    return Ok<IEnumerable<Order>>(EntityManager.Get());
  }

  public async Task<IHttpActionResult> Delete([FromODataUri] long key)
  {
    var entityToBeDeleted = entityManager.Get(key);
    entityManager.Delete(entityToBeDeleted);

    return Updated(default(TEntity));
  }
}

If we now move the logic from the OrdersController to a generic BaseController we can simplify the logic like this:

public abstract class BaseController<TEntity> : ODataController
  where TEntity : BaseEntity
{
  protected readonly IEntityManager<TEntity> EntityManager;

  protected OdataControllerBase(IEntityManager<TEntity> entityManager)
  {
    this.EntityManager = entityManager;
  }

  public virtual async Task<IHttpActionResult> GetSingle([FromODataUri] long key, ODataQueryOptions<TEntity> queryOptions)
  {
    var entity = EntityManager.Get(key);
    return Ok(entity);
  }

  public virtual IHttpActionResult GetList(ODataQueryOptions<TEntity> queryOptions)
  {
    return Ok<IEnumerable<TEntity>>(EntityManager.Get());
  }

  public virtual async Task<IHttpActionResult> Delete([FromODataUri] long key)
  {
    var entityToBeDeleted = EntityManager.Get(key);
    EntityManager.Delete(entityToBeDeleted);

    return Updated(default(TEntity));
  }
}

Now there is only a small problem left regarding the Odata naming convention in that both Get methods (single entity and list retrieval) are named differently for each controller. But we can address this by registering a custom routing convention:

public class OdataControllerBaseRoutingConvention : EntitySetRoutingConvention
{
  public override string SelectAction(ODataPath odataPath, Httpcontext context, ILookup actionMap)
  {
    var result = base.SelectAction(odataPath, context, actionMap);
    if (context.Request.Method != HttpMethod.Get)
    {
      return result;
    }

    switch (odataPath.PathTemplate)
    {
      case "~/entityset":
        result = nameof(OdataControllerBase.GetList);
        break;
      case "~/entityset/key":
        result = nameof(OdataControllerBase.GetSingle);
        context.RouteData.Values[ODataRouteConstants.Key] = odataPath.Segments[1];
        break;
    }

    context.RouteData.Values[ODataRouteConstants.Action] = result;
    return result;
  }
}

In the end there is only very little code left in our specific controller. And that bit can automatically be generated (e.g. with Roslyn). Extending these controllers is equally easy. Defining the stub classes as partial is all we need to leave room for our custom logic.

public partial class OrdersController : BaseController<Order>
{
  public OrdersController(IEntityManager<Order> entityManager)
    : base(entityManager)
  {
    // N/A
  }
}

Trackbacks

  1. […] Dependency Injection with StructureMap to decouple business logic from WebApi OData Controllers and Simplifying OData Controllers by using generic controller classes you will remember that we use managers that contain the business logic and odata controllers only […]

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: