Bagikan melalui


Membuat permintaan HTTP menggunakan IHttpClientFactory di ASP.NET Core

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 di HttpClient.
  • Mengelola pengumpulan dan masa pakai instans HttpClientMessageHandler yang mendasarinya. Manajemen otomatis menghindari masalah DNS umum (Sistem Nama Domain) yang terjadi saat mengelola HttpClient 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 JSkonten ON 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 HttpClientyang 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 AddHttpClientProgram.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:

  1. Buat instans HttpClient.
  2. Membuat instans GitHubService, meneruskan instans HttpClient 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 ke TodoItemJSAKTIF menggunakan System.Text.Json.
  • Membuat instans StringContent untuk mengemas ON yang diserialisasikan JSuntuk dikirim dalam isi permintaan HTTP.
  • PostAsync Panggilan untuk mengirim JSkonten ON 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. IHttpClientFactorymembuat 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:

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:

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 dan Long, 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 menggunakan new HttpClient(handler, disposeHandler: false) sesuai kebutuhan.

Pendekatan sebelumnya menyelesaikan masalah manajemen sumber daya yang IHttpClientFactory diselesaikan dengan cara yang sama.

  • Berbagi SocketsHttpHandler koneksi di seluruh HttpClient instans. Berbagi ini mencegah kelelahan soket.
  • Koneksi SocketsHttpHandler siklus sesuai untuk menghindari masalah DNS kedaluarsa PooledConnectionLifetime .

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
        });

Cookies

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.
  • GitHubServicediminta dari DI, yang pada gilirannya meminta instans .IHttpClientFactory
  • GitHubService menggunakan untuk membuat instans IHttpClientFactoryHttpClient, 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:

  • Instal paket Microsoft.AspNetCore.HeaderPropagation.

  • Konfigurasikan HttpClient alur middleware dan di Program.cs:

    // Add services to the container.
    builder.Services.AddControllers();
    
    builder.Services.AddHttpClient("PropagateHeaders")
        .AddHeaderPropagation();
    
    builder.Services.AddHeaderPropagation(options =>
    {
        options.Headers.Add("X-TraceId");
    });
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    app.UseHttpsRedirection();
    
    app.UseHeaderPropagation();
    
    app.MapControllers();
    
  • Buat permintaan keluar menggunakan instans yang dikonfigurasi HttpClient , yang mencakup header yang ditambahkan.

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 di HttpClient.
  • Mengelola pengumpulan dan masa pakai instans HttpClientMessageHandler yang mendasarinya. Manajemen otomatis menghindari masalah DNS umum (Sistem Nama Domain) yang terjadi saat mengelola HttpClient 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 JSkonten ON 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 HttpClientyang 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:

  1. Buat instans HttpClient.
  2. Membuat instans GitHubService, meneruskan instans HttpClient 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 ke TodoItemJSAKTIF menggunakan System.Text.Json. Metode ini menggunakan instans JsonSerializerOptions untuk mengonfigurasi proses serialisasi.
  • Membuat instans StringContent untuk mengemas ON yang diserialisasikan JSuntuk dikirim dalam isi permintaan HTTP.
  • PostAsync Panggilan untuk mengirim JSkonten ON 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. IHttpClientFactorymembuat 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:

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:

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:

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 menggunakan new HttpClient(handler, disposeHandler: false) sesuai kebutuhan.

Pendekatan sebelumnya menyelesaikan masalah manajemen sumber daya yang IHttpClientFactory diselesaikan dengan cara yang sama.

  • Berbagi SocketsHttpHandler koneksi di seluruh HttpClient instans. Berbagi ini mencegah kelelahan soket.
  • Koneksi SocketsHttpHandler siklus sesuai untuk menghindari masalah DNS kedaluarsa PooledConnectionLifetime .

Cookies

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 membuat HttpClient. HttpClient digunakan untuk mengambil halaman web.
  • Main membuat cakupan untuk menjalankan metode layanan GetPage 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 di Startup:

    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 di HttpClient.
  • Mengelola pengumpulan dan masa pakai instans HttpClientMessageHandler yang mendasarinya. Manajemen otomatis menghindari masalah DNS umum (Sistem Nama Domain) yang terjadi saat mengelola HttpClient 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 JSkonten ON 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 HttpClientyang 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:

  1. Buat instans HttpClient.
  2. Membuat instans GitHubService, meneruskan instans HttpClient 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 ke TodoItemJSAKTIF menggunakan System.Text.Json. Metode ini menggunakan instans JsonSerializerOptions untuk mengonfigurasi proses serialisasi.
  • Membuat instans StringContent untuk mengemas ON yang diserialisasikan JSuntuk dikirim dalam isi permintaan HTTP.
  • PostAsync Panggilan untuk mengirim JSkonten ON 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. IHttpClientFactorymembuat 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:

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:

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:

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 menggunakan new HttpClient(handler, disposeHandler: false) sesuai kebutuhan.

Pendekatan sebelumnya menyelesaikan masalah manajemen sumber daya yang IHttpClientFactory diselesaikan dengan cara yang sama.

  • Berbagi SocketsHttpHandler koneksi di seluruh HttpClient instans. Berbagi ini mencegah kelelahan soket.
  • Koneksi SocketsHttpHandler siklus sesuai untuk menghindari masalah DNS kedaluarsa PooledConnectionLifetime .

Cookies

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 membuat HttpClient. HttpClient digunakan untuk mengambil halaman web.
  • Main membuat cakupan untuk menjalankan metode layanan GetPage 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 di Startup:

    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 mengelola HttpClient 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:

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 HttpRequestExceptionmetode 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 SetHandlerLifetimeIHttpClientBuilder 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 menggunakan new HttpClient(handler, disposeHandler: false) sesuai kebutuhan.

Pendekatan sebelumnya menyelesaikan masalah manajemen sumber daya yang IHttpClientFactory diselesaikan dengan cara yang sama.

  • Berbagi SocketsHttpHandler koneksi di seluruh HttpClient instans. Berbagi ini mencegah kelelahan soket.
  • Koneksi SocketsHttpHandler siklus sesuai untuk menghindari masalah DNS kedaluarsa PooledConnectionLifetime .

Cookies

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.ClientHandlerlog . 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 membuat HttpClient. 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 dari Program.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 di Startup:

    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: