Skip to content

Commit b59b614

Browse files
committed
Update save points
1 parent 48f8129 commit b59b614

File tree

31 files changed

+400
-337
lines changed

31 files changed

+400
-337
lines changed

docs/03-show-order-status.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Then add a `@code` block that makes an asynchronous request for the data we need
8585

8686
```csharp
8787
@code {
88-
List<OrderWithStatus> ordersWithStatus;
88+
IEnumerable<OrderWithStatus> ordersWithStatus;
8989

9090
protected override async Task OnParametersSetAsync()
9191
{
@@ -108,7 +108,7 @@ It's simple to express this using `@if/else` blocks in Razor code. Update the ma
108108
{
109109
<text>Loading...</text>
110110
}
111-
else if (ordersWithStatus.Count == 0)
111+
else if (!ordersWithStatus.Any())
112112
{
113113
<h2>No orders placed</h2>
114114
<a class="btn btn-success" href="">Order some pizza</a>

docs/06-authentication-and-authorization.md

Lines changed: 86 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -210,136 +210,125 @@ The user is logged in and redirected back to the home page.
210210

211211
## Request an access token
212212

213-
Even though you are now logged in, placing an order still fails because the HTTP request to place the order requires a valid access token. To request an access token use the `IAccessTokenProvider` service. If requesting an access token succeeds, add it to the request with a standard Authentication header with scheme Bearer. If the token request fails, use the `NavigationManager` service to redirect the user to the authorization service to request a new token.
213+
Even though you are now logged in, placing an order still fails because the HTTP request to place the order requires a valid access token. To request access tokens and attach them to outbound requests, use the `BaseAddressAuthorizationMessageHandler` with the `HttpClient` that you're using to make the request. This message handler will acquire access tokens using the built-in `IAccessTokenProvider` service and attach them to each request using the standard Authorization header. If an access token cannot be acquired, an `AccessTokenNotAvailableException` is thrown, which can be used to redirect the user to the login page to authorize a new token.
214214

215-
*BlazingPizza.Client/Pages/Checkout.razor*
215+
To add the `BaseAddressAuthorizationMessageHandler` to our `HttpClient` in our app, we'll use the [IHttpClientFactory` helpers from ASP.NET Core](https://docs.microsoft.com/aspnet/core/fundamentals/http-requests) with a strongly typed client.
216216

217-
```razor
218-
@page "/checkout"
219-
@attribute [Authorize]
220-
@inject OrderState OrderState
221-
@inject HttpClient HttpClient
222-
@inject NavigationManager NavigationManager
223-
@inject IAccessTokenProvider TokenProvider
217+
To create the strongly typed client, add a new `OrdersClient` class to the client project. The class should take an `HttpClient` in its constructor, and provide methods getting and placing orders:
224218

225-
<div class="main">
226-
...
227-
</div>
219+
*BlazingPizza.Client/OrdersClient.cs*
228220

229-
@code {
230-
bool isSubmitting;
231-
232-
async Task PlaceOrder()
221+
```csharp
222+
using System;
223+
using System.Collections.Generic;
224+
using System.Linq;
225+
using System.Net.Http;
226+
using System.Net.Http.Json;
227+
using System.Threading.Tasks;
228+
229+
namespace BlazingPizza.Client
230+
{
231+
public class OrdersClient
233232
{
234-
isSubmitting = true;
233+
private readonly HttpClient httpClient;
235234

236-
var tokenResult = await TokenProvider.RequestAccessToken();
237-
if (tokenResult.TryGetToken(out var accessToken))
235+
public OrdersClient(HttpClient httpClient)
238236
{
239-
var request = new HttpRequestMessage(HttpMethod.Post, "orders");
240-
request.Content = JsonContent.Create(OrderState.Order);
241-
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Value);
242-
var response = await HttpClient.SendAsync(request);
243-
var newOrderId = await response.Content.ReadFromJsonAsync<int>();
244-
OrderState.ResetOrder();
245-
NavigationManager.NavigateTo($"myorders/{newOrderId}");
237+
this.httpClient = httpClient;
246238
}
247-
else
239+
240+
public async Task<IEnumerable<OrderWithStatus>> GetOrders() =>
241+
await httpClient.GetFromJsonAsync<IEnumerable<OrderWithStatus>>("orders");
242+
243+
244+
public async Task<OrderWithStatus> GetOrder(int orderId) =>
245+
await httpClient.GetFromJsonAsync<OrderWithStatus>($"orders/{orderId}");
246+
247+
248+
public async Task<int> PlaceOrder(Order order)
248249
{
249-
NavigationManager.NavigateTo(tokenResult.RedirectUrl);
250+
var response = await httpClient.PostAsJsonAsync("orders", order);
251+
response.EnsureSuccessStatusCode();
252+
var orderId = await response.Content.ReadFromJsonAsync<int>();
253+
return orderId;
250254
}
251255
}
252256
}
253257
```
254258

255-
Update the `MyOrders` and `OrderDetails` components to also make authenticated HTTP requests.
259+
Register the `OrdersClient` as a typed client, with the underlying `HttpClient` configured with the correct base address and the `BaseAddressAuthorizationMessageHandler`.
256260

257-
*BlazingPizza.Client/Pages/MyOrders.razor*
261+
```csharp
262+
builder.Services.AddHttpClient<OrdersClient>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
263+
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
264+
```
258265

259-
```razor
260-
@page "/myorders"
261-
@inject HttpClient HttpClient
262-
@inject NavigationManager NavigationManager
263-
@inject IAccessTokenProvider TokenProvider
266+
Update each page where an `HttpClient` is used to manage orders to use the new typed `OrdersClient`. Inject an `OrdersClient` instead of an `HttpClient` and use the new client to make the API call. Wrap each call in a `try-catch` that handles exceptions of type `AccessTokenNotAvailableException` by calling the provided `Redirect()` method.
264267

265-
<div class="main">
266-
...
267-
</div>
268+
*Checkout.razor*
268269

269-
@code {
270-
List<OrderWithStatus> ordersWithStatus;
270+
```csharp
271+
async Task PlaceOrder()
272+
{
273+
isSubmitting = true;
271274

272-
protected override async Task OnParametersSetAsync()
275+
try
273276
{
274-
var tokenResult = await TokenProvider.RequestAccessToken();
275-
if (tokenResult.TryGetToken(out var accessToken))
276-
{
277-
var request = new HttpRequestMessage(HttpMethod.Get, "orders");
278-
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Value);
279-
var response = await HttpClient.SendAsync(request);
280-
ordersWithStatus = await response.Content.ReadFromJsonAsync<List<OrderWithStatus>>();
281-
}
282-
else
283-
{
284-
NavigationManager.NavigateTo(tokenResult.RedirectUrl);
285-
}
277+
var newOrderId = await OrdersClient.PlaceOrder(OrderState.Order);
278+
OrderState.ResetOrder();
279+
NavigationManager.NavigateTo($"myorders/{newOrderId}");
280+
}
281+
catch (AccessTokenNotAvailableException ex)
282+
{
283+
ex.Redirect();
286284
}
287285
}
288-
289286
```
290287

291-
*BlazingPizza.Client/Pages/OrderDetails.razor*
288+
*MyOrders.razor*
292289

293-
```razor
294-
@page "/myorders/{orderId:int}"
295-
@using System.Threading
296-
@inject HttpClient HttpClient
297-
@inject NavigationManager NavigationManager
298-
@inject IAccessTokenProvider TokenProvider
299-
@implements IDisposable
300-
301-
<div class="main">
302-
....
303-
</div>
290+
```csharp
291+
protected override async Task OnParametersSetAsync()
292+
{
293+
try
294+
{
295+
ordersWithStatus = await OrdersClient.GetOrders();
296+
}
297+
catch (AccessTokenNotAvailableException ex)
298+
{
299+
ex.Redirect();
300+
}
301+
}
302+
```
304303

305-
@code {
306-
...
304+
*OrderDetails.razor*
307305

308-
private async void PollForUpdates()
306+
```csharp
307+
private async void PollForUpdates()
308+
{
309+
invalidOrder = false;
310+
pollingCancellationToken = new CancellationTokenSource();
311+
while (!pollingCancellationToken.IsCancellationRequested)
309312
{
310-
var tokenResult = await TokenProvider.RequestAccessToken();
311-
if (tokenResult.TryGetToken(out var accessToken))
313+
try
314+
{
315+
orderWithStatus = await OrdersClient.GetOrder(OrderId);
316+
StateHasChanged();
317+
await Task.Delay(4000);
318+
}
319+
catch (AccessTokenNotAvailableException ex)
312320
{
313-
pollingCancellationToken = new CancellationTokenSource();
314-
while (!pollingCancellationToken.IsCancellationRequested)
315-
{
316-
try
317-
{
318-
invalidOrder = false;
319-
var request = new HttpRequestMessage(HttpMethod.Get, $"orders/{OrderId}");
320-
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Value);
321-
var response = await HttpClient.SendAsync(request);
322-
orderWithStatus = await response.Content.ReadFromJsonAsync<OrderWithStatus>();
323-
}
324-
catch (Exception ex)
325-
{
326-
invalidOrder = true;
327-
pollingCancellationToken.Cancel();
328-
Console.Error.WriteLine(ex);
329-
}
330-
331-
StateHasChanged();
332-
333-
await Task.Delay(4000);
334-
}
321+
pollingCancellationToken.Cancel();
322+
ex.Redirect();
335323
}
336-
else
324+
catch (Exception ex)
337325
{
338-
NavigationManager.NavigateTo(tokenResult.RedirectUrl);
326+
invalidOrder = true;
327+
pollingCancellationToken.Cancel();
328+
Console.Error.WriteLine(ex);
329+
StateHasChanged();
339330
}
340331
}
341-
342-
...
343332
}
344333
```
345334

docs/09-progressive-web-app.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,25 +125,27 @@ You'll then need to define `RequestNotificationSubscriptionAsync`. Add this else
125125
```cs
126126
async Task RequestNotificationSubscriptionAsync()
127127
{
128-
var tokenResult = await TokenProvider.RequestAccessToken();
129-
if (tokenResult.TryGetToken(out var accessToken))
128+
var subscription = await JSRuntime.InvokeAsync<NotificationSubscription>("blazorPushNotifications.requestSubscription");
129+
if (subscription != null)
130130
{
131-
var subscription = await JSRuntime.InvokeAsync<NotificationSubscription>("blazorPushNotifications.requestSubscription");
132-
if (subscription != null)
131+
try
133132
{
134-
var request = new HttpRequestMessage(HttpMethod.Put, "notifications/subscribe");
135-
request.Content = JsonContent.Create(subscription);
136-
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Value);
137-
await HttpClient.SendAsync(request);
133+
await OrdersClient.SubscribeToNotifications(subscription);
134+
}
135+
catch (AccessTokenNotAvailableException ex)
136+
{
137+
ex.Redirect();
138138
}
139-
}
140-
else
141-
{
142-
NavigationManager.NavigateTo(tokenResult.RedirectUrl);
143139
}
144140
}
145141
```
146142

143+
Also add the `SubscribeToNotifications` method to `OrdersClient`.
144+
145+
```csharp
146+
147+
```
148+
147149
You'll also need to inject the `IJSRuntime` service into the `Checkout` component.
148150

149151
```razor

