4

I'm building a program that fetches calendar info from multiple users in AAD. I would like to do this as efficiently as possible, so I started looking into the Microsoft graph batching functionality. I'm able to successfully do a batching query, but I'm having issues to serialize the results:

//1. construct a Batch request 
var batchRequestContent = new BatchRequestContent();
var step = 1;
foreach (var userEmail in userEmails)
{
    var requestUrl = graphServiceClient
        .Users[userEmail]
        .Calendar.Events
        .Request(new List<QueryOption>
        {
            new QueryOption("startDateTime", start.ToString("o")),
            new QueryOption("endDateTime", end.ToString("o"))
        });

    var request = new HttpRequestMessage(HttpMethod.Get, requestUrl.RequestUrl);
    var requestStep = new BatchRequestStep(step.ToString(), request);
    batchRequestContent.AddBatchRequestStep(requestStep);
    step++;
}


//2. Submit request
var batchRequest = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/$batch")
{
    Content = batchRequestContent
};
await graphServiceClient.AuthenticationProvider.AuthenticateRequestAsync(batchRequest);
var httpClient = new HttpClient();
var batchResponse = await httpClient.SendAsync(batchRequest);
//3. Process response
var batchResponseContent = new BatchResponseContent(batchResponse);
var responses = await batchResponseContent.GetResponsesAsync();
var responseHandler = new ResponseHandler(graphServiceClient.HttpProvider.Serializer);
foreach (var response in responses)
{
    if (response.Value.IsSuccessStatusCode)
    {

        var responsestring = await response.Value.Content.ReadAsStringAsync();

        var responseEvent = //?
    }
}

Above all works, but how do I serialize this result to a strongly typed list of Events?

EDIT I tried deserializing with the ResponseHandler like this:

var batchResponseContent = new BatchResponseContent(batchResponse);
        var responses = await batchResponseContent.GetResponsesAsync();
        var responseHandler = new ResponseHandler(new Serializer());
        foreach (var response in responses)
        {
            if (response.Value.IsSuccessStatusCode)
            {
                var events = responseHandler.HandleResponse<ICalendarEventsCollectionPage>(response.Value);
                //...
            }
        }

But this errors out and throws the following exception:

 Newtonsoft.Json.JsonSerializationException: Cannot populate JSON object onto type 'Microsoft.Graph.CalendarEventsCollectionPage'. Path '['@odata.context']', line 2, position 19.

It seems the @odata.context is responsible for the error, see image below for the actual response I get from above request:

enter image description here

Arne Deruwe
  • 1,100
  • 2
  • 11
  • 25

3 Answers3

3

In case someone wants the full solution I went with, I make batch requests like this and they are working fine now. There was a serialisation issue in the client library, but that's fixed since I asked this question (https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/587)

//You need these packages/namespaces
//<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
//<PackageReference Include="Microsoft.Graph" Version="1.20.0" />
//<PackageReference Include="Microsoft.Graph.Auth" Version="1.0.0-preview.0" />
//<PackageReference Include="Microsoft.Graph.Core" Version="1.19.0-preview.3" />
//<PackageReference Include="Microsoft.Identity.Client" Version="4.3.0" />

//using Microsoft.Graph;
//using Microsoft.Graph.Extensions;
//using Microsoft.Graph.Auth;
//using Microsoft.Identity.Client;      
var confidentialClientApplication = ConfidentialClientApplicationBuilder
              .Create("YOUR CLIENT ID")
              .WithTenantId("YOUR TENANT ID")
              .WithClientSecret("YOUR CLIENT SECRET")
              .Build();

        var forUser = "YOUR USER'S EMAIL ADDRESS";

        var authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
        var graphServiceClient = new GraphServiceClient(authenticationProvider);

        var eventRequest = graphServiceClient.Users[forUser].Calendar.CalendarView.Request(
            new List<QueryOption>
              {
                  new QueryOption("startDateTime", DateTime.UtcNow.Date.AddMonths(-3).ToString("O")),
                  new QueryOption("endDateTime", DateTime.UtcNow.Date.ToString("O"))
              }
           );

        var batchRequestContent = new BatchRequestContent();

        var reqId = batchRequestContent.AddBatchRequestStep(eventRequest);
        // add second, 3rd request here

        var returnedResponse = await graphServiceClient.Batch.Request().PostAsync(batchRequestContent);
        var batchEvts = await returnedResponse.GetResponseByIdAsync<CalendarEventsCollectionResponse>(reqId);
        // read second, 3rd responses here
Arne Deruwe
  • 1,100
  • 2
  • 11
  • 25
2

You can use the standard response handling with the ResponseHandler class

            var batchResponseContent = new BatchResponseContent(batchResponse);
            var responses = await batchResponseContent.GetResponsesAsync();
            var responseHandler = new ResponseHandler(new Serializer());
            foreach (var response in responses)
            {
                if (response.Value.IsSuccessStatusCode)
                {
                    var events = responseHandler.HandleResponse<ICalendarEventsCollectionPage>(response.Value);
                    //...
                }
            }

and there is an easier way to create the original request...

var request = graphServiceClient
        .Users[userEmail]
        .Calendar.Events
        .Request(new List<QueryOption>
        {
            new QueryOption("startDateTime", start.ToString("o")),
            new QueryOption("endDateTime", end.ToString("o"))
        }).GetHttpRequestMessage();
Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • Thanks, I didn't find a lot of official docs regarding the batching functionality, is this due to the functionality being rather new? Or do I have to look elsewhere? – Arne Deruwe Sep 05 '19 at 14:30
  • 1
    @ArneDeruwe Unfortunately yes. I am in the process of trying to get the docs published. There is an example of how to use it here https://github.com/microsoftgraph/msgraph-sdk-dotnet/pull/412 – Darrel Miller Sep 06 '19 at 15:07
  • Unfortunately, I'm still having issues. It seems the responsehandler doesn't handle "@odata.context" well and errors out, I've updated my original question – Arne Deruwe Sep 09 '19 at 07:54
  • @ArneDeruwe Sorry for the confusion, my example has an error. Passing an interface to ICalendarEventsCollectionPage is likely the problem. JSON.NET won't be able to instantiate an interface. I'll need to look up what class that should be. It is probably CalendarEventsCollectionPage but I will verify. – Darrel Miller Sep 09 '19 at 13:30
  • 1
    I forgot to mention I already tried using the non-interface variants as I suspected the same thing, unfortunately this yields the same error – Arne Deruwe Sep 09 '19 at 13:54
0

Since in the provided example the response of every query is expected to be of collection of Event resource type the following example demonstrates how to deserialize it:

//...
var batchResponseContent = new BatchResponseContent(batchResponse);
var responses = await batchResponseContent.GetResponsesAsync();
foreach (var response in responses)
{
    if (response.Value.IsSuccessStatusCode)
    {
        var content = await response.Value.Content.ReadAsStringAsync();
        var events = JsonConvert.DeserializeObject<List<Microsoft.Graph.Event>>(JObject.Parse(content)["value"].ToString());
        //...

    }
}   
Vadim Gremyachev
  • 57,952
  • 20
  • 129
  • 193
  • Thank you for your reply, this works, but unfortunately messes with the dateformats of the result. Is there any way to tell the formatter to leave these alone? – Arne Deruwe Aug 23 '19 at 07:12
  • the dateformat issue was due to a sdk bug and is now fixed, see: https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/587 – Arne Deruwe Jan 16 '20 at 07:43