IHttpClientFactory dengan .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
Manajemen HttpClient
instans seumur hidup yang dibuat sama sekali berbeda dari IHttpClientFactory
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 manajemen seumur hidup 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 Tempat Penampung {JSON} 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 keluar via pendelegasian handler di
HttpClient
. - Jenis ini juga menyediakan ekstensi untuk middleware berbasis Polly untuk memanfaatkan pendelegasian handler di
HttpClient
. - Mengelola penembolokan dan masa pakai instans yang mendasarinya 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();
Menggunakan 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 bernama
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 ditarik dari konfigurasi di bawah
"TodoHttpClientName"
. - Alamat dasar
https://jsonplaceholder.typicode.com/
. - Header
"User-Agent"
.
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 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 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.
- Memberikan bantuan IntelliSense dan pengkompilasi saat menggunakan 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:
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 klien berjenis 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 ini GetUserTodosAsync
merangkum kode untuk mengambil objek khusus Todo
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 berjenis terdaftar sebagai sementara dengan DI. Dalam kode sebelumnya, AddHttpClient
mendaftarkan TodoService
sebagai layanan sementara. Pendaftaran ini menggunakan metode pabrik untuk:
- Buat instans
HttpClient
. - Membuat instans
TodoService
, meneruskan instansHttpClient
ke konstruktornya.
Penting
Menggunakan klien yang dititik dalam layanan singleton bisa berbahaya. Untuk informasi selengkapnya, lihat bagian Hindari klien Yang Ditik di layanan singleton.
Catatan
Saat mendaftarkan klien yang ditik dengan AddHttpClient<TClient>
metode , TClient
jenis harus memiliki konstruktor yang menerima HttpClient
sebagai parameter. Selain itu, TClient
jenis tidak boleh didaftarkan ke kontainer DI secara terpisah, karena ini akan menyebabkan pendaftaran selanjutnya menimpa yang pertama.
Klien yang 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 berjenis dapat ditambahkan, 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
cache instans yang HttpClientHandler
dibuat oleh pabrik untuk mengurangi konsumsi sumber daya. Instans HttpClientHandler
dapat digunakan kembali dari cache saat membuat instans baru HttpClient
jika masa pakainya belum kedaluwarsa.
Penembolokan handler diinginkan karena setiap handler biasanya mengelola kumpulan koneksi HTTP yang mendasarnya sendiri. Membuat lebih banyak handler daripada yang diperlukan dapat mengakibatkan kelelahan soket dan 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. 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
HttpMessageHandler
ulang ketika masa pakainya kedaluwarsa sangat penting untukIHttpClientFactory
memastikan handler 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.Membuang instans seperti
HttpClient
itu yang dibuat oleh pabrikHttpMessageHandler
IHttpClientFactory
melacak dan membuang sumber daya yang digunakan untuk membuatHttpClient
instans, khususnyaHttpMessageHandler
instans, segera setelah masa pakainya kedaluwarsa dan tidakHttpClient
ada penggunaannya 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 berumurIHttpClientFactory
Untuk informasi tentang strategi mana yang akan digunakan di aplikasi Anda, lihat Panduan untuk menggunakan klien HTTP.
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 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 bernama. |
AddTypedClient | Mengonfigurasi pengikatan antara TClient dan HttpClient bernama yang terkait dengan IHttpClientBuilder . |
ConfigureHttpClient | Menambahkan delegasi yang akan digunakan untuk mengonfigurasi HttpClient bernama. |
ConfigurePrimaryHttpMessageHandler | Mengonfigurasi primer HttpMessageHandler dari kontainer injeksi dependensi untuk HttpClient 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 diIHttpClientFactory
tingkat tidak lagi diperlukan. 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 yang dititik dalam layanan singleton
Saat menggunakan pendekatan klien bernama, IHttpClientFactory
disuntikkan ke dalam layanan, dan HttpClient
instans dibuat dengan memanggil CreateClient setiap kali HttpClient
diperlukan.
Namun, dengan pendekatan klien yang ditik, klien yang ditik adalah objek sementara yang biasanya disuntikkan ke dalam layanan. Itu dapat menyebabkan masalah karena klien yang ditik dapat disuntikkan ke dalam layanan singleton.
Penting
Klien yang ditik diharapkan berumur pendek dalam arti yang sama dengan HttpClient
instans yang dibuat oleh IHttpClientFactory
(untuk informasi selengkapnya, lihat HttpClient
manajemen seumur hidup). Segera setelah instans klien yang diketik dibuat, IHttpClientFactory
tidak memiliki kontrol atasnya. Jika instans klien yang ditik diambil dalam satuton, instans klien dapat mencegahnya bereaksi terhadap perubahan DNS, mengalahkan salah satu tujuan .IHttpClientFactory
Jika Anda perlu menggunakan HttpClient
instans dalam layanan singleton, pertimbangkan opsi berikut:
- Gunakan pendekatan klien bernama sebagai gantinya, menyuntikkan
IHttpClientFactory
layanan singleton dan membuatHttpClient
ulang instans bila perlu. - Jika Anda memerlukan pendekatan klien yang ditik
PooledConnectionLifetime
Untuk informasi selengkapnya tentang menggunakanSocketsHttpHandler
denganIHttpClientFactory
, lihat bagian Menggunakan IHttpClientFactory bersama dengan SocketsHttpHandler.
Cakupan Handler Pesan di IHttpClientFactory
IHttpClientFactory
membuat cakupan DI terpisah per setiap HttpMessageHandler
instans. 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 Handler Pesan terkait dengan masa pakai handler dan dapat mengeluar cakupan aplikasi, yang dapat menyebabkan, misalnya, menggunakan kembali instans yang sama HttpMessageHandler
dengan dependensi tercakup yang disuntikkan yang sama antara beberapa permintaan 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 aplikasi dari handler pesan Anda, untuk autentikasi sebagai contoh, Anda akan merangkum logika sadar cakupan dalam sementara DelegatingHandler
terpisah, dan membungkusnya di sekitar HttpMessageHandler
instans dari IHttpClientFactory
cache. Untuk mengakses panggilan IHttpMessageHandlerFactory.CreateHandler handler untuk setiap klien bernama terdaftar. Dalam hal ini, Anda akan membuat HttpClient
instans sendiri menggunakan handler yang dibangun.
Contoh berikut menunjukkan pembuatan HttpClient
dengan cakupan sadar DelegatingHandler
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 diikuti dengan metode ekstensi untuk mendaftarkan pendaftaran default DelegatingHandler
yang sadar IHttpClientFactory
cakupan dan mengambil alih oleh layanan sementara dengan 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 mentransmisikan handler Utama ke HttpClientHandler
, yang terjadi pada pekerjaan saat HttpClientHandler
digunakan sebagai Handler Utama "factory-default". Tetapi karena kode apa pun tergantung pada detail implementasi, solusi seperti itu rapuh dan terikat untuk 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) { ... }
});