save-points/03-show-order-status/BlazingPizza.Client/Pages/MyOrders.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{
77
<text>Loading...</text>
88
}
9-
else if (ordersWithStatus.Count == 0)
9+
else if (!ordersWithStatus.Any())
1010
{
1111
<h2>No orders placed</h2>
1212
<a class="btn btn-success" href="">Order some pizza</a>
@@ -39,7 +39,7 @@
3939
</div>
4040

4141
@code {
42-
List<OrderWithStatus> ordersWithStatus;
42+
IEnumerable<OrderWithStatus> ordersWithStatus;
4343

4444
protected override async Task OnParametersSetAsync()
4545
{

save-points/04-refactor-state-management/BlazingPizza.Client/Pages/MyOrders.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{
77
<text>Loading...</text>
88
}
9-
else if (ordersWithStatus.Count == 0)
9+
else if (!ordersWithStatus.Any())
1010
{
1111
<h2>No orders placed</h2>
1212
<a class="btn btn-success" href="">Order some pizza</a>
@@ -39,7 +39,7 @@
3939
</div>
4040

4141
@code {
42-
List<OrderWithStatus> ordersWithStatus;
42+
IEnumerable<OrderWithStatus> ordersWithStatus;
4343

4444
protected override async Task OnParametersSetAsync()
4545
{

save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/MyOrders.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{
77
<text>Loading...</text>
88
}
9-
else if (ordersWithStatus.Count == 0)
9+
else if (!ordersWithStatus.Any())
1010
{
1111
<h2>No orders placed</h2>
1212
<a class="btn btn-success" href="">Order some pizza</a>
@@ -39,7 +39,7 @@
3939
</div>
4040

4141
@code {
42-
List<OrderWithStatus> ordersWithStatus;
42+
IEnumerable<OrderWithStatus> ordersWithStatus;
4343

4444
protected override async Task OnParametersSetAsync()
4545
{
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Net.Http.Json;
6+
using System.Threading.Tasks;
7+
8+
namespace BlazingPizza.Client
9+
{
10+
public class OrdersClient
11+
{
12+
private readonly HttpClient httpClient;
13+
14+
public OrdersClient(HttpClient httpClient)
15+
{
16+
this.httpClient = httpClient;
17+
}
18+
19+
public async Task<IEnumerable<OrderWithStatus>> GetOrders() =>
20+
await httpClient.GetFromJsonAsync<IEnumerable<OrderWithStatus>>("orders");
21+
22+
23+
public async Task<OrderWithStatus> GetOrder(int orderId) =>
24+
await httpClient.GetFromJsonAsync<OrderWithStatus>($"orders/{orderId}");
25+
26+
27+
public async Task<int> PlaceOrder(Order order)
28+
{
29+
var response = await httpClient.PostAsJsonAsync("orders", order);
30+
response.EnsureSuccessStatusCode();
31+
var orderId = await response.Content.ReadFromJsonAsync<int>();
32+
return orderId;
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)