0% found this document useful (0 votes)
215 views

Exercise 1: Using Query Parameters When Querying Microsoft Graph Via HTTP

The document provides instructions for using various query parameters when making requests to Microsoft Graph via HTTP, including $filter to retrieve a subset of data, $select to choose which properties to return, $orderby to sort results, $skip and $top for pagination, $expand to include related resources, and $count and $search. The exercises walk through examples of making requests to Microsoft Graph Explorer using these different parameters to retrieve and filter calendar events, messages, groups and other data for the signed in user.

Uploaded by

Yo Gin Yunen
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
215 views

Exercise 1: Using Query Parameters When Querying Microsoft Graph Via HTTP

The document provides instructions for using various query parameters when making requests to Microsoft Graph via HTTP, including $filter to retrieve a subset of data, $select to choose which properties to return, $orderby to sort results, $skip and $top for pagination, $expand to include related resources, and $count and $search. The exercises walk through examples of making requests to Microsoft Graph Explorer using these different parameters to retrieve and filter calendar events, messages, groups and other data for the signed in user.

Uploaded by

Yo Gin Yunen
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 87

Exercise 1: Using query parameters when querying Microsoft

Graph via HTTP

Note: Users may sign in with their Microsoft account or use the default sample account to
complete the following tasks.

By the end of this exercise you will be able to:

 Use $filter query parameter.
 Use $select query parameter.
 Order results using $orderby query parameter.
 Set page size of results using $skip and $top query parameters.
 Expand and retrieve resources using $expand query parameter.
 Retrieve the total count of matching resources using $count query parameter.
 Search for resources using $search query parameter.

Task 1: Go to the Graph Explorer

1. Sign in to MS600-VM to activate  Ctrl+Alt+Delete  to bring up the logon page.


o Account: Admin
o Password: Pa55w.rd

2. Open the Microsoft Edge browser

3. Go to this link: https://developer.microsoft.com/en-us/graph/graph-
explorer.

This page allows users to interact with Microsoft Graph without writing any code.
Microsoft Graph Explorer provides sample data to use for read operations.

Note: Some organizations may not allow users to sign in or consent to specific scopes
required for some operations.

Task 2: Use \$select to retrieve only some object properties

1. In the request URL box amend the request to


be: https://graph.microsoft.com/v1.0/groups

2. Select Run Query.
This lists all the groups in the tenant that the current user can see in the Response
Preview pane at the bottom of the page. Note there are many properties returned per
record.

3. Select the Response Headers tab.

Note the value of the content-length header. This shows how much data was returned
in the response for this request.

4. To reduce the response to only include the necessary properties, you can make
use of the $select parameter. Update the request URL box to the
following: https://graph.microsoft.com/v1.0/groups?
$select=displayName,mail,visibility
5. Select Run Query.

In the Response Headers tab note the new value for the content-length header. There


is a much smaller amount of data received now.

6. Select the Response Preview tab.

Note that only the selected properties are returned. To allow an application to interact
directly with an object in Microsoft Graph, it is simplest to have the unique identifier
for that object.

7. With this in mind, amend your query to include the ID


property: https://graph.microsoft.com/v1.0/groups?
$select=displayName,mail,visibility,id

8. Select Run Query.
Task 3: Use \$orderby to sort results
When presenting data to end users it’s often necessary to use a sort order other than the
default provided by Microsoft Graph. This should be done using the $orderby parameter.
Continuing from the previous task, sort the groups by the displayName property.

1. Change the request URL box to: https://graph.microsoft.com/v1.0/groups?


$select=displayName,mail,visibility,id&$orderBy=displayName

2. Select Run Query. The sorted set of groups is shown in the Response


Preview pane.
Note: Many properties cannot be used for sorting. For example, if you were to use
mail in the $orderby you would receive an HTTP 400 response like this:
http
{
"error": {
"code": "Request_UnsupportedQuery",
"message": "Unsupported sort property 'mail' for 'Group'.",
"innerError": {
"request-id": "582643b8-7ac2-415a-b58b-59009ec63ec1",
"date": "2019-10-24T19:54:55"
}
}
}

3. Set the sort direction using either asc or desc like


this: https://graph.microsoft.com/v1.0/groups?
$select=displayName,mail,visibility,id&$orderBy=displayName desc

Task 4: Use \$filter to retrieve a subset of data available

1. In the request URL box amend the request to


be: https://graph.microsoft.com/v1.0/me/messages

2. Select Run Query.

This shows a list of messages for the current Response Preview pane at the bottom of
the page.

3. Note that the response also includes an @odata.nextLink property, which allows


developers to fetch additional pages of results and, by its presence, indicates that there
may be a large possible number of results. To find just the email messages that have
attachments add a filter.
4. Add a filter so that the request URL box contains the
following: https://graph.microsoft.com/v1.0/me/messages?$filter=hasAttachments
%20eq%20true

5. Select Run Query.

The results are shown in the Response Preview pane. Note that there is


no @odata.nextLink this time, which indicates that all the matching records have been
returned in this request. If there is an @odata.nextLink it should be used to fetch
additional pages of results. It’s possible to compose queries that traverse object
properties as well; for instance, to find mail sent from a specific email address.

6. Change the request URL box to


read: https://graph.microsoft.com/v1.0/me/messages?
$filter=from/emailAddress/address%20eq%20'[email protected]'

7. Select Run Query.

The results are shown in the Response Preview pane. Note that there are multiple
pages of results as indicated by the presence of the @odata.nextLink. Some properties
support the use of a start with operator.
8. Enter the following into the request
URL box: https://graph.microsoft.com/v1.0/me/messages?
$filter=startswith(subject,'my')

9. Select Run Query.

The results are shown in the Response Preview pane.

10. Some collection properties can be used in $filter using a Lambda expression.

For example, to find all groups that have the Unified type, enter the following query
into the request URL box: https://graph.microsoft.com/v1.0/groups?
$filter=groupTypes/any(c:%20c%20eq%20'Unified')

11. Select Run Query. The list of groups that have the Unified type is shown in
the Response Preview pane.

Task 5: Use \$skip and \$top for explicit pagination of results

1. In the request URL box change the request to


be: https://graph.microsoft.com/v1.0/me/events?

2. Select Run Query. The results are shown in the Response Preview pane.

3. Note the @odata.nextLink property finishes in $skip 10. This is the default first


page size for the /events resource. Different resources have different default first page
sizes. The size of the first page and subsequent pages may not be the same. Edit the
query to fetch an explicit number of results. $top is used to set maximum the number
of results to be returned.

4. In the request URL box amend the request to


be: https://graph.microsoft.com/v1.0/me/events?$top=5

5. Select Run Query. The results are shown in the Response Preview pane.


6. Note the @odata.nextLink property finishes in $skip 5, using the next link here
would fetch the next five records in the data set. To fetch the next page set the \
$skip value to 5: https://graph.microsoft.com/v1.0/me/events?$top=5&$skip=5

This is the same as the @odata.nextLink returned for this resource. The $skip value


tells Microsoft Graph to start returning results after the number provided here.

7. Select Run Query.

The results are shown in the Response Preview pane. The value returned contains
an @odata.nextLink that can be followed. If a client needs to load all data from a
specific resource or query it should keep following the @odata.nextLink until it is no
longer present in the results.

Note: The formula for composing manual pagination queries is this: ?\$top={pageSize}&\


$skip={pageSize*(pageNumber - 1)}

Task 6: Expand and retrieve resources using \$expand query parameter


In this exercise you will fetch the attachments from the mail of the current user. In a
previous task you found the messages for the current user, which have attachments. In the
request URL box amend the request to perform this query again.

1. In the request URL box amend the request to


be: https://graph.microsoft.com/v1.0/me/messages?$filter=hasAttachments eq true

2. Select Run Query. The results are shown in the Response Preview pane. Note


that when examining the results there is no information provided about the
attachments. Amend the query to fetch information about the messages' attachments.

3. In the request URL box amend the request to


be: https://graph.microsoft.com/v1.0/me/messages?$filter=hasAttachments eq
true&$expand=attachments

4. Select Run Query. The results are shown in the Response Preview pane.

5. Note that the attachment objects included in the response include


the contentBytes property. This is the actual file content and can cause the application
to fetch far more data than might be desired.
6. Select the Response Headers tab.

7. Note the value for the content-length header. Amend the query to
use $select on the $expanded attachments collection.

8. In the request URL box amend the request to


be: https://graph.microsoft.com/v1.0/me/messages?$filter=hasAttachments eq
true&$expand=attachments($select=id,name,contentType,size)

9. Select Run Query. Note the new value for the content-length header is much
smaller than for the previous request.

10. Select Response Preview. Note that for each attachment, in addition to the
explicitly requested properties, there are some additional @odata properties.

Task 7: Use \$count to discover the total number of matching resources

1. In the request URL box amend the request to


be: https://graph.microsoft.com/v1.0/me/messages?$count=true

2. Select Run Query.

In the Response Preview pane there is an additional property shown, @odata.count;


this value shows the total number of resources that match the provided query. Note
that as no query has been explicitly provided, the @odata.count value is that of all
resources at this path.

3. Add a query, in this instance query to see how many unread messages the
current user has. In the request URL box amend the request to
be: https://graph.microsoft.com/v1.0/me/messages?$count=true&$filter=isRead eq
false

