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