【问题标题】:Pass Parameters in OData WebApi Url在 OData WebApi Url 中传递参数
【发布时间】:2015-06-13 10:11:00
【问题描述】:

使用 Web Api 我有一个 OData 端点,它可以从数据库返回产品。

我有多个具有相似架构的数据库,并且想在 URL 中传递一个参数来确定 Api 应该使用哪个数据库。

当前 Odata 端点:
http://localhost:62999/Products

我想要什么:
http://localhost:62999/999/Products

在新的 Url 中,我传入 999(数据库 ID)。

数据库 ID 用于指定从哪个数据库加载产品。例如,localhost:62999/999/Products('ABC123') 将从数据库 999 加载产品“ABC123”,但下一个请求 localhost:62999/111/Products('XYZ789') 将从数据库 111 加载产品“XYZ789”。

下面的网址可以用,但我不喜欢它。
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&lt;IErpService&gt;().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)));
    }
}

我坚持的一点是如何在 OData 路由配置中定义 Url 参数。

普通的 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 参数的地方吗? 我也考虑过使用消息处理程序,但我也不确定如何实现。

更新
这个问题试图和我做同样的事情:How to declare a parameter as prefix on OData
但不清楚如何从 url 中读取参数。
var databaseId = HttpContext.Current.Request["database"]; 当前返回 null。
即使将路由配置更新为以下内容:

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 }
    );
}

【问题讨论】:

  • 澄清一下...您是要使用 ID 来指定从数据库中选择哪个单一产品,还是从哪个数据库中选择所有产品?
  • 它旨在指定从哪个数据库加载产品。例如,localhost:62999/999/Products('ABC123') 将从数据库 999 加载产品“ABC123”,但下一个请求 localhost:62999/111/Products('XYZ789') 将从数据库 111 加载产品“XYZ789”。
  • 嗨,philreed,您的问题解决了吗?我已经为您的问题提出了解决方案
  • 抱歉,还没有。我尝试了您的解决方案,但路由仍然无法正常工作。明天我会再花更多的时间来解决这个问题。

标签: c# asp.net asp.net-web-api odata


【解决方案1】:

我遇到了在 OData 上传递动态参数的解决方案,不确定是否正确。

我在特定的上下文中使用了这个解决方案,其中动态参数只是为了验证客户端,但我认为你可以用类似的方式解决你的问题。

问题:您不想在 URL 请求示例中传递动态值:http://localhost:62999/{dynamicValue}/Products('ABC123'),但 ODataRouting 永远不会正确路由,因为额外的 /{dynamicValue} 和 ODataControler “不会命中”。 使用 ApiController 您可以进行自定义路由,但在 OData 中您不能(至少我没有找到简单的方法,可能您必须自己创建或扩展 OData 路由约定)。

所以作为替代解决方案: 如果每个请求都有一个动态值,例如:“http://localhost:62999/{dynamicValue}/Products”,请执行以下步骤:

  1. 在路由请求之前提取动态值(在我的情况下,我使用 IAuthenticationFilter 在路由之前拦截消息,因为该参数与授权相关,但对于您的情况,使用另一个可能更有意义东西)
  2. 存储 dynamicValue(请求上下文中的某处)
  3. 路由不带 {dynamicValue} 的 ODataController。 /Products('ABC123') 而不是 /{dynamicValue}/Products('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。一旦您使用您的方法,您就可以访问请求上下文以获取有关“动态值”的信息并使用它来选择正确的数据库

【讨论】:

  • 我现在可以使用此功能,但我必须对您的答案进行一些更改。我仍然想赞扬你,因为你让我走上了通往解决方案的正确道路,但你发布的答案并不是我完全使用的。
  • 主要变化是在配置OData路由时定义了一个路由模板:routePrefix: "erp/{company}"。这允许我调用var company = HttpContext.Current.Request.RequestContext.RouteData.Values["company"];(带有所需的空检查),这并不意味着我不必像您在过滤器中那样解析字符串以查找“/”的索引。
【解决方案2】:

自这篇原始帖子以来,这些 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);
    }
}

然后您可以点击以下网址:https://localhost/odata/acmecorp/Books

【讨论】:

  • 太棒了!感谢您提供此解决方案。到目前为止与 Microsoft.AspNetCore.OData@7.2.2 一起工作。
猜你喜欢
  • 2015-03-18
  • 1970-01-01
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
  • 1970-01-01
  • 2016-03-27
  • 1970-01-01
  • 2018-07-05
相关资源
最近更新 更多