4. Select Run Query. In the Response Preview pane, the first page displays


matching results. Note the @odata.count value is updated to reflect the query passed
via the $filter parameter.

Task 8: Use \$search to discover the total number of matching resources


1. In the request URL box amend the request to
be: https://graph.microsoft.com/v1.0/me/messages?$search="business"
2. Select Run Query.

In the Response Preview the messages that have matches for the keyword supplied are
shown. Note the number of messages that match (4). The scope of the search can be
adjusted specifying property names that are recognized by the KQL syntax.

See https://docs.microsoft.com/en-us/graph/query-parameters#search-parameter for more
information.

3. Amend the request URL box to scope the search to only target the subject field. In
the request URL box amend the request
to: https://graph.microsoft.com/v1.0/me/messages?
$search="subject:business""subject:business"
4. Select Run Query. In the Response Preview the messages that have matches in the
specified property are shown. Note that there should be fewer results (3).

Review
In this exercise, you learned how to make use of query parameters:

 Fetch resources with properties matching certain parameters by using


the $filter query parameter.
 Fetch only the necessary data by using the $select query parameter.
 Set the order of results using $orderby query parameter.
 Set page size of results using $skip and $top query parameters.
 Expand and retrieve additional resources using $expand query parameter.
 Retrieve the total count of matching resources using $count query parameter.
 Search for resources using $search query parameter.

Congratulations!
Exercise 2: Retrieve and control information returned from
Microsoft Graph

In this exercise, you will create a new Azure AD web application registration using the
Azure Active Directory (Azure AD) admin center, a .NET Core console application, and
query Microsoft Graph.

By the end of this exercise you will be able to use the following queries:

 $select
 $top
 $orderby
 $filter

Task 1: Create an Azure AD application

1. Sign in to MS600-VM to activate  Ctrl+Alt+Delete  to bring up the logon page.


o Account: Admin
o Password: Pa55w.rd

2. Open a browser and navigate to the Azure Active Directory admin


center https://aad.portal.azure.com. Sign in using
using [email protected] with the password w#!l4ivA5hM?.

3. Select Azure Active Directory in the leftmost navigation panel.


4. Select Manage > App registrations in the left navigation panel.

5. On the App registrations page, select New registration.


6. On the Register an application page, set the values as follows:
o Name: Graph Console App
o Supported account types: Accounts in this organizational directory only
(Contoso only - Single tenant)
o Redirect URI: Web = https://localhost/
7. Select Register.

8. On the Graph Console App overview page, copy the value of the Application


(client) ID and Directory (tenant) ID; you will need them later in this exercise.
9. Select Manage > Certificates & secrets.

10. Select New client secret.

11. In the Add a client secret page, enter a value in Description, select one of the
options for Expires, and select Add.
12. Copy the client secret value before you leave this page. You will need it in the
next step.

Important: This client secret is never shown again, so make sure you copy it now.

13. Grant Azure AD application permissions to Microsoft Graph. After creating the
application, you need to grant it the necessary permissions to Microsoft Graph.
Select API Permissions in the leftmost navigation panel.
14. Select the Add a permission button.

15. In the Request API permissions panel that appears, select Microsoft


Graph from the Microsoft APIs tab.
16. When prompted for the type of permission, select Application permissions.
17. Enter User.R in the Select permissions search box and select
the User.Read.All permission, followed by the Add permission button at the bottom
of the panel.

18. Under the Configured permissions section, select the button Grant admin


consent for [tenant], followed by the Yes button to grant all users in your
organization this permission.

Task 2: Create .NET Core console application

1. Open your command prompt, navigate to a directory where you have rights to
create your project, and run the following command to create a new .NET Core
console application: dotnet new console -o graphconsoleapp

2. After creating the application, run the following commands to ensure your new
project runs correctly.

dontnetcli
cd graphconsoleapp
dontnetcli
dotnet add package Microsoft.Identity.Client
dontnetcli
dotnet add package Microsoft.Graph
dontnetcli
dotnet add package Microsoft.Extensions.Configuration
dontnetcli
dotnet add package Microsoft.Extensions.Configuration.FileExtensions
dontnetcli
dotnet add package Microsoft.Extensions.Configuration.Json

3. Open the application in Visual Studio Code using the following


command: code .

4. If Visual Studio Code displays a dialog box asking if you want to add required
assets to the project, select Yes.

Task 3: Update the console app to support Azure AD authentication


It is good practice to type code strings into Notepad so they can keep their proper
formatting, They can then be copied/pasted into VS Code.
1. Create a new file named appsettings.json in the root of the project and add the
following code to it:

json
{
"tenantId": "YOUR_TENANT_ID_HERE",
"applicationId": "YOUR_APP_ID_HERE",
"applicationSecret": "YOUR_APP_SECRET_HERE",
"redirectUri": "YOUR_REDIRECT_URI_HERE"
}

2. Update properties with the following values:


o YOUR_TENANT_ID_HERE: Azure AD directory ID
o YOUR_APP_ID_HERE: Azure AD client ID
o YOUR_APP_SECRET_HERE: Azure AD client secret
o YOUR_REDIRECT_URI_HERE: redirect URI you entered when creating
the Azure AD app (for example, https://localhost)

Task 4: Create helper classes

1. Create a new folder Helpers in the project.

2. Create a new file AuthHandler.cs in the Helpers folder and add the following


code:

csharp
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Graph;
using System.Threading;
namespace Helpers
{
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);
}
}
}

3. Create a new file MsalAuthenticationProvider.cs in the Helpers folder and


add the following code:

csharp
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Graph;
namespace Helpers
{
public class MsalAuthenticationProvider : IAuthenticationProvider
{
private IConfidentialClientApplication _clientApplication;
private string[] _scopes;
public MsalAuthenticationProvider(IConfidentialClientApplication
clientApplication, string[] scopes)
{
_clientApplication = clientApplication;
_scopes = scopes;
}
public async Task AuthenticateRequestAsync(HttpRequestMessage
request)
{
var token = await GetTokenAsync();
request.Headers.Authorization = new
AuthenticationHeaderValue("bearer", token);
}
public async Task<string> GetTokenAsync()
{
AuthenticationResult authResult = null;
authResult = await
_clientApplication.AcquireTokenForClient(_scopes).ExecuteAsync();
return authResult.AccessToken;
}
}
}

Task 5: Incorporate Microsoft Graph into the console app

1. Open the Program.cs file and add the following using statements to the top of
the file below using System:
csharp
using System.Collections.Generic;
using Microsoft.Identity.Client;
using Microsoft.Graph;
using Microsoft.Extensions.Configuration;
using Helpers;

2. Add the following static member to the Program class in the Program.cs file.


This member will be used to instantiate the client used to call Microsoft Graph:

csharp
private static GraphServiceClient _graphClient;

3. Add the following method LoadAppSettings to the Program class. This


method retrieves the configuration details from the appsettings.json file previously
created:

csharp
private static IConfigurationRoot LoadAppSettings()
{
try
{
var config = new ConfigurationBuilder()

.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true)
.Build();
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;
}
}

4. Add the following method CreateAuthorizationProvider to


the Program class. This method will create an instance of the clients used to call
Microsoft Graph:

csharp
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 = ConfidentialClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.WithRedirectUri(redirectUri)

.WithClientSecret(clientSecret)
.Build();
return new MsalAuthenticationProvider(cca, scopes.ToArray());
}

5. Add the following method GetAuthenticatedGraphClient to


the Program class. This method creates an instance of
the GraphServiceClient object:

csharp
private static GraphServiceClient
GetAuthenticatedGraphClient(IConfigurationRoot config)
{
var authenticationProvider = CreateAuthorizationProvider(config);
_graphClient = new GraphServiceClient(authenticationProvider);
return _graphClient;
}

6. Locate the Main method in the Program class. Add the following code to the


end of the Main method to load the configuration settings from
the appsettings.json file:

csharp
var config = LoadAppSettings();
if (config == null)
{
Console.WriteLine("Invalid appsettings.json file.");
return;
}

7. Add the following code to the end of the Main method, just after the code
added in the last step. This code will obtain an authenticated instance of
the GraphServicesClient and submit a request for the first user.

csharp
var client = GetAuthenticatedGraphClient(config);
var graphRequest = client.Users.Request();
var results = graphRequest.GetAsync().Result;
foreach(var user in results)
{
Console.WriteLine(user.Id + ": " + user.DisplayName + " <" +
user.Mail + ">");
}
Console.WriteLine("\nGraph Request:");
Console.WriteLine(graphRequest.GetHttpRequestMessage().RequestUri);

Task 6: Build and test the application

1. Run the following command in a command prompt to compile the console


application: dotnet build

2. Run the following command to run the console application: dotnet run

3. When the application runs, you'll see a list of users displayed. The query
retrieved all information about the users.
Note: Notice the URL written to the console. This is the entire request, including query
parameters, that the Microsoft Graph SDK is generating. Take note for each query you run
in this exercise.

Task 7: Edit the application to optimize the query


The current console application isn't efficient because it retrieves all information about all
users in your organization but only displays three properties. The $select query parameter
can limit the amount of data that is returned by Microsoft Graph, optimizing the query.

1. Update the line that starts with var results = graphRequest in


the Main method with the following to limit the query to just two properties:

