Skip to content

Commit f1f49f2

Browse files
authored
Blazor call web API enhancements (#18240)
1 parent 997f70f commit f1f49f2

9 files changed

+193
-37
lines changed

aspnetcore/blazor/call-web-api.md

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn how to call a web API from a Blazor WebAssembly app using JSO
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 05/07/2020
8+
ms.date: 05/11/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: blazor/call-web-api
1111
---
@@ -170,6 +170,102 @@ In the following code, the Delete `<button>` element calls the `DeleteItem` meth
170170
}
171171
```
172172

173+
## Named HttpClient with IHttpClientFactory
174+
175+
<xref:System.Net.Http.IHttpClientFactory> services and the configuration of a named <xref:System.Net.Http.HttpClient> are supported.
176+
177+
`Program.Main` (*Program.cs*):
178+
179+
```csharp
180+
builder.Services.AddHttpClient("ServerAPI", client =>
181+
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
182+
```
183+
184+
`FetchData` component (*Pages/FetchData.razor*):
185+
186+
```razor
187+
@inject IHttpClientFactory ClientFactory
188+
189+
...
190+
191+
@code {
192+
private WeatherForecast[] forecasts;
193+
194+
protected override async Task OnInitializedAsync()
195+
{
196+
var client = ClientFactory.CreateClient("ServerAPI");
197+
198+
forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
199+
"WeatherForecast");
200+
}
201+
}
202+
```
203+
204+
## Typed HttpClient
205+
206+
Typed <xref:System.Net.Http.HttpClient> uses one or more of the app's <xref:System.Net.Http.HttpClient> instances, default or named, to return data from one or more web API endpoints.
207+
208+
*WeatherForecastClient.cs*:
209+
210+
```csharp
211+
using System.Net.Http;
212+
using System.Net.Http.Json;
213+
using System.Threading.Tasks;
214+
215+
public class WeatherForecastClient
216+
{
217+
private readonly HttpClient client;
218+
219+
public WeatherForecastClient(HttpClient client)
220+
{
221+
this.client = client;
222+
}
223+
224+
public async Task<WeatherForecast[]> GetForecastAsync()
225+
{
226+
var forecasts = new WeatherForecast[0];
227+
228+
try
229+
{
230+
forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
231+
"WeatherForecast");
232+
}
233+
catch
234+
{
235+
...
236+
}
237+
238+
return forecasts;
239+
}
240+
}
241+
```
242+
243+
`Program.Main` (*Program.cs*):
244+
245+
```csharp
246+
builder.Services.AddHttpClient<WeatherForecastClient>(client =>
247+
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
248+
```
249+
250+
Components inject the typed `HttpClient` to call the web API.
251+
252+
`FetchData` component (*Pages/FetchData.razor*):
253+
254+
```razor
255+
@inject WeatherForecastClient Client
256+
257+
...
258+
259+
@code {
260+
private WeatherForecast[] forecasts;
261+
262+
protected override async Task OnInitializedAsync()
263+
{
264+
forecasts = await Client.GetForecastAsync();
265+
}
266+
}
267+
```
268+
173269
## Handle errors
174270

175271
When errors occur while interacting with a web API, they can be handled by developer code. For example, `GetFromJsonAsync` expects a JSON response from the server API with a `Content-Type` of `application/json`. If the response isn't in JSON format, content validation throws a <xref:System.NotSupportedException>.
@@ -210,8 +306,7 @@ To allow other sites to make cross-origin resource sharing (CORS) requests to yo
210306

211307
## Additional resources
212308

213-
* <xref:security/blazor/webassembly/index>
214-
* <xref:security/blazor/webassembly/additional-scenarios>
309+
* <xref:security/blazor/webassembly/additional-scenarios> &ndash; Includes coverage on using `HttpClient` to make secure web API requests.
215310
* <xref:fundamentals/http-requests>
216311
* <xref:security/enforcing-ssl>
217312
* [Kestrel HTTPS endpoint configuration](xref:fundamentals/servers/kestrel#endpoint-configuration)

aspnetcore/security/blazor/webassembly/additional-scenarios.md

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn how to configure Blazor WebAssembly for additional security s
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 05/08/2020
8+
ms.date: 05/11/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: security/blazor/webassembly/additional-scenarios
1111
---
@@ -49,21 +49,23 @@ using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
4949

5050
...
5151

52-
builder.Services.AddHttpClient("BlazorWithIdentityApp1.ServerAPI",
52+
builder.Services.AddHttpClient("BlazorWithIdentity.ServerAPI",
5353
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
5454
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
5555

5656
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>()
57-
.CreateClient("BlazorWithIdentityApp1.ServerAPI"));
57+
.CreateClient("BlazorWithIdentity.ServerAPI"));
5858
```
5959

6060
Where the client is created with `CreateClient` in the preceding example, the <xref:System.Net.Http.HttpClient> is supplied instances that include access tokens when making requests to the server project.
6161

62-
The configured <xref:System.Net.Http.HttpClient> is then used to make authorized requests using a simple `try-catch` pattern. The following `FetchData` component requests weather forecast data:
62+
The configured <xref:System.Net.Http.HttpClient> is then used to make authorized requests using a simple `try-catch` pattern.
63+
64+
`FetchData` component (*Pages/FetchData.razor*):
6365

6466
```csharp
6567
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
66-
@inject HttpClient Http
68+
@inject HttpClient Client
6769

6870
...
6971

@@ -72,7 +74,7 @@ protected override async Task OnInitializedAsync()
7274
try
7375
{
7476
forecasts =
75-
await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
77+
await Client.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
7678
}
7779
catch (AccessTokenNotAvailableException exception)
7880
{
@@ -81,37 +83,36 @@ protected override async Task OnInitializedAsync()
8183
}
8284
```
8385

84-
Alternatively, you can define a typed client that handles all of the HTTP and token acquisition concerns within a single class:
86+
## Typed HttpClient
87+
88+
A typed client can be defined that handles all of the HTTP and token acquisition concerns within a single class.
8589

86-
*WeatherClient.cs*:
90+
*WeatherForecastClient.cs*:
8791

8892
```csharp
89-
using System.Collections.Generic;
9093
using System.Net.Http;
9194
using System.Net.Http.Json;
9295
using System.Threading.Tasks;
9396
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
9497
using static {APP ASSEMBLY}.Data;
9598

96-
public class WeatherClient
99+
public class WeatherForecastClient
97100
{
98-
private readonly HttpClient httpClient;
101+
private readonly HttpClient client;
99102

100-
public WeatherClient(HttpClient httpClient)
103+
public WeatherForecastClient(HttpClient client)
101104
{
102-
this.httpClient = httpClient;
105+
this.client = client;
103106
}
104107

105-
public async Task<IEnumerable<WeatherForecast>> GetWeatherForeacasts()
108+
public async Task<WeatherForecast[]> GetForecastAsync()
106109
{
107-
IEnumerable<WeatherForecast> forecasts = new WeatherForecast[0];
110+
var forecasts = new WeatherForecast[0];
108111

109112
try
110113
{
111-
forecasts = await httpClient.GetFromJsonAsync<WeatherForecast[]>(
114+
forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
112115
"WeatherForecast");
113-
114-
...
115116
}
116117
catch (AccessTokenNotAvailableException exception)
117118
{
@@ -123,37 +124,88 @@ public class WeatherClient
123124
}
124125
```
125126

126-
*Program.cs*:
127+
`Program.Main` (*Program.cs*):
127128

128129
```csharp
129130
using System.Net.Http;
130131
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
131132

132133
...
133134

134-
builder.Services.AddHttpClient<WeatherClient>(
135+
builder.Services.AddHttpClient<WeatherForecastClient>(
135136
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
136137
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
137138
```
138139

139-
*FetchData.razor*:
140+
`FetchData` component (*Pages/FetchData.razor*):
140141

141142
```razor
142-
@inject WeatherClient WeatherClient
143+
@inject WeatherForecastClient Client
143144
144145
...
145146
146147
protected override async Task OnInitializedAsync()
147148
{
148-
forecasts = await WeatherClient.GetWeatherForeacasts();
149+
forecasts = await Client.GetForecastAsync();
149150
}
150151
```
151152

153+
## Configure the HttpClient handler
154+
155+
The handler can be further configured with <xref:Microsoft.AspNetCore.Components.WebAssembly.Authentication.AuthorizationMessageHandler.ConfigureHandler%2A> for outbound HTTP requests.
156+
157+
`Program.Main` (*Program.cs*):
158+
159+
```csharp
160+
builder.Services.AddHttpClient<WeatherForecastClient>(client => client.BaseAddress = new Uri("https://www.example.com/base"))
161+
.AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
162+
.ConfigureHandler(new [] { "https://www.example.com/base" },
163+
scopes: new[] { "example.read", "example.write" }));
164+
```
165+
166+
## Unauthenticated or unauthorized web API requests in an app with a secure default client
167+
168+
If the Blazor WebAssembly app ordinarily uses a secure default <xref:System.Net.Http.HttpClient>, the app can also make unauthenticated or unauthorized web API requests by configuring a named <xref:System.Net.Http.HttpClient>:
169+
170+
`Program.Main` (*Program.cs*):
171+
172+
```csharp
173+
builder.Services.AddHttpClient("ServerAPI.NoAuthenticationClient",
174+
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
175+
```
176+
177+
The preceding registration is in addition to the existing secure default <xref:System.Net.Http.HttpClient> registration.
178+
179+
A component creates the <xref:System.Net.Http.HttpClient> from the <xref:System.Net.Http.IHttpClientFactory> to make unauthenticated or unauthorized requests:
180+
181+
```razor
182+
@inject IHttpClientFactory ClientFactory
183+
184+
...
185+
186+
@code {
187+
private WeatherForecast[] forecasts;
188+
189+
protected override async Task OnInitializedAsync()
190+
{
191+
var client = ClientFactory.CreateClient("ServerAPI.NoAuthenticationClient");
192+
193+
forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
194+
"WeatherForecastNoAuthentication");
195+
}
196+
}
197+
```
198+
199+
> [!NOTE]
200+
> The controller in the server API, `WeatherForecastNoAuthenticationController` for the preceding example, isn't marked with the [`[Authorize]`](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) attribute.
201+
152202
## Request additional access tokens
153203

154204
Access tokens can be manually obtained by calling `IAccessTokenProvider.RequestAccessToken`.
155205

156-
In the following example, additional Azure Active Directory (AAD) Microsoft Graph API scopes are required by an app to read user data and send mail. After adding the Microsoft Graph API permissions in the Azure AAD portal, the additional scopes are configured in the Client app (`Program.Main`, *Program.cs*):
206+
In the following example, additional Azure Active Directory (AAD) Microsoft Graph API scopes are required by an app to read user data and send mail. After adding the Microsoft Graph API permissions in the Azure AAD portal, the additional scopes are configured in the Client app.
207+
208+
`Program.Main` (*Program.cs*):
157209

158210
```csharp
159211
builder.Services.AddMsalAuthentication(options =>
@@ -167,9 +219,11 @@ builder.Services.AddMsalAuthentication(options =>
167219
}
168220
```
169221

170-
The `IAccessTokenProvider.RequestToken` method provides an overload that allows an app to provision an access token with a given set of scopes, as seen in the following example:
222+
The `IAccessTokenProvider.RequestToken` method provides an overload that allows an app to provision an access token with a given set of scopes.
171223

172-
```csharp
224+
In a Razor component:
225+
226+
```razor
173227
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
174228
@inject IAccessTokenProvider TokenProvider
175229

@@ -195,7 +249,7 @@ if (tokenResult.TryGetToken(out var token))
195249

196250
## HttpClient and HttpRequestMessage with Fetch API request options
197251

198-
When running on WebAssembly in a Blazor WebAssembly app, [HttpClient](xref:fundamentals/http-requests) and <xref:System.Net.Http.HttpRequestMessage> can be used to customize requests. For example, you can specify the HTTP method and request headers. The following example makes a `POST` request to a To Do List API endpoint on the server and shows the response body:
252+
When running on WebAssembly in a Blazor WebAssembly app, [HttpClient](xref:fundamentals/http-requests) and <xref:System.Net.Http.HttpRequestMessage> can be used to customize requests. For example, you can specify the HTTP method and request headers. The following component makes a `POST` request to a To Do List API endpoint on the server and shows the response body:
199253

200254
```razor
201255
@page "/todorequest"

aspnetcore/security/blazor/webassembly/hosted-with-azure-active-directory-b2c.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description:
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 04/24/2020
8+
ms.date: 05/11/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: security/blazor/webassembly/hosted-with-azure-active-directory-b2c
1111
---
@@ -355,6 +355,7 @@ Run the app from the Server project. When using Visual Studio, select the Server
355355
## Additional resources
356356
357357
* <xref:security/blazor/webassembly/additional-scenarios>
358+
* [Unauthenticated or unauthorized web API requests in an app with a secure default client](xref:security/blazor/webassembly/additional-scenarios#unauthenticated-or-unauthorized-web-api-requests-in-an-app-with-a-secure-default-client)
358359
* <xref:security/authentication/azure-ad-b2c>
359360
* [Tutorial: Create an Azure Active Directory B2C tenant](/azure/active-directory-b2c/tutorial-create-tenant)
360361
* [Microsoft identity platform documentation](/azure/active-directory/develop/)

aspnetcore/security/blazor/webassembly/hosted-with-azure-active-directory.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description:
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 05/06/2020
8+
ms.date: 05/11/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: security/blazor/webassembly/hosted-with-azure-active-directory
1111
---
@@ -343,6 +343,7 @@ Run the app from the Server project. When using Visual Studio, select the Server
343343
## Additional resources
344344
345345
* <xref:security/blazor/webassembly/additional-scenarios>
346+
* [Unauthenticated or unauthorized web API requests in an app with a secure default client](xref:security/blazor/webassembly/additional-scenarios#unauthenticated-or-unauthorized-web-api-requests-in-an-app-with-a-secure-default-client)
346347
* <xref:security/blazor/webassembly/aad-groups-roles>
347348
* <xref:security/authentication/azure-active-directory/index>
348349
* [Microsoft identity platform documentation](/azure/active-directory/develop/)

aspnetcore/security/blazor/webassembly/hosted-with-identity-server.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: To create a new Blazor hosted app with authentication from within V
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 04/24/2020
8+
ms.date: 05/11/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: security/blazor/webassembly/hosted-with-identity-server
1111
---
@@ -230,3 +230,4 @@ Run the app from the Server project. When using Visual Studio, select the Server
230230
## Additional resources
231231

232232
* <xref:security/blazor/webassembly/additional-scenarios>
233+
* [Unauthenticated or unauthorized web API requests in an app with a secure default client](xref:security/blazor/webassembly/additional-scenarios#unauthenticated-or-unauthorized-web-api-requests-in-an-app-with-a-secure-default-client)

aspnetcore/security/blazor/webassembly/standalone-with-authentication-library.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description:
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 04/24/2020
8+
ms.date: 05/11/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: security/blazor/webassembly/standalone-with-authentication-library
1111
---
@@ -128,3 +128,4 @@ For more information, see the following sections of the *Additional scenarios* a
128128
## Additional resources
129129
130130
* <xref:security/blazor/webassembly/additional-scenarios>
131+
* [Unauthenticated or unauthorized web API requests in an app with a secure default client](xref:security/blazor/webassembly/additional-scenarios#unauthenticated-or-unauthorized-web-api-requests-in-an-app-with-a-secure-default-client)

0 commit comments

Comments
 (0)