5
votes

I have a C# OData endpoint where I need to evaluate the filters that were submitted with the OData query in order to determine if I need to add additional filters to limit the results returned to the user.

My example model is simple:

Students -> Book Catalogs -> Books (All entities have a CampusId property assigned to them.)

When a user the belongs to Campus #5 performs the following query: "Students$select=Id,Name,CampusId" it should be transformed to: "Students$select=Id,Name,CampusId&$filter=CampusId eq 5"

I could do this with simple queries by simply adding a filter as a string.

What I really want to do is: 1) Determine the entities being selected and expanded 2) Determine if any of these entities have a CampusId property 3) Add the necessary filter values into the Uri so that the query for each selected and/or expanded entity is filtered to that Campus

I am trying to use Microsoft.OData.Core.UriParser.ODataUriParser to parse the filter values and then create a new Uri.

For example:

var parser = new Microsoft.OData.Core.UriParser.ODataUriParser(edmModel, new Uri(serviceRootPath), originalUri);
var filter = parser.ParseFilter();

Using the code snippet above you get the "filter" variable to provide a type of Microsoft.OData.Core.UriParser.Semantic.FilterClause which can be used to examine the current filter values in the OData Query Uri.

Does anyone know how to edit the values in the FilterClause to be able to add new filter values to the Uri?

I don't find many examples on how to edit the FilterClause in order to generate an updated Uri with new filter values.

1
I have an answer here to change the filter value in controller : stackoverflow.com/questions/37339114/…Fan Ouyang
Thanks but that does not help. The reality is that the answer in that response you mention only works if you already know the structure of your OData Uri string. In reality, the filter value can include a variety of other settings (startsWith, Contains, etc.) so a simple string replace will not work reliably. The correct solution should allow us to examine the existing filter expression tree and append to it (if necessary) without disturbing any of the existing filter values that it already contains.Klaus Barkhausen
For what it's worth, I think I asked a question almost the same here: stackoverflow.com/questions/33126251/… Haven't got a great answer on it yet, but it may give you some better context on the problem potentially.Zachary Dow

1 Answers

4
votes

I found the solution to my isse by writing an algorithm that adds additional filtering to the request ODataUri based on the properties of my model. It examines any properties at the root level entity and the properties of any expanded entities as well to determine what additional filter expressions to add to the OData query.

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.

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.

Here is an example OData request Url:

/RootEntity?$expand=OtherEntity($expand=SomeOtherEntity)

This is the same OData request Url after I had 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

See below:

var parser = new ODataUriParser(model, new Uri(serviceRootPath), requestUri);   
var odataUri = parser.ParseUri();
  1. 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.

  1. To 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 overriden.

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);
  1. Use ODataUriBuilder to create a new Url based on the updated Uri

See below:

var updatedODataUri = new Microsoft.OData.Core.UriBuilder.ODataUriBuilder(ODataUrlConventions.Default, odataUri).BuildUri();
  1. 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 has provided me with the capability to add any filtering options I need and be 100% sure that I have not altered the OData Url structure incorrectly.

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