csharp
var results = graphRequest
.Select(u => new { u.DisplayName, u.Mail })
.GetAsync()
.Result;

2. Rebuild and rerun the console application by executing the following


commands in the command line:

csharp
dotnet build
dotnet run

3. Notice that the ID property isn't populated with data, as it wasn't included in


the $select query parameter.
4. Let us further limit the results to just the first 15 results. Update the line that
starts with var results = graphRequest in the Main method with the following:

csharp
var results = graphRequest
.Select(u => new { u.DisplayName, u.Mail })
.Top(15)
.GetAsync()
.Result;

5. Rebuild and rerun the console application by executing the following


commands in the command line:
csharp
dotnet build
dotnet run

6. Notice only 15 items are now returned by the query.

7. Sort the results in reverse alphabetic order. Update the line that starts
with var results = graphRequest in the Main method with the following:

csharp
var results = graphRequest
.Select(u => new { u.DisplayName, u.Mail })
.Top(15)
.OrderBy("DisplayName desc")
.GetAsync()
.Result;

8. Rebuild and rerun the console application by executing the following


commands in the command line:

csharp
dotnet build
dotnet run
9. Further refine the results by selecting users whose surname starts with A, B, or
C. You'll need to remove the \$orderby query parameter added previously as the Users
endpoint doesn't support combining the \$filter and \$orderby parameters. Update the
line that starts with var results = graphRequest in the Main method with the
following:

csharp
var results = graphRequest
.Select(u => new { u.DisplayName, u.Mail })
.Top(15)
// .OrderBy("DisplayName desc)
.Filter("startsWith(surname,'A') or
startsWith(surname,'B') or startsWith(surname,'C')")
.GetAsync()
.Result;

10. Rebuild and rerun the console application by executing the following
commands in the command line:

powershell
dotnet build
dotnet run
Review
In this exercise, you created an Azure AD application and .NET console application that
retrieved user data from Microsoft Graph. You then used query parameters to limit and
manipulate the data returned by Microsoft Graph to optimize the query.

Congratulations!

You have successfully completed this exercise. Click Next to advance to the next exercise.

PreviousNext: Exercise 3: Using change notifications...


Exercise 3: Using change notifications and track changes with
Microsoft Graph

This tutorial teaches you how to build a .NET Core app that uses the Microsoft Graph API
to receive notifications (webhooks) when a user account changes in Azure AD and perform
queries using the delta query API to receive all changes to user accounts since the last
query was made.

By the end of this exercise you will be able to:

 Monitor for changes using change notifications


 Get changes using a delta query

Task 1: Create a new Azure AD web application


In this exercise, you will create a new Azure AD web application registration using the
Azure AD admin center and grant administrator consent to the required permission scopes.

1. Sign in to MS600-VM to activate the  Ctrl+Alt+Delete  to bring up the logon page.
o Account: Admin
o Password: Pa55w.rd

2. Open a browser and navigate to the Azure AD admin


center https://aad.portal.azure.com. Sign in using
using [email protected] with the password w#!l4ivA5hM?..

3. Select Azure Active Directory in the leftmost navigation panel, then


select App registrations under Manage.
4. Select New registration on the Register an application page.

5. Set the values as follows:


o Name: Graph Notification Tutorial
o Supported account types: Accounts in any organizational directory and
personal Microsoft accounts
o Redirect URI: Web > http://localhost/

6. Select Register.

7. On the Graph Notification Tutorial page, copy the value of the Application


(client) ID and Directory (tenant) ID; save it, you will need them later in the tutorial.

8. Select Manage > Certificates & secrets.

9. Select New client secret.


10. Enter a value in Description, select one of the options for Expires, and
select Add.
11. Copy the client secret value before you leave this page. You will need it later in
the tutorial.

Important: This client secret is never shown again, so make sure you copy it now.

12. Select Manage > API Permissions.

13. Select Add a permission and select Microsoft Graph.

14. Select Application Permission, expand the User group, and


select User.Read.All scope.

15. Select Add permissions to save your changes.


16. The application requests an application permission with
the User.Read.All scope. This permission requires administrative consent.

17. Select Grant admin consent for Contoso, then select Yes to consent this
application, and grant the application access to your tenant using the scopes you
specified.
Task 2: Create .NET Core App

Run ngrok

In order for the Microsoft Graph to send notifications to your application running on your
development machine you need to use a tool such as ngrok to tunnel calls from the internet
to your development machine. Ngrok allows calls from the internet to be directed to your
application running locally without needing to create firewall rules.

NOTE: Graph requires using https and this lab uses ngrok free.

If you run into any issues please visit Using ngrok to get a public HTTPS address for a
local server already serving HTTPS (for free)

1. Run ngrok by executing the following from the command line:

powershell
ngrok http 5000

2. This will start ngrok and will tunnel requests from an external ngrok url to your
development machine on port 5000. Copy the https forwarding address. In the example
below that would be https://787b8292.ngrok.io. You will need this later.

Create .NET Core WebApi App

1. Open your command prompt, navigate to a directory where you have rights to
create your project, and run the following command to create a new .NET Core
console application: dotnet new webapi -o msgraphapp

2. After creating the application, run the following commands to ensure your new
project runs correctly:

powershell
cd msgraphapp
dotnet add package Microsoft.Identity.Client
dotnet add package Microsoft.Graph
dotnet run

3. The application will start and output the following:

powershell
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: [your file path]\msgraphapp

4. Stop the application running by pressing CTRL+C.

5. Open the application in Visual Studio Code using the following


command: code .

6. If Visual Studio code displays a dialog box asking if you want to add required
assets to the project, select Yes.

Task 3: Code the HTTP API


Open the Startup.cs file and comment out the following line to disable ssl redirection.

csharp
//app.UseHttpsRedirection();

Add model classes

The application uses several new model classes for (de)serialization of messages to/from
the Microsoft Graph.

1. Right-click in the project file tree and select New Folder. Name it Models

2. Right-click the Models folder and add three new files:


o Notification.cs
o ResourceData.cs
o MyConfig.cs

3. Replace the contents of Notification.cs with the following:

csharp
using Newtonsoft.Json;
using System;
namespace msgraphapp.Models
{
public class Notifications
{
[JsonProperty(PropertyName = "value")]
public Notification[] Items { get; set; }
}
// A change notification.
public class Notification
{
// The type of change.
[JsonProperty(PropertyName = "changeType")]
public string ChangeType { get; set; }
// The client state used to verify that the notification is from
Microsoft Graph. Compare the value received with the notification to the
value you sent with the subscription request.
[JsonProperty(PropertyName = "clientState")]
public string ClientState { get; set; }
// The endpoint of the resource that changed. For example, a
message uses the format ../Users/{user-id}/Messages/{message-id}
[JsonProperty(PropertyName = "resource")]
public string Resource { get; set; }
// The UTC date and time when the webhooks subscription expires.
[JsonProperty(PropertyName = "subscriptionExpirationDateTime")]
public DateTimeOffset SubscriptionExpirationDateTime { get; set;
}
// The unique identifier for the webhooks subscription.
[JsonProperty(PropertyName = "subscriptionId")]
public string SubscriptionId { get; set; }
// Properties of the changed resource.
[JsonProperty(PropertyName = "resourceData")]
public ResourceData ResourceData { get; set; }
}
}

4. Replace the contents of ResourceData.cs with the following:

csharp
using Newtonsoft.Json;
namespace msgraphapp.Models
{
public class ResourceData
{
// The ID of the resource.
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
// The OData etag property.
[JsonProperty(PropertyName = "@odata.etag")]
public string ODataEtag { get; set; }
// The OData ID of the resource. This is the same value as the
resource property.
[JsonProperty(PropertyName = "@odata.id")]
public string ODataId { get; set; }
// The OData type of the resource: "#Microsoft.Graph.Message",
"#Microsoft.Graph.Event", or "#Microsoft.Graph.Contact".
[JsonProperty(PropertyName = "@odata.type")]
public string ODataType { get; set; }
}
}

5. Replace the contents of MyConfig.cs with the following:

csharp
namespace msgraphapp
{
public class MyConfig
{
public string AppId { get; set; }
public string AppSecret { get; set; }
public string TenantId { get; set; }
public string Ngrok { get; set; }
}
}

6. Open the Startup.cs file. Locate the method ConfigureServices() method and


replace it with the following code:

csharp
public void ConfigureServices(IServiceCollection services)
{

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3
_0);
var config = new MyConfig();
Configuration.Bind("MyConfig", config);
services.AddSingleton(config);
}

7. Open the appsettings.json file and replace the content with the following


JSON.

csharp
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"MyConfig":
{
"AppId": "<APP ID>",
"AppSecret": "<APP SECRET>",
"TenantId": "<TENANT ID>",
"Ngrok": "<NGROK URL>"
}
}

8. Replace the following variables with the values you copied earlier:
o should be set to the https ngrok url you copied earlier.
o should be your Office 365 tenant id you copied earlier.
o and should be the application id and secret you copied earlier when you
created the application registration.

Add notification controller

The application requires a new controller to process the subscription and notification.

1. Right-click the Controllers folder, select New File, and name the


controller NotificationsController.cs.

2. Replace the contents of NotificationController.cs with the following code:

csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using msgraphapp.Models;
using Newtonsoft.Json;
using System.Net;
using System.Threading;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System.Net.Http.Headers;
namespace msgraphapp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class NotificationsController : ControllerBase
{
private readonly MyConfig config;
public NotificationsController(MyConfig config)
{
this.config = config;
}
[HttpGet]
public async Task<ActionResult<string>> Get()
{
var graphServiceClient = GetGraphClient();
var sub = new Microsoft.Graph.Subscription();
sub.ChangeType = "updated";
sub.NotificationUrl = config.Ngrok + "/api/notifications";
sub.Resource = "/users";
sub.ExpirationDateTime = DateTime.UtcNow.AddMinutes(5);
sub.ClientState = "SecretClientState";
var newSubscription = await graphServiceClient
.Subscriptions
.Request()
.AddAsync(sub);
return $"Subscribed. Id: {newSubscription.Id}, Expiration:
{newSubscription.ExpirationDateTime}";
}
public async Task<ActionResult<string>> Post([FromQuery]string
validationToken = null)
{
// handle validation
if(!string.IsNullOrEmpty(validationToken))
{
Console.WriteLine($"Received Token:
'{validationToken}'");
return Ok(validationToken);
}
// handle notifications
using (StreamReader reader = new StreamReader(Request.Body))
{
string content = await reader.ReadToEndAsync();
Console.WriteLine(content);
var notifications =
JsonConvert.DeserializeObject<Notifications>(content);
foreach(var notification in notifications.Items)
{
Console.WriteLine($"Received notification:
'{notification.Resource}', {notification.ResourceData?.Id}");
}
}
return Ok();
}
private GraphServiceClient GetGraphClient()
{
var graphClient = new GraphServiceClient(new
DelegateAuthenticationProvider((requestMessage) => {
// get an access token for Graph
var accessToken = GetAccessToken().Result;
requestMessage
.Headers
.Authorization = new
AuthenticationHeaderValue("bearer", accessToken);
return Task.FromResult(0);
}));
return graphClient;
}
private async Task<string> GetAccessToken()
{
IConfidentialClientApplication app =
ConfidentialClientApplicationBuilder.Create(config.AppId)
.WithClientSecret(config.AppSecret)

.WithAuthority($"https://login.microsoftonline.com/{config.TenantId}")
.WithRedirectUri("https://daemon")
.Build();
string[] scopes = new string[] {
"https://graph.microsoft.com/.default" };
var result = await
app.AcquireTokenForClient(scopes).ExecuteAsync();
return result.AccessToken;
}
}
}

3. Save all files.

Task 4: Run the application


Update the Visual Studio debugger launch configuration:

Note: By default, the .NET Core launch configuration will open a browser and navigate to
the default URL for the application when launching the debugger. For this application, we
instead want to navigate to the NGrok URL. If you leave the launch configuration as is,
each time you debug the application it will display a broken page. You can just change the
URL, or change the launch configuration to not launch the browser:

1. In Visual Studio Code, open the file .vscode/launch.json.

2. Delete the following section in the default configuration:

json
// Enable launching a web browser when ASP.NET Core starts. For more
information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"
},

3. Save your changes.
4. In Visual Studio Code, select Debug > Start debugging to run the application.
VS Code will build and start the application.

5. Once you see messages in the Debug Console window that states the
application started proceed with the next steps.

6. Open a browser and navigate to http://localhost:5000/api/notifications to


subscribe to change notifications. If successful you will see output that includes a
subscription id.

Note: Your application is now subscribed to receive notifications from the Microsoft


Graph when an update is made on any user in the Office 365 tenant.

7. Trigger a notification:

a. Open a browser and navigate to the Microsoft 365 admin


center https://admin.microsoft.com/AdminPortal

b. If prompted to login, sign-in using an admin account.

c. Select Users > Active users.

8. Select an active user and select Edit for their Contact information.

9. Update the Phone number value with a new number and select Save.

10. In the Visual Studio Code Debug Console, you will see a notification has been
received. Sometimes this may take a few minutes to arrive. An example of the output
is below:

json
Received notification: 'Users/7a7fded6-0269-42c2-a0be-512d58da4463',
7a7fded6-0269-42c2-a0be-512d58da4463

This indicates the application successfully received the notification from the Microsoft
Graph for the user specified in the output. You can then use this information to query the
Microsoft Graph for the users full details if you want to synchronize their details into your
application.

Task 5: Manage notification subscriptions


Subscriptions for notifications expire and need to be renewed periodically. The following
steps will demonstrate how to renew notifications:

Update Code

1. Open Controllers > NotificationsController.cs file

2. Add the following two member declarations to


the NotificationsController class:

csharp
private static Dictionary<string, Subscription> Subscriptions = new
Dictionary<string, Subscription>();
private static Timer subscriptionTimer = null;

3. Add the following new methods. These will implement a background timer that
will run every 15 seconds to check if subscriptions have expired. If they have, they
will be renewed.

