Thực hiện các yêu cầu HTTP bằng IHttpClientFactory trong ASP.NET Core
Tổng quan
Interface (giao diện) IHttpClientFactory cung cấp một vị trí trung tâm để cấu hình và tạo các instance (thực thể) của HttpClient trong ứng dụng ASP.NET Core. Nó mang lại một số lợi ích chính:
- Cấu hình tập trung: Đặt tên và cấu hình các instance
HttpClientlogic ở một nơi duy nhất - Middleware đầu ra: Hỗ trợ các delegating handler (trình xử lý ủy quyền) để xây dựng pipeline middleware cho request
- Quản lý tài nguyên: Tự động pooling (gộp nhóm) và quản lý lifetime (vòng đời) của các instance
HttpMessageHandler - Ngăn chặn sự cố DNS: Tránh các vấn đề DNS cũ thông qua việc tự động luân chuyển handler
- Logging có thể cấu hình: Logging tích hợp sẵn thông qua
ILoggercho tất cả các request
Đăng ký
Đăng ký IHttpClientFactory trong file Program.cs:
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddHttpClient();
Các mẫu sử dụng (Consumption Patterns)
Sử dụng cơ bản
Inject (tiêm phụ thuộc) IHttpClientFactory và tạo client:
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/dotnet/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 (Client được đặt tên)
Cấu hình named client trong quá trình đăng ký:
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// GitHub API yêu cầu hai headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});Tạo một named client:
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/dotnet/AspNetCore.Docs/branches");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}Typed Clients (Client có kiểu)
Tạo một lớp service nhận HttpClient:
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// GitHub API yêu cầu hai 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/dotnet/AspNetCore.Docs/branches");
}Đăng ký typed client:
builder.Services.AddHttpClient<GitHubService>();
Inject và sử dụng:
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)
{
// ...
}
}
}Generated Clients (Client được tạo tự động) với Refit
Định nghĩa một interface:
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}Đăng ký với Refit:
builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// GitHub API yêu cầu hai headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});Thực hiện các yêu cầu HTTP
POST Request (Yêu cầu POST)
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json); // using static System.Net.Mime.MediaTypeNames;
using var httpResponseMessage =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}PUT Request (Yêu cầu PUT)
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json);
using var httpResponseMessage =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}DELETE Request (Yêu cầu DELETE)
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.EnsureSuccessStatusCode();
}Outgoing Request Middleware (Middleware yêu cầu đầu ra)
Tạo một delegating handler (trình xử lý ủy quyền):
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);
}
}Đăng ký và thêm handler:
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();Đăng ký nhiều handler:
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();Sử dụng Polly để tăng khả năng phục hồi (Resilience)
Xử lý lỗi tạm thời (Transient Faults)
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));Chọn policy động
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);Nhiều Polly Handlers
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));Polly Registry (Sổ đăng ký Polly)
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var policyRegistry = builder.Services.AddPolicyRegistry();
policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);
builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");
builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");HttpClient và quản lý Lifetime
Một instance HttpClient mới được trả về mỗi lần CreateClient được gọi. IHttpClientFactory pool (gộp nhóm) các instance HttpMessageHandler để giảm tiêu thụ tài nguyên.
Lifetime mặc định của handler: 2 phút
Ghi đè theo từng client:
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));Các instance HttpClient không cần phải được dispose (giải phóng). IHttpClientFactory tự động theo dõi và giải phóng tài nguyên.
Cấu hình HttpMessageHandler
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});Làm việc với Cookies
Tắt xử lý cookie tự động để tránh chia sẻ các đối tượng CookieContainer:
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});Sử dụng IHttpClientFactory trong Console Apps
Thêm các package references (tham chiếu gói):
Microsoft.Extensions.HostingMicrosoft.Extensions.Http
Ví dụ:
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var host = new HostBuilder()
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<GitHubService>();
})
.Build();
try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await gitHubService.GetAspNetCoreDocsBranchesAsync();
Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");
if (gitHubBranches is not null)
{
foreach (var gitHubBranch in gitHubBranches)
{
Console.WriteLine($"- {gitHubBranch.Name}");
}
}
}
catch (Exception ex)
{
host.Services.GetRequiredService<ILogger<Program>>()
.LogError(ex, "Unable to load branches from GitHub.");
}
public class GitHubService
{
private readonly IHttpClientFactory _httpClientFactory;
public GitHubService(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ "Accept", "application/vnd.github.v3+json" },
{ "User-Agent", "HttpRequestsConsoleSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.EnsureSuccessStatusCode();
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
public record GitHubBranch(
[property: JsonPropertyName("name")] string Name);Header Propagation Middleware (Middleware truyền header)
Truyền các HTTP header từ request đến sang các request HttpClient đầu ra.
Cài đặt: Microsoft.AspNetCore.HeaderPropagation
Cấu hình trong Program.cs:
// 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();Logging (Ghi log)
Các client được tạo thông qua IHttpClientFactory ghi lại các log message cho tất cả các request. Các log category (danh mục log) bao gồm tên client:
System.Net.Http.HttpClient.{ClientName}.LogicalHandler- bên ngoài request pipelineSystem.Net.Http.HttpClient.{ClientName}.ClientHandler- bên trong request pipeline
Bật mức độ log phù hợp trong cấu hình để xem các message. Mức Trace (theo dõi) bao gồm logging header.