在OData WebApi Url中传递参数

时间:2021-02-22 15:12:48

Using Web Api I have an OData EndPoint which can return Products from a database.

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

I have multiple databases with similar schemas, and want to pass a parameter in the URL to identify which database the Api should use.

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

Current Odata Endpoint:
http://localhost:62999/Products

当前Odata端点:http://localhost:62999 /产品

What I want:
http://localhost:62999/999/Products

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

In the new Url, I pass in 999 (the database ID).

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

The database ID is intended to specify which database to load the product from. For example localhost:62999/999/Products('ABC123') would load product 'ABC123' from database 999, but the next request, localhost:62999/111/Products('XYZ789') would load the product 'XYZ789' from database 111.

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

The Url below works, but I don't like it.
localhost:62999/Products('XYZ789')?database=111

下面的Url可以工作,但是我不喜欢它。localhost:62999 /产品(“XYZ789”)?数据库= 111

Here is the code for the controller:

这是控制器的代码:

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

I use Ninject to resolve which implementation of IErpService to inject into the controller by binding to a service provider:

我使用Ninject通过绑定到服务提供者来解决IErpService的哪个实现要注入到控制器中:

kernel.Bind<IErpService>().ToProvider(new ErpServiceProvider()); And the ErpServiceProvider inspects the url to identify the databaseId required by this request:

kernel.Bind < IErpService >()。提供(新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)));
    }
}

The bit I am stuck on is how to define the Url parameter in the OData route config.

我遇到的问题是如何在OData route配置中定义Url参数。

Normal WebApi routes can have parameters defined as follows:

正常的WebApi路由可以定义如下参数:

config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
      );

But how do I define the parameters in the OData route config?

但是如何在OData route配置中定义参数呢?

ODataModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<ProductDto>("Products");
        builder.EntitySet<WorkOrderDto>("WorkOrders");
        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel());

Is this even where I should be defining the Url parameters? I have also thought about using a Message Handler but I am not certain how this can be implemented either.

这就是我应该定义Url参数的地方吗?我也考虑过使用消息处理程序,但我也不确定如何实现它。

UPDATE
This question is trying to do the same thing as me: How to declare a parameter as prefix on OData
But it is not clear how the parameter is to be read from the url.
var databaseId = HttpContext.Current.Request["database"]; returns null currently.
Even after updating the route config to the following:

更新这个问题的目的和我一样:如何在OData上声明一个参数作为前缀,但是不清楚如何从url读取参数。var databaseId = HttpContext.Current.Request(“数据库”);目前返回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 }
    );
}

1 个解决方案

#1


1  

I've encountered a solution to pass dynamic parameter on OData, not sure if is the right one.

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

I've used this solution on a certain context, where the dynamic parameter was just to authenticate the client, but I think you can solve your problem in a similar way.

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

Problem: You wan't to pass a dynamic value at the URL request example: http://localhost:62999/{dynamicValue}/Products('ABC123'), but the ODataRouting will never route correctly, because of that extra /{dynamicValue} and the ODataControler "will not hit". Using ApiController you could made a custom routing, but at OData you can't (at least I didn't found an easy way to do it, probably you had to made your own or extend the OData routing convention).

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

So as alternative solution: If every request will have a dynamicValue for example: "http://localhost:62999/{dynamicValue}/Products" do the following steps:

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

  1. Before routing the request Extract the dynamicValue (In my case, I've used an IAuthenticationFilter to intercept the message before it was routed, since the parameter was related with authorization, but maybe for your case it makes more sense to use another thing)
  2. 在路由请求之前提取dynamicValue(在我的例子中,我使用了IAuthenticationFilter在消息被路由之前截取消息,因为这个参数与授权有关,但是在您的例子中,使用另一个参数可能更有意义)
  3. Store the dynamicValue (somewhere on the request context)
  4. 存储dynamicValue(在请求上下文中的某处)
  5. Route the ODataController without the {dynamicValue}. /Products('ABC123') instead of /{dynamicValue}/Products('ABC123')
  6. 不带{dynamicValue}的ODataController路径。/产品(“ABC123”)而不是/ { dynamicValue } /产品(“ABC123”)

Here is the code:

这是代码:

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

Now you should have the information of the dynamic value stored at the context of the request and OData should route correctly to the ODataController. Once your there at your method, you can access the request context to get information about the "dynamic value" and use it to choose the correct database

现在您应该有动态值的信息存储在请求的上下文中,OData应该正确地路由到ODataController。在方法完成之后,您可以访问请求上下文来获取关于“动态值”的信息,并使用它来选择正确的数据库

#1


1  

I've encountered a solution to pass dynamic parameter on OData, not sure if is the right one.

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

I've used this solution on a certain context, where the dynamic parameter was just to authenticate the client, but I think you can solve your problem in a similar way.

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

Problem: You wan't to pass a dynamic value at the URL request example: http://localhost:62999/{dynamicValue}/Products('ABC123'), but the ODataRouting will never route correctly, because of that extra /{dynamicValue} and the ODataControler "will not hit". Using ApiController you could made a custom routing, but at OData you can't (at least I didn't found an easy way to do it, probably you had to made your own or extend the OData routing convention).

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

So as alternative solution: If every request will have a dynamicValue for example: "http://localhost:62999/{dynamicValue}/Products" do the following steps:

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

  1. Before routing the request Extract the dynamicValue (In my case, I've used an IAuthenticationFilter to intercept the message before it was routed, since the parameter was related with authorization, but maybe for your case it makes more sense to use another thing)
  2. 在路由请求之前提取dynamicValue(在我的例子中,我使用了IAuthenticationFilter在消息被路由之前截取消息,因为这个参数与授权有关,但是在您的例子中,使用另一个参数可能更有意义)
  3. Store the dynamicValue (somewhere on the request context)
  4. 存储dynamicValue(在请求上下文中的某处)
  5. Route the ODataController without the {dynamicValue}. /Products('ABC123') instead of /{dynamicValue}/Products('ABC123')
  6. 不带{dynamicValue}的ODataController路径。/产品(“ABC123”)而不是/ { dynamicValue } /产品(“ABC123”)

Here is the code:

这是代码:

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

Now you should have the information of the dynamic value stored at the context of the request and OData should route correctly to the ODataController. Once your there at your method, you can access the request context to get information about the "dynamic value" and use it to choose the correct database

现在您应该有动态值的信息存储在请求的上下文中,OData应该正确地路由到ODataController。在方法完成之后,您可以访问请求上下文来获取关于“动态值”的信息,并使用它来选择正确的数据库