List of all posts in the #30DaysMSGraph series
In Day 14 we discussed batch processing requests to Microsoft Graph requests. Today we’ll take a big jump and build our first project in .NET Core that can make calls against Microsoft Graph. We recommend you bookmark this day as we’ll be building upon this process for many of the remaining days while we demo various endpoints within the Microsoft.
For those of you who are not familiar with Visual Studio or Visual Studio Code, don’t worry. The goal of this sample project for today is to build a base .NET Core console application utilizing a minimum number of steps to authenticate and call Microsoft Graph. We are adapting the instructions from the dotnetcore-console-sample repo for the instructions below as that repo may change over time.
Note: The repo README file will be a cleaner version of the instructions due to formatting limitations with this blog post. Also, today’s post will likely take longer than the usual 5-15 mins of the other daily posts. If you wish to “skip ahead” you may clone the sample repo and implement the necessary steps (create Azure AD app, assign and grant permissions to Azure AD app, configure appsettings.json file, etc.) to run the project.
Prerequisites
To complete this sample, you need the following:
- Visual Studio Code installed on your development machine. If you do not have Visual Studio Code, visit the previous link for download options. (Note: This tutorial was written with Visual Studio Code version 1.28.2. The steps in this guide may work with other versions, but that has not been tested.)
- .NET Core SDK. (Note: This tutorial was written with .NET Core SDK 2.1.403. The steps in this guide may work with other versions, but that has not been tested.)
- C# extension for Visual Studio Code
- A Microsoft work or school account.
If you don’t have a Microsoft work or school account there is an option to get a free account:
- You can sign up for the Office 365 Developer Program to get a free Office 365 subscription.
Step 1: Create a .NET Core Console Application
- Create a folder called ConsoleGraphTest for the console application.
Note: For the purposes of this sample the project folder was named ConsoleGraphTest. If you choose a different folder name, ensure that the namespace for files matches.
Open the command line and navigate to this folder. Run the following command:
dotnet new console
Before moving on, install the following NuGet packages that you will use later.
- Identity.Client
- Graph
- Extensions.Configuration
- Extensions.Configuration.FileExtensions
- Extensions.Configuration.Json
Run the following commands to install these NuGet packages:
dotnet add package Microsoft.Identity.Client --version 2.3.1-preview dotnet add package Microsoft.Graph dotnet add package Microsoft.Extensions.Configuration dotnet add package Microsoft.Extensions.Configuration.FileExtensions dotnet add package Microsoft.Extensions.Configuration.Json
Step 2: Register a web application with the new Azure AD Portal App Registration
Either re-use the Azure AD app registration from Day 9 or follow those steps again to register a new application. The following settings will need to be configured:
- Name = .NET Core Console App Test
- Redirect URI = https://localhost:8080
- Permissions assigned and admin consented:
- Microsoft Graph – User.Read.All
Step 3: Extend the app for Azure AD Authentication
Create configuration file
In this step you will extend the application from the previous step to support authentication with Azure AD. This is required to obtain the necessary OAuth access token to call the Microsoft Graph. In this step you will integrate the Microsoft Authentication Library library into the application.
On the command line from Step 1, run the following command inside the project folder to open Visual Studio Code with the project folder opened:
code .
Add a file to the folder named appsettings.json with the following content:
{ "applicationId": "YOUR_APP_ID_HERE", "applicationSecret": "YOUR_APP_SECRET_HERE", "tenantId": "YOUR_TENANT_ID_HERE", "redirectUri": "YOUR_REDIRECT_URI_HERE" }
Edit appsettings.json and fill in the values obtained in previous step on the Azure AD Portal app registration UI:
- Replace YOUR_APP_ID_HERE with your application ID.
- Replace YOUR_APP_SECRET_HERE with your client secret (VALUE from Secret1 in previous steps).
- Replace YOUR_TENANT_ID_HERE with your tenant (domain) ID.
- Replace YOUR_REDIRECT_URI_HERE with your application redirect URI.
Important: If you’re using source control such as git, now would be a good time to exclude the appsettings.json file from source control to avoid inadvertently leaking your app ID and secret.
Create helper classes
Create a new folder called Helpers.
Create a new file in the Helpers folder called AuthHandler.cs.
Replace the contents of AuthHandler.cs with the following code:
using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Graph; using Microsoft.Extensions.Configuration; using System.Linq; using System.Threading; namespace ConsoleGraphTest { // This class allows an implementation of IAuthenticationProvider to be inserted into the DelegatingHandler // pipeline of an HttpClient instance. In future versions of GraphSDK, many cross-cutting concernts will // be implemented as DelegatingHandlers. This AuthHandler will come in the box. public class AuthHandler : DelegatingHandler { private IAuthenticationProvider _authenticationProvider; public AuthHandler(IAuthenticationProvider authenticationProvider, HttpMessageHandler innerHandler) { InnerHandler = innerHandler; _authenticationProvider = authenticationProvider; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { await _authenticationProvider.AuthenticateRequestAsync(request); return await base.SendAsync(request,cancellationToken); } } }
Create a new file in the Helpers folder called MsalAuthenticationProvider.cs
Replace the contents of MsalAuthenticationProvider.cs with the following code:
using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Graph; using Microsoft.Extensions.Configuration; using System.Linq; namespace ConsoleGraphTest { // This class encapsulates the details of getting a token from MSAL and exposes it via the // IAuthenticationProvider interface so that GraphServiceClient or AuthHandler can use it. // A significantly enhanced version of this class will in the future be available from // the GraphSDK team. It will supports all the types of Client Application as defined by MSAL. public class MsalAuthenticationProvider : IAuthenticationProvider { private ConfidentialClientApplication _clientApplication; private string[] _scopes; public MsalAuthenticationProvider(ConfidentialClientApplication clientApplication, string[] scopes) { _clientApplication = clientApplication; _scopes = scopes; } /// <summary> /// Update HttpRequestMessage with credentials /// </summary> public async Task AuthenticateRequestAsync(HttpRequestMessage request) { var token = await GetTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token); } /// <summary> /// Acquire Token /// </summary> public async Task<string> GetTokenAsync() { AuthenticationResult authResult = null; authResult = await _clientApplication.AcquireTokenForClientAsync(_scopes); return authResult.AccessToken; } } }
Step 4: Extend the app for Microsoft Graph
In this step you will incorporate the Microsoft Graph into the application. For this application, you will use the Microsoft Graph Client Library for .NET to make calls to Microsoft Graph.
Get user information from tenant
Opening the Program.cs file. Add the following “using” statements to the top of the file.
using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Graph; using Microsoft.Extensions.Configuration;
Ensure that the namespace matches across the project. In this example we have used ConsoleGraphTest. Update the namespace accordingly if needed.
Inside the Program class add static references to GraphServiceClient and HttpClient. These static variables can be used to instantiate the clients used to make calls against the Microsoft Graph.
private static GraphServiceClient _graphServiceClient; private static HttpClient _httpClient;
Inside the Program class add a new method LoadAppSettings with the following definition. This method retrieves the configuration values from a separate file. This allows updating the configuration (client Id, client secret, etc.) independently of the code itself. This is a general best practice when possible to separate configuration from code.
private static IConfigurationRoot LoadAppSettings() { try { var config = new ConfigurationBuilder() .SetBasePath(System.IO.Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", false, true) .Build(); // Validate required settings if (string.IsNullOrEmpty(config["applicationId"]) || string.IsNullOrEmpty(config["applicationSecret"]) || string.IsNullOrEmpty(config["redirectUri"]) || string.IsNullOrEmpty(config["tenantId"])) { return null; } return config; } catch (System.IO.FileNotFoundException) { return null; } }
Inside the Program class add a new method CreateAuthorizationProvider that will be used in later methods to instantiate the clients used for making calls against the Microsoft Graph. This method uses the configuration data with a ConfidentialClientApplication.
private static IAuthenticationProvider CreateAuthorizationProvider(IConfigurationRoot config) { var clientId = config["applicationId"]; var clientSecret = config["applicationSecret"]; var redirectUri = config["redirectUri"]; var authority = $"https://login.microsoftonline.com/{config["tenantId"]}/v2.0"; List<string> scopes = new List<string>(); scopes.Add("https://graph.microsoft.com/.default"); var cca = new ConfidentialClientApplication(clientId, authority, redirectUri, new ClientCredential(clientSecret), null, null); return new MsalAuthenticationProvider(cca, scopes.ToArray()); }
Inside the Program class add a new method GetAuthenticatedGraphClient with the following definition. This method creates an instance of the GraphServiceClient from the static reference. The GraphServiceClient instance uses the configuration returned from previous method.
private static GraphServiceClient GetAuthenticatedGraphClient(IConfigurationRoot config) { var authenticationProvider = CreateAuthorizationProvider(config); _graphServiceClient = new GraphServiceClient(authenticationProvider); return _graphServiceClient; }
Inside the Program class add a new method GetAuthenticatedHTTPClient with the following definition. This method creates an instance of the HTTPClient from the static reference. The HTTPClient instance uses the configuration returned from previous method.
private static HttpClient GetAuthenticatedHTTPClient(IConfigurationRoot config) { var authenticationProvider = CreateAuthorizationProvider(config); _httpClient = new HttpClient(new AuthHandler(authenticationProvider, new HttpClientHandler())); return _httpClient; }
Inside the Main method add the following to load the configuration settings.
var config = LoadAppSettings(); if (null == config) { Console.WriteLine("Missing or invalid appsettings.json file. Please see README.md for configuration instructions."); return; }
Continuing in the Main method add the following to get an authenticated instance of the GraphServiceClient and send a request to retrieve the first user from “/Users” endpoint on the Microsoft Graph.
//Query using Graph SDK (preferred when possible) GraphServiceClient graphClient = GetAuthenticatedGraphClient(config); List<QueryOption> options = new List<QueryOption> { new QueryOption("$top", "1") }; var graphResult = graphClient.Users.Request(options).GetAsync().Result; Console.WriteLine("Graph SDK Result"); Console.WriteLine(graphResult[0].DisplayName);
Continuing in the Main method add the following to get an authenticated instance of the HttpClient and send a request to retrieve the first user from “/Users” endpoint on the Microsoft Graph.
//Direct query using HTTPClient (for beta endpoint calls or not available in Graph SDK) HttpClient httpClient = GetAuthenticatedHTTPClient(config); Uri Uri = new Uri("https://graph.microsoft.com/v1.0/users?$top=1"); var httpResult = httpClient.GetStringAsync(Uri).Result; Console.WriteLine("HTTP Result"); Console.WriteLine(httpResult);
This completes all file edits and additions. Ensure all files are saved. In order to test the console application, run the following commands from the command line:
dotnet build dotnet run
Consider what this code is doing.
- The GetAuthenticatedGraphClient function initializes a GraphServiceClient with an authentication provider that calls AcquireTokenForClientAsync.
- In the Main function:
- The graph endpoint that will be called is /v1.0/users/$top=1.
Try It Out
Create (or re-use) your own Azure AD application from the preview Azure AD app registration portal using Day 9 instructions. Then do one (or both if you are ambitious) of the following:
- Build out the above sample project from an empty .NET Core console application.
- Clone the official .NET Core console sample from Microsoft Graph repo and configure it to use your Azure AD app registration.
Join us tomorrow as we start a series of use case demos extending the .NET Core console application from today to make Microsoft Graph requests against multiple endpoints. First up will be creating a new user by calling the Azure AD endpoint in Day 16.