microsoftgraph / microsoftgraph/msgraph-sdk-dotnet
Different filter escaping behaviour in batch and direct requests
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 789
- Forks
- 264
- Avg merge
- 15h 17m
- Merged PRs (30d)
- 3
Description
Describe the bug
I'm trying to find a user and a selection of their properties through their email. Currently I'm building a filter to find said user like so:
- filter:
identities/any(id:id/issuer eq ' ' and id/issuerAssignedId eq '{emailAddress}')
For most email addresses, the filtering works as expected for both direct requests using _GraphServiceClient.Users.GetAsync() as well as batch requests and building the requests with _GraphServiceClient.Users.ToGetRequestInformation().
I've noticed that some of our users aren't returned by the batch requests but provide valid responses through a direct request.
The emails have in common that they contain a '+' character, e.g. test+mailPostfix@mail.com. Upon further investigation I've noticed that the Graph API can't handle the '+' character and needs it to be escaped.
Apparently, the filter string is escaped when requesting _GraphServiceClient.Users.GetAsync() but not when using _GraphServiceClient.Users.ToGetRequestInformation() followed by a batch request.
Expected behavior
The filter behaviour should be equal throughout the SDK and not differ between usages. I believe the filter should also be escaped when build a request and sending it through a batch request.
How to reproduce
Assuming I have a list of emails for which I want some B2C properties like the display name.
When I use the the SDK by sending the requests one by one like so, everything works as expected:
var usersResponse = await _GraphServiceClient.Users
.GetAsync(requestConfig =>
{
requestConfig.QueryParameters.Top = 999;
requestConfig.QueryParameters.Select = ["displayName", "identities", "otherMails", "id"];
requestConfig.QueryParameters.Filter = $"identities/any(id:id/issuer eq ' ' and id/issuerAssignedId eq '{emailAddress}')";
}, cancellationToken);
if (usersResponse == null)
{
return null;
}
var userList = new List<User>();
var pageIterator = PageIterator<User, UserCollectionResponse>.CreatePageIterator(_GraphServiceClient, usersResponse, user =>
{
userList.Add(user);
return true;
});
await pageIterator.IterateAsync(cancellationToken);
return userList;
But creating a batch request when the list of email addresses might become bigger creates empty results for emails with a '+' character. The request is built like this:
var batchRequestContent = new BatchRequestContentCollection(_GraphServiceClient);
var requestIdsForEmails = new Dictionary<string, string>();
foreach (var emailAddress in emailAddresses)
{
var identityRequest = _GraphServiceClient.Users.ToGetRequestInformation(requestConfig =>
{
requestConfig.QueryParameters.Select = UserSelections;
requestConfig.QueryParameters.Filter = $"identities/any(id:id/issuer eq ' ' and id/issuerAssignedId eq '{emailAddress}')"
});
var identityRequestId = await batchRequestContent.AddBatchRequestStepAsync(identityRequest);
requestIdsForEmails.Add(emailAddress, [identityRequestId, otherMailRequestId]);
}
var batchResponse = await _GraphServiceClient.Batch.PostAsync(batchRequestContent, cancellationToken);
var emailsWithUsers = new Dictionary<string, IEnumerable<User>>();
foreach (var emailWithRequestIds in requestIdsForEmails)
{
try
{
var userResponse = await batchResponse.GetResponseByIdAsync<UserCollectionResponse>(emailWithRequestIds.Value);
emailsWithUsers.Add(emailWithRequestIds.Key, userResponse.Value);
}
catch (Exception)
{
emailsWithUsers.Add(emailWithRequestIds.Key, []);
}
}
return emailsWithUsers;
SDK Version
5.59.0
Latest version known to work for scenario above?
No response
Known Workarounds
I've fixed the issue by explicitly escaping the emailAddress before using it in the filter:
var escapedEmail = Uri.EscapeDataString(emailAddress);
var identityRequest = _GraphServiceClient.Users.ToGetRequestInformation(requestConfig =>
{
requestConfig.QueryParameters.Select = UserSelections;
requestConfig.QueryParameters.Filter = $"identities/any(id:id/issuer eq ' ' and id/issuerAssignedId eq '{escapedEmail }')"
});
But this creates an odd situation since escaping the emailAddress with a direct request to _GraphServiceClient.Users.GetAsync() breaks the direct request. So I need to handle the email addresses differently which creates a confusing situation in the code base.
Debug output
No response
Configuration
- OS: Windows 11
- architecture x64
Other information
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the difference between Users.GetAsync() and Users.ToGetRequestInformation() in a batch request with an email containing '+'. Compare how the filter is serialized in both paths, and verify completion when equivalent requests return the same user results without requiring different caller-side escaping.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- api
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100