Skip to content

Commit bb07e78

Browse files
committed
Re-implemented functionality from original sample
1 parent 7a9bc69 commit bb07e78

5 files changed

Lines changed: 206 additions & 16 deletions

File tree

.vscode/launch.json

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,7 @@
1313
"args": [],
1414
"cwd": "${workspaceFolder}",
1515
"stopAtEntry": false,
16-
"console": "internalConsole"
17-
},
18-
{
19-
"name": "PowerShell: Launch Current File",
20-
"type": "PowerShell",
21-
"request": "launch",
22-
"script": "${file}",
23-
"cwd": "${file}"
16+
"console": "externalTerminal"
2417
}
2518
]
2619
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
using Microsoft.Graph;
4+
using Microsoft.Identity.Client;
5+
using System;
6+
using System.Net.Http;
7+
using System.Net.Http.Headers;
8+
using System.Threading.Tasks;
9+
10+
namespace DeltaQuery.Authentication
11+
{
12+
public class DeviceCodeAuthProvider : IAuthenticationProvider
13+
{
14+
private IPublicClientApplication _msalClient;
15+
private string[] _scopes;
16+
private IAccount _userAccount;
17+
18+
public DeviceCodeAuthProvider(string appId, string[] scopes)
19+
{
20+
_scopes = scopes;
21+
22+
_msalClient = PublicClientApplicationBuilder
23+
.Create(appId)
24+
.WithAuthority(AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount, true)
25+
.Build();
26+
}
27+
28+
public async Task<string> GetAccessToken()
29+
{
30+
// If there is no saved user account, the user must sign-in
31+
if (_userAccount == null)
32+
{
33+
try
34+
{
35+
// Invoke device code flow so user can sign-in with a browser
36+
var result = await _msalClient.AcquireTokenWithDeviceCode(_scopes, callback => {
37+
Console.WriteLine(callback.Message);
38+
return Task.FromResult(0);
39+
}).ExecuteAsync();
40+
41+
_userAccount = result.Account;
42+
return result.AccessToken;
43+
}
44+
catch (Exception exception)
45+
{
46+
Console.WriteLine($"Error getting access token: {exception.Message}");
47+
return null;
48+
}
49+
}
50+
else
51+
{
52+
// If there is an account, call AcquireTokenSilent
53+
// By doing this, MSAL will refresh the token automatically if
54+
// it is expired. Otherwise it returns the cached token.
55+
56+
var result = await _msalClient
57+
.AcquireTokenSilent(_scopes, _userAccount)
58+
.ExecuteAsync();
59+
60+
return result.AccessToken;
61+
}
62+
}
63+
64+
// This is the required method to implement IAuthenticationProvider
65+
// The Graph SDK will call this method each time it makes a Graph
66+
// call.
67+
public async Task AuthenticateRequestAsync(HttpRequestMessage requestMessage)
68+
{
69+
requestMessage.Headers.Authorization =
70+
new AuthenticationHeaderValue("bearer", await GetAccessToken());
71+
}
72+
}
73+
}

DeltaQuery/DeltaQuery.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
12
<Project Sdk="Microsoft.NET.Sdk">
2-
33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>netcoreapp3.1</TargetFramework>
6+
<UserSecretsId>8f7bbee5-dcc4-411f-a100-5a7c1ccef5b6</UserSecretsId>
67
</PropertyGroup>
7-
88
<ItemGroup>
99
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.1" />
1010
<PackageReference Include="Microsoft.Graph" Version="1.21.0" />
1111
<PackageReference Include="Microsoft.Identity.Client" Version="4.8.0" />
1212
</ItemGroup>
13-
14-
</Project>
13+
</Project>

DeltaQuery/Program.cs

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,101 @@
1-
using System;
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
using DeltaQuery.Authentication;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Graph;
6+
using System;
7+
using System.Threading.Tasks;
28

