DEV Community

Cover image for Using Microsoft Graph API with .NET: A Comprehensive Guide
Showmen Dasgupta
Showmen Dasgupta

Posted on

Using Microsoft Graph API with .NET: A Comprehensive Guide

In this blog post, we will explore how to use the Microsoft Graph API in a .NET application. We'll cover essential functionalities, including obtaining access tokens, interacting with user data, and handling group information. By the end of this guide, you'll have a solid understanding of how to leverage the Microsoft Graph API to enhance your applications.

Introduction to Microsoft Graph API

The Microsoft Graph API provides a unified programmability model that you can use to access the tremendous amount of data in Microsoft 365, Windows 10, and Enterprise Mobility + Security. With the Graph API, you can integrate your app with various Microsoft services to enhance its capabilities.

Setting Up Your Project

First, ensure you have the Microsoft.Graph and Azure.Identity NuGet package installed in your project. You can do this via the NuGet Package Manager or by running the following command in your terminal:

dotnet add package Microsoft.Graph
dotnet add package Azure.Identity
Enter fullscreen mode Exit fullscreen mode

Implementing the IGraphService Interface

To keep our code clean and maintainable, we define an interface IGraphService that outlines the various methods we'll implement.

using Microsoft.Graph;
using Microsoft.Graph.Models;

namespace GraphApiBasics.Interfaces
{
    public interface IGraphService
    {
        Task<string> GetAccessTokenConfidentialClientAsync(string clientId, string tenantId, string clientSecret, string authority);
        Task<string> GetAccessTokenWithClientCredentialAsync(string clientId, string tenantId, string clientSecret, CancellationToken cancellationToken = default);
        Task<string> GetAccessTokenByUserNamePassword(string clientId, ICollection<string> scopes, string authority, string userName, string password);
        Task<GraphServiceClient> GetGraphServiceClient(string clientId, string tenantId, string clientSecret);
        Task<User?> GetUserIfExists(GraphServiceClient graphClient, string userEmail);
        Task<User?> CreateUserAsync(GraphServiceClient graphClient, string? displayName, string userPrincipalName, string password);
        Task<List<User>>? GetUserListAsync(GraphServiceClient graphClient);
        Task<PageIterator<User, UserCollectionResponse>>? GetPageIterator(GraphServiceClient graphClient);
        Task<List<User>>? GetUsersWithBatchRequest(GraphServiceClient graphClient);
        Task<User> GetCurrentlyLoggedInUserInfo(GraphServiceClient graphClient);
        Task<int?> GetUsersCount(GraphServiceClient graphClient);
        Task<UserCollectionResponse> GetUsersInGroup(GraphServiceClient graphClient, string groupId);
        Task<ApplicationCollectionResponse> GetApplicationsInGroup(GraphServiceClient graphClient, string groupId);
    }
}

Enter fullscreen mode Exit fullscreen mode

Implementing the GraphService Class

The GraphService class implements the IGraphService interface. This class contains methods to interact with the Microsoft Graph API, such as obtaining access tokens, fetching user data, and handling group information.

Getting Access Tokens

Here we are exploring three methods for obtaining access tokens using different authentication methods: confidential client, client credentials, and username/password. But there are other authentication providers you can use according to your application needs. More info can be found here : https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=csharp

Confidential Client

  public async Task<string> GetAccessTokenConfidentialClientAsync(string clientId, string tenantId,
        string clientSecret, string authority)
    {
        // Define the scopes you need
        var scopes = new[]
        {
            "https://graph.microsoft.com/.default"
        };

        try
        {
            var publicClient = ConfidentialClientApplicationBuilder.Create(clientId)
                .WithClientSecret(clientSecret)
                .WithAuthority(authority)
                .WithTenantId(tenantId)
                .WithRedirectUri("http://localhost:7181/auth/login-callback-ms")
                .Build();

            var token = await publicClient.AcquireTokenForClient(scopes)
                .WithTenantIdFromAuthority(new Uri(authority))
                .ExecuteAsync();

            var accessToken = token.AccessToken;

            return accessToken;
        }
        catch (MsalUiRequiredException ex)
        {
            _logger.LogCritical($"Error acquiring token: {ex.Message}");
            throw;
        }
    }

Enter fullscreen mode Exit fullscreen mode

Client Credentials

public async Task<string> GetAccessTokenWithClientCredentialAsync(string clientId, string tenantId,
        string clientSecret,
        CancellationToken cancellationToken = default)
    {
        // Define the scopes you need
        var scopes = new[]
        {
            "https://graph.microsoft.com/.default"
        };

        try
        {
            var options = new ClientSecretCredentialOptions
            {
                AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
            };

            var credential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);

            var tokenRequestContext = new TokenRequestContext(scopes);
            var token = await credential.GetTokenAsync(tokenRequestContext, cancellationToken);
            var accessToken = token.Token;

            return accessToken;
        }
        catch (MsalUiRequiredException ex)
        {
            _logger.LogCritical($"Error acquiring token: {ex.Message}");
            throw;
        }
    }

Enter fullscreen mode Exit fullscreen mode

