Version 5 of the Microsoft Graph .NET SDK moves to the new code generator Kiota to provide a better developer experience and a number of new features made possible by these changes.
This is a major version upgrade with significant breaking changes. Review all sections carefully before upgrading.
The types in the SDK are now organized into namespaces reflecting their usage, making it easier to consume multiple libraries (e.g., v1.0 and beta) in the same application.Key changes:
The v1.0 service library uses Microsoft.Graph as its root namespace
The beta service library uses Microsoft.Graph.Beta as its root namespace
Model types are now in the Microsoft.Graph.Models or Microsoft.Graph.Beta.Models namespaces
RequestBuilder and RequestBody types reside in namespaces relative to their path
Example:
// v4using Microsoft.Graph;// v5using Microsoft.Graph;using Microsoft.Graph.Models;using Microsoft.Graph.Me.SendMail; // For SendMailPostRequestBody
The GraphServiceClient constructor continues to accept TokenCredential instances from Azure.Identity:
var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveBrowserCredentialOptions);var graphServiceClient = new GraphServiceClient(interactiveBrowserCredential);
Custom authentication flows:Instead of DelegateAuthenticationProvider, create an implementation of IAccessTokenProvider and use it with BaseBearerTokenAuthenticationProvider:
public class TokenProvider : IAccessTokenProvider{ public Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object> additionalAuthenticationContext = default, CancellationToken cancellationToken = default) { var token = "token"; // Get the token and return it in your own way return Task.FromResult(token); } public AllowedHostsValidator AllowedHostsValidator { get; }}// Create the GraphServiceClientvar authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider());var graphServiceClient = new GraphServiceClient(authenticationProvider);
Authentication is no longer handled in the HttpClient middleware pipeline. Using GraphServiceClient(httpClient) assumes the HttpClient is already configured for authentication.
The HeaderOption class is no longer used. Headers are now added using the requestConfiguration modifier:Before (v4):
var options = new List<HeaderOption>{ new HeaderOption("ConsistencyLevel", "eventual")};var user = await graphServiceClient .Users[userId] .Request(options) .GetAsync();
After (v5):
var user = await graphServiceClient .Users[userId] .GetAsync(requestConfiguration => requestConfiguration.Headers.Add("ConsistencyLevel", "eventual"));
The QueryOption class is no longer used. Query options are set using the requestConfiguration modifier:Before (v4):
var options = new List<QueryOption>{ new QueryOption("$select", "id,createdDateTime")};var user = await graphServiceClient .Users[userId] .Request(options) .GetAsync();
After (v5):
var user = await graphServiceClient .Users[userId] .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Select = new string[] { "id", "createdDateTime" });
Multiple parameters:
var groups = await graphServiceClient .Groups .GetAsync(requestConfiguration => { requestConfiguration.QueryParameters.Select = new string[] { "id", "createdDateTime", "displayName" }; requestConfiguration.QueryParameters.Expand = new string[] { "members" }; requestConfiguration.QueryParameters.Filter = "startswith(displayName, 'J')"; });
The $skipToken and $deltaToken query parameters are not included in the metadata. Use the entire URL returned from delta responses.
Using request builders:
// Make the first requestvar deltaResponse = await graphClient.Groups.Delta.GetAsync((requestConfiguration) =>{ requestConfiguration.QueryParameters.Select = new string[] { "displayName", "description", "mailNickname" };});// Use the deltaResponse.OdataDeltaLink/deltaResponse.OdataNextLink to make the next requestvar deltaRequest = new Microsoft.Graph.Beta.Groups.Delta.DeltaRequestBuilder( deltaResponse.OdataDeltaLink, graphClient.RequestAdapter);var secondDeltaResponse = await deltaRequest.GetAsync();
Using PageIterator:
// Fetch the first page of groupsvar deltaResponse = await graphClient.Groups.Delta.GetAsync((requestConfiguration) =>{ requestConfiguration.QueryParameters.Select = new string[] { "displayName", "description", "mailNickname" };});var groups = new List<Group>();var pageIterator = PageIterator<Group, Microsoft.Graph.Beta.Groups.Delta.DeltaResponse>.CreatePageIterator( graphClient, deltaResponse, group => { groups.Add(group); return true; });// Iterate through the odata.nextLink until the last page is reached with an odata.deltaLinkawait pageIterator.IterateAsync();if (pageIterator.State == PagingState.Delta) { await Task.Delay(30000); // Wait for changes to occur Console.WriteLine("Calling delta again with deltaLink"); Console.WriteLine("DeltaLink url is: " + pageIterator.Deltalink); await pageIterator.ResumeAsync();}
The RequestInformation class replaces IBaseRequest:
// Get the requestInformation to make a GET requestvar requestInformation = graphServiceClient .DirectoryObjects .ToGetRequestInformation();// Get the requestInformation to make a POST requestvar directoryObject = new DirectoryObject(){ Id = Guid.NewGuid().ToString()};var requestInformation = graphServiceClient .DirectoryObjects .ToPostRequestInformation(directoryObject);
The CSDL to OpenAPI conversion process avoids generating redundant paths. Use alternative paths as documented in the reference documentation.List children from a user’s drive:
// Get the user's driveIdvar driveItem = await graphServiceClient.Me.Drive.GetAsync();var userDriveId = driveItem.Id;// List children in the drivevar children = await graphServiceClient.Drives[userDriveId].Items["itemId"].Children.GetAsync();// List children in the root drive (root is a shorthand for /drive/items/root)var children = await graphServiceClient.Drives[userDriveId].Items["root"].Children.GetAsync();
Upload small file with conflictBehavior:
var requestInformation = graphClient.Drives[drive.Id.ToString()].Root .ItemWithPath("MediaMeta.xml").Content.ToPutRequestInformation(file);requestInformation.URI = new Uri(requestInformation.URI.OriginalString + "[email protected]=rename");var result = await graphClient.RequestAdapter.SendAsync<DriveItem>( requestInformation, DriveItem.CreateFromDiscriminatorValue);
The backing store enables dirty tracking of changes, allowing you to update only modified properties:
// Get the objectvar @event = await graphServiceClient .Me.Events["event-id"] .GetAsync();// The backing store tracks the property change@event.Recurrence = null; // Set to null// Update the object (only sends changed properties)await graphServiceClient.Me.Events["event-id"] .PatchAsync(@event);
This eliminates the need to place null values in the additionalData bag.
var body = new Microsoft.Graph.Me.SendMail.SendMailPostRequestBody{ Message = message, SaveToSentItems = saveToSentItems};await graphServiceClient.Me .SendMail .PostAsync(body);
Batch requests now support passing RequestInformation instances:
var requestInformation = graphServiceClient .Users .ToGetRequestInformation();// Create the batch requestvar batchRequestContent = new BatchRequestContent(graphServiceClient);// Add one or more steps (up to 20)var requestStepId = await batchRequestContent.AddBatchRequestStepAsync(requestInformation);// Send and get back responsevar batchResponseContent = await graphServiceClient.Batch.PostAsync(batchRequestContent);var usersResponse = await batchResponseContent.GetResponseByIdAsync<UserCollectionResponse>(requestStepId);List<User> userList = usersResponse.Value;
Handle failed responses:
var statusCodes = await batchResponseContent.GetResponsesStatusCodesAsync();// Check if all responses are successfulvar allResponsesSuccessful = statusCodes.Any(x => !BatchResponseContent.IsSuccessStatusCode(x.Value));// Get rate limited responsesvar rateLimitedResponses = statusCodes.Where(x => x.Value == (HttpStatusCode)429);// Retry those rate limitedvar retryBatch = batchRequestContent.NewBatchWithFailedRequests(rateLimitedResponses);var retryResponse = await graphServiceClient.Batch.PostAsync(retryBatch);
Automatic batch size management:
// Replace BatchRequestContent with BatchRequestContentCollectionvar batchRequestContent = new BatchRequestContentCollection(graphServiceClient);// Or with a set batch sizevar batchRequestContent = new BatchRequestContentCollection(graphServiceClient, 4);// Add "unlimited" requests (don't use "DependsOn")var requestStepId = await batchRequestContent.AddBatchRequestStepAsync(requestInformation);
Using batched requests can significantly improve performance when querying multiple endpoints or creating/deleting many items.
Request builders now support OData cast functionality:
// Fetch members of a group who are of type Uservar usersInGroup = await graphServiceClient.Groups["group-id"].Members.GraphUser.GetAsync();// Fetch members of the group of type Applicationvar applicationsInGroup = await graphServiceClient.Groups["group-id"].Members.GraphApplication.GetAsync();