39
namespace DeltaQuery
410
{
511
class Program
612
{
7-
static void Main(string[] args)
13+
// The Microsoft Graph permission scopes used by the app
14+
static string[] _scopes = { "User.Read", "Mail.Read" };
15+
16+
// The number of seconds to wait between delta queries
17+
static int _pollIntervalInSecs = 30;
18+
static GraphServiceClient _graphClient;
19+
20+
static async Task Main(string[] args)
21+
{
22+
var appConfig = LoadAppSettings();
23+
24+
var authProvider = new DeviceCodeAuthProvider(
25+
appConfig["AzureAppId"], _scopes);
26+
27+
_graphClient = new GraphServiceClient(authProvider);
28+
29+
await WatchMailFolders(_pollIntervalInSecs);
30+
}
31+
32+
static async Task WatchMailFolders(int pollInterval)
33+
{
34+
// Get first page of mail folders
35+
IMailFolderDeltaCollectionPage deltaCollection;
36+
deltaCollection = await _graphClient.Me.MailFolders
37+
.Delta()
38+
.Request()
39+
.GetAsync();
40+
41+
// TODO: Keep an in-memory dictionary of folder names and ids, use this to give more info
42+
// Example: "Folder test renamed to test2" or "Folder test2 deleted" or "Folder foo moved to deleted items"
43+
while(true)
44+
{
45+
if (deltaCollection.CurrentPage.Count <= 0)
46+
{
47+
Console.WriteLine("No changes...");
48+
}
49+
else
50+
{
51+
bool morePagesAvailable = false;
52+
do
53+
{
54+
morePagesAvailable = deltaCollection.NextPageRequest != null;
55+
foreach(var mailFolder in deltaCollection.CurrentPage)
56+
{
57+
bool isDeleted = mailFolder.AdditionalData != null ?
58+
mailFolder.AdditionalData.ContainsKey("@removed") :
59+
false;
60+
61+
Console.WriteLine($"Folder {mailFolder.DisplayName} {(isDeleted ? "deleted" : "created/updated")}");
62+
}
63+
64+
if (morePagesAvailable)
65+
{
66+
deltaCollection = await deltaCollection.NextPageRequest.GetAsync();
67+
}
68+
}
69+
while (morePagesAvailable);
70+
}
71+
72+
var deltaLink = deltaCollection.AdditionalData["@odata.deltaLink"];
73+
if (!string.IsNullOrEmpty(deltaLink.ToString()))
74+
{
75+
Console.WriteLine($"Processed current delta. Will check back in {pollInterval} seconds.");
76+
await Task.Delay(pollInterval * 1000);
77+
78+
deltaCollection.InitializeNextPageRequest(_graphClient, deltaLink.ToString());
79+
deltaCollection = await deltaCollection.NextPageRequest.GetAsync();
80+
}
81+
}
82+
}
83+
84+
static IConfigurationRoot LoadAppSettings()
885
{
9-
Console.WriteLine("Hello World!");
86+
// Load the values stored in the secret
87+
// manager
88+
var appConfig = new ConfigurationBuilder()
89+
.AddUserSecrets<Program>()
90+
.Build();
91+
92+
// Check for required settings
93+
if (string.IsNullOrEmpty(appConfig["AzureAppId"]))
94+
{
95+
return null;
96+
}
97+
98+
return appConfig;
1099
}
11100
}
12101
}

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extensions:
1212
---
1313
# Microsoft Graph delta query sample
1414

15-
This console application demonstrates how to make [delta queries](https://docs.microsoft.com/graph/delta-query-overview) to Microsoft Graph, allowing applications to request only changed entities within a target resource. This sample monitors changes of messages in the user's inbox.
15+
This console application demonstrates how to make [delta queries](https://docs.microsoft.com/graph/delta-query-overview) to Microsoft Graph, allowing applications to request only changed entities within a target resource. This sample monitors changes to the mail folders in a user's mailbox.
1616

1717
## How To Run This Sample
1818

@@ -87,8 +87,44 @@ The [RegisterApp.ps1](RegisterApp.ps1) script uses the [Azure AD PowerShell for
8787
8888
### Step 2: Configure the sample
8989
90+
1. Open your command-line interface (CLI) in the directory that contains **DeltaQuery.csproj**.
91+
92+
1. Run the following command.
93+
94+
```Shell
95+
dotnet user-secrets init
96+
```
97+
98+
1. Run the following command to store your application ID (obtained in the previous step) to the secret manager Be sure to replace `YOUR_APP_ID` with your application ID.
99+
100+
```Shell
101+
dotnet user-secrets set AzureAppId YOUR_APP_ID
102+
```
103+
90104
### Step 3: Run the sample
91105
106+
#### Option 1: Using Visual Studio Code
107+
108+
1. Open the root folder of this sample using Visual Studio Code.
109+
110+
1. On the **Debug** menu, choose **Start Debugging**.
111+
112+
#### Option 2: From the command line
113+
114+
1. Open your command-line interface (CLI) in the directory that contains **DeltaQuery.csproj**.
115+
116+
1. Run the following command to build the sample.
117+
118+
```Shell
119+
dotnet build
120+
```
121+
122+
1. Run the following command to run the sample.
123+
124+
```Shell
125+
dotnet run
126+
```
127+
92128
## Contributing
93129
94130
If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md).

0 commit comments

Comments
 (0)