Передать параметры в OData WebApi Url
Используя Web Api, у меня есть OData EndPoint, который может возвращать продукты из базы данных.
У меня есть несколько баз данных со сходными схемами, и я хочу передать параметр в URL, чтобы определить, какую базу данных должен использовать Api.
Текущая конечная точка Odata:
http://localhost:62999/Products
Что я хочу:
http://localhost:62999/ / Продукты
В новом URL я передаю 999 (идентификатор базы данных).
Идентификатор базы данных предназначен для указания, из какой базы данных загружать продукт. Например localhost:62999/999/Products('ABC123')
загрузит продукт 'ABC123' из базы данных 999, но следующий запрос, localhost:62999/111/Products('XYZ789')
загрузит продукт 'XYZ789' из базы данных 111.
URL ниже работает, но мне это не нравится. localhost:62999/Products('XYZ789')?database=111
Вот код для контроллера:
public class ProductsController : ErpApiController //extends ODataController, handles disposing of database resources
{
public ProductsController(IErpService erpService) : base(erpService) { }
[EnableQuery(PageSize = 50)]
public IQueryable<ProductDto> Get(ODataQueryOptions<ProductDto> queryOptions)
{
return ErpService.Products(queryOptions);
}
[EnableQuery]
public SingleResult<ProductDto> Get([FromODataUri] string key, ODataQueryOptions<ProductDto> queryOptions)
{
var result = ErpService.Products(queryOptions).Where(p => p.StockCode == key);
return SingleResult.Create(result);
}
}
Я использую Ninject, чтобы решить, какую реализацию IErpService внедрить в контроллер, связавшись с поставщиком услуг:
kernel.Bind<IErpService>().ToProvider(new ErpServiceProvider());
И ErpServiceProvider
проверяет URL, чтобы идентифицировать databaseId, требуемый этим запросом:
public class ErpServiceProvider : Provider<IErpService>
{
protected override IErpService CreateInstance(IContext context)
{
var databaseId = HttpContext.Current.Request["database"];
return new SageErpService(new SageContext(GetDbConnection(databaseId)));
}
}
Я застрял в том, как определить параметр Url в конфигурации маршрута OData.
Обычные маршруты WebApi могут иметь параметры, определенные следующим образом:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Но как мне определить параметры в конфигурации маршрута OData?
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<ProductDto>("Products");
builder.EntitySet<WorkOrderDto>("WorkOrders");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
Это даже где я должен определить параметры URL? Я также думал об использовании обработчика сообщений, но я не уверен, как это можно реализовать.
ОБНОВИТЬ
Этот вопрос пытается сделать то же самое, что и я: Как объявить параметр как префикс в OData
Но не ясно, как параметр должен быть прочитан из URL. var databaseId = HttpContext.Current.Request["database"];
возвращает ноль в настоящее время.
Даже после обновления конфигурации маршрута до следующего:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ErpApi",
routeTemplate: "{database}/{controller}"
);
// Web API configuration and services
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<ProductDto>("Products");
builder.EntitySet<WorkOrderDto>("WorkOrders");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "{company}/",
model: builder.GetEdmModel());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
1 ответ
Я столкнулся с решением для передачи динамического параметра на OData, не уверен, что это правильный.
Я использовал это решение в определенном контексте, где динамический параметр был просто для аутентификации клиента, но я думаю, что вы можете решить свою проблему аналогичным образом.
Проблема: вы не хотите передавать динамическое значение в примере запроса URL: http://localhost:62999/{dynamicValue} / Products ('ABC123'), но ODataRouting никогда не будет правильно маршрутизироваться из-за этого дополнительного /{dynamicValue} и ODataControler "не ударит". Используя ApiController, вы могли бы сделать собственную маршрутизацию, но в OData вы не можете (по крайней мере, я не нашел простой способ сделать это, возможно, вам пришлось сделать свою собственную или расширить соглашение о маршрутизации OData).
Таким образом, в качестве альтернативного решения: если у каждого запроса будет динамическое значение, например: " http://localhost:62999/{dynamicValue} / Products", выполните следующие действия:
- Перед маршрутизацией запроса Извлеките dynamicValue (в моем случае я использовал IAuthenticationFilter для перехвата сообщения до его перенаправления, поскольку параметр был связан с авторизацией, но, возможно, для вашего случая имеет больше смысла использовать другую вещь)
- Сохранение dynamicValue (где-то в контексте запроса)
- Направьте ODataController без {dynamicValue}. / Продукты ('ABC123') вместо /{dynamicValue}/ Продукты ('ABC123')
Вот код:
// Register the ServiceRoute
public static void Register(HttpConfiguration config)
{
// Register the filter that will intercept the request before it is rooted to OData
config.Filters.Add(CustomAuthenticationFilter>()); // If your dynamic parameter is related with Authentication use an IAuthenticationFilter otherwise you can register a MessageHandler for example.
// Create the default collection of built-in conventions.
var conventions = ODataRoutingConventions.CreateDefault();
config.MapODataServiceRoute(
routeName: "NameOfYourRoute",
routePrefix: null, // Here you can define a prefix if you want
model: GetEdmModel(), //Get the model
pathHandler: new CustomPathHandler(), //Using CustomPath to handle dynamic parameter
routingConventions: conventions); //Use the default routing conventions
}
// Just a filter to intercept the message before it hits the controller and to extract & store the DynamicValue
public class CustomAuthenticationFilter : IAuthenticationFilter, IFilter
{
// Extract the dynamic value
var dynamicValueStr = ((string)context.ActionContext.RequestContext.RouteData.Values["odatapath"])
.Substring(0, ((string)context.ActionContext.RequestContext.RouteData.Values["odatapath"])
.IndexOf('/')); // You can use a more "safer" way to parse
int dynamicValue;
if (int.TryParse(dynamicValueStr, out dynamicValue))
{
// TODO (this I leave it to you :))
// Store it somewhere, probably at the request "context"
// For example as claim
}
}
// Define your custom path handler
public class CustomPathHandler : DefaultODataPathHandler
{
public override ODataPath Parse(IEdmModel model, string serviceRoot, string odataPath)
{
// Code made to remove the "dynamicValue"
// This is assuming the dynamicValue is on the first "/"
int dynamicValueIndex= odataPath.IndexOf('/');
odataPath = odataPath.Substring(dynamicValueIndex + 1);
// Now OData will route the request normaly since the route will only have "/Products('ABC123')"
return base.Parse(model, serviceRoot, odataPath);
}
}
Теперь у вас должна быть информация о динамическом значении, хранящаяся в контексте запроса, и OData должна правильно маршрутизировать ODataController. Как только вы получите доступ к вашему методу, вы можете получить доступ к контексту запроса, чтобы получить информацию о "динамическом значении" и использовать его для выбора правильной базы данных.
Эти API-интерфейсы, вероятно, немного изменились со времени написания этого исходного сообщения. Но я смог добиться этого, сделав префикс маршрута данных по умолчанию, содержащий параметр:
b.MapODataServiceRoute("odata", "odata/{customerName}", GetEdmModel());
В моем сценарии у меня есть база данных для каждого клиента, поэтому я хочу, чтобы префикс маршрута принимал имя клиента (база данных):
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMvc(b =>
{
b.MapODataServiceRoute("odata", "odata/{customerName}", GetEdmModel());
});
}
Пример контроллера (примечаниеcustomerName
параметр в действии):
public class BooksController : ODataController
{
private IContextResolver _contextResolver;
public BooksController(IContextResolver contextResolver)
{
_contextResolver = contextResolver;
}
[EnableQuery]
public IActionResult Get(string customerName)
{
var context = _contextResolver.Resolve(customerName);
return Ok(context.Books);
}
}
Затем вы можете нажать на URL-адрес, например: https://localhost/odata/acmecorp/Books