1
- /*
2
- The MIT License (MIT)
3
-
4
- Copyright (c) 2018 Microsoft Corporation
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in all
14
- copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- SOFTWARE.
23
- */
1
+ // Copyright (c) Microsoft Corporation. All rights reserved.
2
+ // Licensed under the MIT License.
24
3
25
4
using Microsoft . Identity . Client ;
26
5
using Newtonsoft . Json ;
27
6
using System . Collections . Generic ;
28
7
using System . Configuration ;
8
+ using System . Diagnostics ;
29
9
// The following using statements were added for this sample.
30
10
using System . Globalization ;
11
+ using System . IO ;
31
12
using System . Linq ;
32
13
using System . Net . Http ;
33
14
using System . Net . Http . Headers ;
15
+ using System . Reflection ;
34
16
using System . Text ;
35
17
using System . Threading . Tasks ;
36
18
using System . Windows ;
@@ -55,18 +37,25 @@ public partial class MainWindow : Window
55
37
56
38
private static readonly string Authority = string . Format ( CultureInfo . InvariantCulture , AadInstance , Tenant ) ;
57
39
58
- //
59
- // To authenticate to the To Do list service, the client needs to know the service's App ID URI.
60
- // To contact the To Do list service we need it's URL as well.
61
- //
40
+ // To authenticate to the To Do list service, the client needs to know the service's App ID URI and URL
41
+
62
42
private static readonly string TodoListScope = ConfigurationManager . AppSettings [ "todo:TodoListScope" ] ;
63
43
private static readonly string TodoListBaseAddress = ConfigurationManager . AppSettings [ "todo:TodoListBaseAddress" ] ;
64
44
private static readonly string [ ] Scopes = { TodoListScope } ;
45
+ private static string TodoListApiAddress
46
+ {
47
+ get
48
+ {
49
+ string baseAddress = TodoListBaseAddress ;
50
+ return baseAddress . EndsWith ( "/" ) ? TodoListBaseAddress + "api/todolist"
51
+ : TodoListBaseAddress + "/api/todolist" ;
52
+ }
53
+ }
65
54
66
55
private readonly HttpClient _httpClient = new HttpClient ( ) ;
67
56
private readonly IPublicClientApplication _app ;
68
57
69
- // Button strings
58
+ // Button content
70
59
const string SignInString = "Sign In" ;
71
60
const string ClearCacheString = "Clear Cache" ;
72
61
@@ -84,7 +73,7 @@ public MainWindow()
84
73
85
74
private void GetTodoList ( )
86
75
{
87
- GetTodoList ( SignInButton . Content . ToString ( ) != ClearCacheString ) ;
76
+ GetTodoList ( SignInButton . Content . ToString ( ) != ClearCacheString ) . ConfigureAwait ( false ) ;
88
77
}
89
78
90
79
private async Task GetTodoList ( bool isAppStarting )
@@ -95,9 +84,8 @@ private async Task GetTodoList(bool isAppStarting)
95
84
SignInButton . Content = SignInString ;
96
85
return ;
97
86
}
98
- //
87
+
99
88
// Get an access token to call the To Do service.
100
- //
101
89
AuthenticationResult result = null ;
102
90
try
103
91
{
@@ -135,29 +123,42 @@ private async Task GetTodoList(bool isAppStarting)
135
123
return ;
136
124
}
137
125
138
- // Once the token has been returned by ADAL , add it to the http authorization header, before making the call to access the To Do list service.
126
+ // Once the token has been returned by MSAL , add it to the http authorization header, before making the call to access the To Do list service.
139
127
_httpClient . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , result . AccessToken ) ;
140
128
141
129
// Call the To Do list service.
142
- HttpResponseMessage response = await _httpClient . GetAsync ( TodoListBaseAddress + "/api/todolist" ) ;
130
+ HttpResponseMessage response = await _httpClient . GetAsync ( TodoListApiAddress ) ;
143
131
144
132
if ( response . IsSuccessStatusCode )
145
133
{
146
-
147
134
// Read the response and data-bind to the GridView to display To Do items.
148
135
string s = await response . Content . ReadAsStringAsync ( ) ;
149
136
List < TodoItem > toDoArray = JsonConvert . DeserializeObject < List < TodoItem > > ( s ) ;
150
137
151
138
Dispatcher . Invoke ( ( ) =>
152
139
{
153
-
154
140
TodoList . ItemsSource = toDoArray . Select ( t => new { t . Title } ) ;
155
141
} ) ;
156
142
}
157
143
else
158
144
{
159
- string failureDescription = await response . Content . ReadAsStringAsync ( ) ;
160
- MessageBox . Show ( $ "{ response . ReasonPhrase } \n { failureDescription } ", "An error occurred while getting /api/todolist" , MessageBoxButton . OK ) ;
145
+ await DisplayErrorMessage ( response ) ;
146
+ }
147
+ }
148
+
149
+ private static async Task DisplayErrorMessage ( HttpResponseMessage httpResponse )
150
+ {
151
+ string failureDescription = await httpResponse . Content . ReadAsStringAsync ( ) ;
152
+ if ( failureDescription . StartsWith ( "<!DOCTYPE html>" ) )
153
+ {
154
+ string path = Path . GetDirectoryName ( Assembly . GetEntryAssembly ( ) . Location ) ;
155
+ string errorFilePath = Path . Combine ( path , "error.html" ) ;
156
+ File . WriteAllText ( errorFilePath , failureDescription ) ;
157
+ Process . Start ( errorFilePath ) ;
158
+ }
159
+ else
160
+ {
161
+ MessageBox . Show ( $ "{ httpResponse . ReasonPhrase } \n { failureDescription } ", "An error occurred while getting /api/todolist" , MessageBoxButton . OK ) ;
161
162
}
162
163
}
163
164
@@ -176,17 +177,19 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
176
177
return ;
177
178
}
178
179
179
- //
180
180
// Get an access token to call the To Do service.
181
- //
182
181
AuthenticationResult result = null ;
183
182
try
184
183
{
185
184
result = await _app . AcquireTokenSilent ( Scopes , accounts . FirstOrDefault ( ) )
186
185
. ExecuteAsync ( )
187
186
. ConfigureAwait ( false ) ;
188
- SetUserName ( result . Account ) ;
189
- UserName . Content = Properties . Resources . UserNotSignedIn ;
187
+
188
+ Dispatcher . Invoke ( ( ) =>
189
+ {
190
+ SetUserName ( result . Account ) ;
191
+ UserName . Content = Properties . Resources . UserNotSignedIn ;
192
+ } ) ;
190
193
}
191
194
// There is no access token in the cache, so prompt the user to sign-in.
192
195
catch ( MsalUiRequiredException )
@@ -217,7 +220,7 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
217
220
// Call the To Do service.
218
221
//
219
222
220
- // Once the token has been returned by ADAL , add it to the http authorization header, before making the call to access the To Do service.
223
+ // Once the token has been returned by MSAL , add it to the http authorization header, before making the call to access the To Do service.
221
224
_httpClient . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , result . AccessToken ) ;
222
225
223
226
// Forms encode Todo item, to POST to the todo list web api.
@@ -227,7 +230,7 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
227
230
228
231
// Call the To Do list service.
229
232
230
- HttpResponseMessage response = await _httpClient . PostAsync ( TodoListBaseAddress + "/api/todolist" , content ) ;
233
+ HttpResponseMessage response = await _httpClient . PostAsync ( TodoListApiAddress , content ) ;
231
234
232
235
if ( response . IsSuccessStatusCode )
233
236
{
@@ -236,8 +239,7 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
236
239
}
237
240
else
238
241
{
239
- string failureDescription = await response . Content . ReadAsStringAsync ( ) ;
240
- MessageBox . Show ( $ "{ response . ReasonPhrase } \n { failureDescription } ", "An error occurred while posting to /api/todolist" , MessageBoxButton . OK ) ;
242
+ await DisplayErrorMessage ( response ) ;
241
243
}
242
244
}
243
245
@@ -262,18 +264,13 @@ private async void SignIn(object sender = null, RoutedEventArgs args = null)
262
264
return ;
263
265
}
264
266
265
- //
266
267
// Get an access token to call the To Do list service.
267
- //
268
268
try
269
269
{
270
- // Force a sign-in (PromptBehavior.Always), as the ADAL web browser might contain cookies for the current user, and using .Auto
271
- // would re-sign-in the same user
272
- var result = await _app . AcquireTokenInteractive ( Scopes )
273
- . WithAccount ( accounts . FirstOrDefault ( ) )
274
- . WithPrompt ( Prompt . SelectAccount )
270
+ var result = await _app . AcquireTokenSilent ( Scopes , accounts . FirstOrDefault ( ) )
275
271
. ExecuteAsync ( )
276
272
. ConfigureAwait ( false ) ;
273
+
277
274
Dispatcher . Invoke ( ( ) =>
278
275
{
279
276
SignInButton . Content = ClearCacheString ;
@@ -282,25 +279,49 @@ private async void SignIn(object sender = null, RoutedEventArgs args = null)
282
279
}
283
280
) ;
284
281
}
285
- catch ( MsalException ex )
282
+ catch ( MsalUiRequiredException )
286
283
{
287
- if ( ex . ErrorCode == "access_denied" )
288
- {
289
- // The user canceled sign in, take no action.
284
+ try
285
+ {
286
+ // Force a sign-in (Prompt.SelectAccount), as the MSAL web browser might contain cookies for the current user
287
+ // and we don't necessarily want to re-sign-in the same user
288
+ var result = await _app . AcquireTokenInteractive ( Scopes )
289
+ . WithAccount ( accounts . FirstOrDefault ( ) )
290
+ . WithPrompt ( Prompt . SelectAccount )
291
+ . ExecuteAsync ( )
292
+ . ConfigureAwait ( false ) ;
293
+
294
+ Dispatcher . Invoke ( ( ) =>
295
+ {
296
+ SignInButton . Content = ClearCacheString ;
297
+ SetUserName ( result . Account ) ;
298
+ GetTodoList ( ) ;
299
+ }
300
+ ) ;
290
301
}
291
- else
302
+ catch ( MsalException ex )
292
303
{
293
- // An unexpected error occurred.
294
- string message = ex . Message ;
295
- if ( ex . InnerException != null )
304
+ if ( ex . ErrorCode == "access_denied" )
296
305
{
297
- message += "Error Code: " + ex . ErrorCode + "Inner Exception : " + ex . InnerException . Message ;
306
+ // The user canceled sign in, take no action.
298
307
}
308
+ else
309
+ {
310
+ // An unexpected error occurred.
311
+ string message = ex . Message ;
312
+ if ( ex . InnerException != null )
313
+ {
314
+ message += "Error Code: " + ex . ErrorCode + "Inner Exception : " + ex . InnerException . Message ;
315
+ }
299
316
300
317
MessageBox . Show ( message ) ;
301
318
}
302
319
303
- UserName . Content = Properties . Resources . UserNotSignedIn ;
320
+ Dispatcher . Invoke ( ( ) =>
321
+ {
322
+ UserName . Content = Properties . Resources . UserNotSignedIn ;
323
+ } ) ;
324
+ }
304
325
}
305
326
}
306
327
0 commit comments