Extend Graph SDK with oData filter generated from LINQ expression.

Saying that Graph SKD is a useful tool is a gross understatement. One could even argue this is the most important and one of the most flexible API SDKs there is. Not only it provides model for Graph API but also fluent way of accessing child objects and authentication helpers.

TLDR;
Check my repo for a solution. There is a unit test that builds oData query: DisplayName eq ‘Test User’ from expression: user => user.DisplayName == “Test User”

There is however one functionality I would love to be able to use. What I have in mind is generating oData filter query parameter from LINQ expression. To put that in code I wish I could do this

var users = await graphClient.Users.GetWithFilterAsync(user => user.DisplayName == "Test User");

This can be of course abstracted to more complex problem, not only for Graph but from any API. If You control the API, the problem is trivial as we do have the access to EdmModel, but that’s not the case. As we have access only to domain object (User, Event ect) we have to be able to build our oData query just from that.

Implementation details

The core of this solution is to go through the expression tree and append relevant data to string builder. The most difficult expression to visit is MemberExpression. There are a lot of cases we need to consider such as is there a ConstantExpression as Expression field or is there JsonPropertyName property associated with visited node. Full implementation can be found here.

Once we have our query builder implemented, it’s just a matter of extending Graph SDK. I hoped there is an abstract class for RequestBuilder which exposes GetAsync method, but that’s not the case. We have to extend each entity separately. For my proof of concept I chose UserRequestBuilder. From now on it’s just a matter of calling GetAsync with correct filter parameter

    public static class UsersRequestBuilderExtensions
    {
        public static async Task<UserCollectionResponse?> GetWithFilterAsync(this UsersRequestBuilder builder, Expression<Func<User,bool>> expression)
        {
            FilterQueryMapper mapper = new FilterQueryMapper();
            string filterQuery = mapper.BuildFilterQuery(expression);
            return await builder.GetAsync((Microsoft.Graph.Users.UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration config) =>
            {
                config.QueryParameters.Filter = HttpUtility.UrlDecode(filterQuery);
            });
        }
    }

The filter will be encoded, so I decode it before passing to make sure it will not decode characters twice.

Now when I can use this extension like this

var users = await graphClient.Users.GetWithFilterAsync(user => user.DisplayName == "Test User")

Which was exactly what I was aiming for.

Hope You’ll find that helpful.

Thanks for reading and have a great day.

Published by Marcin Wojciechowski

Coding enthusiast, TDD evangelist, OOP champion and SOLID proponent. Likes to code as well as talking about the code. For fun, besides software development, plays guitar, basketball and video games. Enjoys cooking and learning.

Leave a comment

Design a site like this with WordPress.com
Get started