OData V4 modify $filter on server side

asp.net-web-api asp.net-web-api2 entity-framework-6 odata

Question

I want to be able to change the filter inside the controller and then have the modified filter return the data.

So that I may examine the FilterQueryOption, I have an ODataQueryOptions parameter on the server side.

Let's say the filter is something like "$filter=ID eq -1," but if I see "-1" for an ID on the server side, it tells me the user wants to pick all entries.

I made an attempt to convert "$filter=ID eq -1" to "$filter=ID ne -1," which would give me all by adjusting the Filter. RawValue, however this can only be read.
I attempted to construct a new FilterQueryOption, but I'm not sure how to do it because it needs an ODataQueryContext and an ODataQueryOptionParser.

When I establish a break point in the controller and check this on the current window, I then tried setting the Filter = Null and using ApplyTo, but as it leaves the GET function on the controller, it "reverts" back to what was supplied in the URL.

This article describes a fairly similar procedure, except it switches back to the URL query filter once it exits the controller's GET method.

REVISION WITH EXAMPLE CODE

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

Running this code won't produce anything. this is because I switched the ID filter for product 1 with product 2 because the original URL's query sought out product 1.
Now if I run SQL Profiler, I can see that it added something like "Select * from Product WHERE ID = 1 AND ID = 2".

If I do the same thing by swapping out the $top, it succeeds BUT.

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

THE RESULTS
with the aid of Microsoft. The finished product, which includes filter, count, and paging, is seen here.

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;
    }
}
1
16
5/23/2017 11:46:21 AM

Accepted Answer

If you remove the [EnableQuery] property, your scenario should still work since, if you use it, OData/WebApi will utilize your original query option when you return data to the controller method. If you already use that option, you shouldn't use the [EnableQuery] attribute.

We use a wrapper to represent the result of $select, thus if your query option contains $select, those codes won't work because the result's type isn't Product. Instead, I recommend trying this:

Create a personalized 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);
    }
}

In your controller method, use this attribute.

[MyEnableQueryAttribute]
public IHttpActionResult Get()
{
    return Ok(_products);
}

Hope this can help with your issue; many thanks!

Fan.

10
11/16/2015 1:25:44 AM

Popular Answer

I've posted my own approach in answer to @Chris Schaller as follows:

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


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow