Skip to content

Latest commit

 

History

History
322 lines (240 loc) · 12.7 KB

upgrade-to-v5.md

File metadata and controls

322 lines (240 loc) · 12.7 KB

Microsoft Graph .NET SDK v5 changelog and upgrade guide

The purpose of this document is to outline any breaking change and migration work SDK users might run into while upgrading to the v5 of the SDK.

Overview

V5 of the Microsoft Graph .NET SDK moves to the new code generator Kiota to provide a better user experience for the SDK users as well as a number of new features made possible by these changes.

Breaking changes

The following section lists out the breaking changes requiring code changes from SDK users.

Namespaces/Usings changes

The types in the sdk are now organized into namespaces reflecting their usage as compared to all types being present in the single Microsoft.Graph namespace and therefore makes it easier to consume multiple libraries(e.g v1.0 and beta) in the same application.

This therefore comes with the following changes,

  • The beta 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/Microsoft.Graph.Beta.Models namespaces.
  • RequestBuilder and RequestBody types reside in namespaces relative to the path they are calling. e.g. The SendMailPostRequestBody type will reside in the Microsoft.Graph.Beta.Me.SendMail/Microsoft.Graph.Me.SendMail namespace if you are sending a mail via the client.Me.SendMail.PostAsync(sendMailPostRequestBody) path using the request builders

Authentication

The GraphServiceClient constructor accepts instances of TokenCredential from Azure.Identity similar to previous library version as follows

var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveBrowserCredentialOptions);
var graphServiceClient = new GraphServiceClient(interactiveBrowserCredential);

In place of the DelegateAuthenticationProvider, custom authentication flows can be done creating an implementation of IAccessTokenProvider, and using with the BaseBearerTokenAuthenticationProvider from the Kiota abstractions as follows

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; }
}

Then create the GraphServiceClient as follows

var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider());
var graphServiceClient = new GraphServiceClient(authenticationProvider);

Authentication using the graph client is no longer handled in the HttpClient middleware pipeline. Therefore, using the GraphServiceClient(httpClient) constructor will assume that the passed httpClient has already been configured to handle authentication in its pipeline. Otherwise, passing an instance of IAuthenticationProvider to the constructor (GraphServiceClient(httpClient, authenticationProvider)) will make authenticated requests if the passed HttpClient is not already configured.

Use of RequestInformation from Kiota in place of IBaseRequest

The RequestInformation class is now used to represent requests in the SDK and the IBaseRequest is dropped. Using the fluent API, you can always get an instance of the RequestInformation as follows.

// Get the requestInformation to make a GET request
var requestInformation = graphServiceClient
                         .DirectoryObjects
                         .ToGetRequestInformation();

// Get the requestInformation to make a POST request
var directoryObject = new DirectoryObject()
{
    Id = Guid.NewGuid().ToString()
};
var requestInformation = graphServiceClient
                            .DirectoryObjects
                            .ToPostRequestInformation(directoryObject);

Removal of Request() from the fluent API

In previous versions, of the SDK, calls involved the calling of Request() in the request API as follows

var user = await graphServiceClient
    .Me
    .Request()  // this is removed
    .GetAsync();

A similar call in the V5 will have the Request() section removed to be called as below.

var user = await graphServiceClient
    .Me
    .GetAsync();

Request executor methods

V5 of the SDK simplifies the request methods to simply reflect the HTTP methods being used. This therefore means that

  • UpdateAsync() methods are now PatchAsync()
  • AddAsync() methods are now PostAsync()

Headers

To pass headers, the HeaderOption class is no longer used. Headers are added using the requestConfiguration modifier as follows

var user = await graphServiceClient
    .Users["{user-id}"]
    .GetAsync(requestConfiguration => requestConfiguration.Headers.Add("ConsistencyLevel","eventual"));

Query Options

To pass query Options, the QueryOption class is no longer used. Query options are set using the requestConfiguration modifier as follows

var user = await graphServiceClient
    .Users["{user-id}"]
    .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Select = new string[] { "id", "createdDateTime"});

Example with 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%2C+'J')";
    });

Collections

Querying for collections are done as follows and resembles the response from API.

var usersResponse = await graphServiceClient
    .Users
    .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Select = new string[] { "id", "createdDateTime"});

List<User> userList = usersResponse.Value;

PageIterator

To iterate through page collections, use the pageIterator as follows

var usersResponse = await graphServiceClient
    .Users
    .GetAsync(requestConfiguration => { 
        requestConfiguration.QueryParameters.Select = new string[] { "id", "createdDateTime" }; 
        requestConfiguration.QueryParameters.Top = 1; 
        });

var userList = new List<User>();
var pageIterator = PageIterator<User,UserCollectionResponse>.CreatePageIterator(graphServiceClient,usersResponse, (user) => { userList.Add(user); return true; });

await pageIterator.IterateAsync();

Error handling

Errors and exceptions from the new generated version will be exception classed derived from the ApiException class from the Kiota abstrations library. Typically, this will be an instance of OdataError and can be handled as below.

try
{
    await graphServiceClient.Me.PatchAsync(user);
}
catch (ODataError odataError)
{
    Console.WriteLine(odataError.Error.Code);
    Console.WriteLine(odataError.Error.Message);
    throw;
}

Drive Item paths

The current CSDL to OpenAPI conversion process avoids generation of redundant paths which impacts request builders for driveItems. To mitigate this paths should be available through alternative paths as documented in the reference documentation as seen here.

Examples of using alternative paths are as shown below.

  1. List children from a user's drive.
// Get the user's driveId
var driveItem = await graphServiceClient.Me.Drive.GetAsync();
var userDriveId = driveItem.Id;
// List children in the drive
var children = await graphServiceClient.Drives[userDriveId].Items["itemId"].Children.GetAsync();
  1. List children from a site's drive.
// Get the site's driveId
var driveItem = await graphServiceClient.Sites["site-id"].Drive.GetAsync();
var siteDriveId = driveItem.Id;
// List children in the drive
var children = await graphServiceClient.Drives[siteDriveId].Items["itemId"].Children.GetAsync();
  1. List children from a groups's drive.
// Get the group's driveId
var driveItem = await graphServiceClient.Groups["group-id"].Drive.GetAsync();
var groupDriveId = driveItem.Id;
// List children in the drive
var children = await graphServiceClient.Drives[groupDriveId].Items["itemId"].Children.GetAsync();

New Features

Backing Store

The backing store allows multiple things like dirty tracking of changes, making it possible to get an object from the API, update a property, send that object back with only the changed property and not the full objects.

This has the added advantage in that SDK user can simply get an object from the API and set a property to null and send back the object without having to use known workarounds where setting properties to null would require to be placed in the additionalData bag.

// get the object
var @event = await graphServiceClient
    .Me.Events["event-id"]
    .GetAsync();

// the backing store will keep track that the property change and send the updated value.
@event.Recurrence = null;// set to null 

// update the object
await graphServiceClient.Me.Events["event-id"]
    .PatchAsync(@event);

Use of Parameter objects calling Odata functions/actions

To add parameters to an odata action, the SDK uses parameter objects rather than using function overloads to reduce likelihood of the SDK shpping breaking changes. In the v4 SDK, calling a function/action would look like this.

//var message = ....
//var saveToSentItems = ...

await graphClient.Me
	.SendMail(message,saveToSentItems)
	.Request()
	.PostAsync();

This changes to

//var message = ....
//var saveToSentItems = ...

var body = new Microsoft.Graph.Me.SendMail.SendMailPostRequestBody
{
    Message = message,
    SaveToSentItems = saveToSentItems
};

await graphServiceClient.Me
    .SendMail
    .PostAsync(body);

Batch Requests

Apart from passing instances of HttpRequestMessage, batch requests support the passing of RequestInformation instances as follows.

var requestInformation = graphServiceClient
                         .Users
                         .ToGetRequestInformation();

// create the content
var batchRequestContent = new BatchRequestContent(graphServiceClient);
// add steps
var requestStepId = await batchRequestContent.AddBatchRequestStepAsync(requestInformation);

// send and get back response
var batchResponseContent = await graphServiceClient.Batch.PostAsync(batchRequestContent);

var usersResponse = await batchResponseContent.GetResponseByIdAsync<UserCollectionResponse>(requestStepId);
List<User> userList = usersResponse.Value;

Support for $count in request builders

The request builders are now enriched with Count sections where applicable to enable the use of the $count segment

var count = await graphServiceClient.Users.Count.GetAsync(requestConfiguration => requestConfiguration.Headers.Add("ConsistencyLevel","eventual"));

This addresses the workarounds that SDK users had to make in order to call $count which looked as follows in previous SDK versions(ref here)

string requestUrl = graphClient.Users.AppendSegmentToRequestUrl("$count");
Option [] options = new Option[] { new HeaderOption("ConsistencyLevel", "eventual") };
HttpResponseMessage responseMessage = await new UserRequest(requestUrl, graphClient, options)
                                        .SendRequestAsync(null,CancellationToken.None);
string userCount = await responseMessage.Content.ReadAsStringAsync();

Support for OData Casts in request builders

The request builders are now enriched with segments to enable requesting a specific type in the event an API endpoint supports the odata cast functionality.

An example is fetching the members of a group who are of the type User which would be done as below.

var usersInGroup = await graphServiceClient.Groups["group-id"].Members.GraphUser.GetAsync();

Similarly, members of the group of type Application would be done as below.

var applicationsInGroup = await graphServiceClient.Groups["group-id"].Members.GraphApplication.GetAsync();