csharp
private void CheckSubscriptions(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine($"Checking subscriptions
{DateTime.Now.ToString("h:mm:ss.fff")}");
Console.WriteLine($"Current subscription count
{Subscriptions.Count()}");
foreach(var subscription in Subscriptions)
{
// if the subscription expires in the next 2 min, renew it
if(subscription.Value.ExpirationDateTime <
DateTime.UtcNow.AddMinutes(2))
{
RenewSubscription(subscription.Value);
}
}
}
private async void RenewSubscription(Subscription subscription)
{
Console.WriteLine($"Current subscription: {subscription.Id},
Expiration: {subscription.ExpirationDateTime}");
var graphServiceClient = GetGraphClient();
var newSubscription = new Subscription
{
ExpirationDateTime = DateTime.UtcNow.AddMinutes(5)
};
await graphServiceClient
.Subscriptions[subscription.Id]
.Request()
.UpdateAsync(newSubscription);
subscription.ExpirationDateTime =
newSubscription.ExpirationDateTime;
Console.WriteLine($"Renewed subscription: {subscription.Id}, New
Expiration: {subscription.ExpirationDateTime}");
}

4. The CheckSubscriptions method is called every 15 seconds by the timer. For


production use this should be set to a more reasonable value to reduce the number of
unnecessary calls to Microsoft Graph. The RenewSubscription method renews a
subscription and is only called if a subscription is going to expire in the next two
minutes.

5. Locate the method Get() and replace it with the following code:

csharp
[HttpGet]
public async Task<ActionResult<string>> Get()
{
var graphServiceClient = GetGraphClient();
var sub = new Microsoft.Graph.Subscription();
sub.ChangeType = "updated";
sub.NotificationUrl = config.Ngrok + "/api/notifications";
sub.Resource = "/users";
sub.ExpirationDateTime = DateTime.UtcNow.AddMinutes(5);
sub.ClientState = "SecretClientState";
var newSubscription = await graphServiceClient
.Subscriptions
.Request()
.AddAsync(sub);
Subscriptions[newSubscription.Id] = newSubscription;
if (subscriptionTimer == null)
{
subscriptionTimer = new Timer(CheckSubscriptions, null, 5000,
15000);
}
return $"Subscribed. Id: {newSubscription.Id}, Expiration:
{newSubscription.ExpirationDateTime}";
}

Test the changes

1. Within Visual Studio Code, select Debug > Start debugging to run the
application. Navigate to the following url: http://localhost:5000/api/notifications.
This will register a new subscription.

2. In the Visual Studio Code Debug Console window, approximately every 15


seconds, notice the timer checking the subscription for expiration:
powershell
Checking subscriptions 12:32:51.882
Current subscription count 1

3. Wait a few minutes and you will see the following when the subscription needs
renewing:

powershell
Renewed subscription: 07ca62cd-1a1b-453c-be7b-4d196b3c6b5b, New Expiration:
3/10/2019 7:43:22 PM +00:00

This indicates that the subscription was renewed and shows the new expiry time.

Task 5: Query for changes


Microsoft Graph offers the ability to query for changes to a particular resource since you
last called it. Using this option, combined with Change Notifications, enables a robust
pattern for ensuring you don't miss any changes to the resources.

1. Locate and open the following


controller: Controllers > NotificationsController.cs. Add the following code to the
existing **NotificationsControlle**r class.

csharp
private static object DeltaLink = null;
private static IUserDeltaCollectionPage lastPage = null;
private async Task CheckForUpdates()
{
var graphClient = GetGraphClient();
// get a page of users
var users = await GetUsers(graphClient, DeltaLink);
OutputUsers(users);
// go through all of the pages so that we can get the delta link on
the last page.
while (users.NextPageRequest != null)
{
users = users.NextPageRequest.GetAsync().Result;
OutputUsers(users);
}
object deltaLink;
if (users.AdditionalData.TryGetValue("@odata.deltaLink", out
deltaLink))
{
DeltaLink = deltaLink;
}
}
private void OutputUsers(IUserDeltaCollectionPage users)
{
foreach(var user in users)
{
var message = $"User: {user.Id}, {user.GivenName} {user.Surname}";
Console.WriteLine(message);
}
}
private async Task<IUserDeltaCollectionPage> GetUsers(GraphServiceClient
graphClient, object deltaLink)
{
IUserDeltaCollectionPage page;
if (lastPage == null)
{
page = await graphClient
.Users
.Delta()
.Request()
.GetAsync();
}
else
{
lastPage.InitializeNextPageRequest(graphClient,
deltaLink.ToString());
page = await lastPage.NextPageRequest.GetAsync();
}
lastPage = page;
return page;
}

2. This code includes a new method, CheckForUpdates(), that will call the


Microsoft Graph using the delta url and then pages through the results until it finds a
new deltalink on the final page of results. It stores the url in memory until the code is
notified again when another notification is triggered.

3. Locate the existing Post() method and replace it with the following code:

csharp
public async Task<ActionResult<string>> Post([FromQuery]string
validationToken = null)
{
// handle validation
if (!string.IsNullOrEmpty(validationToken))
{
Console.WriteLine($"Received Token: '{validationToken}'");
return Ok(validationToken);
}
// handle notifications
using (StreamReader reader = new StreamReader(Request.Body))
{
string content = await reader.ReadToEndAsync();
Console.WriteLine(content);
var notifications =
JsonConvert.DeserializeObject<Notifications>(content);
foreach (var notification in notifications.Items)
{
Console.WriteLine($"Received notification:
'{notification.Resource}', {notification.ResourceData?.Id}");
}
}
// use deltaquery to query for all updates
await CheckForUpdates();
return Ok();
}

4. The Post method will now call CheckForUpdates when a notification is


received. Save all files.

Test the changes

1. Within Visual Studio Code, select Debug > Start debugging to run the


application. Navigate to the following url http://localhost:5000/api/notifications This
will register a new subscription.

2. Open a browser and navigate to the Microsoft 365 admin


center https://admin.microsoft.com/AdminPortal.

3. If prompted to login, sign-in using an admin account.

a. Select Users > Active users.

b. Select an active user and select Edit for their Contact information.

c. Update the Mobile phone value with a new number and select Save.

4. Wait for the notification to be received as indicated in the Visual Studio Code
Debug Console:

powershell
Received notification: 'Users/7a7fded6-0269-42c2-a0be-512d58da4463',
7a7fded6-0269-42c2-a0be-512d58da4463

5. The application will now initiate a delta query with the graph to get all the users
and log out some of their details to the console output.

powershell
User: 19e429d2-541a-4e0b-9873-6dff9f48fabe, Allan Deyoung
User: 05501e79-f527-4913-aabf-e535646d7ffa, Christie Cline
User: fecac4be-76e7-48ec-99df-df745854aa9c, Debra Berger
User: 4095c5c4-b960-43b9-ba53-ef806d169f3e, Diego Siciliani
User: b1246157-482f-420c-992c-fc26cbff74a5, Emily Braun
User: c2b510b7-1f76-4f75-a9c1-b3176b68d7ca, Enrico Cattaneo
User: 6ec9bd4b-fc6a-4653-a291-70d3809f2610, Grady Archie
User: b6924afe-cb7f-45a3-a904-c9d5d56e06ea, Henrietta Mueller
User: 0ee8d076-4f13-4e1a-a961-eac2b29c0ef6, Irvin Sayers
User: 31f66f05-ac9b-4723-9b5d-8f381f5a6e25, Isaiah Langer
User: 7ee95e20-247d-43ef-b368-d19d96550c81, Johanna Lorenz
User: b2fa93ac-19a0-499b-b1b6-afa76c44a301, Joni Sherman
User: 01db13c5-74fc-470a-8e45-d6d736f8a35b, Jordan Miller
User: fb0b8363-4126-4c34-8185-c998ff697a60, Lee Gu
User: ee75e249-a4c1-487b-a03a-5a170c2aa33f, Lidia Holloway
User: 5449bd61-cc63-40b9-b0a8-e83720eeefba, Lynne Robbins
User: 7ce295c3-25fa-4d79-8122-9a87d15e2438, Miriam Graham
User: 737fe0a7-0b67-47dc-b7a6-9cfc07870705, Nestor Wilke
User: a1572b58-35cd-41a0-804a-732bd978df3e, Patti Fernandez
User: 7275e1c4-5698-446c-8d1d-fa8b0503c78a, Pradeep Gupta
User: 96ab25eb-6b69-4481-9d28-7b01cf367170, Megan Bowen
User: 846327fa-e6d6-4a82-89ad-5fd313bff0cc, Alex Wilber
User: 200e4c7a-b778-436c-8690-7a6398e5fe6e, MOD Administrator
User: 7a7fded6-0269-42c2-a0be-512d58da4463, Adele Vance
User: 752f0102-90f2-4b8d-ae98-79dee995e35e, Removed?:deleted
User: 4887248a-6b48-4ba5-bdd5-fed89d8ea6a0, Removed?:deleted
User: e538b2d5-6481-4a90-a20a-21ad55ce4c1d, Removed?:deleted
User: bc5994d9-4404-4a14-8fb0-46b8dccca0ad, Removed?:deleted
User: d4e3a3e0-72e9-41a6-9538-c23e10a16122, Removed?:deleted

6. In the Microsoft 365 Admin Portal, repeat the process of editing a user and
Save again.

The application will receive another notification and will query the graph again using the
last delta link it received. However, this time you will notice that only the modified user
was returned in the results.

powershell
User: 7a7fded6-0269-42c2-a0be-512d58da4463, Adele Vance

Using this combination of notifications with delta query you can be assured you won’t miss
any updates to a resource. Notifications may be missed due to transient connection issues,
however the next time your application gets a notification it will pick up all the changes
since the last successful query.

Review
You've now completed this exercise. In this exercise, you created.NET Core app that used
the Microsoft Graph API to receive notifications (webhooks) when a user account changes
in Azure AD and perform queries using the delta query API to receive all changes to user
accounts since the last query was made.
Congratulations!

You have successfully completed this exercise. Click Next to advance to the next exercise.

PreviousNext: Exercise 4: Reduce traffic with...


Exercise 4: Reduce traffic with batched requests

In this exercise, you'll use the Graph Explorer to create and issue a single request that
contains multiple child requests. This batching of requests enables developers to submit
multiple requests in a single round-trip request to Microsoft Graph, creating more
optimized queries.

Task 1: Sign in to Microsoft Graph Explorer


The tool Graph Explorer enables developers to create and test queries using the Microsoft
Graph REST API. Previously in this module, you used the Graph Explorer as an
anonymous user and executed queries using the sample data collection.

In this example, you'll sign in to Microsoft Graph with a real user.

1. Sign in to MS600-VM to activate the  Ctrl+Alt+Delete  to bring up the logon page.
o Account: Admin
o Password: Pa55w.rd

2. Open a browser and navigate


to https://developer.microsoft.com/graph/graph-explorer
3. Select the Sign in to Graph Explorer button in the leftmost panel and enter the
credentials [email protected] with the password w#!
l4ivA5hM?.

4. After signing in, click select permissions and verify that the user has the
permissions to submit the requests in this exercise. You must have at least these
minimum permissions:
o Mail.Read
o Calendars.Read
o Files.ReadWrite

Task 2: Submit three (3) GET requests in a single batch


All batch requests are submitted as HTTP POSTs to a specific
endpoint: https://graph.microsoft.com/v1.0/$batch. The \$batch query parameter is what
tells Microsoft Graph to unpack the requests submitted in the body.

1. Set the request to an HTTP POST and the endpoint of the request


to https://graph.microsoft.com/v1.0/\$batch.
2. Add the following JSON code to the Request Body input box. This JSON code
will issue three requests:
o Request the current user's displayName, jobTitle,
and userPrincipalName properties.
o Request the current user's email messages that are marked with high
importance.
o Request all the current user's calendar events.

json
{
"requests":
[
{
"url": "/me?
$select=displayName,jobTitle,userPrincipalName",
"method": "GET",
"id": "1"
},
{
"url": "/me/messages?$filter=importance eq
'high'&$select=from,subject,receivedDateTime,bodyPreview",
"method": "GET",
"id": "2"
},
{
"url": "/me/events",
"method": "GET",
"id": "3"
}
]
}

3.

4. Select the Run Query button.


5. Observe the results in the Response Preview box at the bottom of the page.

Notice the response includes three individual responses within the responses collection.
Also notice for response id:3, the data that was returned, as indicated by the
@odata.nextLink property, is from the /me/events collection. This query matches the third
request in the initial request submitted.

Task 3: Combine POST and GET requests in a single batch request


Batch requests can also include both POST and GET requests.

In this example, you'll submit a request that creates a new folder in the current user's
OneDrive [for Business] and then requests the newly created folder. If the first request
failed, the second request should come back empty as well.

1. Enter the following JSON code to the Request Body input box. This will issue
three requests:

json
{
"requests":
[
{
"url": "/me/drive/root/children",
"method": "POST",
"id": "1",
"body": {
"name": "TestBatchingFolder",
"folder": {}
},
"headers": {
"Content-Type": "application/json"
}
},
{
"url": "/me/drive/root/children/TestBatchingFolder",
"method": "GET",
"id": "2",
"DependsOn": [
"1"
]
}
]
}

2. Select the Run Query button.

3. Observe the results in the Response Preview box at the bottom of the page.


Notice that this response contains two objects. The first request resulted in an HTTP
201 message that says the item, or folder, was created. The second request was also
successful, and the name of the folder returned matched the folder the first request
created.

Review
In this exercise, you used Microsoft Graph to demonstrate how you can combine multiple
requests using a single request. This capability of submitting batch requests using the
$batch query parameter enables you to optimize your applications to minimize the number
of requests to Microsoft Graph.

Congratulations!

You have successfully completed this exercise. Click Next to advance to the next exercise.

PreviousNext: Exercise 5: Understand throttling in...


Exercise 5: Understand throttling in Microsoft Graph

In this exercise, you will create a new Azure AD web application registration using the
Azure AD admin center, a .NET Core console application, and query Microsoft Graph. You
will issue many requests in parallel to trigger your requests to be throttled. This application
will allow you to see the response you will receive.

Task 1: Create an Azure AD application

1. Sign in to MS600-VM to activate the  Ctrl+Alt+Delete  to bring up the logon page.
o Account: Admin
o Password: Pa55w.rd

2. Open a browser and navigate to the Azure Active Directory admin


center https://aad.portal.azure.com. Sign in
using [email protected] with the password w#!l4ivA5hM?

3. Select Azure Active Directory in the leftmost navigation panel.

4. Select Manage > App registrations in the left navigation panel.


5. On the App registrations page, select New registration.

6. On the Register an application page, set the values as follows:


o Name: Graph Console Throttle App.
o Supported account types: Accounts in this organizational directory only
(Contoso only - Single tenant).
7. Select Register.

8. On the Graph Console App page, copy the value of the Application (client)


ID and Directory (tenant) ID; you will need these in the application.

9. Select Manage > Authentication.

10. Under Platform configurations, select Add a platform.

11. In Configure platforms, select Mobile and desktop applications.


12. In the section Redirect URIs, select the entry that begins with msal and
enter https://contoso in Custom redirect URIs section, and then
click Configure button.
13. Scroll down to the Advanced settings section and set the toggle to Yes.
14. Select Save in the top menu to save your changes.

Task 2: Grant Azure AD application permissions to Microsoft Graph


After creating the application, you need to grant it the necessary permissions to Microsoft
Graph.

1. Select API Permissions in the left navigation panel.

2. Select the Add a permission button.


3. In the Request API permissions panel that appears, select Microsoft
Graph from the Microsoft APIs tab.

4. When prompted for the type of permission, select Delegated permissions.


5. Enter Mail.R in the Select permissions search box and select
the Mail.Read permission, followed by the Add permission button at the bottom of
the panel.

6. At the bottom of the API Permissions panel, select the button Grant admin


consent for [tenant], followed by the Yes button, to grant all users in your
organization this permission.

The option to Grant admin consent here in the Azure AD admin center is pre-consenting
the permissions to the users in the tenant to simplify the exercise. This approach allows the
console application to use the resource owner password credential grant, so the user isn't
prompted to grant consent to the application that simplifies the process of obtaining an
OAuth access token. You could elect to implement alternative options such as the device
code flow to utilize dynamic consent as another option.

Task 3: Create .NET Core console application


1. Open your command prompt, navigate to a directory where you have rights to
create your project, and run the following command to create a new .NET Core
console application: dotnet new console -o graphconsolethrottlepp

2. After creating the application, run the following commands to ensure your new
project runs correctly:

dontnetcli
cd graphconsolethrottlepp
dontnetcli
dotnet add package Microsoft.Identity.Client
dontnetcli
dotnet add package Microsoft.Graph
dontnetcli
dotnet add package Microsoft.Extensions.Configuration
dontnetcli
dotnet add package Microsoft.Extensions.Configuration.FileExtensions
dontnetcli
dotnet add package Microsoft.Extensions.Configuration.Json

3. Open the application in Visual Studio Code using the following


command: code .

4. If Visual Studio Code displays a dialog box asking if you want to add required
assets to the project, select Yes.

Task 4: Update the console app to support Azure AD authentication

1. Create a new file named appsettings.json in the root of the project and add the
following code to it:

json
{
"tenantId": "YOUR_TENANT_ID_HERE",
"applicationId": "YOUR_APP_ID_HERE"
}

2. Update properties with the following values:


o YOUR_TENANT_ID_HERE: Azure AD directory ID
o YOUR_APP_ID_HERE: Azure AD client ID

Task 5: Create authentication helper classes


1. Create a new folder Helpers in the project.

2. Create a new file AuthHandler.cs in the Helpers folder and add the following


code:

csharp
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
namespace Helpers
{
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);
}
}
}

3. Create a new file MsalAuthenticationProvider.cs in the Helpers folder and


add the following code:

csharp
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Graph;
namespace Helpers
{
public class MsalAuthenticationProvider : IAuthenticationProvider
{
private static MsalAuthenticationProvider _singleton;
private IPublicClientApplication _clientApplication;
private string[] _scopes;
private string _username;
private SecureString _password;
private string _userId;
private MsalAuthenticationProvider(IPublicClientApplication
clientApplication, string[] scopes, string username, SecureString
password)
{
_clientApplication = clientApplication;
_scopes = scopes;
_username = username;
_password = password;
_userId = null;
}
public static MsalAuthenticationProvider
GetInstance(IPublicClientApplication clientApplication, string[] scopes,
string username, SecureString password)
{
if (_singleton == null)
{
_singleton = new
MsalAuthenticationProvider(clientApplication, scopes, username,
password);
}
return _singleton;
}
public async Task AuthenticateRequestAsync(HttpRequestMessage
request)
{
var accessToken = await GetTokenAsync();
request.Headers.Authorization = new
AuthenticationHeaderValue("bearer", accessToken);
}
public async Task<string> GetTokenAsync()
{
if (!string.IsNullOrEmpty(_userId))
{
try
{
var account = await
_clientApplication.GetAccountAsync(_userId);
if (account != null)
{
var silentResult = await
_clientApplication.AcquireTokenSilent(_scopes, account).ExecuteAsync();
return silentResult.AccessToken;
}
}
catch (MsalUiRequiredException){ }
}
var result = await
_clientApplication.AcquireTokenByUsernamePassword(_scopes, _username,
_password).ExecuteAsync();
_userId = result.Account.HomeAccountId.Identifier;
return result.AccessToken;
}
}
}

Task 6: Incorporate Microsoft Graph into the console app

1. Open the Program.cs file and add the following using statements to the top of


the file below using System; line:

csharp
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Graph;
using Microsoft.Extensions.Configuration;
using Helpers;

2. Add the following method LoadAppSettings to the Program class. The


method retrieves the configuration details from the appsettings.json file previously
created:

csharp
private static IConfigurationRoot LoadAppSettings()
{
try
{
var config = new ConfigurationBuilder()

.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true)
.Build();
if (string.IsNullOrEmpty(config["applicationId"]) ||
string.IsNullOrEmpty(config["tenantId"]))
{
return null;
}
return config;
}
catch (System.IO.FileNotFoundException)
{
return null;
}
}

3. Add the following method CreateAuthorizationProvider to


the Program class. The method will create an instance of the clients used to call
Microsoft Graph.
csharp
private static IAuthenticationProvider
CreateAuthorizationProvider(IConfigurationRoot config, string userName,
SecureString userPassword)
{
var clientId = config["applicationId"];
var authority =
$"https://login.microsoftonline.com/{config["tenantId"]}/v2.0";
List<string> scopes = new List<string>();
scopes.Add("User.Read");
scopes.Add("Mail.Read");
var cca = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.Build();
return MsalAuthenticationProvider.GetInstance(cca,
scopes.ToArray(), userName, userPassword);
}

4. Add the following method GetAuthenticatedHTTPClient to


the Program class. The method creates an instance of the HttpClient object.

csharp
private static HttpClient
GetAuthenticatedHTTPClient(IConfigurationRoot config, string userName,
SecureString userPassword)
{
var authenticationProvider = CreateAuthorizationProvider(config,
userName, userPassword);
var httpClient = new HttpClient(new
AuthHandler(authenticationProvider, new HttpClientHandler()));
return httpClient;
}

5. Add the following method ReadPassword to the Program class. The method


prompts the user for their password:

csharp
private static SecureString ReadPassword()
{
Console.WriteLine("Enter your password");
SecureString password = new SecureString();
while (true)
{
ConsoleKeyInfo c = Console.ReadKey(true);
if (c.Key == ConsoleKey.Enter)
{
break;
}
password.AppendChar(c.KeyChar);
Console.Write("*");
}
Console.WriteLine();
return password;
}

6. Add the following method ReadUsername to the Program class. The method


prompts the user for their username:

csharp
private static string ReadUsername()
{
string username;
Console.WriteLine("Enter your username");
username = Console.ReadLine();
return username;
}

7. Locate the Main method in the Program class. Add the following code


below Console.WriteLine("Hello World!"); to load the configuration settings from
the appsettings.json file:

csharp
var config = LoadAppSettings();
if (config == null)
{
Console.WriteLine("Invalid appsettings.json file.");
return;
}

8. Add the following code to the end of the Main method, just after the code
added in the last step. This code will obtain an authenticated instance of
the HttpClient and submit a request for the current user's email:

csharp
var userName = ReadUsername();
var userPassword = ReadPassword();
var client = GetAuthenticatedHTTPClient(config, userName,
userPassword);

9. Add the following code below var client =


GetAuthenticatedHTTPClient(config, userName, userPassword); to issue many
requests to Microsoft Graph. This code will create a collection of tasks to request a
specific Microsoft Graph endpoint. When a task succeeds, it will write a dot to the
console, while a failed request will write an X to the console. The most recent failed
request's status code and headers are saved. All tasks are then executed in parallel. At
the conclusion of all requests, the results are written to the console:

csharp
var totalRequests = 100;
var successRequests = 0;
var tasks = new List<Task>();
var failResponseCode = HttpStatusCode.OK;
HttpResponseHeaders failedHeaders = null;
for (int i = 0; i < totalRequests; i++)
{
tasks.Add(Task.Run(() =>
{
var response =
client.GetAsync("https://graph.microsoft.com/v1.0/me/messages").Result;
Console.Write(".");
if (response.StatusCode == HttpStatusCode.OK)
{
successRequests++;
}
else
{
Console.Write('X');
failResponseCode = response.StatusCode;
failedHeaders = response.Headers;
}
}));
}
var allWork = Task.WhenAll(tasks);
try
{
allWork.Wait();
}
catch { }
Console.WriteLine();
Console.WriteLine("{0}/{1} requests succeeded.", successRequests,
totalRequests);
if (successRequests != totalRequests)
{
Console.WriteLine("Failed response code: {0}",
failResponseCode.ToString());
Console.WriteLine("Failed response headers: {0}",
failedHeaders);
}

Task 7: Build and test the application

1. Run the following command in a command prompt to compile the console


application: dotnet build

2. Run the following command to run the console application: dotnet run

Note: The console app may take one or two minutes to complete the process of
authenticating and obtaining an access token from Azure AD and issuing the requests to
Microsoft Graph.
1. After entering the username and password of a user, you will see the results
written to the console.

There is a mix of success and failure indicators in the console. The summary states only
39% of the requests were successful.

After the results, the console has two lines that begin with Failed response. Notice the
code states TooManyRequests that is the representation of the HTTP status code 429. This
status code is the indication that your requests are being throttled.

Also notice within the collection of response headers, the presence of Retry-After. This
header is the value in seconds that Microsoft Graph tells you to wait before sending your
next request to avoid being further throttled.

Add helper class to deserialize the message object returned in a REST request

It is easier to work with strongly typed objects instead of untyped JSON responses from
a REST request. Create a helper class to simplify working with the messages objects
returned from the REST request.
1. Create a new file, Messages.cs in the root of the project, and add the following
code to it:

csharp
using Newtonsoft.Json;
using System;
namespace graphconsolethrottlepp
{
public class Messages
{
[JsonProperty(PropertyName = "@odata.context")]
public string ODataContext { get; set; }
[JsonProperty(PropertyName = "@odata.nextLink")]
public string ODataNextLink { get; set; }
[JsonProperty(PropertyName = "value")]
public Message[] Items { get; set; }
}
public class Message
{
[JsonProperty(PropertyName = "@odata.etag")]
public string ETag { get; set; }
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "subject")]
public string Subject { get; set; }
}
}

