Overview [ Documentation]
Via IHttpClientFactory
Register IHttpClientFactory
in the DI container:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
Inject an IHttpClientFactory
:
public class BasicModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public BasicModel(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/_net/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
Named clients
Use when app requires many distinct uses of HttpClient
, or many HttpClients
have different configurations.
Creating
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, "HttpRequestsSample");
});
Using
Each time CreateClient
is called, a new instance of HttpClient
is created and the configuration action is called:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public NamedClientModel(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync("repos/_net/AspNetCore.Docs/branches");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
Typed clients
Work like named clients without the need to use strings as keys. Provides IntelliSense and compiler help. Use for a single backend endpoint or to encapsulate logic dealing with an endpoint.
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(HttpClient httpClient) // Typed clients accept an HttpClient in their constructor
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, "HttpRequestsSample");
}
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>("repos/_net/AspNetCore.Docs/branches");
}
Instead of configuring in the typed client’s constructor as above, configuration can be supplied during registration:
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
Register the typed client with DI:
builder.Services.AddHttpClient<GitHubService>(); // Registered as a transient service
Injecting and consuming
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public TypedClientModel(GitHubService gitHubService) =>_gitHubService = gitHubService;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}
Outgoing request middleware [ Documentation]
IHttpClientFactory
enables you to build an outgoing request middleware.
In this pattern, handlers are defined for each named client.
Creating a Delegating Handler
Two steps:
- Inherit
DelegatingHandler
- Override
SendAsync
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
return new HttpResponseMessage(HttpStatusCode.BadRequest) =>
Content = new StringContent("The API key header X-API-KEY is required.")
return await base.SendAsync(request, cancellationToken);
}
}
HttpClient
and HttpMessageHandler
Lifetimes
With IHttpClientFactory
, an HttpMessageHandler
is created per named client. The factory:
- Manages handler lifetimes
- Pools handler instances (allowing for it to be reused if not expired)
- Tracks and disposes resources used by
HttpClient
instances
The default handler lifetime is two minutes. To override, for each named client:
builder.Services.AddHttpClient("HandlerLifetime").SetHandlerLifetime(TimeSpan.FromMinutes(5));
Configuring HttpMessageHandler
Use the ConfigurePrimaryHttpMessageHandler extension method:
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
Cookies
The pooled HttpMessageHandler
instances results in CookieContainer
objects being shared.
For apps that require cookies, either:
- Disable automatic cookie handling
- Avoid using
IHttpClientFactory
To disable automatic cookie handling:
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { UseCookies = false });
Logging
Clients created with IHttpClientFactory
log messages for all requests.
Header Propagation Middleware
This middleware propagates HTTP headers from incoming requests to the outgoing HttpClient requests.
To use:
- Install
Microsoft.AspNetCore.HeaderPropagation
package - Configure
HttpClient
and the middleware pipeline:
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddHttpClient("PropagateHeaders").AddHeaderPropagation();
builder.Services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); });
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseHeaderPropagation();
app.MapControllers();
From PluralSight
Via HttpClient
(From Pluralsight/ASP.NET Core 6 Blazor Fundamentals)
Configuring
Program.cs
builder.Services.AddScoped(sp =>
new HttpClient()
{
BaseAddress = new Uri("http://*some-api-endpoint*")
});
Using
SomeComponent.razor.cs
// In a Razor component, you must use the [Inject] attribute instead of the constructor dependency injection approach:
[Inject]
public HttpClient HttpClient { get; set; }
// ...
protected override async Task OnInitializedAsync()
{
Employees = await Httpclient.GetFromJsonAsync<Employee[]>("api/employee");
}
Via IHttpClientFactory
(From Pluralsight/ASP.NET Core 6 Blazor Fundamentals)
Configuring
dotnet add package microsoft.extensions.http
Program.cs
// the AddHttpClient extension method is what brings in support for IHttpClientFactory
builder.Services.AddHttpClient<IEmployeeDataService, EmployeeDataService>(client =>
client.BaseAddress = new Uri("https://localhost:44340/"));
Using
EmployeeDataService.cs
public class EmployeeDataService : IEmployeeDataService
{
private readonly HttpClient _httpClient;
public EmployeeDataService(HttpClient httpClient)
{
_httpClient = httpClient;
}
}