IHttpClientFactory dalam .NET
Dalam artikel ini, Anda akan mempelajari cara menggunakan IHttpClientFactory
antarmuka untuk membuat HttpClient
jenis dengan berbagai dasar -dasar .NET, seperti injeksi dependensi (DI), pengelogan, dan konfigurasi. Jenis HttpClient ini diperkenalkan pada .NET Framework 4.5, yang dirilis pada tahun 2012. Dengan kata lain, tipe ini sudah ada sejak beberapa waktu yang lalu.
HttpClient
digunakan untuk membuat permintaan HTTP dan menangani respons HTTP dari sumber daya web yang diidentifikasi oleh Uri. Protokol HTTP merupakan sebagian besar dari seluruh lalu lintas internet.
Dengan prinsip pengembangan aplikasi modern yang mendorong praktik terbaik, IHttpClientFactory berfungsi sebagai abstraksi pabrik yang bisa membuat instans HttpClient
dengan konfigurasi kustom.
IHttpClientFactory diperkenalkan dalam .NET Core 2.1. Beban kerja .NET berbasis HTTP umum bisa memanfaatkan middleware pihak ketiga yang tangguh dan menangani kesalahan sementara dengan mudah.
Catatan
Jika aplikasi Anda memerlukan cookie, mungkin lebih baik untuk menghindari penggunaan IHttpClientFactory di aplikasi Anda. Untuk cara alternatif mengelola klien, lihat Panduan menggunakan klien HTTP.
Penting
Pengelolaan seumur hidup untuk instans yang dibuat oleh IHttpClientFactory
sepenuhnya berbeda dari instans yang dibuat secara manual. Strateginya adalah menggunakan klien berumur pendek yang dibuat oleh IHttpClientFactory
atau klien berumur panjang dengan PooledConnectionLifetime
penyiapan. Untuk informasi selengkapnya, lihat bagian pengelolaan masa pakai HttpClient dan Panduan untuk menggunakan klien HTTP.
Jenis IHttpClientFactory
Semua kode sumber sampel yang disediakan dalam artikel ini memerlukan penginstalan Microsoft.Extensions.Http
paket NuGet. Selain itu, contoh kode menunjukkan penggunaan permintaan HTTP GET
untuk mengambil objek pengguna Todo
dari API JSON Placeholder gratis.
Saat Anda memanggil salah satu metode ekstensi AddHttpClient, Anda menambahkan IHttpClientFactory
dan layanan terkait ke IServiceCollection. Tipe IHttpClientFactory
menawarkan manfaat berikut ini:
- Mengekspos kelas
HttpClient
sebagai jenis yang siap dengan DI. - Menyediakan lokasi pusat untuk penamaan dan konfigurasi instans
HttpClient
logis. - Mengkodifikasi konsep middleware outbound melalui pendelegasian handler di
HttpClient
. - Menyediakan metode ekstensi untuk middleware berbasis Polly agar dapat memanfaatkan handler pendelegasian di
HttpClient
. - Mengelola penyimpanan sementara dan masa pakai instans dasar HttpClientHandler. Manajemen otomatis menghindari masalah Domain Name System (DNS) yang terjadi saat mengelola masa pakai
HttpClient
secara manual. - Menambahkan pengalaman pengelogan yang bisa dikonfigurasi (melalui ILogger) untuk semua permintaan yang dikirim melalui klien yang dibuat oleh pabrik.
Pola konsumsi
Ada beberapa cara menggunakan IHttpClientFactory
dalam aplikasi:
Pendekatan terbaiknya tergantung pada persyaratan aplikasi.
Penggunaan dasar
Untuk mendaftarkan IHttpClientFactory
, panggil AddHttpClient
:
using Shared;
using BasicHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHttpClient();
builder.Services.AddTransient<TodoService>();
using IHost host = builder.Build();
Mengonsumsi layanan dapat membutuhkan IHttpClientFactory
sebagai parameter konstruktor dengan DI. Kode berikut ini menggunakan IHttpClientFactory
untuk membuat instansHttpClient
:
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;
namespace BasicHttp.Example;
public sealed class TodoService(
IHttpClientFactory httpClientFactory,
ILogger<TodoService> logger)
{
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
// Create the client
using HttpClient client = httpClientFactory.CreateClient();
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo types
Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
$"https://jsonplaceholder.typicode.com/todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
}
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 yang disebutkan
Klien bernama adalah pilihan yang baik saat:
- Aplikasi membutuhkan banyak kegunaan
HttpClient
yang berbeda. - Banyak
HttpClient
instans memiliki konfigurasi yang berbeda.
Konfigurasi untuk nama HttpClient
dapat ditentukan selama pendaftaran pada IServiceCollection
:
using Shared;
using NamedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
string? httpClientName = builder.Configuration["TodoHttpClientName"];
ArgumentException.ThrowIfNullOrEmpty(httpClientName);
builder.Services.AddHttpClient(
httpClientName,
client =>
{
// Set the base address of the named client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
Pada kode sebelumnya, klien dikonfigurasi dengan:
- Nama yang diambil dari konfigurasi di bawah
"TodoHttpClientName"
. - Alamat dasar
https://jsonplaceholder.typicode.com/
. - Sebuah
"User-Agent"
header.
Anda bisa menggunakan konfigurasi untuk menentukan nama klien HTTP, yang sangat membantu untuk menghindari kesalahan nama klien saat menambahkan dan membuat. Pada contoh ini, file appsettings.json digunakan untuk mengonfigurasi nama klien HTTP:
{
"TodoHttpClientName": "JsonPlaceholderApi"
}
Sangat mudah untuk memperluas konfigurasi ini dan menyimpan lebih banyak detail tentang bagaimana Anda ingin memfungsikan klien HTTP Anda. Untuk informasi selengkapnya, lihat Konfigurasi di .NET.
Membuat klien
Setiap kali CreateClient disebut:
- Instans
HttpClient
baru dibuat. - Tindakan konfigurasi telah dipanggil.
Untuk membuat klien bernama, berikan namanya ke dalam CreateClient
:
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared;
namespace NamedHttp.Example;
public sealed class TodoService
{
private readonly IHttpClientFactory _httpClientFactory = null!;
private readonly IConfiguration _configuration = null!;
private readonly ILogger<TodoService> _logger = null!;
public TodoService(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
ILogger<TodoService> logger) =>
(_httpClientFactory, _configuration, _logger) =
(httpClientFactory, configuration, logger);
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
// Create the client
string? httpClientName = _configuration["TodoHttpClientName"];
using HttpClient client = _httpClientFactory.CreateClient(httpClientName ?? "");
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo type
Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
$"todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
_logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
}
Pada kode sebelumnya, permintaan HTTP tidak perlu menentukan nama host. Kode dapat mengirimkan hanya jalurnya karena alamat dasar yang dikonfigurasi untuk klien digunakan.
Klien yang berjenis
Klien yang memiliki tipe:
- Memberikan kemampuan yang sama seperti klien bernama tanpa perlu menggunakan string sebagai kunci.
- Memberikan bantuan IntelliSense dan bantuan kompilator saat menggunakan layanan klien.
- Menyediakan satu lokasi untuk mengonfigurasi dan berinteraksi dengan
HttpClient
tertentu. Misalnya, klien dengan tipe tertentu 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 memiliki tipe menerima parameter HttpClient
dalam konstruktornya.
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;
namespace TypedHttp.Example;
public sealed class TodoService(
HttpClient httpClient,
ILogger<TodoService> logger) : IDisposable
{
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo type
Todo[]? todos = await httpClient.GetFromJsonAsync<Todo[]>(
$"todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
public void Dispose() => httpClient?.Dispose();
}
Dalam kode sebelumnya:
- Konfigurasi diatur ketika client bertipe ditambahkan ke koleksi layanan.
-
HttpClient
ditetapkan sebagai variabel cakupan kelas (bidang), dan digunakan dengan API yang diekspos.
Metode khusus API bisa dibuat yang mengekspos fungsionalitas HttpClient
. Misalnya, metode GetUserTodosAsync
ini merangkum kode untuk mengambil objek Todo
yang khusus untuk pengguna.
Kode berikut memanggil AddHttpClient untuk mendaftarkan kelas klien yang ditik:
using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHttpClient<TodoService>(
client =>
{
// Set the base address of the typed client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
Klien bertipe didaftarkan sebagai transient dengan DI. Dalam kode sebelumnya, AddHttpClient
mendaftarkan TodoService
sebagai layanan sementara. Pendaftaran ini menggunakan metode pabrik untuk:
- Buat instans
HttpClient
. - Buatlah instans
TodoService
, lalu teruskan instansHttpClient
ke konstruktornya.
Penting
Menggunakan klien bertipe dalam layanan *singleton* bisa berbahaya. Untuk informasi selengkapnya, lihat bagian Hindari klien Tipe di layanan singleton.
Catatan
Saat mendaftarkan klien bertipe dengan metode AddHttpClient<TClient>
, tipe TClient
harus memiliki konstruktor yang menerima sebuah HttpClient
sebagai parameter. Selain itu, TClient
jenis tidak boleh didaftarkan ke kontainer DI secara individual, karena hal ini akan menyebabkan pendaftaran berikutnya menimpa yang sebelumnya.
Klien yang telah dihasilkan
IHttpClientFactory
bisa digunakan dalam kombinasi dengan pustaka pihak ketiga seperti Refit. Refit adalah pustaka REST untuk .NET. Refit memungkinkan definisi REST API deklaratif, memetakan metode antarmuka ke titik akhir. Implementasi antarmuka dihasilkan secara dinamis oleh RestService
, memakai HttpClient
untuk melakukan panggilan HTTP eksternal.
Pertimbangkan jenis berikut record
:
namespace Shared;
public record class Todo(
int UserId,
int Id,
string Title,
bool Completed);
Contoh berikut ini bergantung pada paket NuGet Refit.HttpClientFactory
, dan merupakan antarmuka sederhana:
using Refit;
using Shared;
namespace GeneratedHttp.Example;
public interface ITodoService
{
[Get("/todos?userId={userId}")]
Task<Todo[]> GetUserTodosAsync(int userId);
}
Antarmuka C# sebelumnya:
- Menentukan metode bernama
GetUserTodosAsync
yang mengembalikan instansTask<Todo[]>
. - Mendeklarasikan atribut
Refit.GetAttribute
dengan jalur dan string kueri ke API eksternal.
Klien yang diketik dapat ditambahkan dengan menggunakan Refit untuk menghasilkan implementasi.
using GeneratedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Refit;
using Shared;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddRefitClient<ITodoService>()
.ConfigureHttpClient(client =>
{
// Set the base address of the named client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
Antarmuka yang ditentukan bisa digunakan jika diperlukan, dengan implementasi yang disediakan oleh DI dan Refit.
Membuat permintaan POST, PUT, dan DELETE
Pada contoh sebelumnya, semua permintaan HTTP menggunakan kata kerja HTTP GET
.
HttpClient
juga mendukung kata kerja HTTP lainnya, termasuk:
POST
PUT
DELETE
PATCH
Untuk daftar lengkap kata kerja HTTP yang didukung, lihat HttpMethod. Untuk informasi selengkapnya tentang membuat permintaan HTTP, lihat Mengirim permintaan menggunakan HttpClient.
Contoh berikut ini menunjukkan cara melakukan permintaan HTTP POST
:
public async Task CreateItemAsync(Item item)
{
using StringContent json = new(
JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Encoding.UTF8,
MediaTypeNames.Application.Json);
using HttpResponseMessage httpResponse =
await httpClient.PostAsync("/api/items", json);
httpResponse.EnsureSuccessStatusCode();
}
Dalam kode sebelumnya, metode CreateItemAsync
:
- Menserialisasikan parameter
Item
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 ini menunjukkan permintaan HTTP PUT
:
public async Task UpdateItemAsync(Item item)
{
using StringContent json = new(
JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Encoding.UTF8,
MediaTypeNames.Application.Json);
using HttpResponseMessage httpResponse =
await httpClient.PutAsync($"/api/items/{item.Id}", json);
httpResponse.EnsureSuccessStatusCode();
}
Kode sebelumnya sangat mirip dengan contoh POST
. Metode UpdateItemAsync
memanggil PutAsync alih-alih PostAsync
.
Contoh berikut ini menunjukkan permintaan HTTP DELETE
:
public async Task DeleteItemAsync(Guid id)
{
using HttpResponseMessage httpResponse =
await httpClient.DeleteAsync($"/api/items/{id}");
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.
HttpClient
manajemen seumur hidup
Instans HttpClient
baru dikembalikan setiap kali CreateClient
dipanggil pada IHttpClientFactory
. Satu HttpClientHandler instans dibuat per nama klien. Pabrik mengelola masa pakai instans HttpClientHandler
.
IHttpClientFactory
menyimpan dalam cache instans HttpClientHandler
yang dibuat oleh pembuat untuk mengurangi konsumsi sumber daya. Instans HttpClientHandler
dapat digunakan kembali dari cache saat membuat instans baru HttpClient
jika masa pakainya belum kedaluwarsa.
Penembolokan handler diperlukan karena setiap handler biasanya mengelola kumpulan koneksi HTTP miliknya sendiri. Membuat lebih banyak handler daripada yang diperlukan dapat mengakibatkan kelelahan soket dan penundaan koneksi. Beberapa pengendali juga membiarkan koneksi tetap terbuka selamanya, yang dapat mencegah pengendali bereaksi terhadap perubahan DNS.
Masa pakai handler default adalah dua menit. Untuk mengambil alih nilai default, panggil SetHandlerLifetime untuk setiap klien, pada IServiceCollection
:
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Penting
HttpClient
instans yang dibuat oleh IHttpClientFactory
dimaksudkan untuk berumur pendek.
Mendaur ulang dan membuat ulang
HttpMessageHandler
ketika masa pakainya kedaluwarsa sangat penting untukIHttpClientFactory
memastikan handler-handler dapat bereaksi terhadap perubahan DNS.HttpClient
terkait dengan instans handler tertentu setelah pembuatannya, sehingga instans baruHttpClient
harus diminta tepat waktu untuk memastikan klien akan mendapatkan handler yang diperbarui.Pembuangan instans
HttpClient
semacam itu yang dibuat oleh fabrik tidak akan menyebabkan kehabisan soket, karena pembuangannya tidak akan memicu pembuangan dariHttpMessageHandler
.IHttpClientFactory
melacak dan membuang sumber daya yang digunakan untuk membuat instansHttpClient
, khususnya instansHttpMessageHandler
, segera setelah masa pakainya kedaluwarsa dan tidak adaHttpClient
yang menggunakannya lagi.
Menjaga satu HttpClient
instans tetap hidup untuk durasi panjang adalah pola umum yang dapat digunakan sebagai alternatif untuk IHttpClientFactory
, namun, pola ini memerlukan pengaturan tambahan, seperti PooledConnectionLifetime
. Anda dapat menggunakan klien berumur panjang dengan PooledConnectionLifetime
, atau klien berumur pendek yang dibuat oleh IHttpClientFactory
. Untuk informasi tentang strategi mana yang akan digunakan di aplikasi Anda, lihat Panduan untuk menggunakan klien HTTP.
Konfigurasikan HttpMessageHandler
Anda mungkin perlu mengontrol konfigurasi HttpMessageHandler bagian dalam yang digunakan oleh klien.
Sebuah IHttpClientBuilder dikembalikan saat menambahkan klien yang diberi nama atau dengan tipe tertentu. Metode ConfigurePrimaryHttpMessageHandler ekstensi bisa digunakan untuk menentukan delegasi pada IServiceCollection
. Delegasi dipakai untuk membuat dan mengonfigurasi primer HttpMessageHandler
yang digunakan oleh klien tersebut:
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Mengonfigurasi HttClientHandler
memungkinkan Anda menentukan proksi untuk instans di HttpClient
antara berbagai properti handler lainnya. Untuk informasi selengkapnya, lihat Proksi per klien.
Konfigurasi tambahan
Terdapat beberapa opsi konfigurasi tambahan untuk mengontrol IHttpClientHandler
:
Metode | Deskripsi |
---|---|
AddHttpMessageHandler | Menambahkan handler pesan tambahan untuk HttpClient yang bernama. |
AddTypedClient | Mengonfigurasi pengikatan antara TClient dan HttpClient yang diberi nama, yang dikaitkan dengan IHttpClientBuilder . |
ConfigureHttpClient | Menetapkan delegasi yang akan digunakan untuk mengonfigurasi sebuah HttpClient yang bernama. |
ConfigurePrimaryHttpMessageHandler | Mengonfigurasi HttpMessageHandler utama dari kontainer injeksi dependensi untuk HttpClient yang bernama. |
RedactLoggedHeaders | Mengatur koleksi nama header HTTP yang nilainya harus disunting lebih dahulu sebelum masuk. |
SetHandlerLifetime | Mengatur lamanya waktu instans HttpMessageHandler dapat digunakan kembali. Setiap klien bernama dapat memiliki nilai masa pakai handler yang dikonfigurasi sendiri. |
UseSocketsHttpHandler | Mengonfigurasi instans baru atau yang sebelumnya ditambahkan SocketsHttpHandler dari kontainer injeksi dependensi untuk digunakan sebagai handler utama untuk bernama HttpClient . (hanya .NET 5+) |
Menggunakan IHttpClientFactory bersama dengan SocketsHttpHandler
Implementasi SocketsHttpHandler
HttpMessageHandler
ditambahkan dalam .NET Core 2.1, yang memungkinkan PooledConnectionLifetime
untuk dikonfigurasi. Pengaturan ini digunakan untuk memastikan bahwa handler bereaksi terhadap perubahan DNS, jadi menggunakan SocketsHttpHandler
dianggap sebagai alternatif untuk menggunakan IHttpClientFactory
. Untuk informasi selengkapnya, lihat Panduan untuk menggunakan klien HTTP.
Namun, SocketsHttpHandler
dan IHttpClientFactory
dapat digunakan bersama-sama meningkatkan konfigurasi. Dengan menggunakan kedua API ini, Anda mendapat manfaat dari konfigurasi pada tingkat rendah (misalnya, menggunakan LocalCertificateSelectionCallback
untuk pemilihan sertifikat dinamis) dan tingkat tinggi (misalnya, memanfaatkan integrasi DI dan beberapa konfigurasi klien).
Untuk menggunakan kedua API:
- Tentukan
SocketsHttpHandler
sebagaiPrimaryHandler
melalui ConfigurePrimaryHttpMessageHandler, atau UseSocketsHttpHandler (hanya .NET 5+). - Siapkan SocketsHttpHandler.PooledConnectionLifetime berdasarkan interval yang Anda harapkan DNS diperbarui; misalnya, ke nilai yang sebelumnya ada di
HandlerLifetime
. - (Opsional) Karena
SocketsHttpHandler
akan menangani pengumpulan dan daur ulang koneksi, daur ulang handler pada tingkatIHttpClientFactory
tidak diperlukan lagi. Anda dapat menonaktifkannya dengan mengaturHandlerLifetime
keTimeout.InfiniteTimeSpan
.
services.AddHttpClient(name)
.UseSocketsHttpHandler((handler, _) =>
handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2)) // Recreate connection every 2 minutes
.SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime
Dalam contoh di atas, 2 menit dipilih secara segan-segan untuk tujuan ilustrasi, selaras dengan nilai default HandlerLifetime
. Anda harus memilih nilai berdasarkan frekuensi DNS yang diharapkan atau perubahan jaringan lainnya. Untuk informasi selengkapnya, lihat bagian Perilaku DNS di HttpClient
panduan, dan bagian Keterangan dalam PooledConnectionLifetime dokumentasi API.
Hindari klien bertipe dalam layanan singleton
Saat menggunakan pendekatan klien yang dinamai, IHttpClientFactory
disuntikkan ke layanan, dan instans HttpClient
dibuat dengan memanggil CreateClient setiap kali HttpClient
diperlukan.
Namun, dengan pendekatan klien yang diketik, klien yang diketik adalah objek sementara yang biasanya diinjeksi ke dalam layanan. Itu dapat menyebabkan masalah karena klien yang ditik dapat disuntikkan ke dalam layanan singleton.
Penting
Klien bertipe diharapkan berumur pendek dalam arti yang sama dengan HttpClient
instans yang dibuat oleh IHttpClientFactory
(untuk informasi selengkapnya, lihat HttpClient
manajemen siklus hidup). Segera setelah instans klien yang diketik dibuat, IHttpClientFactory
tidak memiliki kontrol atasnya. Jika instans klien bertipe ditangkap dalam singleton, instans klien dapat mencegahnya bereaksi terhadap perubahan DNS, menggagalkan salah satu tujuan IHttpClientFactory
.
Jika Anda perlu menggunakan HttpClient
instance dalam layanan singleton, pertimbangkan opsi berikut:
- Gunakan pendekatan klien bernama sebagai gantinya, dengan menyuntikkan
IHttpClientFactory
ke dalam layanan singleton dan membuat ulang instansHttpClient
bila perlu. - Jika Anda memerlukan pendekatan klien bertipe, gunakan
SocketsHttpHandler
denganPooledConnectionLifetime
yang telah dikonfigurasi sebagai penangan utama. Untuk informasi selengkapnya tentang menggunakanSocketsHttpHandler
denganIHttpClientFactory
, lihat bagian Menggunakan IHttpClientFactory bersama dengan SocketsHttpHandler.
Cakupan Pengelola Pesan di IHttpClientFactory
IHttpClientFactory
membuat cakupan DI terpisah untuk setiap instans HttpMessageHandler
. Cakupan DI ini terpisah dari cakupan DI aplikasi (misalnya, ASP.NET cakupan permintaan masuk, atau cakupan DI manual yang dibuat pengguna), sehingga mereka tidak akan berbagi instans layanan tercakup. Cakupan Pengendali Pesan terikat dengan masa pakai pengendali dan dapat melampaui cakupan aplikasi, yang dapat menyebabkan, misalnya, pengulangan penggunaan instans yang sama dengan dependensi lingkup yang disuntikkan sama di antara beberapa permintaan yang masuk.
Pengguna sangat disarankan untuk tidak menyimpan informasi terkait cakupan (seperti data dari HttpContext
) di dalam HttpMessageHandler
instans dan menggunakan dependensi terlingkup dengan hati-hati untuk menghindari kebocoran informasi sensitif.
Jika Anda memerlukan akses ke cakupan DI dari aplikasi dari handler pesan Anda, untuk otentikasi sebagai contoh, Anda akan merangkum logika yang mempertimbangkan cakupan dalam sementara DelegatingHandler
terpisah, dan membungkusnya di sekitar instans HttpMessageHandler
yang berasal dari cache IHttpClientFactory
. Untuk mengakses handler panggilan IHttpMessageHandlerFactory.CreateHandler bagi setiap klien yang bernama terdaftar. Dalam hal ini, Anda akan membuat instance HttpClient
sendiri menggunakan pengolah yang dibangun.
Contoh berikut menunjukkan pembuatan HttpClient
dengan DelegatingHandler
yang sadar akan cakupan:
if (scopeAwareHandlerType != null)
{
if (!typeof(DelegatingHandler).IsAssignableFrom(scopeAwareHandlerType))
{
throw new ArgumentException($"""
Scope aware HttpHandler {scopeAwareHandlerType.Name} should
be assignable to DelegatingHandler
""");
}
// Create top-most delegating handler with scoped dependencies
scopeAwareHandler = (DelegatingHandler)_scopeServiceProvider.GetRequiredService(scopeAwareHandlerType); // should be transient
if (scopeAwareHandler.InnerHandler != null)
{
throw new ArgumentException($"""
Inner handler of a delegating handler {scopeAwareHandlerType.Name} should be null.
Scope aware HttpHandler should be registered as Transient.
""");
}
}
// Get or create HttpMessageHandler from HttpClientFactory
HttpMessageHandler handler = _httpMessageHandlerFactory.CreateHandler(name);
if (scopeAwareHandler != null)
{
scopeAwareHandler.InnerHandler = handler;
handler = scopeAwareHandler;
}
HttpClient client = new(handler);
Solusi lebih lanjut dapat diterapkan dengan metode ekstensi untuk mendaftarkan DelegatingHandler
yang sadar cakupan dan menggantikan pendaftaran default IHttpClientFactory
dengan layanan sementara yang memiliki akses ke cakupan aplikasi saat ini.
public static IHttpClientBuilder AddScopeAwareHttpHandler<THandler>(
this IHttpClientBuilder builder) where THandler : DelegatingHandler
{
builder.Services.TryAddTransient<THandler>();
if (!builder.Services.Any(sd => sd.ImplementationType == typeof(ScopeAwareHttpClientFactory)))
{
// Override default IHttpClientFactory registration
builder.Services.AddTransient<IHttpClientFactory, ScopeAwareHttpClientFactory>();
}
builder.Services.Configure<ScopeAwareHttpClientFactoryOptions>(
builder.Name, options => options.HttpHandlerType = typeof(THandler));
return builder;
}
Untuk informasi selengkapnya, lihat contoh lengkapnya.
Hindari bergantung pada Handler Utama "factory-default"
Di bagian ini, istilah "factory-default" Primary Handler mengacu pada Primary Handler yang ditetapkan oleh implementasi default IHttpClientFactory
(atau lebih tepatnya, implementasi default HttpMessageHandlerBuilder
) jika tidak dikonfigurasi dengan cara apa pun.
Catatan
"Handler Utama standar pabrik adalah detail implementasi dan dapat berubah."
❌ AVOID tergantung pada implementasi tertentu yang digunakan sebagai "default pabrik" (misalnya, HttpClientHandler
).
Ada kasus di mana Anda perlu mengetahui jenis tertentu dari Pengendali Utama, terutama jika mengerjakan perpustakaan kelas. Saat mempertahankan konfigurasi pengguna akhir, Anda mungkin ingin memperbarui properti khusus seperti HttpClientHandler
, ClientCertificates
, UseCookies
, dan UseProxy
. Mungkin menggoda untuk mengubah handler Utama ke HttpClientHandler
, yang kebetulan bekerja saat HttpClientHandler
digunakan sebagai Handler Utama "factory-default". Tetapi karena kode apa pun tergantung pada detail implementasi, solusi seperti itu rapuh dan cenderung rusak.
Alih-alih mengandalkan Handler Utama "factory-default", Anda dapat menggunakan ConfigureHttpClientDefaults
untuk menyiapkan instans Handler Utama default "tingkat aplikasi":
// Contract with the end-user: Only HttpClientHandler is supported.
// --- "Pre-configure" stage ---
// The default is fixed as HttpClientHandler to avoid depending on the "factory-default"
// Primary Handler.
services.ConfigureHttpClientDefaults(b =>
b.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { UseCookies = false }));
// --- "End-user" stage ---
// IHttpClientBuilder builder = services.AddHttpClient("test", /* ... */);
// ...
// --- "Post-configure" stage ---
// The code can rely on the contract, and cast to HttpClientHandler only.
builder.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
{
if (handler is not HttpClientHandler h)
{
throw new InvalidOperationException("Only HttpClientHandler is supported");
}
h.ClientCertificates.Add(GetClientCert(provider, builder.Name));
//X509Certificate2 GetClientCert(IServiceProvider p, string name) { ... }
});
Atau, Anda dapat mempertimbangkan untuk memeriksa jenis Handler Utama, dan mengonfigurasi spesifikasi seperti sertifikat klien hanya dalam jenis pendukung terkenal (kemungkinan besar, HttpClientHandler
dan SocketsHttpHandler
):
// --- "End-user" stage ---
// IHttpClientBuilder builder = services.AddHttpClient("test", /* ... */);
// ...
// --- "Post-configure" stage ---
// No contract is in place. Trying to configure main handler types supporting client
// certs, logging and skipping otherwise.
builder.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
{
if (handler is HttpClientHandler h)
{
h.ClientCertificates.Add(GetClientCert(provider, builder.Name));
}
else if (handler is SocketsHttpHandler s)
{
s.SslOptions ??= new System.Net.Security.SslClientAuthenticationOptions();
s.SslOptions.ClientCertificates ??= new X509CertificateCollection();
s.SslOptions.ClientCertificates!.Add(GetClientCert(provider, builder.Name));
}
else
{
// Log warning
}
//X509Certificate2 GetClientCert(IServiceProvider p, string name) { ... }
});