Username and Password

public async Task<string> GetAccessTokenByUserNamePassword(string clientId, ICollection<string> scopes,
        string authority, string userName,
        string password)
    {
        try
        {
            var app = PublicClientApplicationBuilder.Create(clientId)
                .WithAuthority(authority)
                .WithRedirectUri("http://localhost:7181/auth/login-callback-ms")
                .Build();

            var result = await app.AcquireTokenByUsernamePassword(scopes, userName, password)
                .ExecuteAsync();

            return result.AccessToken;
        }
        catch (Exception ex)
        {
            throw new BadHttpRequestException(ex.Message);
        }
    }

Enter fullscreen mode Exit fullscreen mode

GraphServiceClient Instance

We need a 'GraphServiceClient' instance to interact with the Microsoft Graph API.

public Task<GraphServiceClient> GetGraphServiceClient(string clientId, string tenantId, string clientSecret)
{
    var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
    var graphClient = new GraphServiceClient(credential);
    return Task.FromResult(graphClient);
}

Enter fullscreen mode Exit fullscreen mode

User Operations

Check if a User Exists:

public async Task<User?> GetUserIfExists(GraphServiceClient graphClient, string userEmail)
{
    var userCollection = await graphClient.Users
        .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{userEmail}'");
    return userCollection?.Value?.FirstOrDefault();
}
Enter fullscreen mode Exit fullscreen mode

You can search an existing user or a valid user using the 'userPrincipalName' which is basically the 'userEmail'.

Create a New User

public async Task<User?> CreateUserAsync(GraphServiceClient graphClient, string? displayName, string userPrincipalName, string password)
{
    var newUser = new User
    {
        AccountEnabled = true,
        DisplayName = displayName,
        MailNickname = userPrincipalName.Split('@')[0],
        Mail = userPrincipalName,
        UserPrincipalName = userPrincipalName,
        PasswordProfile = new PasswordProfile
        {
            ForceChangePasswordNextSignIn = true,
            Password = password
        }
    };
    return await graphClient.Users.PostAsync(newUser);
}

Enter fullscreen mode Exit fullscreen mode

Since you are creating a user with a system-generated password, Microsoft will require the user to change their password and set up a new one the next time they log in. The ForceChangePasswordNextSignIn = true flag ensures this.

Get a List of All Users

public async Task<List<User>>? GetUserListAsync(GraphServiceClient graphClient)
{
    var usersResponse = await graphClient.Users
        .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Select = ["id", "createdDateTime", "userPrincipalName"]);
    return usersResponse?.Value;
}

Enter fullscreen mode Exit fullscreen mode

You can add more parameters on the array to fetch more info for the users. you can get more detailed information about it here in this documentation: https://learn.microsoft.com/en-us/graph/query-parameters?tabs=http

Group Operations

Get Users in a Group:

public async Task<UserCollectionResponse> GetUsersInGroup(GraphServiceClient graphClient, string groupId)
{
    var usersInGroup = await graphClient.Groups[groupId].Members.GraphUser.GetAsync();
    return usersInGroup;
}

Enter fullscreen mode Exit fullscreen mode

Get Applications in a Group:

public async Task<ApplicationCollectionResponse> GetApplicationsInGroup(GraphServiceClient graphClient,
        string groupId)
    {
        try
        {
            var applicationsInGroup = await graphClient.Groups[groupId].Members.GraphApplication.GetAsync();
            return applicationsInGroup ?? throw new InvalidOperationException();
        }
        catch (Exception ex)
        {
            throw new BadHttpRequestException(ex.Message);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Batch Requests and User Count

Get Users Count:

public async Task<int?> GetUsersCount(GraphServiceClient graphClient)
{
    var count = await graphClient.Users.Count.GetAsync(requestConfiguration =>
        requestConfiguration.Headers.Add("ConsistencyLevel", "eventual"));
    return count;
}

Enter fullscreen mode Exit fullscreen mode

Get Users with Batch Request:

// Step 1: Create the request information for getting users
    var requestInformation = graphClient
        .Users
        .ToGetRequestInformation();

// Step 2: Create a batch request content collection
    var batchRequestContent = new BatchRequestContentCollection(graphClient);

// Step 3: Add the user request to the batch and get the step ID
    var requestStepId = await batchRequestContent.AddBatchRequestStepAsync(requestInformation);

// Step 4: Send the batch request and get the response
    var batchResponseContent = await graphClient.Batch.PostAsync(batchRequestContent);

// Step 5: Extract the user response from the batch response using the step ID
    var usersResponse = await batchResponseContent.GetResponseByIdAsync<UserCollectionResponse>(requestStepId);
    var userList = usersResponse.Value;

 // Step 6: Return the list of users or throw an exception if null
    return userList ?? throw new InvalidOperationException();

Enter fullscreen mode Exit fullscreen mode

Creating the API Controller

The 'GraphApiController' uses the 'IGraphService' to provide API endpoints for interacting with Microsoft Graph

using GraphApiBasics.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.Graph;

namespace GraphApiBasics.Controllers
{
    [Route("api/v1/graph")]
    public class GraphApiController : Controller
    {
        private readonly GraphSecretOptions _graphSecretOptions;
        private readonly IGraphService _graphService;

        public GraphApiController(IOptions<GraphSecretOptions> graphSecretOptions, IGraphService graphService)
        {
            _graphSecretOptions = graphSecretOptions.Value;
            _graphService = graphService;
        }

        private async Task<GraphServiceClient> GetGraphClientAsync()
        {
            return await _graphService.GetGraphServiceClient(_graphSecretOptions.ClientId, _graphSecretOptions.TenantId, _graphSecretOptions.ClientSecret);
        }

        [HttpGet("get-access-token-confidential-client-credentials")]
        public async Task<IActionResult> GetAccessTokenWithConfidentialClientCredential()
        {
            var accessToken = await _graphService.GetAccessTokenConfidentialClientAsync(
                _graphSecretOptions.ClientId,
                _graphSecretOptions.TenantId,
                _graphSecretOptions.ClientSecret,
                _graphSecretOptions.Authority
            );
            return Ok(new { accessToken });
        }

        [HttpGet("get-access-token-client-credentials")]
        public async Task<IActionResult> GetAccessTokenWithClientCredential()
        {
            var accessToken = await _graphService.GetAccessTokenWithClientCredentialAsync(
                _graphSecretOptions.ClientId,
                _graphSecretOptions.TenantId,
                _graphSecretOptions.ClientSecret
            );
            return Ok(new { accessToken });
        }

        [HttpPost("create-user-if-not-exists")]
        public async Task<IActionResult> CreateUserIfNotExists(string userEmail, string password, string displayName)
        {
            var graphClient = await GetGraphClientAsync();
            var validUser = await _graphService.GetUserIfExists(graphClient, userEmail);
            if (validUser != null)
            {
                return NotFound("User Already Exists");
            }

            var user = await _graphService.CreateUserAsync(graphClient, displayName, userEmail, password);
            return Ok(new { user });
        }

        [HttpGet("get-list-of-users")]
        public async Task<IActionResult> GetUsersList()
        {
            var graphClient = await GetGraphClientAsync();
            var users = await _graphService.GetUserListAsync(graphClient);
            return Ok(new { users });
        }

        [HttpGet("get-page-iterator")]
        public async Task<IActionResult> GetPageIterator()
        {
            var graphClient = await GetGraphClientAsync();
            var pageIterator = await _graphService.GetPageIterator(graphClient);
            await pageIterator.IterateAsync();

            return Ok(new { pageIterator });
        }

        [HttpGet("get-users-with-batch-request")]
        public async Task<IActionResult> GetUsersWithBatchRequest()
        {
            var graphClient = await GetGraphClientAsync();
            var users = await _graphService.GetUsersWithBatchRequest(graphClient);
            return Ok(new { users });
        }

        [HttpGet("get-currently-logged-in-user-info")]
        public async Task<IActionResult> GetCurrentlyLoggedInUserInfo()
        {
            var graphClient = await GetGraphClientAsync();
            var loggedInUserInfo = await _graphService.GetCurrentlyLoggedInUserInfo(graphClient);
            return Ok(new { loggedInUserInfo });
        }

        [HttpGet("get-users-count")]
        public async Task<IActionResult> GetUsersCount()
        {
            var graphClient = await GetGraphClientAsync();
            var usersCount = await _graphService.GetUsersCount(graphClient);
            return Ok(new { usersCount });
        }

        [HttpGet("get-users-in-group")]
        public async Task<IActionResult> GetUsersInGroup(string groupId)
        {
            var graphClient = await GetGraphClientAsync();
            var usersInGroup = await _graphService.GetUsersInGroup(graphClient, groupId);
            return Ok(new { usersInGroup });
        }

        [HttpGet("get-applications-in-group")]
        public async Task<IActionResult> GetApplicationsInGroup(string groupId)
        {
            var graphClient = await GetGraphClientAsync();
            var applicationsInGroup = await _graphService.GetApplicationsInGroup(graphClient, groupId);
            return Ok(new { applicationsInGroup });
        }

        [HttpPost("get-access-token-username-password")]
        public async Task<IActionResult> GetAccessTokenWithUserNamePassword(string userName, string password)
        {
            var accessToken = await _graphService.GetAccessTokenByUserNamePassword(
                _graphSecretOptions.ClientId,
                new[] { "User.Read", "User.ReadAll" },
                _graphSecretOptions.Authority,
                userName,
                password
            );
            return Ok(new { accessToken });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this comprehensive guide, we have explored how to use the Microsoft Graph API in a .NET application. We covered obtaining access tokens, interacting with user data, and handling group information. By implementing the IGraphService interface and GraphService class, you can efficiently manage user and group data in your Azure AD tenant. The GraphApiController provides convenient API endpoints for interacting with the Graph API, ensuring a clean, maintainable, and scalable approach to using the Microsoft Graph API in your projects.

To follow latest Microsoft Graph .NET SDK v5 change log and upgrade guide : https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/feature/5.0/docs/upgrade-to-v5.md

Top comments (0)