Note: This class is used by the JSON deserializer to translate a JSON response into a
Messages object.

Add method to implement delayed retry strategy when requests are throttled

The application is going to be modified to first get a list of messages in the current user's
mailbox, then issue a separate request for the details of each message. In most scenarios, a
separate request will trigger Microsoft Graph to throttle the requests.

To address this, your code should inspect each response for situations when the request has
been throttled. In those situations, the code should check for the presence of a Retry-After
header in the response that specifies the number of seconds your application should wait
before issuing another request. If a Retry-After header isn't present, you should have a
default value to fall back on.

1. Within the Program.cs file, add a new method GetMessageDetail() and the


following code to it:

csharp
private static Message GetMessageDetail(HttpClient client, string
messageId, int defaultDelay = 2)
{
Message messageDetail = null;
string endpoint = "https://graph.microsoft.com/v1.0/me/messages/" +
messageId;
// add code here
return messageDetail;
}

2. Add the following code before the // add code here comment to create a
request and wait for the response from Microsoft Graph:

csharp
// submit request to Microsoft Graph & wait to process response
var clientResponse = client.GetAsync(endpoint).Result;
var httpResponseTask = clientResponse.Content.ReadAsStringAsync();
httpResponseTask.Wait();

3. In the case of a successful response, return the deserialized response back to the
caller to display the messages. Add the following lines to the top of
the Program.cs file to update the using statements:

