OData V4在服务器端修改$ filter

gor*_*oth 15 odata asp.net-web-api entity-framework-6 asp.net-web-api2

我希望能够修改控制器内的过滤器,然后根据更改的过滤器返回数据.

因此,我在服务器端有一个ODataQueryOptions参数,我可以用它来查看FilterQueryOption.

我们假设过滤器类似于"$ filter = ID eq -1",但在服务器端,如果我看到ID为"-1",则告诉我用户想要选择所有记录.

我试图将"$ filter = ID eq -1"更改为"$ filter = ID ne -1",这将通过设置Filter.RawValue给我所有,但这是只读的.
我试图创建一个新的FilterQueryOption,但这需要一个ODataQueryContext和一个ODataQueryOptionParser,我无法弄清楚如何创建.

然后我尝试设置Filter = Null,然后设置ApplyTo,当我在控制器中设置断点并在立即窗口上检查时,它似乎有效但是一旦它在控制器上离开GET方法,它就会"恢复"回来到URL中传递的内容.

本文讨论了做一些非常相似的" 修改WebAPI OData QueryOptions.Filter的最佳方法 ",但是一旦它离开控制器GET方法,它就会恢复到URL查询过滤器.

使用示例代码更新

[EnableQuery]
[HttpGet]
public IQueryable<Product> GetProducts(ODataQueryOptions<Product> queryOptions)
{
    if (queryOptions.Filter != null)
    {
        var url = queryOptions.Request.RequestUri.AbsoluteUri;
        string filter = queryOptions.Filter.RawValue;

        url = url.Replace("$filter=ID%20eq%201", "$filter=ID%20eq%202");
        var req = new HttpRequestMessage(HttpMethod.Get, url);

        queryOptions = new ODataQueryOptions<Product>(queryOptions.Context, req);
    }

    IQueryable query = queryOptions.ApplyTo(db.Products.AsQueryable());
    return query as IQueryable<Product>;
}
Run Code Online (Sandbox Code Playgroud)

运行此代码不会返回任何产品,因为URL中的原始查询需要产品1,我将产品1的ID过滤器与产品2交换.
现在,如果我运行SQL事件探查器,我可以看到它添加了类似"选择"的内容*来自产品WHERE ID = 1 AND ID = 2".

但是,如果我通过替换$ top尝试同样的事情,那么它工作正常.

[EnableQuery]
[HttpGet]
public IQueryable<Product> GetProducts(ODataQueryOptions<Product> queryOptions)
{
    if (queryOptions.Top != null)
    {
        var url = queryOptions.Request.RequestUri.AbsoluteUri;
        string filter = queryOptions.Top.RawValue;

        url = url.Replace("$top=2", "$top=1");
        var req = new HttpRequestMessage(HttpMethod.Get, url);

        queryOptions = new ODataQueryOptions<Product>(queryOptions.Context, req);
    }

    IQueryable query = queryOptions.ApplyTo(db.Products.AsQueryable());
    return query as IQueryable<Product>;
}
Run Code Online (Sandbox Code Playgroud)

最终结果
在微软的帮助下.这是支持过滤,计数和分页的最终输出.

using System.Net.Http;
using System.Web.OData;
using System.Web.OData.Extensions;
using System.Web.OData.Query;

/// <summary>
/// Used to create custom filters, selects, groupings, ordering, etc...
/// </summary>
public class CustomEnableQueryAttribute : EnableQueryAttribute
{
    public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
    {
        IQueryable result = default(IQueryable);

        // get the original request before the alterations
        HttpRequestMessage originalRequest = queryOptions.Request;

        // get the original URL before the alterations
        string url = originalRequest.RequestUri.AbsoluteUri;

        // rebuild the URL if it contains a specific filter for "ID = 0" to select all records
        if (queryOptions.Filter != null && url.Contains("$filter=ID%20eq%200")) 
        {
            // apply the new filter
            url = url.Replace("$filter=ID%20eq%200", "$filter=ID%20ne%200");

            // build a new request for the filter
            HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, url);

            // reset the query options with the new request
            queryOptions = new ODataQueryOptions(queryOptions.Context, req);
        }

        // set a top filter if one was not supplied
        if (queryOptions.Top == null) 
        {
            // apply the query options with the new top filter
            result = queryOptions.ApplyTo(queryable, new ODataQuerySettings { PageSize = 100 });
        } 
        else 
        {
            // apply any pending information that was not previously applied
            result = queryOptions.ApplyTo(queryable);
        }

        // add the NextLink if one exists
        if (queryOptions.Request.ODataProperties().NextLink != null) 
        {
            originalRequest.ODataProperties().NextLink = queryOptions.Request.ODataProperties().NextLink;
        }
        // add the TotalCount if one exists
        if (queryOptions.Request.ODataProperties().TotalCount != null) 
        {
            originalRequest.ODataProperties().TotalCount = queryOptions.Request.ODataProperties().TotalCount;
        }

        // return all results
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

Fan*_*ang 9

删除[EnableQuery]属性,您的方案应该可以工作,因为在使用此属性后,OData/WebApi将在您在控制器中返回数据后应用原始查询选项,如果您已经在控制器方法中应用,那么您不应该使用该属性.

但是如果你的查询选项包含$ select,那些代码不起作用,因为结果的类型不是Product,我们使用包装器来表示$ select的结果,所以我建议你使用试试这个:

制作自定义的EnableQueryAttribute

public class MyEnableQueryAttribute : EnableQueryAttribute
{
    public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
    {
        if (queryOptions.Filter != null)
        {
            queryOptions.ApplyTo(queryable);
            var url = queryOptions.Request.RequestUri.AbsoluteUri;

            url = url.Replace("$filter=Id%20eq%201", "$filter=Id%20eq%202");
            var req = new HttpRequestMessage(HttpMethod.Get, url);

            queryOptions = new ODataQueryOptions(queryOptions.Context, req);
        }

        return queryOptions.ApplyTo(queryable);
    }
}
Run Code Online (Sandbox Code Playgroud)

在控制器方法中使用此属性

[MyEnableQueryAttribute]
public IHttpActionResult Get()
{
    return Ok(_products);
}
Run Code Online (Sandbox Code Playgroud)

希望这可以解决您的问题,谢谢!

风扇.


Afs*_*bbi 5

响应@Chris Schaller,我发布了自己的解决方案,如下所示:

public class CustomEnableQueryAttribute : EnableQueryAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var url = actionContext.Request.RequestUri.OriginalString;

        //change something in original url, 
        //for example change all A charaters to B charaters,
        //consider decoding url using WebUtility.UrlDecode() if necessary
        var newUrl = ModifyUrl(url); 

        actionContext.Request.RequestUri = new Uri(newUrl);
        base.OnActionExecuting(actionContext);
    }
}
Run Code Online (Sandbox Code Playgroud)