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:
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.