在OData Web Api Url中传递参数

时间:2015-04-08 12:38:46

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

使用Web Api我有一个可以从数据库返回产品的OData EndPoint。

我有多个具有相似模式的数据库,并希望在URL中传递一个参数来识别Api应该使用哪个数据库。

当前的Odata终点:
http://localhost:62999/Products

我想要的是什么:
http://localhost:62999/ 999 /产品

在新的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<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)));
    }
}

我坚持的是如何在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参数? 我也考虑过使用Message Handler,但我不确定如何实现它。

更新
这个问题试图和我做同样的事情:How to declare a parameter as prefix on OData
但目前尚不清楚如何从网址读取参数。
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 }
    );
}

2 个答案:

答案 0 :(得分:2)

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

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

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

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

  1. 在路由请求之前解压缩dynamicValue(在我的情况下,我使用IAuthenticationFilter在路由之前拦截消息,因为参数与授权有关,但是对于你的情况,使用另一个更有意义的东西)
  2. 存储dynamicValue(请求上下文中的某个位置)
  3. 在没有{dynamicValue}的情况下路由ODataController。 /产品('ABC123')代替/ {dynamicValue} / Products('ABC123')
  4. 以下是代码:

    // 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。在您的方法中,您可以访问请求上下文以获取有关“动态值”的信息并使用它来选择正确的数据库

答案 1 :(得分:1)

自这篇原始文章以来,这些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