Exercise 1: Using Query Parameters When Querying Microsoft Graph Via HTTP
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.
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.
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.
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.
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.
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.
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.
2. Select Run Query.
This shows a list of messages for the current Response Preview pane at the bottom of
the page.
5. Select Run Query.
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.
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.
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.
7. Note the value for the content-length header. Amend the query to
use $select on the $expanded attachments collection.
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.
2. Select Run Query.
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
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:
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
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.
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
4. If Visual Studio Code displays a dialog box asking if you want to add required
assets to the project, select Yes.
json
{
"tenantId": "YOUR_TENANT_ID_HERE",
"applicationId": "YOUR_APP_ID_HERE",
"applicationSecret": "YOUR_APP_SECRET_HERE",
"redirectUri": "YOUR_REDIRECT_URI_HERE"
}
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);
}
}
}
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;
}
}
}
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;
csharp
private static GraphServiceClient _graphClient;
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;
}
}
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());
}
csharp
private static GraphServiceClient
GetAuthenticatedGraphClient(IConfigurationRoot config)
{
var authenticationProvider = CreateAuthorizationProvider(config);
_graphClient = new GraphServiceClient(authenticationProvider);
return _graphClient;
}
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);
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.
csharp
var results = graphRequest
.Select(u => new { u.DisplayName, u.Mail })
.GetAsync()
.Result;
csharp
dotnet build
dotnet run
csharp
var results = graphRequest
.Select(u => new { u.DisplayName, u.Mail })
.Top(15)
.GetAsync()
.Result;
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;
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.
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.
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
6. Select Register.
Important: This client secret is never shown again, so make sure you copy it now.
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)
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.
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
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
6. If Visual Studio code displays a dialog box asking if you want to add required
assets to the project, select Yes.
csharp
//app.UseHttpsRedirection();
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
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; }
}
}
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; }
}
}
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; }
}
}
csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3
_0);
var config = new MyConfig();
Configuration.Bind("MyConfig", config);
services.AddSingleton(config);
}
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.
The application requires a new controller to process the subscription and notification.
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.
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:
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.
7. Trigger a notification:
c. Select Users > Active users.
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.
Update Code
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}");
}
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}";
}
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.
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.
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;
}
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. 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.
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.
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
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
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.
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.
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"
]
}
]
}
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.
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.
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
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.
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
4. If Visual Studio Code displays a dialog box asking if you want to add required
assets to the project, select Yes.
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"
}
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);
}
}
}
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;
}
}
}
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;
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;
}
}
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;
}
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;
}
csharp
private static string ReadUsername()
{
string username;
Console.WriteLine("Enter your username");
username = Console.ReadLine();
return username;
}
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);
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);
}
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.
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;
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);
}
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);
}
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.
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.
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;
}
The next step is to update the Main method to use the new method so the application will
use an intelligent throttling strategy.
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 { }
csharp
stopwatch.Stop();
Console.WriteLine();
Console.WriteLine("Elapsed time: {0} seconds",
stopwatch.Elapsed.Seconds);
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.
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.
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:
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;
csharp
private static GraphServiceClient
GetAuthenticatedGraphClient(IConfigurationRoot config, string userName,
SecureString userPassword)
{
var authenticationProvider = CreateAuthorizationProvider(config,
userName, userPassword);
var graphClient = new GraphServiceClient(authenticationProvider);
return graphClient;
}
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.
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;
csharp
foreach (var graphMessage in clientResponse.CurrentPage)
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;
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.
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.
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
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.
2. Select Run Query.
The data for the current users’ profile is shown in the Response Preview pane at the
bottom of the page.
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.
5. Select Run Query.
Review
In this exercise, you learned how to:
Congratulations!
You have successfully completed this Lab 02, to mark the lab as complete click End.
PreviousEnd