Asp.net 如何从字符串解析ODataQueryOptions?
我必须在ASP.NET API上为我们的EF6实体提供一些符合OData规范的读取端点。基于接受实例的函数,实体检索工作得很好 现在,根据文档,OData的实现不支持Asp.net 如何从字符串解析ODataQueryOptions?,asp.net,.net,entity-framework,entity-framework-6,odata,Asp.net,.net,Entity Framework,Entity Framework 6,Odata,我必须在ASP.NET API上为我们的EF6实体提供一些符合OData规范的读取端点。基于接受实例的函数,实体检索工作得很好 现在,根据文档,OData的实现不支持$count 然而,我们希望至少提供检索(过滤)数据集总计数的功能,如图所示,例如(通过稍微组合几个样本): (根据规范,我理解这应该是一个有效的、符合规范的OData查询,用于查询价格大于5的产品数量。如果我错了,请纠正我。) 现在,根据从ODataQuerySettings.ApplyTo返回的IQueryable检索计数是很简
$count
然而,我们希望至少提供检索(过滤)数据集总计数的功能,如图所示,例如(通过稍微组合几个样本):
(根据规范,我理解这应该是一个有效的、符合规范的OData查询,用于查询价格大于5的产品数量。如果我错了,请纠正我。)
现在,根据从ODataQuerySettings.ApplyTo
返回的IQueryable
检索计数是很简单的。捕获对此路由的请求也是如此:
[Route("$count({queryOptions})")]
public int Count(ODataQueryOptions<ProductEntity> queryOptions)
在这个方法中,rawQueryOptions
包含我希望传递给OData的查询选项,即解析以填充ODataQueryOptions
实例
这必须非常简单,因为任何其他OData端点都会发生同样的情况。作为比较:
[Route("")]
public IEnumerable<object> Filter(ODataQueryOptions<ProductEntity> queryOptions)
[路线(“”)]
公共IEnumerable筛选器(ODataQueryOptions查询选项)
这是有效的;查询选项按预期填充,与上面端点的情况不同
如何根据从路线提取的字符串填充OData查询选项实例?
我还尝试了一些东西:
- 将
应用于[FromUri]
参数查询选项
- 将
应用于[ODataQueryParameterBinding]
参数查询选项
假设您已经在ASP.NETAPI上安装了EF6。。。为什么不在另一条路线上公开OData控制器?通过这种方式,您可以获得查询支持的完整实现,而无需解析
QueryOptions
更新
如果在单独的路由中添加新控制器不合适,您可以轻松升级现有的ApiController
s,并实施OData路由来代替现有路由
继承自ApicController,但包含一些帮助器方法,这些方法简化了OData响应约定的使用,因此就地升级通常不会中断
例如,以下是允许所有受支持的OData查询选项从EFDbSet
返回数据所需的唯一控制器代码,这包括对$select
,$expand
,$filter
的完全支持,$apply
甚至在嵌套的$filter
中应用$count
public partial class ResidentsController : ODataController
{
protected MyEF.Context db = new MyEF.Context();
[EnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<MyEF.Resident> options)
{
return Ok(db.Residents);
}
}
如何配置所需的$count语法
虽然OOTB不支持过滤集合计数的预期语法,但支持的语法非常接近,因此您可以使用URL重写模块轻松地操作查询
- 您期望的语法:
http://host/service/Products/$count($filter=Price gt 5.00)
- .Net支持的语法
http://host/service/Products/$count?$filter=Price gt 5.00
“为什么不公开另一条路线上的OData控制器并使其只读?”-这一决定不在我的直接影响范围内。你需要更具体一些,我建议你发布一个新问题,如果你
我必须提供一些读取端点,但你不能在另一条路线上创建新控制器。。。那你到底能做什么?如果无法修改API,则必须修改客户端;如果无法修改客户端,则唯一的其他选项是中继服务,该服务将预期调用转换为符合URL的URL。一般原则是,每个实体有一个基本路由。在该路径上,我们提供了一个接受OData参数进行过滤的GET端点。我可以在每个这样的实体路由上添加额外的端点(例如附加一个/$count
),但我不能“突破”特定于实体的路由。现在我不太清楚您当时的问题是什么。我们的控制器应该继承自ODataController
,这样您就可以获得ApiController的所有优点以及所有OData特性。不要尝试“插接”OData功能,您将在每个控制器中手动实现许多查询,这些查询已经为您烘焙到基本控制器和属性中。我将您的读取端点解释为“只读”,我将从回答中删除这些查询,因为这会误导您
[Route("")]
public IEnumerable<object> Filter(ODataQueryOptions<ProductEntity> queryOptions)
public partial class ResidentsController : ODataController
{
protected MyEF.Context db = new MyEF.Context();
[EnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<MyEF.Resident> options)
{
return Ok(db.Residents);
}
}
public static void Register(HttpConfiguration config)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Resident>("Residents");
IEdmModel model = builder.GetEdmModel();
// To enable $select and $filter on all fields by default
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
// can also be configured like this
config.SetDefaultQuerySettings(new Microsoft.AspNet.OData.Query.DefaultQuerySettings()
{
EnableCount = true,
EnableExpand = true,
EnableFilter = true,
EnableOrderBy = true,
EnableSelect = true,
MaxTop = null
});
// Map the routes from the model using OData Conventions
config.MapODataServiceRoute("odata", "odata", model);
}
/// <summary>
/// Rewrite incoming OData requests that are implemented differently in the .Net pipeline
/// </summary>
public class ODataConventionUrlRewriter : OwinMiddleware
{
private static readonly PathString CountUrlSegments = PathString.FromUriComponent("/$count");
public ODataConventionUrlRewriter(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
// OData spec says this should work: http://host/service/Products/$count($filter=Price gt 5.00)
// But in .Net the filter needs to be in the query: http://host/service/Products/$count?$filter=Price gt 5.00
var regex = new System.Text.RegularExpressions.Regex(@"\/\$count\((.+)\)$");
var match = regex.Match(context.Request.Path.Value);
if(match != null && match.Success)
{
// So move the $filter expression to a query option
// We have to use redirect here, we can't affect the query inflight
context.Response.Redirect($"{context.Request.Uri.GetLeftPart(UriPartial.Authority)}{regex.Replace(context.Request.Path.Value, "/$count")}?{match.Groups[1].Value}");
}
else
await Next.Invoke(context);
}
}
app.Use(typeof(ODataConventionUrlRewriter));