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.