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.
243
254using Microsoft . Identity . Client ;
265using Newtonsoft . Json ;
276using System . Collections . Generic ;
287using System . Configuration ;
8+ using System . Diagnostics ;
299// The following using statements were added for this sample.
3010using System . Globalization ;
11+ using System . IO ;
3112using System . Linq ;
3213using System . Net . Http ;
3314using System . Net . Http . Headers ;
15+ using System . Reflection ;
3416using System . Text ;
3517using System . Threading . Tasks ;
3618using System . Windows ;
@@ -55,18 +37,25 @@ public partial class MainWindow : Window
5537
5638 private static readonly string Authority = string . Format ( CultureInfo . InvariantCulture , AadInstance , Tenant ) ;
5739
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+
6242 private static readonly string TodoListScope = ConfigurationManager . AppSettings [ "todo:TodoListScope" ] ;
6343 private static readonly string TodoListBaseAddress = ConfigurationManager . AppSettings [ "todo:TodoListBaseAddress" ] ;
6444 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+ }
6554
6655 private readonly HttpClient _httpClient = new HttpClient ( ) ;
6756 private readonly IPublicClientApplication _app ;
6857
69- // Button strings
58+ // Button content
7059 const string SignInString = "Sign In" ;
7160 const string ClearCacheString = "Clear Cache" ;
7261
@@ -84,7 +73,7 @@ public MainWindow()
8473
8574 private void GetTodoList ( )
8675 {
87- GetTodoList ( SignInButton . Content . ToString ( ) != ClearCacheString ) ;
76+ GetTodoList ( SignInButton . Content . ToString ( ) != ClearCacheString ) . ConfigureAwait ( false ) ;
8877 }
8978
9079 private async Task GetTodoList ( bool isAppStarting )
@@ -95,9 +84,8 @@ private async Task GetTodoList(bool isAppStarting)
9584 SignInButton . Content = SignInString ;
9685 return ;
9786 }
98- //
87+
9988 // Get an access token to call the To Do service.
100- //
10189 AuthenticationResult result = null ;
10290 try
10391 {
@@ -135,29 +123,42 @@ private async Task GetTodoList(bool isAppStarting)
135123 return ;
136124 }
137125
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.
139127 _httpClient . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , result . AccessToken ) ;
140128
141129 // Call the To Do list service.
142- HttpResponseMessage response = await _httpClient . GetAsync ( TodoListBaseAddress + "/api/todolist" ) ;
130+ HttpResponseMessage response = await _httpClient . GetAsync ( TodoListApiAddress ) ;
143131
144132 if ( response . IsSuccessStatusCode )
145133 {
146-
147134 // Read the response and data-bind to the GridView to display To Do items.
148135 string s = await response . Content . ReadAsStringAsync ( ) ;
149136 List < TodoItem > toDoArray = JsonConvert . DeserializeObject < List < TodoItem > > ( s ) ;
150137
151138 Dispatcher . Invoke ( ( ) =>
152139 {
153-
154140 TodoList . ItemsSource = toDoArray . Select ( t => new { t . Title } ) ;
155141 } ) ;
156142 }
157143 else
158144 {
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 ) ;
161162 }
162163 }
163164
@@ -176,17 +177,19 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
176177 return ;
177178 }
178179
179- //
180180 // Get an access token to call the To Do service.
181- //
182181 AuthenticationResult result = null ;
183182 try
184183 {
185184 result = await _app . AcquireTokenSilent ( Scopes , accounts . FirstOrDefault ( ) )
186185 . ExecuteAsync ( )
187186 . 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+ } ) ;
190193 }
191194 // There is no access token in the cache, so prompt the user to sign-in.
192195 catch ( MsalUiRequiredException )
@@ -217,7 +220,7 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
217220 // Call the To Do service.
218221 //
219222
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.
221224 _httpClient . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , result . AccessToken ) ;
222225
223226 // Forms encode Todo item, to POST to the todo list web api.
@@ -227,7 +230,7 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
227230
228231 // Call the To Do list service.
229232
230- HttpResponseMessage response = await _httpClient . PostAsync ( TodoListBaseAddress + "/api/todolist" , content ) ;
233+ HttpResponseMessage response = await _httpClient . PostAsync ( TodoListApiAddress , content ) ;
231234
232235 if ( response . IsSuccessStatusCode )
233236 {
@@ -236,8 +239,7 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
236239 }
237240 else
238241 {
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 ) ;
241243 }
242244 }
243245
@@ -262,18 +264,13 @@ private async void SignIn(object sender = null, RoutedEventArgs args = null)
262264 return ;
263265 }
264266
265- //
266267 // Get an access token to call the To Do list service.
267- //
268268 try
269269 {
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 ( ) )
275271 . ExecuteAsync ( )
276272 . ConfigureAwait ( false ) ;
273+
277274 Dispatcher . Invoke ( ( ) =>
278275 {
279276 SignInButton . Content = ClearCacheString ;
@@ -282,25 +279,49 @@ private async void SignIn(object sender = null, RoutedEventArgs args = null)
282279 }
283280 ) ;
284281 }
285- catch ( MsalException ex )
282+ catch ( MsalUiRequiredException )
286283 {
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+ ) ;
290301 }
291- else
302+ catch ( MsalException ex )
292303 {
293- // An unexpected error occurred.
294- string message = ex . Message ;
295- if ( ex . InnerException != null )
304+ if ( ex . ErrorCode == "access_denied" )
296305 {
297- message += "Error Code: " + ex . ErrorCode + "Inner Exception : " + ex . InnerException . Message ;
306+ // The user canceled sign in, take no action.
298307 }
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+ }
299316
300317 MessageBox . Show ( message ) ;
301318 }
302319
303- UserName . Content = Properties . Resources . UserNotSignedIn ;
320+ Dispatcher . Invoke ( ( ) =>
321+ {
322+ UserName . Content = Properties . Resources . UserNotSignedIn ;
323+ } ) ;
324+ }
304325 }
305326 }
306327
0 commit comments