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 Full-Stack Web-Entwickler (Angular, TypeScript, C#/.NET) im Raum Frankfurt/Main. Mit Leidenschaft für Software-Design, Clean Code, moderne Technologien und agile Vorgehensmodelle.

6 Kommentare

  1. maxmus 8 Jahren vor

    Thank you! This works perfectly. Life-saver.

  2. Martin 7 Jahren vor

    There is an issue where if you specify $select, you cannot get the returned items, because SelectExpandWrapper is returned instead. Does this solve that issue https://github.com/OData/WebApi/issues/521

  3. Martin 7 Jahren vor

    FYI: I used the code in this answer (http://stackoverflow.com/a/30552434/592449) to convert the the response into a dictionary, map the dictionary to my model and return an IList to the client. Works beautifully.

  4. ismo 7 Jahren vor

    Works this by $select …..

  5. FakaMaka 6 Jahren vor

    Perfect! Thank you!

Pingbacks

Eine Antwort hinterlassen

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

*

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.