Objects in the AdditionalData bag are now of type JsonElement from System.Text.Json rather than Newtonsoft’s derivatives of JToken (i.e., JArray, String, or JObject).Working with JsonElement:You can infer if the JsonElement is an array/string/boolean/object from the ValueKind property:
if (additionalData["customProperty"] is JsonElement jsonElement){ switch (jsonElement.ValueKind) { case JsonValueKind.String: var stringValue = jsonElement.GetString(); break; case JsonValueKind.Number: var numberValue = jsonElement.GetInt32(); break; case JsonValueKind.Array: foreach (var item in jsonElement.EnumerateArray()) { // Process array items } break; case JsonValueKind.Object: // Process object properties break; }}
System.Text.Json enforces stricter JSON standards than Newtonsoft (e.g., trailing commas and comments are not allowed).If you need to allow these features, you can override the default serializer options:
// Add extra optionsvar options = new JsonSerializerOptions{ ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true,};Serializer serializer = new Serializer(options);IResponseHandler responseHandler = new ResponseHandler(serializer); // Response Handler with custom SerializerUser me = await graphClient.Me.Request() .WithResponseHandler(responseHandler) .GetAsync();
The Method property in the IBaseRequest interface is now of type enum instead of string. Update any existing code that derives from it to use the enum values provided in the library.
GraphServiceClient No Longer Implements IGraphServiceClient
The IGraphServiceClient interface has been removed because it continued to change with metadata changes, making it not ideal to mock or inherit.To support mocking frameworks (such as Moq), the properties/methods of the GraphServiceClient have been made virtual.Example using Moq:
// Arrangevar mockAuthProvider = new Mock<IAuthenticationProvider>();var mockHttpProvider = new Mock<IHttpProvider>();var mockGraphClient = new Mock<GraphServiceClient>(mockAuthProvider.Object, mockHttpProvider.Object);ManagedDevice md = new ManagedDevice{ Id = "1", DeviceCategory = new DeviceCategory() { Description = "Sample Description" }};// Setup the callsmockGraphClient.Setup(g => g.DeviceManagement.ManagedDevices["1"].Request().GetAsync(CancellationToken.None)) .Returns(Task.Run(() => md)) .Verifiable();// Actvar graphClient = mockGraphClient.Object;var device = await graphClient.DeviceManagement.ManagedDevices["1"].Request().GetAsync(CancellationToken.None);// AssertAssert.Equal("1", device.Id);
Response for collection types are now deserialized into the NextLink property in the collection response object and are not available in the additionalData bag.
var users = await graphServiceClient.Users.Request().GetAsync();var nextLink = users.NextPageRequest.GetHttpRequestMessage().RequestUri.OriginalString;
It is recommended to use the PageIterator when paging through collections for advanced functionality such as configuring pausing, managing state, and accessing DeltaLink and NextLink.
Example using PageIterator with delta:
int count = 0;int pauseAfter = 25;var messages = await graphClient.Me.Messages .Request() .Select(e => new { e.Sender, e.Subject }) .Top(10) .GetAsync();var pageIterator = PageIterator<Message> .CreatePageIterator( graphClient, messages, (m) => { Console.WriteLine(m.Subject); count++; // If we've iterated over the limit, stop the iteration return count < pauseAfter; } );await pageIterator.IterateAsync();while (pageIterator.State != PagingState.Complete){ if (pageIterator.State == PagingState.Delta) { string deltaLink = pageIterator.Deltalink; Console.WriteLine($"Paged through results and found deltaLink: {deltaLink}"); } Console.WriteLine("Iteration paused for 5 seconds..."); await Task.Delay(5000); // Reset count count = 0; await pageIterator.ResumeAsync();}
The Microsoft Graph library now supports the use of TokenCredential classes in the Azure.Identity library through the new TokenCredentialAuthProvider.Read more about available Credential classes here. This is encouraged to be used in place of the Microsoft.Graph.Auth package.Before (using Microsoft.Graph.Auth):
string[] scopes = {"User.Read"};IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder .Create(clientId) .Build();InteractiveAuthenticationProvider authProvider = new InteractiveAuthenticationProvider(publicClientApplication, scopes);GraphServiceClient graphClient = new GraphServiceClient(authProvider);User me = await graphClient.Me.Request() .GetAsync();
After (using TokenCredential):
string[] scopes = {"User.Read"};InteractiveBrowserCredentialOptions interactiveBrowserCredentialOptions = new InteractiveBrowserCredentialOptions() { ClientId = clientId};InteractiveBrowserCredential interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveBrowserCredentialOptions);// You can pass the TokenCredential directly to the GraphServiceClientGraphServiceClient graphClient = new GraphServiceClient(interactiveBrowserCredential, scopes);User me = await graphClient.Me.Request() .GetAsync();
The GraphResponse object enables easier access to response information like response headers and status codes.New methods corresponding to existing API methods:
User me = await graphClient.Me.Request() .GetAsync();
Accessing response headers and status codes:
GraphResponse<User> userResponse = await graphClient.Me.Request() .GetResponseAsync();// Get the status codeHttpStatusCode status = userResponse.StatusCode;// Get the headersHttpResponseHeaders headers = userResponse.HttpHeaders;// Get the user object using inbuilt serializerUser me = await userResponse.GetResponseObjectAsync();
Custom deserialization:
Using a custom IResponseHandler:
ISerializer serializer = new CustomSerializer(); // Custom SerializerIResponseHandler responseHandler = new ResponseHandler(serializer); // Response Handler with custom Serializervar patchUser = new User(){ DisplayName = "Graph User"};GraphResponse<User> graphResponse = await graphServiceClient.Me.Request() .WithResponseHandler(responseHandler) // Customized response handler .UpdateWithGraphResponseAsync<User>(patchUser, cancellationToken);User user = graphResponse.GetResponseObjectAsync(); // Calls the Response Handler with custom serializer
Reading and deserializing the response:
GraphResponse<User> userResponse = await graphClient.Me.Request() .GetResponseAsync();JsonSerializer serializer = new JsonSerializer(); // Custom serializerusing (StreamReader sr = new StreamReader(userResponse.Content.ReadAsStreamAsync()))using (JsonTextReader jsonTextReader = new JsonTextReader(sr)){ User deserializedProduct = serializer.Deserialize(jsonTextReader);}
In v3, all generated types had the @odata.type property set, which led to serialization errors. This required workarounds:Before (v3 - workaround needed):
In v3, query parameters were not encoded by default, causing errors with the API endpoint. In v4, query parameters are now encoded by default.Before (v3 - workaround needed):