OData V4 在服务器端修改 $filter
OData V4 modify $filter on server side
我希望能够修改控制器内的过滤器,然后 return 基于修改后的过滤器的数据。
所以我在服务器端有一个 ODataQueryOptions 参数,我可以用它来查看 FilterQueryOption。
让我们假设过滤器是这样的“$filter=ID eq -1”,但在服务器端,如果我看到 ID 为“-1”,这告诉我用户想要 select所有记录。
我试图将“$filter=ID eq -1”更改为“$filter=ID ne -1”,这将通过设置 Filter.RawValue 来提供所有信息,但这是只读的。
我试图创建一个新的 FilterQueryOption,但这需要一个 ODataQueryContext 和一个我不知道如何创建的 ODataQueryOptionParser。
然后我尝试设置 Filter = Null,然后我们设置 ApplyTo,当我在控制器中设置一个断点并立即检查它时,它似乎起作用了 window 但是一旦它离开 GET 方法在控制器上然后它 "reverts" 返回到 URL 中传递的内容。
这篇文章讨论了做一些非常类似于“The best way to modify a 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>;
}
运行 此代码不会 return 任何产品,这是因为 URL 中的原始查询想要产品 1,而我将产品 1 的 ID 过滤器与产品 2 交换。
现在,如果我 运行 SQL Profiler,我可以看到它添加了类似 "Select * from Product 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>;
}
最终结果
在微软的帮助下。这是支持过滤、计数和分页的最终输出。
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;
}
}
删除 [EnableQuery] 属性,您的方案应该有效,因为在使用此属性后,OData/WebApi 将在您 return 控制器中的数据之后应用您的原始查询选项,如果您已经在您的控制器方法,那么你不应该使用那个属性。
但是,如果您的查询选项包含 $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);
}
}
在你的控制器方法中使用这个属性
[MyEnableQueryAttribute]
public IHttpActionResult Get()
{
return Ok(_products);
}
希望能解决您的问题,谢谢!
范.
作为对@Chris Schaller 的回应,我 post 我自己的解决方案如下:
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);
}
}
我希望能够修改控制器内的过滤器,然后 return 基于修改后的过滤器的数据。
所以我在服务器端有一个 ODataQueryOptions 参数,我可以用它来查看 FilterQueryOption。
让我们假设过滤器是这样的“$filter=ID eq -1”,但在服务器端,如果我看到 ID 为“-1”,这告诉我用户想要 select所有记录。
我试图将“$filter=ID eq -1”更改为“$filter=ID ne -1”,这将通过设置 Filter.RawValue 来提供所有信息,但这是只读的。
我试图创建一个新的 FilterQueryOption,但这需要一个 ODataQueryContext 和一个我不知道如何创建的 ODataQueryOptionParser。
然后我尝试设置 Filter = Null,然后我们设置 ApplyTo,当我在控制器中设置一个断点并立即检查它时,它似乎起作用了 window 但是一旦它离开 GET 方法在控制器上然后它 "reverts" 返回到 URL 中传递的内容。
这篇文章讨论了做一些非常类似于“The best way to modify a 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>;
}
运行 此代码不会 return 任何产品,这是因为 URL 中的原始查询想要产品 1,而我将产品 1 的 ID 过滤器与产品 2 交换。
现在,如果我 运行 SQL Profiler,我可以看到它添加了类似 "Select * from Product 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>;
}
最终结果
在微软的帮助下。这是支持过滤、计数和分页的最终输出。
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;
}
}
删除 [EnableQuery] 属性,您的方案应该有效,因为在使用此属性后,OData/WebApi 将在您 return 控制器中的数据之后应用您的原始查询选项,如果您已经在您的控制器方法,那么你不应该使用那个属性。
但是,如果您的查询选项包含 $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);
}
}
在你的控制器方法中使用这个属性
[MyEnableQueryAttribute]
public IHttpActionResult Get()
{
return Ok(_products);
}
希望能解决您的问题,谢谢!
范.
作为对@Chris Schaller 的回应,我 post 我自己的解决方案如下:
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);
}
}