Intercepting and post-processing OData queries on the server

Intercepting and post-processing OData queries on the server

Now and then you need to post-process OData queries, e.g. to „correct“ entity values or to filter query results that could not be filtered on database side. The problem: how to do it? Normally you have a controller action with return type IQueryable<T> and you leave it for further filtering to OData… you just don’t have the query result at this moment.

The first thing we have to do is executing the OData query in C#… this is possible by internalizing the [EnableQuery(...)] attribute in the controller method, which gets a new auto-resolved parameter of type ODataQueryOptions<TEntity>. Then, you can call options.ApplyTo(...) to manually execute the given OData query:

public IList<MyEntity> GetEntities(ODataQueryOptions<MyEntity> options)
{
    var validationSettings = new ODataValidationSettings
        {
            AllowedQueryOptions = AllowedQueryOptions.Filter | AllowedQueryOptions.Expand,
            AllowedFunctions = AllowedFunctions.None,
            ...
        };

    options.Validate(validationSettings);

    IQueryable<MyEntity> query = GetQuery();
    var result = options.ApplyTo(query, new ODataQuerySettings());

    return result;
}

Now you could think: great, let’s post-process the result and then return it. Well, unfortunately it’s not this easy. If the OData query includes an $expand, then the result will not be of type MyEntity, but of type SelectAllAndExpand<MyEntity>. Furthermore, this framework class is private, so you don’t have a chance to cast to it and evaluate it strongly typed. This is really shitty… there is a public API that returns a private implementation without providing a public interface or something for it. In my opinion, it’s a bad example of framework design…

So, to evaluate it and get an IList<MyEntity>, we are forced to use reflection to get the resulting entity out of the private SelectAllAndExpand class:

var resultList = new List<MyEntity>();
var result = options.ApplyTo(query, new ODataQuerySettings());
foreach (var item in result)
{
    if (item is MyEntity)
    {
        resultList.Add((MyEntity)item);
    }
    else if (item.GetType().Name == "SelectAllAndExpand`1")
    {
        var entityProperty = item.GetType().GetProperty("Instance");
        resultList.Add((MyEntity)entityProperty.GetValue(item));
    }
}

Finally, it’s easy to post-process the resultList as IList<MyEntity> as you want.

Ich bin freiberuflicher Senior C#/.NET Softwareentwickler im Raum Frankfurt/Main. Mit Leidenschaft für Software-Design, Clean Code und moderne Technologien in den Bereichen Web, Win und Mobile.

1 Kommentar

  1. maxmus 8 Monaten vor

    Thank you! This works perfectly. Life-saver.

Eine Antwort hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*