如何从字符串中解析 ODataQueryOptions?

2024-03-25

我必须在符合 OData 规范的 ASP.NET API 上为 EF6 实体提供一些读取端点。实体检索基于接受实体的函数而工作良好System.Web.Http.OData.Query.ODataQueryOptions<TEntity> https://learn.microsoft.com/en-us/previous-versions/aspnet/jj890615%28v%3dvs.118%29实例。

现在,根据文档,OData 的实现不支持$count.

然而,我们希望至少提供检索(过滤后的)数据集总数的功能,如图所示在文档中 http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360944,例如(通过稍微组合其中几个样本):

http://host/service/Products/$count($filter=Price gt 5.00)

(根据规范,我理解这应该是一个有效的、符合规范的 OData 查询,用于查询价格大于 5¤ 的产品数量。如果我错了,请纠正我。)

现在,根据以下内容检索计数IQueryable从返回ODataQuerySettings.ApplyTo是微不足道的。也是如此捕获对此路由的请求:

[Route("$count({queryOptions})")]
public int Count(ODataQueryOptions<ProductEntity> queryOptions)

唯一缺少的是queryOptions路线的一部分应解析为ODataQueryOptions<ProductEntity>实例。在其他 OData 端点上,这无需任何进一步的麻烦即可工作。但是,即使我用$filter,我得到的只是一个“空”(即初始化为默认值)ODataQueryOptions<ProductEntity>没有设置过滤器的实例。

或者,我可以像这样声明我的 Web API 端点:

[Route("$count({queryOptions})")]
public int Count(string rawQueryOptions)

在这个方法中,rawQueryOptions包含我希望传递给 OData 的查询选项,即解析以填充ODataQueryOptions<ProductEntity>实例。

这必须非常简单,因为任何其他 OData 端点都会发生同样的事情。进行比较:

[Route("")]
public IEnumerable<object> Filter(ODataQueryOptions<ProductEntity> queryOptions)

这有效;查询选项按预期填充,与我的上述端点的情况不同。

如何根据从路线中提取的字符串填充 OData 查询选项实例?

我还尝试过一些其他事情:

  • Applied [FromUri] to the queryOptions范围。
  • Applied [ODataQueryParameterBinding] to the queryOptions范围。

尽管语法与您的请求略有不同,但 .Net OData 实现具有您需要的 OOTB 支持,如果您确实问这个问题,则表明您正在尝试向标准 API 添加 OData 支持。

鉴于您已经在 ASP.Net API 上安装了 EF6...为什么不在另一条路线上公开 OData 控制器呢?通过这种方式,您可以获得查询支持的完整实现,而无需解析QueryOptions at all.

Updated

如果不适合在单独的路线中添加新控制器,您可以轻松升级现有控制器ApiController并实施 OData 路由来代替现有路由。

ODataController https://learn.microsoft.com/en-us/previous-versions/aspnet/jj890597(v=vs.118)继承自 ApiController,但包含一些简化使用 OData 响应约定的辅助方法,因此就地升级通常不会造成破坏。

作为示例,以下是允许所有受支持的 OData 查询选项从 EF 返回数据所需的唯一控制器代码DbSet,这包括全力支持$select, $expand, $filter, $apply乃至$count跨越嵌套$filter.

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

允许这种情况发生的魔法并不是ODataController本身,EnableQueryAttribute解析/翻译QueryOptions并将其应用到实体连接从方法返回的表达式。

完成这项工作的最后一个组件是注册路由,这比标准 API 更复杂一些,因为您需要定义EdmModel首先,但这样做我们永远不需要解析传入的查询参数。

为上述控制器配置模型和路由的最小示例可能如下所示:

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

如何配置您想要的 $count 语法

虽然 OOTB 不支持您预期的过滤集合计数语法,但支持的语法非常接近,因此您可以使用 URL 重写模块轻松操作查询

  • 您期望的语法:
    http://host/service/Products/$count($filter=Price gt 5.00)
  • .Net 支持的语法
    http://host/service/Products/$count?$filter=Price gt 5.00

Owin中间件:

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

在注册 OData 路由之前添加到 Startup.cs

app.Use(typeof(ODataConventionUrlRewriter));
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何从字符串中解析 ODataQueryOptions? 的相关文章

随机推荐