csharp
using Newtonsoft.Json;

4. Go back to the method GetMessageDetail() and the following code before


the // add code here comment:

csharp
Console.WriteLine("...Response status code: {0} ",
clientResponse.StatusCode);
// IF request successful (not throttled), set message to retrieved
message
if (clientResponse.StatusCode == HttpStatusCode.OK)
{
messageDetail =
JsonConvert.DeserializeObject<Message>(httpResponseTask.Result);
}

5. In the case of a throttled response, add the following else statement to the if


statement you just added:

csharp
// ELSE IF request was throttled (429, aka: TooManyRequests)...
else if (clientResponse.StatusCode == HttpStatusCode.TooManyRequests)
{
// get retry-after if provided; if not provided default to 2s
int retryAfterDelay = defaultDelay;
if (clientResponse.Headers.RetryAfter.Delta.HasValue &&
(clientResponse.Headers.RetryAfter.Delta.Value.Seconds > 0))
{
retryAfterDelay =
clientResponse.Headers.RetryAfter.Delta.Value.Seconds;
}
// wait for specified time as instructed by Microsoft Graph's Retry-
After header,
// or fall back to default
Console.WriteLine(">>>>>>>>>>>>> sleeping for {0} seconds...",
retryAfterDelay);
System.Threading.Thread.Sleep(retryAfterDelay * 1000);
// call method again after waiting
messageDetail = GetMessageDetail(client, messageId);
}

This code will do the following:

 Set a default delay of two seconds before the next request is made.
 If the Retry-After header value is present and greater than zero seconds, use that
value to overwrite the default delay.
 Set the thread to sleep for the specified, or default, number of seconds.
 Recursively call the same method to retry the request.

Tip: In cases where the response does not include a Retry-After header, it is


recommended to consider implementing an exponential back-off default delay. In
this code, the application will initially pause for two seconds before retrying the
request. Future requests will double the delay if Microsoft Graph continues to
throttle the request.

Real-world applications should have an upper limit on how long they will delay so to avoid
an unreasonable delay so users are not left with an unresponsive experience.

The resulting method should look like the following:

