OData V4 изменить фильтр $ на стороне сервера

Я хотел бы иметь возможность изменить фильтр внутри контроллера, а затем вернуть данные на основе измененного фильтра.

Так что у меня есть параметр ODataQueryOptions на стороне сервера, который я могу использовать для просмотра FilterQueryOption.

Давайте предположим, что фильтр похож на этот "$filter=ID eq -1", но на стороне сервера, если я вижу "-1" для идентификатора, это говорит мне о том, что пользователь хочет выбрать все записи.

Я попытался изменить "$filter=ID eq -1" на "$filter=ID ne -1", что дало бы мне все, установив Filter.RawValue, но это только для чтения.
Я пытался создать новый FilterQueryOption, но для этого требуется ODataQueryContext и ODataQueryOptionParser, который я не могу понять, как создать.

Затем я попытался установить Filter = Null, а затем использовать ApplyTo, который, кажется, работает, когда я устанавливаю точку останова в контроллере и проверяю это в ближайшем окне, но как только он оставляет метод GET на контроллере, он "возвращается" назад. на то, что было передано в URL.

В этой статье рассказывается о том, как сделать что-то очень похожее: " Лучший способ изменить ODAP QueryOptions.Filter в WebAPI", но как только он покидает метод 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>;
}

Выполнение этого кода не вернет ни одного продукта, это потому, что исходный запрос в URL-адресе хотел продукт 1, и я поменял фильтр идентификаторов продукта 1 на продукт 2.
Теперь, если я запускаю SQL Profiler, я вижу, что он добавил что-то вроде "Выбрать * из продукта 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>;
}

КОНЕЧНЫЙ РЕЗУЛЬТАТ
С помощью Microsoft. Вот окончательный вывод, который поддерживает фильтрацию, подсчет и разбиение на страницы.

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

2 ответа

Решение

Удалите атрибут [EnableQuery], ваш сценарий должен работать, потому что после использования этого атрибута OData/WebApi применит ваш исходный параметр запроса после того, как вы вернете данные в контроллер, если вы уже применили его в методе контроллера, тогда вы не должны использовать этот атрибут,

Но если ваш вариант запроса содержит $select, этот код не работает, потому что тип результата не является Product, мы используем обертку для представления результата $select, поэтому я предлагаю вам использовать try:

Создайте настроенный атрибут 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 я публикую свое собственное решение, как показано ниже:

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);
    }
}
Другие вопросы по тегам