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 } }
1 Comment »