Blazor HttpClient
Install Microsoft.Extensions.Http
learn.microsoft.com (these pages discuss WASM only, no Blazor server)
HttpClient in wasm is actually a facade over javascript FETCH api
learn.microsoft.com (these pages discuss WASM only, no Blazor server)
HttpClient in wasm is actually a facade over javascript FETCH api
Set up DI in Program.cs
This is setting up a named HttpClient (name and the API uri from settings).
var appSettings = builder.Configuration.GetSection("settings").Get<AppSettings>();
//install Microsoft.Extensions.Http
//this adds the IHttpClientFactory service
builder.Services.AddHttpClient(appSettings.ApiName, c =>
{
//or builder.HostEnvironment.BaseAddress if self-hosted
c.BaseAddress = new Uri(appSettings.Api);
});
//this add an HttpClient
builder.Services.AddScoped(sp => sp.GetService<IHttpClientFactory>().CreateClient(appSettings.ApiName));
//install Microsoft.Extensions.Http
//this adds the IHttpClientFactory service
builder.Services.AddHttpClient(appSettings.ApiName, c =>
{
//or builder.HostEnvironment.BaseAddress if self-hosted
c.BaseAddress = new Uri(appSettings.Api);
});
//this add an HttpClient
builder.Services.AddScoped(sp => sp.GetService<IHttpClientFactory>().CreateClient(appSettings.ApiName));
In .net8 you can set up keyed services (here getting the named HttpClient)
builder.Services.AddKeyedScoped(appSettings.ApiName,
(sp, _) => sp.GetService<IHttpClientFactory>().CreateClient(appSettings.ApiName));
(sp, _) => sp.GetService<IHttpClientFactory>().CreateClient(appSettings.ApiName));
Inject into component
Either as an HttpClient
@page "/useredit/{Id:int}"
@using BlazorApp.Shared
@inject HttpClient Http
<p>Editing user @Id</p>
@if (_user == null)
{
<p>User not found</p>
}
else
{
<p>@(_user.Name)</p>
}
@code {
[Parameter]
public int Id { get; set; }
private User? _user;
protected override async Task OnInitializedAsync()
{
_user = await Http.GetFromJsonAsync<User>($"User/{Id}");
}
}
@using BlazorApp.Shared
@inject HttpClient Http
<p>Editing user @Id</p>
@if (_user == null)
{
<p>User not found</p>
}
else
{
<p>@(_user.Name)</p>
}
@code {
[Parameter]
public int Id { get; set; }
private User? _user;
protected override async Task OnInitializedAsync()
{
_user = await Http.GetFromJsonAsync<User>($"User/{Id}");
}
}
Or as an HttpClientFactory.
@page "/useredit/{Id:int}"
@using BlazorApp.Shared
@inject IHttpClientFactory ClientFactory
@inject IConfiguration Configuration
<p>Editing user @Id</p>
@if (_user == null)
{
<p>User not found</p>
}
else
{
<p>@(_user.Name)</p>
}
@code {
[Parameter]
public int Id { get; set; }
private User? _user;
protected override async Task OnInitializedAsync()
{
var appSettings = Configuration.GetSection("settings").Get<AppSettings>();
var client = ClientFactory.CreateClient(appSettings.ApiName);
_user = await client.GetFromJsonAsync<User>($"User/{Id}");
}
}
@using BlazorApp.Shared
@inject IHttpClientFactory ClientFactory
@inject IConfiguration Configuration
<p>Editing user @Id</p>
@if (_user == null)
{
<p>User not found</p>
}
else
{
<p>@(_user.Name)</p>
}
@code {
[Parameter]
public int Id { get; set; }
private User? _user;
protected override async Task OnInitializedAsync()
{
var appSettings = Configuration.GetSection("settings").Get<AppSettings>();
var client = ClientFactory.CreateClient(appSettings.ApiName);
_user = await client.GetFromJsonAsync<User>($"User/{Id}");
}
}
Or use property injection (you have to do this for keyed services in .net8)
[Inject(Key = "key")]
public HttpClient Http { get; set; } = default!;
public HttpClient Http { get; set; } = default!;
Adding authentication
Install Microsoft.AspNetCore.Components.WebAssembly.Authentication to use PKCE authorization code flow (docs).
There are quite a few steps...
- Add appsettings.json
services.AddMsalAuthentication
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("IDP", options.ProviderOptions.Authentication);
options.ProviderOptions.LoginMode = "redirect";
options.ProviderOptions.Cache.CacheLocation = "localStorage";
options.ProviderOptions.Authentication.ValidateAuthority = true;
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
});- AuthenticationService.js into index.html
- in app.razor <CascadingAuthenticationState> and AuthorizeRouteView
- Shared/RedirectToLogin.razor
But when that is setup, hooking up your httpclient to always provide the token is simple.
In Program.cs add a MessageHandlerService to the HttpClient
builder.Services.AddHttpClient(appSettings.ApiName, c =>
{
//or builder.HostEnvironment.BaseAddress if self-hosted
c.BaseAddress = new Uri(appSettings.Api);
}).AddHttpMessageHandler<BearerAuthorizationMessageHandler>();
{
//or builder.HostEnvironment.BaseAddress if self-hosted
c.BaseAddress = new Uri(appSettings.Api);
}).AddHttpMessageHandler<BearerAuthorizationMessageHandler>();
And implement an AuthorizationMessageHandler which hooks up the api Urls. Note the library will supply the IAccessTokenProvider
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
namespace BlazorApp.Client;
/// <summary>
/// A <see cref="DelegatingHandler"/> that attaches access tokens to outgoing <see cref="HttpResponseMessage"/> instances.
/// Access tokens will only be added when the request URI is within one of the base addresses configured using
/// <see cref="ConfigureHandler(IEnumerable{string}, IEnumerable{string}, string)"/>.
/// </summary>
public class BearerAuthorizationMessageHandler : AuthorizationMessageHandler
{
public BearerAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation, IConfiguration config)
: base(provider, navigation)
{
var api = config.GetSection("settings").GetValue<string>("api");
ConfigureHandler(
authorizedUrls: new[] { api }
);
}
}
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
namespace BlazorApp.Client;
/// <summary>
/// A <see cref="DelegatingHandler"/> that attaches access tokens to outgoing <see cref="HttpResponseMessage"/> instances.
/// Access tokens will only be added when the request URI is within one of the base addresses configured using
/// <see cref="ConfigureHandler(IEnumerable{string}, IEnumerable{string}, string)"/>.
/// </summary>
public class BearerAuthorizationMessageHandler : AuthorizationMessageHandler
{
public BearerAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation, IConfiguration config)
: base(provider, navigation)
{
var api = config.GetSection("settings").GetValue<string>("api");
ConfigureHandler(
authorizedUrls: new[] { api }
);
}
}