Adding custom filters to an MVC API OData query request

I needed to examine every OData request and add custom filters based on the specific business rules. The additional filters would be based on who is logged in (and their access levels), the properties of the entities requested at the root level and the properties of any entities that are being expanded.

My solution was to examine all entities (root and expanded) for their properties and then add any additional $filter options I needed at the root filter of the request ODataUri.

(OData v4 supports filtering in $expand clauses but the filterOption in the expanded entities is read only so you cannot modify the filter expressions for the expanded entities. You can only examine the filterOption contents at the expanded entities.)

Here is an example OData request Url:


Below is the same OData request Url after I updated it:

/RootEntity?$filter=OtherEntity/SomeOtherEntity/Id eq 3&$expand=OtherEntity($expand=SomeOtherEntity)

Steps I used to accomplish this:

1. Use ODataUriParser to parse the incoming Url into a Uri object

var parser = new ODataUriParser(model, new Uri(serviceRootPath), requestUri);
var odataUri = parser.ParseUri();

2. Create a method that will traverse down from the root to all expanded entities and pass the ODataUri by ref (so that you can update it as needed as you examine each entity)

The first method will examine the root entity and add any additional filters based on the properties of the root entity.

AddCustomFilters(ref ODataUri odataUri);

The AddCustomFilters method will the traverse the expanded entities and call the AddCustomFiltersToExpandedEntity which will continue to traverse down all expanded entities to add any necessary filters.

foreach (var item in odatauri.SelectAndExpand.SelectedItems)
   AddCustomFiltersToExpandedEntity(ref ODataUri odataUri, ExpandedNavigationSelectItem expandedNavigationSelectItem, string parentNavigationNameProperty)

The method **AddCustomFiltersToExpandedEntity** should call itself as it loops over the expanded entities at each level.

3. Update the root filter as you examine each entity

Create a new filter clause with your additional filter requirements and overwrite the existing filter clause at the root level. The $filter at the root level of the ODataUri has a setter so it can be overridden.

odataUri.Filter = new FilterClause(newFilterExpression, newFilterRange);

Note: I created a new filter clause using a **BinaryOperatorKind.And** so that any additional filter expressions are simply appended to any existing filter expressions already in the ODataUri

var combinedFilterExpression = new BinaryOperatorNode(BinaryOperatorKind.And, odataUri.Filter.Expression, newFilterExpression);
odataUri.Filter = new FilterClause(combinedFilterExpression, newFilterRange);

4. Use ODataUriBuilder to create a new Uri based on the updated ODataUri

var updatedODataUri = new Microsoft.OData.Core.UriBuilder.ODataUriBuilder(ODataUrlConventions.Default, odataUri).BuildUri();

5. Replace the request Uri with the updated Uri.

This allows the OData controller to complete processing the request using the updated OData Url which includes the additional filter options you just added to the root level filer.

ActionContext.Request.RequestUri = updatedODataUri;

This provided me with the capability to add any filtering options I need and I can be sure sure that I have not altered the OData Url structure incorrectly.

I hope this helps someone else when facing this same issue.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s