Use the Microsoft Graph SDKs to batch requests

Batching is a way of combining multiple requests into a single HTTP request. The requests are combined in a single JSON payload, which is sent via POST to the \$batch endpoint. Microsoft Graph SDKs have a set of classes to simplify how you create batch payloads and parse batch response payloads.

Important

For current limitations with JSON batching in Microsoft Graph, see Known Issues.

Create a batch request

The Microsoft Graph SDKs provide three classes to work with batch requests and responses.

  • BatchRequestStep - Represents a single request (such as GET /me) within a batch. It enables assigning a unique identifier to the request and specifying dependencies between requests.
  • BatchRequestContent - Simplifies creating the batch request payload. It contains multiple BatchRequestStep objects.
  • BatchResponseContent - Simplifies parsing the response from a batch request. It provides the ability to get all responses, get a specific response by ID, and get the @odata.nextLink property if present.

Simple batching example

This example shows how to send multiple requests in a batch that are not dependent on each other. The requests can be run by the service in any order. This example gets the user and gets the user's calendar view for the current day.

// Use the request builder to generate a regular
// request to /me
var userRequest = graphClient.Me.Request();

var today = DateTime.Now.Date;
var start = today.ToString("yyyy-MM-ddTHH:mm:ssK");
var end = today.AddDays(1).ToString("yyyy-MM-ddTHH:mm:ssK");

var queryOptions = new List<QueryOption>
{
    new QueryOption("startDateTime", start),
    new QueryOption("endDateTime", end)
};

// Use the request builder to generate a regular
// request to /me/calendarview?startDateTime="start"&endDateTime="end"
var eventsRequest = graphClient.Me.CalendarView.Request(queryOptions);

// Build the batch
var batchRequestContent = new BatchRequestContent();

// Using AddBatchRequestStep adds each request as a step
// with no specified order of execution
var userRequestId = batchRequestContent.AddBatchRequestStep(userRequest);
var eventsRequestId = batchRequestContent.AddBatchRequestStep(eventsRequest);

var returnedResponse = await graphClient.Batch.Request().PostAsync(batchRequestContent);

// De-serialize response based on known return type
try
{
    var user = await returnedResponse
        .GetResponseByIdAsync<User>(userRequestId);
    Console.WriteLine($"Hello {user.DisplayName}!");
}
catch (ServiceException ex)
{
    Console.WriteLine($"Get user failed: {ex.Error.Message}");
}

// For collections, must use the *CollectionResponse class to deserialize
// The .Value property will contain the *CollectionPage type that the Graph client
// returns from GetAsync().
try
{
    var events = await returnedResponse
        .GetResponseByIdAsync<UserCalendarViewCollectionResponse>(eventsRequestId);
    Console.WriteLine($"You have {events.Value.CurrentPage.Count} events on your calendar today.");
}
catch (ServiceException ex)
{
    Console.WriteLine($"Get calendar view failed: {ex.Error.Message}");
}

Batches with dependent requests

This example shows how to send multiple requests in a batch that are dependent on each other. The requests will be run by the service in the order specified by the dependencies. This example adds an event with a start time during the current day to the user's calendar and gets the user's calendar view for the current day. To make sure that the calendar review returned includes the new event created, the request for the calendar view is configured as dependent on the request to add the new event. This ensures that the add event request will execute first.

Note

If the add event request fails, the get calendar view request will fail with a 424 Failed Dependency error.

var today = DateTime.Now.Date;

var newEvent = new Event
{
    Subject = "File end-of-day report",
    Start = new DateTimeTimeZone
    {
        // 5:00 PM
        DateTime = today.AddHours(17).ToString("yyyy-MM-ddTHH:mm:ss"),
        TimeZone = TimeZoneInfo.Local.StandardName
    },
    End = new DateTimeTimeZone
    {
        // 5:30 PM
        DateTime = today.AddHours(17).AddMinutes(30).ToString("yyyy-MM-ddTHH:mm:ss"),
        TimeZone = TimeZoneInfo.Local.StandardName
    }
};

// POST requests are handled a bit differently
// The SDK request builders generate GET requests, so
// you must get the HttpRequestMessage and convert to a POST
var jsonEvent = graphClient.HttpProvider.Serializer.SerializeAsJsonContent(newEvent);

var addEventRequest = graphClient.Me.Events.Request().GetHttpRequestMessage();
addEventRequest.Method = HttpMethod.Post;
addEventRequest.Content = jsonEvent;

var start = today.ToString("yyyy-MM-ddTHH:mm:ssK");
var end = today.AddDays(1).ToString("yyyy-MM-ddTHH:mm:ssK");

var queryOptions = new List<QueryOption>
{
    new QueryOption("startDateTime", start),
    new QueryOption("endDateTime", end)
};

// Use the request builder to generate a regular
// request to /me/calendarview?startDateTime="start"&endDateTime="end"
var calendarViewRequest = graphClient.Me.CalendarView.Request(queryOptions);

