在 OData WebApi 中传递参数 Url
Pass Parameters in OData WebApi Url
使用 Web Api 我有一个 OData 端点,它可以 return 来自数据库的产品。
我有多个具有相似模式的数据库,想在 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'。
下面的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)));
}
}
我遇到的问题是如何在 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 参数的地方吗?
我也考虑过使用消息处理程序,但我也不确定如何实现它。
更新
这个问题试图和我做同样的事情:
但是不清楚如何从url中读取参数。
var databaseId = HttpContext.Current.Request["database"];
return当前为空。
即使将路由配置更新为以下内容:
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 }
);
}
我遇到过在 OData 上传递动态参数的解决方案,不确定是否正确。
我在特定上下文中使用过此解决方案,其中动态参数只是为了对客户端进行身份验证,但我认为您可以用类似的方式解决您的问题。
问题: 您不想在 URL 请求示例中传递动态值:http://localhost:62999/{dynamicValue}/Products('ABC123'),但 ODataRouting 永远不会正确路由,因为额外的 /{dynamicValue} 和 ODataControler "will not hit"。
使用 ApiController 您可以创建自定义路由,但在 OData 中您不能(至少我没有找到一种简单的方法来做到这一点,可能您必须创建自己的或扩展 OData 路由约定)。
因此作为替代方案解决方案:
例如,如果每个请求都有一个动态值:“http://localhost:62999/{dynamicValue}/Products”,请执行以下步骤:
- 在路由请求之前提取动态值(在我的例子中,我使用 IAuthenticationFilter 在消息被路由之前拦截消息,因为参数与授权相关,但也许对于你的情况更有意义使用另一件事)
- 存储动态值(在请求上下文中的某处)
- 在没有 {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。一旦进入您的方法,您就可以访问请求上下文以获取有关 "dynamic value" 的信息并使用它来选择正确的数据库
自最初 post 以来,这些 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
使用 Web Api 我有一个 OData 端点,它可以 return 来自数据库的产品。
我有多个具有相似模式的数据库,想在 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'。
下面的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)));
}
}
我遇到的问题是如何在 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 参数的地方吗? 我也考虑过使用消息处理程序,但我也不确定如何实现它。
更新
这个问题试图和我做同样的事情:
但是不清楚如何从url中读取参数。
var databaseId = HttpContext.Current.Request["database"];
return当前为空。
即使将路由配置更新为以下内容:
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 }
);
}
我遇到过在 OData 上传递动态参数的解决方案,不确定是否正确。
我在特定上下文中使用过此解决方案,其中动态参数只是为了对客户端进行身份验证,但我认为您可以用类似的方式解决您的问题。
问题: 您不想在 URL 请求示例中传递动态值:http://localhost:62999/{dynamicValue}/Products('ABC123'),但 ODataRouting 永远不会正确路由,因为额外的 /{dynamicValue} 和 ODataControler "will not hit"。 使用 ApiController 您可以创建自定义路由,但在 OData 中您不能(至少我没有找到一种简单的方法来做到这一点,可能您必须创建自己的或扩展 OData 路由约定)。
因此作为替代方案解决方案: 例如,如果每个请求都有一个动态值:“http://localhost:62999/{dynamicValue}/Products”,请执行以下步骤:
- 在路由请求之前提取动态值(在我的例子中,我使用 IAuthenticationFilter 在消息被路由之前拦截消息,因为参数与授权相关,但也许对于你的情况更有意义使用另一件事)
- 存储动态值(在请求上下文中的某处)
- 在没有 {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。一旦进入您的方法,您就可以访问请求上下文以获取有关 "dynamic value" 的信息并使用它来选择正确的数据库
自最初 post 以来,这些 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