简体   繁体   中英

NULL HttpContext for batched OData requests in Web API - wrong design pattern?

I am trying to determine the correct method to inject a dependency into a controller where the concrete type to be injected is a variable based on a route data parameter.

So far I have the following set up which works perfectly for normal requests:

Controller

 public class OrdersController : ODataController
 {

    private IOrderService ErpService { get; }

    public OrdersController(IOrderService orderService)
    {
        ErpService = orderService;
    }

    [EnableQuery(PageSize = 100)]
    public IQueryable<OrderDto> Get(ODataQueryOptions<OrderDto> queryOptions)
    {
        return ErpService.Orders(queryOptions);
    }

    ...
    // Post
    // Patch/Put
    // Delete
}

With the following OData route config, I can specify the route template should include a 'company' parameter:

Config

config.MapODataServiceRoute( "ODataRoute", "data/{company}", model, new DefaultODataPathHandler(),
                conventions, new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));

This allows me to have a static method to read the company ID from the URL:

public static string GetSalesCompanyFromRequest()
{
    var salesCompany = "";
    if (HttpContext.Current == null) return "";

    var routeData = HttpContext.Current.Request.RequestContext.RouteData;
    if (routeData.Values.ContainsKey("company"))
    {
        salesCompany = routeData.Values["company"].ToString();
    }

    return salesCompany;
}

Then, using Ninject, I can chose which concrete instance of IOrderService to use (simplified for brevity):

kernel.Bind<IOrderService>()
            .To<SageOrderService>()
            .When(ctx => GetSalesCompanyFromRequest() == "101").InRequestScope();

kernel.Bind<IOrderService>()
            .To<DynamicsAxOrderService>()
            .When(ctx => GetSalesCompanyFromRequest() == "222").InRequestScope();

kernel.Bind<IOrderService>()
            .To<SapOrderService>()
            .When(ctx => GetSalesCompanyFromRequest() == "333").InRequestScope();

Connector Config

Id          ErpType        ConnectionString
--------------------------------------------
111         Sage           "connectionstring1"
222         DynamicsAx     "connectionstring2"
333         SAP            "connectionstring3"

So here's how the following URLs get processed:

http://odata-demo/data/101/Orders
Creates and injects a SageOrderService into OrdersController

http://odata-demo/data/ 222 /Orders
Creates and injects a DynamicsAxOrderService into OrdersController

The same logic applies to many different services, like:

  • SageStockService/AxStockService
  • SageBomService/AxBomService
  • etc

Note: I chose to put the company Id in the URL so I could configure a reverse proxy to forward requests to a local web server closer to the target database.

This all works perfectly until I try to use OData Batching . It seems then there is no HttpContext.Current (it is null) when I send a batched request.

This question asks something similar but does not account for OData batched requests.

Comments in this answer suggest injection by route data is code smell but does not elaborate.

So, the question is, how to I get HttpContext.Current for batched OData requests? Or Is there a better way to do what I'm trying to do?

Since the company is already in the route data, I could add an additional company parameter to every single action as follows to allow the company number to be passed in, then use a factory to get the right concrete type:

public class OrdersController : ODataController
{

    [EnableQuery(PageSize = 100)]
    public IQueryable<OrderDto> Get(ODataQueryOptions<OrderDto> queryOptions, string company)
    {

        var erpService = ErpServiceFactory.GetService(company);

        return erpService.Orders(queryOptions);
    }

    ...
    // Post
    // Patch/Put
    // Delete
}

This means that I would also have to initialise the OrderService within each action which smells a bit.

I suppose this could be less smelly if I used an ActionFilter to locate and pass in the correct concrete type to the action:

public class RequiresOrderServiceAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        string salesCompany = "";
        var data = actionContext.Request.GetRouteData();

        if (data.Values.ContainsKey("company"))
        {
            salesCompany = data.Values["company"].ToString();

            var orderService = ErpServiceFactory.GetService(company);

            actionContext.ActionArguments.Add("erpService", orderService);
        }
    }
}

public class OrdersController : ODataController
{

    [EnableQuery(PageSize = 100)]
    public IQueryable<OrderDto> Get(ODataQueryOptions<OrderDto> queryOptions, IOrderService erpService)
    {
        return erpService.Orders(queryOptions);
    }

    ...
    // Post
    // Patch/Put
    // Delete
}

Thoughts?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM