Передать параметры в 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", выполните следующие действия:

  1. Перед маршрутизацией запроса Извлеките dynamicValue (в моем случае я использовал IAuthenticationFilter для перехвата сообщения до его перенаправления, поскольку параметр был связан с авторизацией, но, возможно, для вашего случая имеет больше смысла использовать другую вещь)
  2. Сохранение dynamicValue (где-то в контексте запроса)
  3. Направьте 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

Другие вопросы по тегам