Membuat permintaan HTTP menggunakan IHttpClientFactory di ASP.NET Core
Catatan
Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Peringatan
Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Penting
Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.
Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Oleh Kirk Larkin, Steve Gordon, Glenn Condron, dan Ryan Nowak.
IHttpClientFactory Dapat didaftarkan dan digunakan untuk mengonfigurasi dan membuat HttpClient instans di aplikasi. IHttpClientFactory
menawarkan manfaat berikut:
- Menyediakan lokasi pusat untuk penamaan dan konfigurasi instans
HttpClient
logis. Misalnya, klien bernama github dapat didaftarkan dan dikonfigurasi untuk mengakses GitHub. Klien default dapat didaftarkan untuk akses umum. - Mengkodifikasi konsep middleware keluar via pendelegasian handler di
HttpClient
. Menyediakan ekstensi untuk middleware berbasis Polly untuk memanfaatkan pendelegasian handler diHttpClient
. - Mengelola pengumpulan dan masa pakai instans
HttpClientMessageHandler
yang mendasarinya. Manajemen otomatis menghindari masalah DNS umum (Sistem Nama Domain) yang terjadi saat mengelolaHttpClient
masa pakai secara manual. - Menambahkan pengalaman pengelogan yang bisa dikonfigurasi (melalui
ILogger
) untuk semua permintaan yang dikirim melalui klien yang dibuat oleh pabrik.
Kode sampel dalam versi topik ini menggunakan System.Text.Json untuk mendeserialisasi konten JSON yang dikembalikan dalam respons HTTP. Untuk sampel yang menggunakan Json.NET
dan ReadAsAsync<T>
, gunakan pemilih versi untuk memilih versi 2.x dari topik ini.
Pola konsumsi
Ada beberapa cara menggunakan IHttpClientFactory
dalam aplikasi:
Pendekatan terbaiknya tergantung pada persyaratan aplikasi.
Penggunaan dasar
Daftar IHttpClientFactory
dengan memanggil AddHttpClient
di Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
Dapat IHttpClientFactory
diminta menggunakan injeksi dependensi (DI). Kode berikut ini menggunakan IHttpClientFactory
untuk membuat instansHttpClient
:
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);
}
}
}
Menggunakan IHttpClientFactory
seperti pada contoh sebelumnya adalah cara yang baik untuk merefaktor aplikasi yang ada. Ini tidak berdampak pada bagaimana HttpClient
digunakan. Di tempat-tempat di mana instans HttpClient
dibuat di aplikasi yang ada, ganti kemunculan tersebut dengan panggilan ke CreateClient.
Klien bernama
Klien bernama adalah pilihan yang baik saat:
- Aplikasi membutuhkan banyak kegunaan
HttpClient
yang berbeda. - Banyak
HttpClient
yang memiliki konfigurasi yang berbeda.
Tentukan konfigurasi untuk bernama HttpClient
selama pendaftarannya di Program.cs
:
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");
});
Dalam kode sebelumnya, klien dikonfigurasi dengan:
- Alamat dasar
https://api.github.com/
. - Dua header yang diperlukan untuk bekerja dengan API GitHub.
CreateClient
Setiap kali CreateClient disebut:
- Instans
HttpClient
baru dibuat. - Tindakan konfigurasi dipanggil.
Untuk membuat klien bernama, berikan namanya ke dalam CreateClient
:
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);
}
}
}
Dalam kode sebelumnya, permintaan tidak perlu menentukan nama host. Kode hanya bisa melewati jalur karena alamat dasar yang dikonfigurasi untuk klien digunakan.
Klien yang berjenis
Klien yang berjenis:
- Memberikan kemampuan yang sama seperti klien bernama tanpa perlu menggunakan string sebagai kunci.
- Menyediakan bantuan IntelliSense dan pengkompilasi saat mengkonsumsi klien.
- Menyediakan satu lokasi untuk mengonfigurasi dan berinteraksi dengan
HttpClient
tertentu. Misalnya, klien bertitik tunggal dapat digunakan:- Untuk satu titik akhir backend.
- Untuk merangkum semua logika yang berhubungan dengan titik akhir.
- Bekerja dengan DI dan bisa disuntikkan jika diperlukan di aplikasi.
Klien yang berjenis menerima parameter HttpClient
dalam konstruktornya:
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;
// 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/dotnet/AspNetCore.Docs/branches");
}
Dalam kode sebelumnya:
- Konfigurasi dipindahkan ke klien yang ditik.
- Instans yang disediakan
HttpClient
disimpan sebagai bidang privat.
Metode khusus API bisa dibuat yang mengekspos fungsionalitas HttpClient
. Misalnya, metode merangkum GetAspNetCoreDocsBranches
kode untuk mengambil dokumen cabang GitHub.
Kode berikut memanggil AddHttpClient Program.cs
untuk mendaftarkan kelas klien yang ditik GitHubService
:
builder.Services.AddHttpClient<GitHubService>();
Klien berjenis terdaftar sebagai sementara dengan DI. Dalam kode sebelumnya, AddHttpClient
mendaftarkan GitHubService
sebagai layanan sementara. Pendaftaran ini menggunakan metode pabrik untuk:
- Buat instans
HttpClient
. - Membuat instans
GitHubService
, meneruskan instansHttpClient
ke konstruktornya.
Klien yang ditik dapat disuntikkan dan dikonsumsi secara langsung:
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)
{
// ...
}
}
}
Konfigurasi untuk klien yang ditik juga dapat ditentukan selama pendaftarannya di Program.cs
, bukan di konstruktor klien yang ditik:
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
Klien yang dihasilkan
IHttpClientFactory
bisa digunakan dalam kombinasi dengan pustaka pihak ketiga seperti Refit. Refit adalah REST pustaka untuk .NET. Ini mengonversi REST API menjadi antarmuka langsung. Panggilan AddRefitClient
untuk menghasilkan implementasi dinamis antarmuka, yang menggunakan HttpClient
untuk melakukan panggilan HTTP eksternal.
Antarmuka kustom mewakili API eksternal:
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
Panggil AddRefitClient
untuk menghasilkan implementasi dinamis lalu panggil ConfigureHttpClient
untuk mengonfigurasi yang mendasar HttpClient
:
builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(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");
});
Gunakan DI untuk mengakses implementasi dinamis dari IGitHubClient
:
public class RefitModel : PageModel
{
private readonly IGitHubClient _gitHubClient;
public RefitModel(IGitHubClient gitHubClient) =>
_gitHubClient = gitHubClient;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}
Membuat permintaan POST, PUT, dan DELETE
Dalam contoh sebelumnya, semua permintaan HTTP menggunakan kata kerja HTTP GET. HttpClient
juga mendukung kata kerja HTTP lainnya, termasuk:
- POST
- TARUH
- DELETE
- PATCH
Untuk daftar lengkap kata kerja HTTP yang didukung, lihat HttpMethod.
Contoh berikut menunjukkan cara membuat permintaan HTTP 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();
}
Dalam kode sebelumnya, metode CreateItemAsync
:
- Menserialisasikan parameter
TodoItem
ke JSON menggunakanSystem.Text.Json
. - Membuat instans StringContent untuk mengemas JSON berseri untuk dikirim di dalam isi permintaan HTTP.
- Memanggil PostAsync untuk mengirim konten JSON ke URL yang ditentukan. Ini adalah URL relatif yang ditambahkan ke HttpClient.BaseAddress.
- EnsureSuccessStatusCode Panggilan untuk melemparkan pengecualian jika kode status respons tidak menunjukkan keberhasilan.
HttpClient
juga mendukung jenis konten lainnya. Misalnya, MultipartContent dan StreamContent. Untuk daftar lengkap konten yang didukung, lihat HttpContent.
Contoh berikut menunjukkan permintaan HTTP 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();
}
Kode sebelumnya mirip dengan contoh POST. Metode SaveItemAsync
memanggil PutAsync alih-alih PostAsync
.
Contoh berikut menunjukkan permintaan HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.EnsureSuccessStatusCode();
}
Dalam kode sebelumnya, metode DeleteItemAsync
memanggil DeleteAsync. Karena permintaan HTTP DELETE biasanya tidak memiliki isi, metode DeleteAsync
ini tidak menyediakan kelebihan beban yang menerima instans HttpContent
.
Untuk mempelajari selengkapnya mengenai penggunaan kata kerja HTTP yang berbeda dengan HttpClient
, lihat HttpClient.
Middleware permintaan keluar
HttpClient
memiliki konsep mendelegasikan handler yang dapat ditautkan bersama untuk permintaan HTTP keluar. IHttpClientFactory
:
- Menyederhanakan penentuan handler untuk melamar setiap klien bernama.
- Mendukung pendaftaran dan penautan beberapa handler untuk membangun alur middleware permintaan keluar. Masing-masing handler ini dapat melakukan pekerjaan sebelum dan sesudah permintaan keluar. Pola ini:
- Mirip dengan alur middleware masuk di ASP.NET Core.
- Menyediakan mekanisme untuk mengelola masalah lintas pemotongan seputar permintaan HTTP, seperti:
- Caching
- penanganan kesalahan
- serialisasi
- pencatatan
Untuk membuat penangan pendelegasian:
- Berasal dari DelegatingHandler.
- Ambil alih SendAsync. Jalankan kode sebelum meneruskan permintaan ke handler berikutnya dalam alur:
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);
}
}
Kode sebelumnya memeriksa apakah X-API-KEY
header ada dalam permintaan. Jika X-API-KEY
hilang, BadRequest dikembalikan.
Lebih dari satu handler dapat ditambahkan ke konfigurasi untuk HttpClient
dengan Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
Dalam kode sebelumnya, ValidateHeaderHandler
terdaftar di DI. Setelah terdaftar, AddHttpMessageHandler dapat dipanggil, meneruskan jenis untuk handler.
Beberapa handler dapat didaftarkan dalam urutan yang harus mereka jalankan. Setiap handler membungkus handler berikutnya hingga final HttpClientHandler
menjalankan permintaan:
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();
Dalam kode sebelumnya, SampleHandler1
berjalan terlebih dahulu, sebelum SampleHandler2
.
Menggunakan DI dalam middleware permintaan keluar
Saat IHttpClientFactory
membuat handler pendelegasian baru, ia menggunakan DI untuk memenuhi parameter konstruktor handler. IHttpClientFactory
membuat cakupan DI terpisah untuk setiap handler, yang dapat menyebabkan perilaku mengejutkan ketika handler mengonsumsi layanan tercakup.
Misalnya, pertimbangkan antarmuka berikut dan implementasinya, yang mewakili tugas sebagai operasi dengan pengidentifikasi, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Seperti namanya, IOperationScoped
terdaftar di DI menggunakan masa pakai tercakup:
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
Penangan pendelegasian IOperationScoped
berikut menggunakan dan menggunakan untuk mengatur X-OPERATION-ID
header untuk permintaan keluar:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationScoped;
public OperationHandler(IOperationScoped operationScoped) =>
_operationScoped = operationScoped;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
Dalam unduhanHttpRequestsSample
, navigasikan ke /Operation
dan refresh halaman. Nilai cakupan permintaan berubah untuk setiap permintaan, tetapi nilai cakupan handler hanya berubah setiap 5 detik.
Handler dapat bergantung pada layanan dari cakupan apa pun. Layanan yang bergantung pada handler dibuang ketika handler dibuang.
Gunakan salah satu pendekatan berikut untuk berbagi status per permintaan dengan penangan pesan:
- Teruskan data ke handler menggunakan HttpRequestMessage.Options.
- Gunakan IHttpContextAccessor untuk mengakses permintaan saat ini.
- Buat objek penyimpanan kustom AsyncLocal<T> untuk meneruskan data.
Menggunakan handler berbasis Polly
IHttpClientFactory
terintegrasi dengan pustaka pihak ketiga Polly. Polly adalah pustaka penanganan kesalahan ketahanan dan sementara yang komprehensif untuk .NET. Hal ini memungkinkan pengembang untuk mengekspresikan kebijakan seperti Retry, Circuit Breaker, Timeout, Bulkhead Isolation, dan Fallback dengan cara yang lancar dan aman di utas.
Metode ekstensi disediakan untuk memungkinkan penggunaan kebijakan Polly dengan instans yang dikonfigurasi HttpClient
. Ekstensi Polly mendukung penambahan handler berbasis Polly ke klien. Polly memerlukan paket NuGet Microsoft.Extensions.Http.Polly .
Menangani kesalahan sementara
Kesalahan biasanya terjadi ketika panggilan HTTP eksternal bersifat sementara. AddTransientHttpErrorPolicy memungkinkan kebijakan didefinisikan untuk menangani kesalahan sementara. Kebijakan yang dikonfigurasi dengan AddTransientHttpErrorPolicy
menangani respons berikut:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
menyediakan akses ke objek yang dikonfigurasi PolicyBuilder
untuk menangani kesalahan yang mewakili kemungkinan kesalahan sementara:
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));
Dalam kode sebelumnya, WaitAndRetryAsync
kebijakan ditentukan. Permintaan yang gagal dicoba ulang hingga tiga kali dengan penundaan 600 md antar upaya.
Memilih kebijakan secara dinamis
Metode ekstensi disediakan untuk menambahkan handler berbasis Polly, misalnya, AddPolicyHandler. Kelebihan beban berikut AddPolicyHandler
memeriksa permintaan untuk memutuskan kebijakan mana yang akan diterapkan:
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);
Dalam kode sebelumnya, jika permintaan keluar adalah HTTP GET, batas waktu 10 detik diterapkan. Untuk metode HTTP lainnya, batas waktu 30 detik digunakan.
Menambahkan beberapa handler Polly
Adalah umum untuk menumpuk kebijakan Polly:
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Dalam contoh sebelumnya:
- Dua handler ditambahkan.
- Handler pertama menggunakan AddTransientHttpErrorPolicy untuk menambahkan kebijakan coba lagi. Permintaan yang gagal dicoba ulang hingga tiga kali.
- Panggilan kedua
AddTransientHttpErrorPolicy
menambahkan kebijakan pemutus sirkuit. Permintaan eksternal lebih lanjut diblokir selama 30 detik jika 5 upaya gagal terjadi secara berurutan. Kebijakan pemutus sirkuit bersifat stateful. Semua panggilan melalui klien ini memiliki status sirkuit yang sama.
Menambahkan kebijakan dari registri Polly
Pendekatan untuk mengelola kebijakan yang digunakan secara teratur adalah menentukannya sekali dan mendaftarkannya dengan PolicyRegistry
. Contohnya:
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");
Dalam kode sebelumnya:
- Dua kebijakan,
Regular
danLong
, ditambahkan ke registri Polly. - AddPolicyHandlerFromRegistry mengonfigurasi klien bernama individu untuk menggunakan kebijakan ini dari registri Polly.
Untuk informasi selengkapnya tentang IHttpClientFactory
dan integrasi Polly, lihat wiki Polly.
HttpClient dan manajemen seumur hidup
Instans HttpClient
baru dikembalikan setiap kali CreateClient
dipanggil pada IHttpClientFactory
. HttpMessageHandler Dibuat per klien bernama. Pabrik mengelola masa pakai instans HttpMessageHandler
.
IHttpClientFactory
mengumpulkan instans HttpMessageHandler
yang dibuat oleh pabrik untuk mengurangi konsumsi sumber daya. Instans HttpMessageHandler
bisa digunakan kembali dari kumpulan saat membuat instans HttpClient
baru jika masa pakainya belum kedaluwarsa.
Pengumpulan handler diinginkan karena setiap handler biasanya mengelola koneksi HTTP yang mendasarnya sendiri. Membuat lebih banyak handler daripada yang dibutuhkan dapat mengakibatkan penundaan koneksi. Beberapa handler juga menjaga koneksi tetap terbuka tanpa batas waktu, yang dapat mencegah handler bereaksi terhadap perubahan DNS (Domain Name System).
Masa pakai handler default adalah dua menit. Nilai default dapat diambil alih berdasarkan klien per bernama:
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
HttpClient
instans umumnya dapat diperlakukan sebagai objek .NET yang tidak memerlukan pembuangan. Pembuangan membatalkan permintaan keluar dan menjamin instans HttpClient
yang diberikan tidak dapat digunakan setelah memanggil Dispose. IHttpClientFactory
melacak dan membuang sumber daya yang digunakan oleh instans HttpClient
.
Menjaga satu instans HttpClient
tetap hidup untuk durasi yang lama adalah pola umum yang digunakan sebelum adanya IHttpClientFactory
. Pola ini menjadi tidak perlu setelah bermigrasi ke IHttpClientFactory
.
Alternatif untuk IHttpClientFactory
Menggunakan IHttpClientFactory
dalam aplikasi yang diaktifkan DI menghindari:
- Masalah kelelahan sumber daya dengan mengumpulkan
HttpMessageHandler
instans. - Masalah DNS kedaluarsa dengan bersepeda
HttpMessageHandler
instans secara berkala.
Ada cara alternatif untuk menyelesaikan masalah sebelumnya menggunakan instans berumur SocketsHttpHandler panjang.
- Buat instans
SocketsHttpHandler
saat aplikasi dimulai dan gunakan untuk masa pakai aplikasi. - Konfigurasikan PooledConnectionLifetime ke nilai yang sesuai berdasarkan waktu refresh DNS.
- Buat
HttpClient
instans menggunakannew HttpClient(handler, disposeHandler: false)
sesuai kebutuhan.
Pendekatan sebelumnya menyelesaikan masalah manajemen sumber daya yang IHttpClientFactory
diselesaikan dengan cara yang sama.
- Berbagi
SocketsHttpHandler
koneksi di seluruhHttpClient
instans. Berbagi ini mencegah kelelahan soket. - Koneksi
SocketsHttpHandler
siklus sesuai untuk menghindari masalah DNS kedaluarsaPooledConnectionLifetime
.
Pencatatan
Klien dibuat melalui IHttpClientFactory
pesan log rekaman untuk semua permintaan. Aktifkan tingkat informasi yang sesuai dalam konfigurasi pengelogan untuk melihat pesan log default. Pengelogan tambahan, seperti pengelogan header permintaan, hanya disertakan pada tingkat pelacakan.
Kategori log yang digunakan untuk setiap klien menyertakan nama klien. Klien bernama MyNamedClient, misalnya, mencatat pesan dengan kategori "System.Net.Http.HttpClient.MyNamedClient. LogicalHandler". Pesan yang diabaikan dengan LogicalHandler terjadi di luar alur penangan permintaan. Pada permintaan, pesan dicatat sebelum penangan lain dalam alur telah memprosesnya. Pada respons, pesan dicatat setelah handler alur lain menerima respons.
Pengelogan juga terjadi di dalam alur handler permintaan. Dalam contoh MyNamedClient, pesan tersebut dicatat dengan kategori log "System.Net.Http.Http.HttpClient.MyNamedClient. ClientHandler". Untuk permintaan, ini terjadi setelah semua handler lain berjalan dan segera sebelum permintaan dikirim. Pada respons, pengelogan ini mencakup status respons sebelum melewati kembali melalui alur handler.
Mengaktifkan pengelogan di luar dan di dalam alur memungkinkan inspeksi perubahan yang dilakukan oleh penangan alur lainnya. Ini dapat mencakup perubahan pada header permintaan atau ke kode status respons.
Menyertakan nama klien dalam kategori log memungkinkan pemfilteran log untuk klien bernama tertentu.
Mengonfigurasi HttpMessageHandler
Anda mungkin perlu mengontrol konfigurasi HttpMessageHandler
bagian dalam yang digunakan oleh klien.
IHttpClientBuilder
dikembalikan saat menambahkan klien bernama atau berjenis. Metode ConfigurePrimaryHttpMessageHandler ekstensi dapat digunakan untuk menentukan delegasi. Delegasi dipakai untuk membuat dan mengonfigurasi primer HttpMessageHandler
yang digunakan oleh klien tersebut:
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
Cookie
Instans yang dikumpulkan HttpMessageHandler
menghasilkan CookieContainer
objek yang dibagikan. Berbagi objek yang tidak diantipi sering CookieContainer
kali menghasilkan kode yang salah. Untuk aplikasi yang memerlukan cookie, pertimbangkan:
- Menonaktifkan penanganan otomatis cookie
- Menghindari
IHttpClientFactory
Panggilan ConfigurePrimaryHttpMessageHandler untuk menonaktifkan penanganan otomatis cookie :
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Menggunakan IHttpClientFactory di aplikasi konsol
Di aplikasi konsol, tambahkan referensi paket berikut ke proyek:
Dalam contoh berikut:
- IHttpClientFactory dan
GitHubService
terdaftar dalam kontainer layanan Host Generik. GitHubService
diminta dari DI, yang pada gilirannya meminta instans .IHttpClientFactory
GitHubService
menggunakan untuk membuat instansIHttpClientFactory
HttpClient
, yang digunakannya untuk mengambil dokumen cabang GitHub.
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);
Middleware penyebaran header
Penyebaran header adalah middleware core ASP.NET untuk menyebarluaskan header HTTP dari permintaan masuk ke permintaan keluar HttpClient
. Untuk menggunakan penyebaran header:
Konfigurasikan
HttpClient
alur middleware dan diProgram.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();
Buat permintaan keluar menggunakan instans yang dikonfigurasi
HttpClient
, yang mencakup header yang ditambahkan.
Sumber Daya Tambahan:
- Melihat atau mengunduh kode sampel (cara mengunduh)
- Gunakan HttpClientFactory untuk menerapkan permintaan HTTP yang tangguh
- Menerapkan percobaan ulang panggilan HTTP dengan backoff eksponensial dengan kebijakan HttpClientFactory dan Polly
- Menerapkan pola Circuit Breaker
- Cara membuat serialisasi dan mendeserialisasi JSON di .NET
Oleh Kirk Larkin, Steve Gordon, Glenn Condron, dan Ryan Nowak.
IHttpClientFactory Dapat didaftarkan dan digunakan untuk mengonfigurasi dan membuat HttpClient instans di aplikasi. IHttpClientFactory
menawarkan manfaat berikut:
- Menyediakan lokasi pusat untuk penamaan dan konfigurasi instans
HttpClient
logis. Misalnya, klien bernama github dapat didaftarkan dan dikonfigurasi untuk mengakses GitHub. Klien default dapat didaftarkan untuk akses umum. - Mengkodifikasi konsep middleware keluar via pendelegasian handler di
HttpClient
. Menyediakan ekstensi untuk middleware berbasis Polly untuk memanfaatkan pendelegasian handler diHttpClient
. - Mengelola pengumpulan dan masa pakai instans
HttpClientMessageHandler
yang mendasarinya. Manajemen otomatis menghindari masalah DNS umum (Sistem Nama Domain) yang terjadi saat mengelolaHttpClient
masa pakai secara manual. - Menambahkan pengalaman pengelogan yang bisa dikonfigurasi (melalui
ILogger
) untuk semua permintaan yang dikirim melalui klien yang dibuat oleh pabrik.
Lihat atau unduh sampel kode (cara mengunduh).
Kode sampel dalam versi topik ini menggunakan System.Text.Json untuk mendeserialisasi konten JSON yang dikembalikan dalam respons HTTP. Untuk sampel yang menggunakan Json.NET
dan ReadAsAsync<T>
, gunakan pemilih versi untuk memilih versi 2.x dari topik ini.
Pola konsumsi
Ada beberapa cara menggunakan IHttpClientFactory
dalam aplikasi:
Pendekatan terbaiknya tergantung pada persyaratan aplikasi.
Penggunaan dasar
IHttpClientFactory
dapat didaftarkan dengan memanggil AddHttpClient
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
Dapat IHttpClientFactory
diminta menggunakan injeksi dependensi (DI). Kode berikut ini menggunakan IHttpClientFactory
untuk membuat instansHttpClient
:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Menggunakan IHttpClientFactory
seperti pada contoh sebelumnya adalah cara yang baik untuk merefaktor aplikasi yang ada. Ini tidak berdampak pada bagaimana HttpClient
digunakan. Di tempat-tempat di mana instans HttpClient
dibuat di aplikasi yang ada, ganti kemunculan tersebut dengan panggilan ke CreateClient.
Klien bernama
Klien bernama adalah pilihan yang baik saat:
- Aplikasi membutuhkan banyak kegunaan
HttpClient
yang berbeda. - Banyak
HttpClient
yang memiliki konfigurasi yang berbeda.
Konfigurasi untuk HttpClient
yang bernama dapat ditentukan selama pendaftaran di Startup.ConfigureServices
:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Dalam kode sebelumnya, klien dikonfigurasi dengan:
- Alamat dasar
https://api.github.com/
. - Dua header yang diperlukan untuk bekerja dengan API GitHub.
CreateClient
Setiap kali CreateClient disebut:
- Instans
HttpClient
baru dibuat. - Tindakan konfigurasi dipanggil.
Untuk membuat klien bernama, berikan namanya ke dalam CreateClient
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
Dalam kode sebelumnya, permintaan tidak perlu menentukan nama host. Kode hanya bisa melewati jalur karena alamat dasar yang dikonfigurasi untuk klien digunakan.
Klien yang berjenis
Klien yang berjenis:
- Memberikan kemampuan yang sama seperti klien bernama tanpa perlu menggunakan string sebagai kunci.
- Menyediakan bantuan IntelliSense dan pengkompilasi saat mengkonsumsi klien.
- Menyediakan satu lokasi untuk mengonfigurasi dan berinteraksi dengan
HttpClient
tertentu. Misalnya, klien bertitik tunggal dapat digunakan:- Untuk satu titik akhir backend.
- Untuk merangkum semua logika yang berhubungan dengan titik akhir.
- Bekerja dengan DI dan bisa disuntikkan jika diperlukan di aplikasi.
Klien yang berjenis menerima parameter HttpClient
dalam konstruktornya:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
}
}
Dalam kode sebelumnya:
- Konfigurasi dipindahkan ke klien yang ditik.
- Objek
HttpClient
diekspos sebagai properti publik.
Metode khusus API bisa dibuat yang mengekspos fungsionalitas HttpClient
. Misalnya, metode merangkum GetAspNetDocsIssues
kode untuk mengambil masalah terbuka.
Kode berikut ini memanggil AddHttpClient pada Startup.ConfigureServices
untuk mendaftarkan kelas klien berjenis:
services.AddHttpClient<GitHubService>();
Klien berjenis terdaftar sebagai sementara dengan DI. Dalam kode sebelumnya, AddHttpClient
mendaftarkan GitHubService
sebagai layanan sementara. Pendaftaran ini menggunakan metode pabrik untuk:
- Buat instans
HttpClient
. - Membuat instans
GitHubService
, meneruskan instansHttpClient
ke konstruktornya.
Klien yang ditik dapat disuntikkan dan dikonsumsi secara langsung:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Konfigurasi untuk klien yang ditik dapat ditentukan selama pendaftaran di Startup.ConfigureServices
, bukan di konstruktor klien yang ditik:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
HttpClient
dapat dienkapsulasi dalam klien yang ditik. Daripada mengeksposnya sebagai properti, tentukan metode yang memanggil instans HttpClient
secara internal:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
Dalam kode sebelumnya, HttpClient
disimpan di bidang privat. Akses ke HttpClient
adalah dengan metode publik GetRepos
.
Klien yang dihasilkan
IHttpClientFactory
bisa digunakan dalam kombinasi dengan pustaka pihak ketiga seperti Refit. Refit adalah REST pustaka untuk .NET. Ini mengonversi REST API menjadi antarmuka langsung. Implementasi antarmuka dihasilkan secara dinamis oleh RestService
, memakai HttpClient
untuk melakukan panggilan HTTP eksternal.
Antarmuka dan balasan didefinisikan untuk mewakili API eksternal dan responsnya:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Klien berjenis dapat ditambahkan, menggunakan Refit untuk menghasilkan implementasi:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
Antarmuka yang ditentukan dapat dikonsumsi jika perlu, dengan implementasi yang disediakan oleh DI dan Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Membuat permintaan POST, PUT, dan DELETE
Dalam contoh sebelumnya, semua permintaan HTTP menggunakan kata kerja HTTP GET. HttpClient
juga mendukung kata kerja HTTP lainnya, termasuk:
- POST
- TARUH
- DELETE
- PATCH
Untuk daftar lengkap kata kerja HTTP yang didukung, lihat HttpMethod.
Contoh berikut menunjukkan cara membuat permintaan HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Dalam kode sebelumnya, metode CreateItemAsync
:
- Menserialisasikan parameter
TodoItem
ke JSON menggunakanSystem.Text.Json
. Metode ini menggunakan instans JsonSerializerOptions untuk mengonfigurasi proses serialisasi. - Membuat instans StringContent untuk mengemas JSON berseri untuk dikirim di dalam isi permintaan HTTP.
- Memanggil PostAsync untuk mengirim konten JSON ke URL yang ditentukan. Ini adalah URL relatif yang ditambahkan ke HttpClient.BaseAddress.
- Memanggil EnsureSuccessStatusCode untuk melemparkan pengecualian jika kode status respons tidak menunjukkan keberhasilan.
HttpClient
juga mendukung jenis konten lainnya. Misalnya, MultipartContent dan StreamContent. Untuk daftar lengkap konten yang didukung, lihat HttpContent.
Contoh berikut menunjukkan permintaan HTTP PUT:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Kode sebelumnya sangat mirip dengan contoh POST. Metode SaveItemAsync
memanggil PutAsync alih-alih PostAsync
.
Contoh berikut menunjukkan permintaan HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
Dalam kode sebelumnya, metode DeleteItemAsync
memanggil DeleteAsync. Karena permintaan HTTP DELETE biasanya tidak memiliki isi, metode DeleteAsync
ini tidak menyediakan kelebihan beban yang menerima instans HttpContent
.
Untuk mempelajari selengkapnya mengenai penggunaan kata kerja HTTP yang berbeda dengan HttpClient
, lihat HttpClient.
Middleware permintaan keluar
HttpClient
memiliki konsep mendelegasikan handler yang dapat ditautkan bersama untuk permintaan HTTP keluar. IHttpClientFactory
:
- Menyederhanakan penentuan handler untuk melamar setiap klien bernama.
- Mendukung pendaftaran dan penautan beberapa handler untuk membangun alur middleware permintaan keluar. Masing-masing handler ini dapat melakukan pekerjaan sebelum dan sesudah permintaan keluar. Pola ini:
- Mirip dengan alur middleware masuk di ASP.NET Core.
- Menyediakan mekanisme untuk mengelola masalah lintas pemotongan seputar permintaan HTTP, seperti:
- Caching
- penanganan kesalahan
- serialisasi
- pencatatan
Untuk membuat penangan pendelegasian:
- Berasal dari DelegatingHandler.
- Ambil alih SendAsync. Jalankan kode sebelum meneruskan permintaan ke handler berikutnya dalam alur:
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(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Kode sebelumnya memeriksa apakah X-API-KEY
header ada dalam permintaan. Jika X-API-KEY
hilang, BadRequest dikembalikan.
Lebih dari satu handler dapat ditambahkan ke konfigurasi untuk HttpClient
dengan Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
Dalam kode sebelumnya, ValidateHeaderHandler
terdaftar di DI. Setelah terdaftar, AddHttpMessageHandler dapat dipanggil, meneruskan jenis untuk handler.
Beberapa handler dapat didaftarkan dalam urutan yang harus mereka jalankan. Setiap handler membungkus handler berikutnya hingga final HttpClientHandler
menjalankan permintaan:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Menggunakan DI dalam middleware permintaan keluar
Saat IHttpClientFactory
membuat handler pendelegasian baru, ia menggunakan DI untuk memenuhi parameter konstruktor handler. IHttpClientFactory
membuat cakupan DI terpisah untuk setiap handler, yang dapat menyebabkan perilaku mengejutkan ketika handler mengonsumsi layanan tercakup.
Misalnya, pertimbangkan antarmuka berikut dan implementasinya, yang mewakili tugas sebagai operasi dengan pengidentifikasi, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Seperti namanya, IOperationScoped
terdaftar di DI menggunakan masa pakai tercakup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
Penangan pendelegasian IOperationScoped
berikut menggunakan dan menggunakan untuk mengatur X-OPERATION-ID
header untuk permintaan keluar:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
HttpRequestsSample
Dalam pengunduhan], navigasi ke /Operation
dan segarkan halaman. Nilai cakupan permintaan berubah untuk setiap permintaan, tetapi nilai cakupan handler hanya berubah setiap 5 detik.
Handler dapat bergantung pada layanan dari cakupan apa pun. Layanan yang bergantung pada handler dibuang ketika handler dibuang.
Gunakan salah satu pendekatan berikut untuk berbagi status per permintaan dengan penangan pesan:
- Teruskan data ke handler menggunakan HttpRequestMessage.Options.
- Gunakan IHttpContextAccessor untuk mengakses permintaan saat ini.
- Buat objek penyimpanan kustom AsyncLocal<T> untuk meneruskan data.
Menggunakan handler berbasis Polly
IHttpClientFactory
terintegrasi dengan pustaka pihak ketiga Polly. Polly adalah pustaka penanganan kesalahan ketahanan dan sementara yang komprehensif untuk .NET. Hal ini memungkinkan pengembang untuk mengekspresikan kebijakan seperti Retry, Circuit Breaker, Timeout, Bulkhead Isolation, dan Fallback dengan cara yang lancar dan aman di utas.
Metode ekstensi disediakan untuk memungkinkan penggunaan kebijakan Polly dengan instans yang dikonfigurasi HttpClient
. Ekstensi Polly mendukung penambahan handler berbasis Polly ke klien. Polly memerlukan paket NuGet Microsoft.Extensions.Http.Polly .
Menangani kesalahan sementara
Kesalahan biasanya terjadi ketika panggilan HTTP eksternal bersifat sementara. AddTransientHttpErrorPolicy memungkinkan kebijakan didefinisikan untuk menangani kesalahan sementara. Kebijakan yang dikonfigurasi dengan AddTransientHttpErrorPolicy
menangani respons berikut:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
menyediakan akses ke objek yang dikonfigurasi PolicyBuilder
untuk menangani kesalahan yang mewakili kemungkinan kesalahan sementara:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
Dalam kode sebelumnya, WaitAndRetryAsync
kebijakan ditentukan. Permintaan yang gagal dicoba ulang hingga tiga kali dengan penundaan 600 md antar upaya.
Memilih kebijakan secara dinamis
Metode ekstensi disediakan untuk menambahkan handler berbasis Polly, misalnya, AddPolicyHandler. Kelebihan beban berikut AddPolicyHandler
memeriksa permintaan untuk memutuskan kebijakan mana yang akan diterapkan:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Dalam kode sebelumnya, jika permintaan keluar adalah HTTP GET, batas waktu 10 detik diterapkan. Untuk metode HTTP lainnya, batas waktu 30 detik digunakan.
Menambahkan beberapa handler Polly
Adalah umum untuk menumpuk kebijakan Polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Dalam contoh sebelumnya:
- Dua handler ditambahkan.
- Handler pertama menggunakan AddTransientHttpErrorPolicy untuk menambahkan kebijakan coba lagi. Permintaan yang gagal dicoba ulang hingga tiga kali.
- Panggilan kedua
AddTransientHttpErrorPolicy
menambahkan kebijakan pemutus sirkuit. Permintaan eksternal lebih lanjut diblokir selama 30 detik jika 5 upaya gagal terjadi secara berurutan. Kebijakan pemutus sirkuit bersifat stateful. Semua panggilan melalui klien ini memiliki status sirkuit yang sama.
Menambahkan kebijakan dari registri Polly
Pendekatan untuk mengelola kebijakan yang digunakan secara teratur adalah menentukannya sekali dan mendaftarkannya dengan PolicyRegistry
.
Dalam kode berikut:
- Kebijakan "reguler" dan "panjang" ditambahkan.
- AddPolicyHandlerFromRegistry menambahkan kebijakan "reguler" dan "panjang" dari registri.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Untuk informasi selengkapnya tentang IHttpClientFactory
dan integrasi Polly, lihat wiki Polly.
HttpClient dan manajemen seumur hidup
Instans HttpClient
baru dikembalikan setiap kali CreateClient
dipanggil pada IHttpClientFactory
. HttpMessageHandler Dibuat per klien bernama. Pabrik mengelola masa pakai instans HttpMessageHandler
.
IHttpClientFactory
mengumpulkan instans HttpMessageHandler
yang dibuat oleh pabrik untuk mengurangi konsumsi sumber daya. Instans HttpMessageHandler
bisa digunakan kembali dari kumpulan saat membuat instans HttpClient
baru jika masa pakainya belum kedaluwarsa.
Pengumpulan handler diinginkan karena setiap handler biasanya mengelola koneksi HTTP yang mendasarnya sendiri. Membuat lebih banyak handler daripada yang dibutuhkan dapat mengakibatkan penundaan koneksi. Beberapa handler juga menjaga koneksi tetap terbuka tanpa batas waktu, yang dapat mencegah handler bereaksi terhadap perubahan DNS (Domain Name System).
Masa pakai handler default adalah dua menit. Nilai default dapat diambil alih berdasarkan klien per bernama:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
instans umumnya dapat diperlakukan sebagai objek .NET yang tidak memerlukan pembuangan. Pembuangan membatalkan permintaan keluar dan menjamin instans HttpClient
yang diberikan tidak dapat digunakan setelah memanggil Dispose. IHttpClientFactory
melacak dan membuang sumber daya yang digunakan oleh instans HttpClient
.
Menjaga satu instans HttpClient
tetap hidup untuk durasi yang lama adalah pola umum yang digunakan sebelum adanya IHttpClientFactory
. Pola ini menjadi tidak perlu setelah bermigrasi ke IHttpClientFactory
.
Alternatif untuk IHttpClientFactory
Menggunakan IHttpClientFactory
dalam aplikasi yang diaktifkan DI menghindari:
- Masalah kelelahan sumber daya dengan mengumpulkan
HttpMessageHandler
instans. - Masalah DNS kedaluarsa dengan bersepeda
HttpMessageHandler
instans secara berkala.
Ada cara alternatif untuk menyelesaikan masalah sebelumnya menggunakan instans berumur SocketsHttpHandler panjang.
- Buat instans
SocketsHttpHandler
saat aplikasi dimulai dan gunakan untuk masa pakai aplikasi. - Konfigurasikan PooledConnectionLifetime ke nilai yang sesuai berdasarkan waktu refresh DNS.
- Buat
HttpClient
instans menggunakannew HttpClient(handler, disposeHandler: false)
sesuai kebutuhan.
Pendekatan sebelumnya menyelesaikan masalah manajemen sumber daya yang IHttpClientFactory
diselesaikan dengan cara yang sama.
- Berbagi
SocketsHttpHandler
koneksi di seluruhHttpClient
instans. Berbagi ini mencegah kelelahan soket. - Koneksi
SocketsHttpHandler
siklus sesuai untuk menghindari masalah DNS kedaluarsaPooledConnectionLifetime
.
Cookie
Instans yang dikumpulkan HttpMessageHandler
menghasilkan CookieContainer
objek yang dibagikan. Berbagi objek yang tidak diantipi sering CookieContainer
kali menghasilkan kode yang salah. Untuk aplikasi yang memerlukan cookie, pertimbangkan:
- Menonaktifkan penanganan otomatis cookie
- Menghindari
IHttpClientFactory
Panggilan ConfigurePrimaryHttpMessageHandler untuk menonaktifkan penanganan otomatis cookie :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Pencatatan
Klien dibuat melalui IHttpClientFactory
pesan log rekaman untuk semua permintaan. Aktifkan tingkat informasi yang sesuai dalam konfigurasi pengelogan untuk melihat pesan log default. Pengelogan tambahan, seperti pengelogan header permintaan, hanya disertakan pada tingkat pelacakan.
Kategori log yang digunakan untuk setiap klien menyertakan nama klien. Klien bernama MyNamedClient, misalnya, mencatat pesan dengan kategori "System.Net.Http.HttpClient.MyNamedClient. LogicalHandler". Pesan yang diabaikan dengan LogicalHandler terjadi di luar alur penangan permintaan. Pada permintaan, pesan dicatat sebelum penangan lain dalam alur telah memprosesnya. Pada respons, pesan dicatat setelah handler alur lain menerima respons.
Pengelogan juga terjadi di dalam alur handler permintaan. Dalam contoh MyNamedClient, pesan tersebut dicatat dengan kategori log "System.Net.Http.Http.HttpClient.MyNamedClient. ClientHandler". Untuk permintaan, ini terjadi setelah semua handler lain berjalan dan segera sebelum permintaan dikirim. Pada respons, pengelogan ini mencakup status respons sebelum melewati kembali melalui alur handler.
Mengaktifkan pengelogan di luar dan di dalam alur memungkinkan inspeksi perubahan yang dilakukan oleh penangan alur lainnya. Ini dapat mencakup perubahan pada header permintaan atau ke kode status respons.
Menyertakan nama klien dalam kategori log memungkinkan pemfilteran log untuk klien bernama tertentu.
Mengonfigurasi HttpMessageHandler
Anda mungkin perlu mengontrol konfigurasi HttpMessageHandler
bagian dalam yang digunakan oleh klien.
IHttpClientBuilder
dikembalikan saat menambahkan klien bernama atau berjenis. Metode ConfigurePrimaryHttpMessageHandler ekstensi dapat digunakan untuk menentukan delegasi. Delegasi dipakai untuk membuat dan mengonfigurasi primer HttpMessageHandler
yang digunakan oleh klien tersebut:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Menggunakan IHttpClientFactory di aplikasi konsol
Di aplikasi konsol, tambahkan referensi paket berikut ke proyek:
Dalam contoh berikut:
- IHttpClientFactory terdaftar dalam kontainer layanan Host Generik.
MyService
membuat instans pabrik klien dari layanan, yang digunakan untuk membuatHttpClient
.HttpClient
digunakan untuk mengambil halaman web.Main
membuat cakupan untuk menjalankan metode layananGetPage
dan menulis 500 karakter pertama konten halaman web ke konsol.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware penyebaran header
Penyebaran header adalah middleware core ASP.NET untuk menyebarluaskan header HTTP dari permintaan masuk ke permintaan Klien HTTP keluar. Untuk menggunakan penyebaran header:
Referensi paket Microsoft.AspNetCore.HeaderPropagation .
Konfigurasikan middleware dan
HttpClient
diStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
Klien menyertakan header yang dikonfigurasi pada permintaan keluar:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Sumber Daya Tambahan:
Oleh Kirk Larkin, Steve Gordon, Glenn Condron, dan Ryan Nowak.
IHttpClientFactory Dapat didaftarkan dan digunakan untuk mengonfigurasi dan membuat HttpClient instans di aplikasi. IHttpClientFactory
menawarkan manfaat berikut:
- Menyediakan lokasi pusat untuk penamaan dan konfigurasi instans
HttpClient
logis. Misalnya, klien bernama github dapat didaftarkan dan dikonfigurasi untuk mengakses GitHub. Klien default dapat didaftarkan untuk akses umum. - Mengkodifikasi konsep middleware keluar via pendelegasian handler di
HttpClient
. Menyediakan ekstensi untuk middleware berbasis Polly untuk memanfaatkan pendelegasian handler diHttpClient
. - Mengelola pengumpulan dan masa pakai instans
HttpClientMessageHandler
yang mendasarinya. Manajemen otomatis menghindari masalah DNS umum (Sistem Nama Domain) yang terjadi saat mengelolaHttpClient
masa pakai secara manual. - Menambahkan pengalaman pengelogan yang bisa dikonfigurasi (melalui
ILogger
) untuk semua permintaan yang dikirim melalui klien yang dibuat oleh pabrik.
Lihat atau unduh sampel kode (cara mengunduh).
Kode sampel dalam versi topik ini menggunakan System.Text.Json untuk mendeserialisasi konten JSON yang dikembalikan dalam respons HTTP. Untuk sampel yang menggunakan Json.NET
dan ReadAsAsync<T>
, gunakan pemilih versi untuk memilih versi 2.x dari topik ini.
Pola konsumsi
Ada beberapa cara menggunakan IHttpClientFactory
dalam aplikasi:
Pendekatan terbaiknya tergantung pada persyaratan aplikasi.
Penggunaan dasar
IHttpClientFactory
dapat didaftarkan dengan memanggil AddHttpClient
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
Dapat IHttpClientFactory
diminta menggunakan injeksi dependensi (DI). Kode berikut ini menggunakan IHttpClientFactory
untuk membuat instansHttpClient
:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Menggunakan IHttpClientFactory
seperti pada contoh sebelumnya adalah cara yang baik untuk merefaktor aplikasi yang ada. Ini tidak berdampak pada bagaimana HttpClient
digunakan. Di tempat-tempat di mana instans HttpClient
dibuat di aplikasi yang ada, ganti kemunculan tersebut dengan panggilan ke CreateClient.
Klien bernama
Klien bernama adalah pilihan yang baik saat:
- Aplikasi membutuhkan banyak kegunaan
HttpClient
yang berbeda. - Banyak
HttpClient
yang memiliki konfigurasi yang berbeda.
Konfigurasi untuk HttpClient
yang bernama dapat ditentukan selama pendaftaran di Startup.ConfigureServices
:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Dalam kode sebelumnya, klien dikonfigurasi dengan:
- Alamat dasar
https://api.github.com/
. - Dua header yang diperlukan untuk bekerja dengan API GitHub.
CreateClient
Setiap kali CreateClient disebut:
- Instans
HttpClient
baru dibuat. - Tindakan konfigurasi dipanggil.
Untuk membuat klien bernama, berikan namanya ke dalam CreateClient
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
Dalam kode sebelumnya, permintaan tidak perlu menentukan nama host. Kode hanya bisa melewati jalur karena alamat dasar yang dikonfigurasi untuk klien digunakan.
Klien yang berjenis
Klien yang berjenis:
- Memberikan kemampuan yang sama seperti klien bernama tanpa perlu menggunakan string sebagai kunci.
- Menyediakan bantuan IntelliSense dan pengkompilasi saat mengkonsumsi klien.
- Menyediakan satu lokasi untuk mengonfigurasi dan berinteraksi dengan
HttpClient
tertentu. Misalnya, klien bertitik tunggal dapat digunakan:- Untuk satu titik akhir backend.
- Untuk merangkum semua logika yang berhubungan dengan titik akhir.
- Bekerja dengan DI dan bisa disuntikkan jika diperlukan di aplikasi.
Klien yang berjenis menerima parameter HttpClient
dalam konstruktornya:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Jika Anda ingin melihat komentar kode yang diterjemahkan ke bahasa selain bahasa Inggris, beri tahu kami dalam masalah diskusi GitHub ini.
Dalam kode sebelumnya:
- Konfigurasi dipindahkan ke klien yang ditik.
- Objek
HttpClient
diekspos sebagai properti publik.
Metode khusus API bisa dibuat yang mengekspos fungsionalitas HttpClient
. Misalnya, metode merangkum GetAspNetDocsIssues
kode untuk mengambil masalah terbuka.
Kode berikut ini memanggil AddHttpClient pada Startup.ConfigureServices
untuk mendaftarkan kelas klien berjenis:
services.AddHttpClient<GitHubService>();
Klien berjenis terdaftar sebagai sementara dengan DI. Dalam kode sebelumnya, AddHttpClient
mendaftarkan GitHubService
sebagai layanan sementara. Pendaftaran ini menggunakan metode pabrik untuk:
- Buat instans
HttpClient
. - Membuat instans
GitHubService
, meneruskan instansHttpClient
ke konstruktornya.
Klien yang ditik dapat disuntikkan dan dikonsumsi secara langsung:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Konfigurasi untuk klien yang ditik dapat ditentukan selama pendaftaran di Startup.ConfigureServices
, bukan di konstruktor klien yang ditik:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
HttpClient
dapat dienkapsulasi dalam klien yang ditik. Daripada mengeksposnya sebagai properti, tentukan metode yang memanggil instans HttpClient
secara internal:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
Dalam kode sebelumnya, HttpClient
disimpan di bidang privat. Akses ke HttpClient
adalah dengan metode publik GetRepos
.
Klien yang dihasilkan
IHttpClientFactory
bisa digunakan dalam kombinasi dengan pustaka pihak ketiga seperti Refit. Refit adalah REST pustaka untuk .NET. Ini mengonversi REST API menjadi antarmuka langsung. Implementasi antarmuka dihasilkan secara dinamis oleh RestService
, memakai HttpClient
untuk melakukan panggilan HTTP eksternal.
Antarmuka dan balasan didefinisikan untuk mewakili API eksternal dan responsnya:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Klien berjenis dapat ditambahkan, menggunakan Refit untuk menghasilkan implementasi:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
Antarmuka yang ditentukan dapat dikonsumsi jika perlu, dengan implementasi yang disediakan oleh DI dan Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Membuat permintaan POST, PUT, dan DELETE
Dalam contoh sebelumnya, semua permintaan HTTP menggunakan kata kerja HTTP GET. HttpClient
juga mendukung kata kerja HTTP lainnya, termasuk:
- POST
- TARUH
- DELETE
- PATCH
Untuk daftar lengkap kata kerja HTTP yang didukung, lihat HttpMethod.
Contoh berikut menunjukkan cara membuat permintaan HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Dalam kode sebelumnya, metode CreateItemAsync
:
- Menserialisasikan parameter
TodoItem
ke JSON menggunakanSystem.Text.Json
. Metode ini menggunakan instans JsonSerializerOptions untuk mengonfigurasi proses serialisasi. - Membuat instans StringContent untuk mengemas JSON berseri untuk dikirim di dalam isi permintaan HTTP.
- Memanggil PostAsync untuk mengirim konten JSON ke URL yang ditentukan. Ini adalah URL relatif yang ditambahkan ke HttpClient.BaseAddress.
- Memanggil EnsureSuccessStatusCode untuk melemparkan pengecualian jika kode status respons tidak menunjukkan keberhasilan.
HttpClient
juga mendukung jenis konten lainnya. Misalnya, MultipartContent dan StreamContent. Untuk daftar lengkap konten yang didukung, lihat HttpContent.
Contoh berikut menunjukkan permintaan HTTP PUT:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Kode sebelumnya sangat mirip dengan contoh POST. Metode SaveItemAsync
memanggil PutAsync alih-alih PostAsync
.
Contoh berikut menunjukkan permintaan HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
Dalam kode sebelumnya, metode DeleteItemAsync
memanggil DeleteAsync. Karena permintaan HTTP DELETE biasanya tidak memiliki isi, metode DeleteAsync
ini tidak menyediakan kelebihan beban yang menerima instans HttpContent
.
Untuk mempelajari selengkapnya mengenai penggunaan kata kerja HTTP yang berbeda dengan HttpClient
, lihat HttpClient.
Middleware permintaan keluar
HttpClient
memiliki konsep mendelegasikan handler yang dapat ditautkan bersama untuk permintaan HTTP keluar. IHttpClientFactory
:
- Menyederhanakan penentuan handler untuk melamar setiap klien bernama.
- Mendukung pendaftaran dan penautan beberapa handler untuk membangun alur middleware permintaan keluar. Masing-masing handler ini dapat melakukan pekerjaan sebelum dan sesudah permintaan keluar. Pola ini:
- Mirip dengan alur middleware masuk di ASP.NET Core.
- Menyediakan mekanisme untuk mengelola masalah lintas pemotongan seputar permintaan HTTP, seperti:
- Caching
- penanganan kesalahan
- serialisasi
- pencatatan
Untuk membuat penangan pendelegasian:
- Berasal dari DelegatingHandler.
- Ambil alih SendAsync. Jalankan kode sebelum meneruskan permintaan ke handler berikutnya dalam alur:
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(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Kode sebelumnya memeriksa apakah X-API-KEY
header ada dalam permintaan. Jika X-API-KEY
hilang, BadRequest dikembalikan.
Lebih dari satu handler dapat ditambahkan ke konfigurasi untuk HttpClient
dengan Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
Dalam kode sebelumnya, ValidateHeaderHandler
terdaftar di DI. Setelah terdaftar, AddHttpMessageHandler dapat dipanggil, meneruskan jenis untuk handler.
Beberapa handler dapat didaftarkan dalam urutan yang harus mereka jalankan. Setiap handler membungkus handler berikutnya hingga final HttpClientHandler
menjalankan permintaan:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Menggunakan DI dalam middleware permintaan keluar
Saat IHttpClientFactory
membuat handler pendelegasian baru, ia menggunakan DI untuk memenuhi parameter konstruktor handler. IHttpClientFactory
membuat cakupan DI terpisah untuk setiap handler, yang dapat menyebabkan perilaku mengejutkan ketika handler mengonsumsi layanan tercakup.
Misalnya, pertimbangkan antarmuka berikut dan implementasinya, yang mewakili tugas sebagai operasi dengan pengidentifikasi, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Seperti namanya, IOperationScoped
terdaftar di DI menggunakan masa pakai tercakup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
Penangan pendelegasian IOperationScoped
berikut menggunakan dan menggunakan untuk mengatur X-OPERATION-ID
header untuk permintaan keluar:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
HttpRequestsSample
Dalam pengunduhan], navigasi ke /Operation
dan segarkan halaman. Nilai cakupan permintaan berubah untuk setiap permintaan, tetapi nilai cakupan handler hanya berubah setiap 5 detik.
Handler dapat bergantung pada layanan dari cakupan apa pun. Layanan yang bergantung pada handler dibuang ketika handler dibuang.
Gunakan salah satu pendekatan berikut untuk berbagi status per permintaan dengan penangan pesan:
- Teruskan data ke handler menggunakan HttpRequestMessage.Properties.
- Gunakan IHttpContextAccessor untuk mengakses permintaan saat ini.
- Buat objek penyimpanan kustom AsyncLocal<T> untuk meneruskan data.
Menggunakan handler berbasis Polly
IHttpClientFactory
terintegrasi dengan pustaka pihak ketiga Polly. Polly adalah pustaka penanganan kesalahan ketahanan dan sementara yang komprehensif untuk .NET. Hal ini memungkinkan pengembang untuk mengekspresikan kebijakan seperti Retry, Circuit Breaker, Timeout, Bulkhead Isolation, dan Fallback dengan cara yang lancar dan aman di utas.
Metode ekstensi disediakan untuk memungkinkan penggunaan kebijakan Polly dengan instans yang dikonfigurasi HttpClient
. Ekstensi Polly mendukung penambahan handler berbasis Polly ke klien. Polly memerlukan paket NuGet Microsoft.Extensions.Http.Polly .
Menangani kesalahan sementara
Kesalahan biasanya terjadi ketika panggilan HTTP eksternal bersifat sementara. AddTransientHttpErrorPolicy memungkinkan kebijakan didefinisikan untuk menangani kesalahan sementara. Kebijakan yang dikonfigurasi dengan AddTransientHttpErrorPolicy
menangani respons berikut:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
menyediakan akses ke objek yang dikonfigurasi PolicyBuilder
untuk menangani kesalahan yang mewakili kemungkinan kesalahan sementara:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
Dalam kode sebelumnya, WaitAndRetryAsync
kebijakan ditentukan. Permintaan yang gagal dicoba ulang hingga tiga kali dengan penundaan 600 md antar upaya.
Memilih kebijakan secara dinamis
Metode ekstensi disediakan untuk menambahkan handler berbasis Polly, misalnya, AddPolicyHandler. Kelebihan beban berikut AddPolicyHandler
memeriksa permintaan untuk memutuskan kebijakan mana yang akan diterapkan:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Dalam kode sebelumnya, jika permintaan keluar adalah HTTP GET, batas waktu 10 detik diterapkan. Untuk metode HTTP lainnya, batas waktu 30 detik digunakan.
Menambahkan beberapa handler Polly
Adalah umum untuk menumpuk kebijakan Polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Dalam contoh sebelumnya:
- Dua handler ditambahkan.
- Handler pertama menggunakan AddTransientHttpErrorPolicy untuk menambahkan kebijakan coba lagi. Permintaan yang gagal dicoba ulang hingga tiga kali.
- Panggilan kedua
AddTransientHttpErrorPolicy
menambahkan kebijakan pemutus sirkuit. Permintaan eksternal lebih lanjut diblokir selama 30 detik jika 5 upaya gagal terjadi secara berurutan. Kebijakan pemutus sirkuit bersifat stateful. Semua panggilan melalui klien ini memiliki status sirkuit yang sama.
Menambahkan kebijakan dari registri Polly
Pendekatan untuk mengelola kebijakan yang digunakan secara teratur adalah menentukannya sekali dan mendaftarkannya dengan PolicyRegistry
.
Dalam kode berikut:
- Kebijakan "reguler" dan "panjang" ditambahkan.
- AddPolicyHandlerFromRegistry menambahkan kebijakan "reguler" dan "panjang" dari registri.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Untuk informasi selengkapnya tentang IHttpClientFactory
dan integrasi Polly, lihat wiki Polly.
HttpClient dan manajemen seumur hidup
Instans HttpClient
baru dikembalikan setiap kali CreateClient
dipanggil pada IHttpClientFactory
. HttpMessageHandler Dibuat per klien bernama. Pabrik mengelola masa pakai instans HttpMessageHandler
.
IHttpClientFactory
mengumpulkan instans HttpMessageHandler
yang dibuat oleh pabrik untuk mengurangi konsumsi sumber daya. Instans HttpMessageHandler
bisa digunakan kembali dari kumpulan saat membuat instans HttpClient
baru jika masa pakainya belum kedaluwarsa.
Pengumpulan handler diinginkan karena setiap handler biasanya mengelola koneksi HTTP yang mendasarnya sendiri. Membuat lebih banyak handler daripada yang dibutuhkan dapat mengakibatkan penundaan koneksi. Beberapa handler juga menjaga koneksi tetap terbuka tanpa batas waktu, yang dapat mencegah handler bereaksi terhadap perubahan DNS (Domain Name System).
Masa pakai handler default adalah dua menit. Nilai default dapat diambil alih berdasarkan klien per bernama:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
instans umumnya dapat diperlakukan sebagai objek .NET yang tidak memerlukan pembuangan. Pembuangan membatalkan permintaan keluar dan menjamin instans HttpClient
yang diberikan tidak dapat digunakan setelah memanggil Dispose. IHttpClientFactory
melacak dan membuang sumber daya yang digunakan oleh instans HttpClient
.
Menjaga satu instans HttpClient
tetap hidup untuk durasi yang lama adalah pola umum yang digunakan sebelum adanya IHttpClientFactory
. Pola ini menjadi tidak perlu setelah bermigrasi ke IHttpClientFactory
.
Alternatif untuk IHttpClientFactory
Menggunakan IHttpClientFactory
dalam aplikasi yang diaktifkan DI menghindari:
- Masalah kelelahan sumber daya dengan mengumpulkan
HttpMessageHandler
instans. - Masalah DNS kedaluarsa dengan bersepeda
HttpMessageHandler
instans secara berkala.
Ada cara alternatif untuk menyelesaikan masalah sebelumnya menggunakan instans berumur SocketsHttpHandler panjang.
- Buat instans
SocketsHttpHandler
saat aplikasi dimulai dan gunakan untuk masa pakai aplikasi. - Konfigurasikan PooledConnectionLifetime ke nilai yang sesuai berdasarkan waktu refresh DNS.
- Buat
HttpClient
instans menggunakannew HttpClient(handler, disposeHandler: false)
sesuai kebutuhan.
Pendekatan sebelumnya menyelesaikan masalah manajemen sumber daya yang IHttpClientFactory
diselesaikan dengan cara yang sama.
- Berbagi
SocketsHttpHandler
koneksi di seluruhHttpClient
instans. Berbagi ini mencegah kelelahan soket. - Koneksi
SocketsHttpHandler
siklus sesuai untuk menghindari masalah DNS kedaluarsaPooledConnectionLifetime
.
Cookie
Instans yang dikumpulkan HttpMessageHandler
menghasilkan CookieContainer
objek yang dibagikan. Berbagi objek yang tidak diantipi sering CookieContainer
kali menghasilkan kode yang salah. Untuk aplikasi yang memerlukan cookie, pertimbangkan:
- Menonaktifkan penanganan otomatis cookie
- Menghindari
IHttpClientFactory
Panggilan ConfigurePrimaryHttpMessageHandler untuk menonaktifkan penanganan otomatis cookie :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Pencatatan
Klien dibuat melalui IHttpClientFactory
pesan log rekaman untuk semua permintaan. Aktifkan tingkat informasi yang sesuai dalam konfigurasi pengelogan untuk melihat pesan log default. Pengelogan tambahan, seperti pengelogan header permintaan, hanya disertakan pada tingkat pelacakan.
Kategori log yang digunakan untuk setiap klien menyertakan nama klien. Klien bernama MyNamedClient, misalnya, mencatat pesan dengan kategori "System.Net.Http.HttpClient.MyNamedClient. LogicalHandler". Pesan yang diabaikan dengan LogicalHandler terjadi di luar alur penangan permintaan. Pada permintaan, pesan dicatat sebelum penangan lain dalam alur telah memprosesnya. Pada respons, pesan dicatat setelah handler alur lain menerima respons.
Pengelogan juga terjadi di dalam alur handler permintaan. Dalam contoh MyNamedClient, pesan tersebut dicatat dengan kategori log "System.Net.Http.Http.HttpClient.MyNamedClient. ClientHandler". Untuk permintaan, ini terjadi setelah semua handler lain berjalan dan segera sebelum permintaan dikirim. Pada respons, pengelogan ini mencakup status respons sebelum melewati kembali melalui alur handler.
Mengaktifkan pengelogan di luar dan di dalam alur memungkinkan inspeksi perubahan yang dilakukan oleh penangan alur lainnya. Ini dapat mencakup perubahan pada header permintaan atau ke kode status respons.
Menyertakan nama klien dalam kategori log memungkinkan pemfilteran log untuk klien bernama tertentu.
Mengonfigurasi HttpMessageHandler
Anda mungkin perlu mengontrol konfigurasi HttpMessageHandler
bagian dalam yang digunakan oleh klien.
IHttpClientBuilder
dikembalikan saat menambahkan klien bernama atau berjenis. Metode ConfigurePrimaryHttpMessageHandler ekstensi dapat digunakan untuk menentukan delegasi. Delegasi dipakai untuk membuat dan mengonfigurasi primer HttpMessageHandler
yang digunakan oleh klien tersebut:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Menggunakan IHttpClientFactory di aplikasi konsol
Di aplikasi konsol, tambahkan referensi paket berikut ke proyek:
Dalam contoh berikut:
- IHttpClientFactory terdaftar dalam kontainer layanan Host Generik.
MyService
membuat instans pabrik klien dari layanan, yang digunakan untuk membuatHttpClient
.HttpClient
digunakan untuk mengambil halaman web.Main
membuat cakupan untuk menjalankan metode layananGetPage
dan menulis 500 karakter pertama konten halaman web ke konsol.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware penyebaran header
Penyebaran header adalah middleware core ASP.NET untuk menyebarluaskan header HTTP dari permintaan masuk ke permintaan Klien HTTP keluar. Untuk menggunakan penyebaran header:
Referensi paket Microsoft.AspNetCore.HeaderPropagation .
Konfigurasikan middleware dan
HttpClient
diStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
Klien menyertakan header yang dikonfigurasi pada permintaan keluar:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Sumber Daya Tambahan:
Oleh Glenn Condron, Ryan Nowak, dan Steve Gordon
IHttpClientFactory Dapat didaftarkan dan digunakan untuk mengonfigurasi dan membuat HttpClient instans di aplikasi. Alokasi IP dinamis baru menawarkan manfaat berikut:
- Menyediakan lokasi pusat untuk penamaan dan konfigurasi instans
HttpClient
logis. Misalnya, klien github dapat didaftarkan dan dikonfigurasi untuk mengakses GitHub. Klien default dapat didaftarkan untuk tujuan lain. - Mengkodifikasi konsep middleware keluar melalui pendelegasian handler masuk
HttpClient
dan menyediakan ekstensi untuk middleware berbasis Polly untuk memanfaatkannya. - Mengelola pengumpulan dan masa pakai instans yang mendasarinya
HttpClientMessageHandler
untuk menghindari masalah DNS umum yang terjadi saat mengelolaHttpClient
masa pakai secara manual. - Menambahkan pengalaman pengelogan yang bisa dikonfigurasi (melalui
ILogger
) untuk semua permintaan yang dikirim melalui klien yang dibuat oleh pabrik.
Melihat atau mengunduh kode sampel (cara mengunduh)
Prasyarat
Proyek yang menargetkan .NET Framework memerlukan penginstalan paket NuGet Microsoft.Extensions.Http . Proyek yang menargetkan .NET Core dan mereferensikan metapackage Microsoft.AspNetCore.App sudah menyertakan Microsoft.Extensions.Http
paket.
Pola konsumsi
Ada beberapa cara menggunakan IHttpClientFactory
dalam aplikasi:
Tidak satu pun dari mereka benar-benar lebih unggul daripada yang lain. Pendekatan terbaik tergantung pada batasan aplikasi.
Penggunaan dasar
IHttpClientFactory
dapat didaftarkan dengan memanggil AddHttpClient
metode ekstensi pada IServiceCollection
, di dalam Startup.ConfigureServices
metode .
services.AddHttpClient();
Setelah terdaftar, kode dapat menerima IHttpClientFactory
layanan di mana saja dapat disuntikkan dengan injeksi dependensi (DI). IHttpClientFactory
dapat digunakan untuk membuat HttpClient
instans:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
Branches = await response.Content
.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Menggunakan IHttpClientFactory
dengan cara ini adalah cara yang baik untuk merefaktor aplikasi yang ada. Ini tidak berdampak pada cara HttpClient
digunakan. Di tempat-tempat di mana HttpClient
instans saat ini dibuat, ganti kemunculan tersebut dengan panggilan ke CreateClient.
Klien bernama
Jika aplikasi memerlukan banyak penggunaan yang berbeda dari HttpClient
, masing-masing dengan konfigurasi yang berbeda, opsinya adalah menggunakan klien bernama. Konfigurasi untuk nama HttpClient
dapat ditentukan selama pendaftaran di Startup.ConfigureServices
.
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Dalam kode sebelumnya, AddHttpClient
dipanggil, memberikan nama github. Klien ini memiliki beberapa konfigurasi default yang diterapkan—yaitu alamat dasar dan dua header yang diperlukan untuk bekerja dengan API GitHub.
Setiap kali CreateClient
dipanggil, instans HttpClient
baru dibuat dan tindakan konfigurasi dipanggil.
Untuk menggunakan klien bernama, parameter string dapat diteruskan ke CreateClient
. Tentukan nama klien yang akan dibuat:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content
.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
Dalam kode sebelumnya, permintaan tidak perlu menentukan nama host. Ini hanya dapat melewati jalur, karena alamat dasar yang dikonfigurasi untuk klien digunakan.
Klien yang berjenis
Klien yang berjenis:
- Memberikan kemampuan yang sama seperti klien bernama tanpa perlu menggunakan string sebagai kunci.
- Menyediakan bantuan IntelliSense dan pengkompilasi saat mengkonsumsi klien.
- Menyediakan satu lokasi untuk mengonfigurasi dan berinteraksi dengan
HttpClient
tertentu. Misalnya, satu klien yang diketik mungkin digunakan untuk satu titik akhir backend dan merangkum semua logika yang berurusan dengan titik akhir tersebut. - Bekerja dengan DI dan dapat disuntikkan jika diperlukan di aplikasi Anda.
Klien yang berjenis menerima parameter HttpClient
dalam konstruktornya:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<GitHubIssue>>();
return result;
}
}
Dalam kode sebelumnya, konfigurasi dipindahkan ke klien yang ditik. Objek HttpClient
diekspos sebagai properti publik. Dimungkinkan untuk menentukan metode khusus API yang mengekspos HttpClient
fungsionalitas. Metode ini GetAspNetDocsIssues
merangkum kode yang diperlukan untuk mengkueri dan mengurai masalah terbuka terbaru dari repositori GitHub.
Untuk mendaftarkan klien yang ditik, metode ekstensi generik AddHttpClient dapat digunakan dalam Startup.ConfigureServices
, menentukan kelas klien yang ditik:
services.AddHttpClient<GitHubService>();
Klien berjenis terdaftar sebagai sementara dengan DI. Klien yang ditik dapat disuntikkan dan dikonsumsi secara langsung:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Jika lebih disukai, konfigurasi untuk klien yang ditik dapat ditentukan selama pendaftaran di Startup.ConfigureServices
, daripada di konstruktor klien yang ditik:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Dimungkinkan untuk sepenuhnya merangkum HttpClient
dalam klien yang ditik. Daripada mengeksposnya sebagai properti, metode publik dapat disediakan yang memanggil HttpClient
instans secara internal.
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<string>>();
return result;
}
}
Dalam kode sebelumnya, HttpClient
disimpan sebagai bidang privat. Semua akses untuk melakukan panggilan eksternal melewati GetRepos
metode .
Klien yang dihasilkan
IHttpClientFactory
dapat digunakan dalam kombinasi dengan pustaka pihak ketiga lainnya seperti Refit. Refit adalah REST pustaka untuk .NET. Ini mengonversi REST API menjadi antarmuka langsung. Implementasi antarmuka dihasilkan secara dinamis oleh RestService
, memakai HttpClient
untuk melakukan panggilan HTTP eksternal.
Antarmuka dan balasan didefinisikan untuk mewakili API eksternal dan responsnya:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Klien berjenis dapat ditambahkan, menggunakan Refit untuk menghasilkan implementasi:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddMvc();
}
Antarmuka yang ditentukan dapat dikonsumsi jika perlu, dengan implementasi yang disediakan oleh DI dan Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Middleware permintaan keluar
HttpClient
sudah memiliki konsep mendelegasikan handler yang dapat ditautkan bersama untuk permintaan HTTP keluar. IHttpClientFactory
membuatnya mudah untuk menentukan handler untuk diterapkan untuk setiap klien bernama. Ini mendukung pendaftaran dan penautan beberapa handler untuk membangun alur middleware permintaan keluar. Masing-masing handler ini dapat melakukan pekerjaan sebelum dan sesudah permintaan keluar. Pola ini mirip dengan alur middleware masuk di ASP.NET Core. Pola ini menyediakan mekanisme untuk mengelola masalah lintas pemotongan seputar permintaan HTTP, termasuk penembolokan, penanganan kesalahan, serialisasi, dan pengelogan.
Untuk membuat handler, tentukan kelas yang berasal dari DelegatingHandler. Ambil alih SendAsync
metode untuk menjalankan kode sebelum meneruskan permintaan ke handler berikutnya di alur:
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(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Kode sebelumnya mendefinisikan handler dasar. Ini memeriksa untuk melihat apakah X-API-KEY
header telah disertakan pada permintaan. Jika header hilang, header dapat menghindari panggilan HTTP dan mengembalikan respons yang sesuai.
Selama pendaftaran, satu atau beberapa handler dapat ditambahkan ke konfigurasi untuk HttpClient
. Tugas ini dicapai melalui metode ekstensi pada IHttpClientBuilder.
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
Dalam kode sebelumnya, ValidateHeaderHandler
terdaftar di DI. Handler harus terdaftar di DI sebagai layanan sementara, tidak pernah tercakup. Jika handler terdaftar sebagai layanan tercakup dan layanan apa pun yang diandalkan handler dapat digunakan:
- Layanan handler dapat dibuang sebelum handler keluar dari cakupan.
- Layanan handler yang dibuang menyebabkan handler gagal.
Setelah terdaftar, AddHttpMessageHandler dapat dipanggil, melewati jenis handler.
Beberapa handler dapat didaftarkan dalam urutan yang harus mereka jalankan. Setiap handler membungkus handler berikutnya hingga final HttpClientHandler
menjalankan permintaan:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Gunakan salah satu pendekatan berikut untuk berbagi status per permintaan dengan penangan pesan:
- Teruskan data ke handler menggunakan
HttpRequestMessage.Properties
. - Gunakan
IHttpContextAccessor
untuk mengakses permintaan saat ini. - Buat objek penyimpanan kustom
AsyncLocal
untuk meneruskan data.
Menggunakan handler berbasis Polly
IHttpClientFactory
terintegrasi dengan pustaka pihak ketiga populer yang disebut Polly. Polly adalah pustaka penanganan kesalahan ketahanan dan sementara yang komprehensif untuk .NET. Hal ini memungkinkan pengembang untuk mengekspresikan kebijakan seperti Retry, Circuit Breaker, Timeout, Bulkhead Isolation, dan Fallback dengan cara yang lancar dan aman di utas.
Metode ekstensi disediakan untuk memungkinkan penggunaan kebijakan Polly dengan instans yang dikonfigurasi HttpClient
. Ekstensi Polly:
- Dukungan menambahkan handler berbasis Polly ke klien.
- Dapat digunakan setelah menginstal paket NuGet Microsoft.Extensions.Http.Polly . Paket tidak disertakan dalam kerangka kerja bersama ASP.NET Core.
Menangani kesalahan sementara
Kesalahan paling umum terjadi ketika panggilan HTTP eksternal bersifat sementara. Metode ekstensi mudah yang disebut AddTransientHttpErrorPolicy
disertakan yang memungkinkan kebijakan didefinisikan untuk menangani kesalahan sementara. Kebijakan yang dikonfigurasi dengan handle HttpRequestException
metode ekstensi ini, respons HTTP 5xx, dan respons HTTP 408.
AddTransientHttpErrorPolicy
Ekstensi dapat digunakan dalam Startup.ConfigureServices
. Ekstensi ini menyediakan akses ke objek yang dikonfigurasi PolicyBuilder
untuk menangani kesalahan yang mewakili kemungkinan kesalahan sementara:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
Dalam kode sebelumnya, WaitAndRetryAsync
kebijakan ditentukan. Permintaan yang gagal dicoba ulang hingga tiga kali dengan penundaan 600 md antar upaya.
Memilih kebijakan secara dinamis
Ada metode ekstensi tambahan yang dapat digunakan untuk menambahkan handler berbasis Polly. Salah satu ekstensi tersebut adalah AddPolicyHandler
, yang memiliki beberapa kelebihan beban. Satu kelebihan beban memungkinkan permintaan diperiksa saat menentukan kebijakan mana yang akan diterapkan:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Dalam kode sebelumnya, jika permintaan keluar adalah HTTP GET, batas waktu 10 detik diterapkan. Untuk metode HTTP lainnya, batas waktu 30 detik digunakan.
Menambahkan beberapa handler Polly
Umum untuk menumpuk kebijakan Polly untuk menyediakan fungsionalitas yang ditingkatkan:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Dalam contoh sebelumnya, dua handler ditambahkan. Yang pertama menggunakan AddTransientHttpErrorPolicy
ekstensi untuk menambahkan kebijakan coba lagi. Permintaan yang gagal dicoba ulang hingga tiga kali. Panggilan kedua untuk AddTransientHttpErrorPolicy
menambahkan kebijakan pemutus sirkuit. Permintaan eksternal lebih lanjut diblokir selama 30 detik jika lima upaya yang gagal terjadi secara berurutan. Kebijakan pemutus sirkuit bersifat stateful. Semua panggilan melalui klien ini memiliki status sirkuit yang sama.
Menambahkan kebijakan dari registri Polly
Pendekatan untuk mengelola kebijakan yang digunakan secara teratur adalah menentukannya sekali dan mendaftarkannya dengan PolicyRegistry
. Metode ekstensi disediakan yang memungkinkan handler ditambahkan menggunakan kebijakan dari registri:
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
Dalam kode sebelumnya, dua kebijakan didaftarkan ketika PolicyRegistry
ditambahkan ke ServiceCollection
. Untuk menggunakan kebijakan dari registri, AddPolicyHandlerFromRegistry
metode ini digunakan, meneruskan nama kebijakan yang akan diterapkan.
Informasi lebih lanjut tentang IHttpClientFactory
dan integrasi Polly dapat ditemukan di wiki Polly.
HttpClient dan manajemen seumur hidup
Instans HttpClient
baru dikembalikan setiap kali CreateClient
dipanggil pada IHttpClientFactory
. Ada klien per HttpMessageHandler bernama. Pabrik mengelola masa pakai instans HttpMessageHandler
.
IHttpClientFactory
mengumpulkan instans HttpMessageHandler
yang dibuat oleh pabrik untuk mengurangi konsumsi sumber daya. Instans HttpMessageHandler
bisa digunakan kembali dari kumpulan saat membuat instans HttpClient
baru jika masa pakainya belum kedaluwarsa.
Pengumpulan handler diinginkan karena setiap handler biasanya mengelola koneksi HTTP yang mendasarnya sendiri. Membuat lebih banyak handler daripada yang dibutuhkan dapat mengakibatkan penundaan koneksi. Beberapa penanganan juga menjaga koneksi tetap terbuka tanpa batas, yang dapat mencegah penanganan bereaksi terhadap perubahan DNS.
Masa pakai handler default adalah dua menit. Nilai default dapat ditimpa berdasarkan klien per bernama. Untuk mengambil alihnya, panggil SetHandlerLifetime IHttpClientBuilder
yang dikembalikan saat membuat klien:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Pembuangan klien tidak diperlukan. Pembuangan membatalkan permintaan keluar dan menjamin instans HttpClient
yang diberikan tidak dapat digunakan setelah memanggil Dispose. IHttpClientFactory
melacak dan membuang sumber daya yang digunakan oleh instans HttpClient
. Instans HttpClient
umumnya dapat diperlakukan sebagai objek .NET yang tidak memerlukan pembuangan.
Menjaga satu instans HttpClient
tetap hidup untuk durasi yang lama adalah pola umum yang digunakan sebelum adanya IHttpClientFactory
. Pola ini menjadi tidak perlu setelah bermigrasi ke IHttpClientFactory
.
Alternatif untuk IHttpClientFactory
Menggunakan IHttpClientFactory
dalam aplikasi yang diaktifkan DI menghindari:
- Masalah kelelahan sumber daya dengan mengumpulkan
HttpMessageHandler
instans. - Masalah DNS kedaluarsa dengan bersepeda
HttpMessageHandler
instans secara berkala.
Ada cara alternatif untuk menyelesaikan masalah sebelumnya menggunakan instans berumur SocketsHttpHandler panjang.
- Buat instans
SocketsHttpHandler
saat aplikasi dimulai dan gunakan untuk masa pakai aplikasi. - Konfigurasikan PooledConnectionLifetime ke nilai yang sesuai berdasarkan waktu refresh DNS.
- Buat
HttpClient
instans menggunakannew HttpClient(handler, disposeHandler: false)
sesuai kebutuhan.
Pendekatan sebelumnya menyelesaikan masalah manajemen sumber daya yang IHttpClientFactory
diselesaikan dengan cara yang sama.
- Berbagi
SocketsHttpHandler
koneksi di seluruhHttpClient
instans. Berbagi ini mencegah kelelahan soket. - Koneksi
SocketsHttpHandler
siklus sesuai untuk menghindari masalah DNS kedaluarsaPooledConnectionLifetime
.
Cookie
Instans yang dikumpulkan HttpMessageHandler
menghasilkan CookieContainer
objek yang dibagikan. Berbagi objek yang tidak diantipi sering CookieContainer
kali menghasilkan kode yang salah. Untuk aplikasi yang memerlukan cookie, pertimbangkan:
- Menonaktifkan penanganan otomatis cookie
- Menghindari
IHttpClientFactory
Panggilan ConfigurePrimaryHttpMessageHandler untuk menonaktifkan penanganan otomatis cookie :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Pencatatan
Klien dibuat melalui IHttpClientFactory
pesan log rekaman untuk semua permintaan. Aktifkan tingkat informasi yang sesuai dalam konfigurasi pengelogan Anda untuk melihat pesan log default. Pengelogan tambahan, seperti pengelogan header permintaan, hanya disertakan pada tingkat pelacakan.
Kategori log yang digunakan untuk setiap klien menyertakan nama klien. Klien bernama MyNamedClient, misalnya, mencatat pesan dengan kategori System.Net.Http.HttpClient.MyNamedClient.LogicalHandler
. Pesan yang diabaikan dengan LogicalHandler terjadi di luar alur penangan permintaan. Pada permintaan, pesan dicatat sebelum penangan lain dalam alur telah memprosesnya. Pada respons, pesan dicatat setelah handler alur lain menerima respons.
Pengelogan juga terjadi di dalam alur handler permintaan. Dalam contoh MyNamedClient, pesan tersebut dicatat terhadap kategori System.Net.Http.HttpClient.MyNamedClient.ClientHandler
log . Untuk permintaan, ini terjadi setelah semua handler lain berjalan dan segera sebelum permintaan dikirimkan di jaringan. Pada respons, pengelogan ini mencakup status respons sebelum melewati kembali melalui alur handler.
Mengaktifkan pengelogan di luar dan di dalam alur memungkinkan inspeksi perubahan yang dilakukan oleh penangan alur lainnya. Ini mungkin termasuk perubahan pada header permintaan, misalnya, atau ke kode status respons.
Menyertakan nama klien dalam kategori log memungkinkan pemfilteran log untuk klien bernama tertentu jika perlu.
Mengonfigurasi HttpMessageHandler
Anda mungkin perlu mengontrol konfigurasi HttpMessageHandler
bagian dalam yang digunakan oleh klien.
IHttpClientBuilder
dikembalikan saat menambahkan klien bernama atau berjenis. Metode ConfigurePrimaryHttpMessageHandler ekstensi dapat digunakan untuk menentukan delegasi. Delegasi dipakai untuk membuat dan mengonfigurasi primer HttpMessageHandler
yang digunakan oleh klien tersebut:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Menggunakan IHttpClientFactory di aplikasi konsol
Di aplikasi konsol, tambahkan referensi paket berikut ke proyek:
Dalam contoh berikut:
- IHttpClientFactory terdaftar dalam kontainer layanan Host Generik.
MyService
membuat instans pabrik klien dari layanan, yang digunakan untuk membuatHttpClient
.HttpClient
digunakan untuk mengambil halaman web.- Metode layanan
GetPage
dijalankan untuk menulis 500 karakter pertama konten halaman web ke konsol. Untuk informasi selengkapnya tentang layanan panggilan dariProgram.Main
, lihat Injeksi dependensi di ASP.NET Core.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware penyebaran header
Penyebaran header adalah middleware yang didukung komunitas untuk menyebarluaskan header HTTP dari permintaan masuk ke permintaan Klien HTTP keluar. Untuk menggunakan penyebaran header:
Referensikan port yang didukung komunitas dari paket HeaderPropagation. ASP.NET Core 3.1 dan yang lebih baru mendukung Microsoft.AspNetCore.HeaderPropagation.
Konfigurasikan middleware dan
HttpClient
diStartup
:public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseMvc(); }
Klien menyertakan header yang dikonfigurasi pada permintaan keluar:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Sumber Daya Tambahan:
ASP.NET Core