// Build the batch
var batchRequestContent = new BatchRequestContent();

// Force the requests to execute in order, so that the request for
// today's events will include the new event created.

// First request, no dependency
var addEventRequestId = batchRequestContent.AddBatchRequestStep(addEventRequest);

// Second request, depends on addEventRequestId
var eventsRequestId = Guid.NewGuid().ToString();
batchRequestContent.AddBatchRequestStep(new BatchRequestStep(
    eventsRequestId,
    calendarViewRequest.GetHttpRequestMessage(),
    new List<string> { addEventRequestId }
));

var returnedResponse = await graphClient.Batch.Request().PostAsync(batchRequestContent);

// De-serialize response based on known return type
try
{
    var createdEvent = await returnedResponse
        .GetResponseByIdAsync<Event>(addEventRequestId);
    Console.WriteLine($"New event created with ID: {createdEvent.Id}");
}
catch (ServiceException ex)
{
    Console.WriteLine($"Add event failed: {ex.Error.Message}");
}

// For collections, must use the *CollectionResponse class to deserialize
// The .Value property will contain the *CollectionPage type that the Graph client
// returns from GetAsync().
try
{
    var events = await returnedResponse
        .GetResponseByIdAsync<UserCalendarViewCollectionResponse>(eventsRequestId);
    Console.WriteLine($"You have {events.Value.CurrentPage.Count} events on your calendar today.");
}
catch (ServiceException ex)
{
    Console.WriteLine($"Get calendar view failed: {ex.Error.Message}");
}

Implementing batching using BatchRequestContent, BatchRequestStep, and HttpRequestMessage

The following example shows how to use BatchRequestContent,BatchRequestStep, and HttpRequestMessage to send multiple requests in a batch and how to handle the limit of 20 with Microsoft Graph API requests. This example creates meeting links using the onlineMeetings/createOrGet endpoint for the specified user ID. You can use this example with other Microsoft Graph endpoints as well.

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Graph;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public async void GenerateBatchedMeetingLink(List<ItemCollections> meetingLinksToBeGenerated)
        {            
            List<string> _joinWebUrls = new List<string>();
            //Total number of items per batch supported is 20
            int maxNoBatchItems = 20;
            try
            {
                //valid GraphAccessToken is required to execute the call
                var graphClient = GetAuthenticatedClient(GraphAccessToken);
                var events = new List<OnlineMeetingCreateOrGetRequestBody>();
                foreach (var item in meetingLinksToBeGenerated)
                {
                    var externalId = Guid.NewGuid().ToString();
                    var @event = new OnlineMeetingCreateOrGetRequestBody
                    {
                        StartDateTime = item.StartTime,
                        EndDateTime = item.EndTime,
                        Subject = "Test Meeting",
                        ExternalId = externalId,
                        
                    };
                    events.Add(@event);
                }
                // if the requests are more than 20 limit, we need to create multiple batches of the BatchRequestContent
                List<BatchRequestContent> batches = new List<BatchRequestContent>();
                var batchRequestContent = new BatchRequestContent();
                foreach (OnlineMeetingCreateOrGetRequestBody e in events)
                { 
                    //create online meeting for particular user or we can use /me as well
                    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, $"https://graph.microsoft.com/v1.0/users/{userID}/onlineMeetings/createOrGet")
                    {
                        Content = new StringContent(JsonConvert.SerializeObject(e), Encoding.UTF8, "application/json")
                    };
                    BatchRequestStep requestStep = new BatchRequestStep(events.IndexOf(e).ToString(), httpRequestMessage, null);
                    batchRequestContent.AddBatchRequestStep(requestStep);
                    if (events.IndexOf(e) > 0 && ((events.IndexOf(e) + 1) % maxNoBatchItems == 0))
                    {
                        batches.Add(batchRequestContent);
                        batchRequestContent = new BatchRequestContent();
                    }
                }
                if (batchRequestContent.BatchRequestSteps.Count < maxNoBatchItems)
                {
                    batches.Add(batchRequestContent);
                }

                if (batches.Count == 0 && batchRequestContent != null)
                {
                    batches.Add(batchRequestContent);
                }

                foreach (BatchRequestContent batch in batches)
                {
                    BatchResponseContent response = null;
                    response = await graphClient.Batch.Request().PostAsync(batch);
                    Dictionary<string, HttpResponseMessage> responses = await response.GetResponsesAsync();
                    foreach (string key in responses.Keys)
                    {
                        HttpResponseMessage httpResponse = await response.GetResponseByIdAsync(key);
                        var responseContent = await httpResponse.Content.ReadAsStringAsync();
                        JObject eventResponse = JObject.Parse(responseContent);
                        //do something below
                        Console.writeline(eventResponse["joinWebUrl"].ToString());                      
                    }                 
                }
            }
            catch (Exception ex)
            {
                Console.Writeline(ex.Message + ex.StackTrace);               
            }
        }