csharp
private static Message GetMessageDetail(HttpClient client, string messageId,
int defaultDelay = 2)
{
Message messageDetail = null;
string endpoint = "https://graph.microsoft.com/v1.0/me/messages/"
+ messageId;
// submit request to Microsoft Graph & wait to process response
var clientResponse = client.GetAsync(endpoint).Result;
var httpResponseTask =
clientResponse.Content.ReadAsStringAsync();
httpResponseTask.Wait();
Console.WriteLine("...Response status code: {0} ",
clientResponse.StatusCode);
// IF request successful (not throttled), set message to
retrieved message
if (clientResponse.StatusCode == HttpStatusCode.OK)
{
messageDetail =
JsonConvert.DeserializeObject<Message>(httpResponseTask.Result);
}
// ELSE IF request was throttled (429, aka: TooManyRequests)...
else if (clientResponse.StatusCode ==
HttpStatusCode.TooManyRequests)
{
// get retry-after if provided; if not provided default to 2s
int retryAfterDelay = defaultDelay;
if (clientResponse.Headers.RetryAfter.Delta.HasValue &&
(clientResponse.Headers.RetryAfter.Delta.Value.Seconds > 0))
{
retryAfterDelay =
clientResponse.Headers.RetryAfter.Delta.Value.Seconds;
}
// wait for specified time as instructed by Microsoft Graph's
Retry-After header,
// or fall back to default
Console.WriteLine(">>>>>>>>>>>>> sleeping for {0}
seconds...", retryAfterDelay);
System.Threading.Thread.Sleep(retryAfterDelay * 1000);
// call method again after waiting
messageDetail = GetMessageDetail(client, messageId);
}
// add code here
return messageDetail;
}

Update application to use retry strategy

The next step is to update the Main method to use the new method so the application will
use an intelligent throttling strategy.

1. Locate the following line that obtains an instance of an


authenticated HttpClient object in the Main method. Delete all code in
the Main method after this line:

csharp
var client = GetAuthenticatedHTTPClient(config, userName, userPassword);

2. Add the following code after obtaining the HttpClient object. This code will
request the top 100 messages from the current user's mailbox and deserialize the
response into a typed object you previously created:

csharp
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
var clientResponse =
client.GetAsync("https://graph.microsoft.com/v1.0/me/messages?
$select=id&$top=100").Result;
// enumerate through the list of messages
var httpResponseTask = clientResponse.Content.ReadAsStringAsync();
httpResponseTask.Wait();
var graphMessages =
JsonConvert.DeserializeObject<Messages>(httpResponseTask.Result);

3. Add the following code to create individual requests for each message. These
tasks are created as asynchronous tasks that will be executed in parallel:

csharp
var tasks = new List<Task>();
foreach (var graphMessage in graphMessages.Items)
{
tasks.Add(Task.Run(() =>
{
Console.WriteLine("...retrieving message: {0}",
graphMessage.Id);
var messageDetail = GetMessageDetail(client, graphMessage.Id);
Console.WriteLine("SUBJECT: {0}", messageDetail.Subject);
}));
}

4. Next, add the following code to execute all tasks in parallel and wait for them to
complete:

csharp
// do all work in parallel & wait for it to complete
var allWork = Task.WhenAll(tasks);
try
{
allWork.Wait();
}
catch { }

5. With all work complete, write the results to the console:

csharp
stopwatch.Stop();
Console.WriteLine();
Console.WriteLine("Elapsed time: {0} seconds",
stopwatch.Elapsed.Seconds);

Build and test the updated application


1. Run the following command in a command prompt to compile the console
application: dotnet build

2. Run the following command to run the console application: dotnet run

After entering the username and password for the current user, the application will write
multiple log entries to the console, as in the following figure.

Within one or two minutes, the application will display the results of the application.
Depending on the speed of your workstation and internet connection, your requests may or
may not have triggered Microsoft Graph to throttle you. If not, try running the application a
few more times.

If your application ran fast enough, you should see some instances where Microsoft Graph
returned the HTTP status code 429, indicated by the TooManyRequests entries.
In this case, the messages endpoint returned a Retry-After value of one (1) because the
application displays messages on the console that it slept for one second.

The important point is that the application completed successfully, retrieving all 100
messages, even when some requests were rejected due to being throttled by Microsoft
Graph.

Task 8: Implement Microsoft Graph SDK for throttling retry strategy


In the last section exercise, you modified the application to implement a strategy to
determine if a request is throttled. In the case where the request was throttled, as indicated
by the response to the REST endpoint request, you implemented a retry strategy using the
HttpClient.

Let's change the application to use the Microsoft Graph SDK client, which has all the logic
built in for implementing the retry strategy when a request is throttled.

Update the GetAuthenticatedHttpClient method

The application will use the Microsoft Graph SDK to submit requests, not the HttpClient,
so you need to update it.
1. Locate the method GetAuthenticatedHttpClient and make the following
changes to it:

a. Set the return type from HttpClient to GraphServiceClient.

b. Rename
the method from GetAuthenticatedHttpClient to GetAuthenticatedGraphClie
nt.

c. Replace the last two lines in the method with the following lines to
obtain and return an instance of the GraphServiceClient:

csharp
var graphClient = new GraphServiceClient(authenticationProvider);
return graphClient;

2. Your updated method GetAuthenticatedGraphClient should look similar to


this:

csharp
private static GraphServiceClient
GetAuthenticatedGraphClient(IConfigurationRoot config, string userName,
SecureString userPassword)
{
var authenticationProvider = CreateAuthorizationProvider(config,
userName, userPassword);
var graphClient = new GraphServiceClient(authenticationProvider);
return graphClient;
}

Update the application to use the GraphServiceClient.

1. The next step is to update the application to use the Graph SDK that includes an
intelligent throttling strategy. Locate the Messages.cs file in the project. Delete this
file or comment all code within the file out. Otherwise, the application will get
the Message object this file contains confused with the Message object in the
Microsoft Graph SDK.

2. Next, within the Main method, locate the following line:

csharp
var client = GetAuthenticatedHTTPClient(config, userName, userPassword);
3. Update the method called in that line to use the method you
updated, GetAuthenticatedGraphClient:

csharp
var client = GetAuthenticatedGraphClient(config, userName, userPassword);

4. The next few lines used the HttpClient to call the Microsoft Graph REST
endpoint to get a list of all messages. Find these lines, as shown, and remove them:

csharp
var clientResponse =
client.GetAsync("https://graph.microsoft.com/v1.0/me/messages?
$select=id&$top=100").Result;
// enumerate through the list of messages
var httpResponseTask = clientResponse.Content.ReadAsStringAsync();
httpResponseTask.Wait();
var graphMessages =
JsonConvert.DeserializeObject<Messages>(httpResponseTask.Result);

5. Replace those lines with the following code to request the same information
using the Microsoft Graph SDK. The collection returned by the SDK is in a different
format than what the REST API returned:

csharp
var clientResponse = client.Me.Messages
.Request()
.Select(m => new { m.Id })
.Top(100)
.GetAsync()
.Result;

6. Locate the foreach loop that enumerates through all returned messages to


request each message's details. Change the collection to the following code:

csharp
foreach (var graphMessage in clientResponse.CurrentPage)

Update the GetMessageDetail method to return

The last step is to modify the GetMessageDetail method that retrieved the message details
for each message. Recall from the previous section in this unit that you had to write the
code to detect when requests were throttled. In the where case they were throttled, you
added code to retry the request after a specified delay. Fortunately, the Microsoft Graph
SDK has this logic included in it.
1. Locate the GetMessageDetail() method.

2. Update the signature of the method so the first parameter expects an instance of
the GraphServiceClient, not the HttpClient, and remove the last parameter of a
default delay. The method signature should now look like the following:

csharp
private static Microsoft.Graph.Message GetMessageDetail(GraphServiceClient
client, string messageId)

3. Next, remove all code within this method and replace it with this single line:

csharp
// submit request to Microsoft Graph & wait to process response
return client.Me.Messages[messageId].Request().GetAsync().Result;

Task 9: Build and test the updated application

1. Run the following command in a command prompt to compile the console


application: dotnet build

2. Run the following command to run the console application: dotnet run

3. After entering the username and password for the current user, the application
will write multiple log entries to the console, as in the following image.
The application will do the same thing as the HttpClient version of the application.
However, one difference is that the application will not display the status code returned in
the response to the requests or any of the sleeping log messages, because the Microsoft
Graph SDK handles all the retry logic internally.

Review
In this exercise, you used the Azure AD application and .NET console application you
previously created and modified them to demonstrate two strategies to account for
throttling in your application. One strategy used the HttpClient object but required you to
implement the detect, delay, and retry logic yourself when requests were throttled. The
other strategy used the Microsoft Graph SDK's included support for handling this same
scenario.

Congratulations!

You have successfully completed this exercise. Click Next to advance to the next exercise.

PreviousNext: Exercise 6: Querying user data from...


Exercise 6: Querying user data from Microsoft Graph

This exercise leads the user through a series of tasks utilizing Microsoft Graph Explorer.
For this exercise you will use the default sample account and will not sign in.

Note: The trainer should confirm users are not signed on in a browser with a Microsoft 365
account. You may also direct them to open an InPrivate window session.

By the end of this exercise you will be able to:

 Get the signed-in user’s profile.


 Get a list of users in the organization.
 Get the user’s profile photo.
 Get a user object based on the user’s unique identifier.
 Get the user’s manager profile.

Task 1: Go to the Graph Explorer

1. Sign in to MS600-VM to activate the  Ctrl+Alt+Delete  to bring up the logon page.
o Account: Admin
o Password: Pa55w.rd

2. Open the Microsoft Edge browser.

3. Go to this link: https://developer.microsoft.com/en-us/graph/graph-
explorer.

This page allows users to interact with the Microsoft Graph without needing to write any
code. The Microsoft Graph Explorer provides sample data to use for read operations.

Note: Some organizations may not allow users to sign in or consent to specific scopes
required for some operations.

Task 2: Get the signed in user's profile

1. The page loads with a request of /v1.0/me entered in the request URL box.

2. Select Run Query.
The data for the current users’ profile is shown in the Response Preview pane at the
bottom of the page.

Task 3: Get a list of users in the organization

1. In the request URL box amend the request to be: /v1.0/users

2. Select Run Query.

This shows a list of users in the organization in the Response Preview pane at the bottom
of the page.

Task 4: Get the user object based on the user’s unique identifier

1. In the second item shown in the results, select and copy the ID property. This
should be the ID for the “Adele Vance” user.

2. In the request URL box change the URL to request the profile for a specific
user by appending the copied ID to the request URL in the form /users/{id}; for
example: /users/87d349ed-44d7-43e1-9a83-5f2406dee5bd

3. Select Run Query.

The profile information for that user is shown in the Response Preview pane at the bottom
of the page.

Task 5: Get the user's profile photo

1. In the request URL box append /photo

2. The URL should now be something


like: https://graph.microsoft.com/v1.0/users/87d349ed-44d7-43e1-9a83-
5f2406dee5bd/photo

3. Select Run Query. In the Response Preview, metadata about the profile


picture for the user is shown.

4. In the request URL box, append /$value


5. The URL should now be something
like: https://graph.microsoft.com/v1.0/users/87d349ed-44d7-43e1-9a83-
5f2406dee5bd/photo/$value

6. Select Run Query. The image for the default profile picture is shown in


the Response Preview pane at the bottom of the page.

Task 6: Get the user's manager profile

1. In the request URL box, replace /photo/$value with /manager

2. The URL should now be something


like: https://graph.microsoft.com/v1.0/users/87d349ed-44d7-43e1-9a83-
5f2406dee5bd/manager

3. Select Run Query. The profile information for the manager of the specified


user is shown in the Response Preview pane.

4. The URL should now be something


like: https://graph.microsoft.com/v1.0/users/87d349ed-44d7-43e1-9a83-
5f2406dee5bd/photo

5. Select Run Query.

Review
In this exercise, you learned how to:

 Get the signed-in user’s profile.


 Get a list of users in the organization.
 Get the user’s profile photo.
 Get a user object based on the user’s unique identifier.
 Get the user’s manager profile.

Congratulations!

You have successfully completed this Lab 02, to mark the lab as complete click End.
PreviousEnd

You might also like