İlkay İlknur

ASP.NET Core Uygulamalarında IHttpClientFactory Kullanımı

Temmuz 06, 2020

Uygulamalarımızda en sık kullandığımız tiplerden biri de şüphesiz ki HttpClient. Bu yazıda ise özellikle ASP.NET Core uygulamalarımızda HttpClient tipini nasıl en doğru şekilde kullanabiliriz konusunu inceleyeceğiz.

İlk olarak HttpClient ile ilgili doğru bilinen bir yanlıştan bahsedelim. HttpClient tipine baktığımızda IDisposable interface'ini implemente ettiğini görüyoruz. Bu nedenle HttpClient ile işimiz bittiğinde aynı Stream vb.. gibi tiplerde olduğu gibi dispose etmemiz gerektiğini düşünebilirsiniz.

public IActionResult Index()
{
    using (var client = new HttpClient())
    {
        var response = client.GetStringAsync("http://www.fakeapi.com");
    }
    return View();
}

Bu kullanımdaki sıkıntı HttpClient nesnesi dispose olduğunda arka planda kullandığı socketin de aynı anda serbest bırakılmaması. Bu nedenle yüksek request alan API veya web sayfalarında bu şekilde bir kullanımda bulunulması socketlerin tükenmesi gibi hataların oluşmasına neden olmakta. Bu hataların önüne geçmek ve daha etkin çalışmak için uygulama içerisinde HttpClient tipini singleton veya statik kullanmak tavsiye edilen bir yöntem. YOU'RE USING HTTPCLIENT WRONG AND IT IS DESTABILIZING YOUR SOFTWARE

Bu yöntemin de beraberinde getirdiği bir sorun var. O da HttpClient'ın DNS değişikliklerini algılamaması. Uzun süren processler içerisinde bu şekilde bir kullanım yaptığınızda oluşacak DNS değişikliklerinden HttpClient malesef haberdar olmuyor. Bunun nedeni HttpClient'ın default constructorının arka planda yarattığı HttpMessageHandler. Github issue

Hem bu gibi DNS güncelleme sorunlarının önüne geçmek için hem de HttpClient konfigurasyonunu merkezileştirilmesi için .NET Core 2.1 ile beraber gelen IHttpClientClientFactory'i kullabiliriz. IHttpClientFactory'nin birden fazla farklı kullanım şekli var. Öncelikle en basit olanından başlayalım.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
    services.AddControllers();
}
public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IHttpClientFactory factory;
 
    public HomeController(ILogger<HomeController> logger, IHttpClientFactory factory)
    {
        _logger = logger;
        this.factory = factory;
    }
 
    public IActionResult Index()
    {
        var client = factory.CreateClient();
        var response = client.GetStringAsync("http://www.fakeapi.com");
        return View();
    }
}

En basit kullanım yukarıda gördüğümüz gibi factory üzerinden CreateClient metodu ile yaratılan HttpClient'ı kullanmak. Bu şekilde bir kullanımda sürekli olarak yeni bir HttpClient yaratılıyor diye düşünebilirsiniz. Bu doğru. Ancak socket yönetimi yapılan ve DNS değişikliklerini de algılayan kısım olan HttpClientMessageHandler'lar için pooling yapılıyor ve lifetimeları otomatik olarak yönetiliyor. Bu nedenle de manuel olarak HttpClient yaratılırken oluşan sorunlar burada olmuyor.

IHttpClientFactory'nin bir diğer kullanım şekli ise named clientlar. Basitçe anlatmamız gerekirse HttpClient'a string olarak bir isim veriyoruz ve konfigürasyonunu tanımlıyoruz. Sonrasında ise verdiğimiz ismi parametre geçerek ilgili konfigürasyondaki HttpClient'ı kullanabiliyoruz.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("fakeApi", (client) =>
    {
        client.BaseAddress = new Uri("https://fakeapi.com");
        client.DefaultRequestHeaders.Add("Api-Version""1.0");
    });
    services.AddControllers();
}
public IActionResult Index()
{
    var client = factory.CreateClient("fakeApi");
    var response = client.GetStringAsync("http://www.fakeapi.com");
    return View();
}

Bir diğer kullanım metodu ise typed client olarak isimlendirilen kullanım. Burada HttpClient'lara string olarak key vermek yerine kendi yarattığımız bir client tipi üzerinden kullanımda bulunuyoruz. Böylece named clientları kullanırken yanlış isim verme gibi sorunların önüne geçiyoruz ve doğrudan hataları build sırasında alıyoruz.

public class FakeApiClient
{
    public HttpClient Client { get; }
    public FakeApiClient(HttpClient client)
    {
        client.BaseAddress = new Uri("https://fakeapi.com");
        client.DefaultRequestHeaders.Add("Api-Version""1.0");
        Client = client;
    }
 
    public Task<string> GetAsync()
    {
        return Client.GetStringAsync("/api/weather");
    }
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<FakeApiClient>();
    services.AddControllers();
}

Tanımlanan typed clientın kullanımı

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly FakeApiClient apiClient;
 
    public HomeController(ILogger<HomeController> logger, FakeApiClient apiClient)
    {
        _logger = logger;
        this.apiClient = apiClient;
    }
 
    public async Task<IActionResult> Index()
    {
        var response = await apiClient.GetAsync();
        return View();
    }
}

IHttpClientFactory aynı zamanda third-party clientlarla da kullanılabilmekte. Detaylı bilgi için buraya bakabilirsiniz.

IHttpClientFactory üzerinden Polly kütüphanesini kullanıp retry mekanizmalarını da yönetebiliyoruz. Bunun için öncelikle Microsoft.Extensions.Http.Polly nuget paketini yüklememiz gerekiyor. Sonrasında örneğin transient errorlar için aşağıdaki gibi policyler tanımlayabiliyoruz.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<FakeApiClient>()
            .AddTransientHttpErrorPolicy(t => t.WaitAndRetryAsync(3, (p)=>TimeSpan.FromMilliseconds(500)));
    services.AddControllers();
}

Polly tabanlı policyler için daha detaylı bilgi almak isterseniz buraya bakabilirsiniz.

Bu yazıda ASP.NET Core uygulamalarında HttpClient tipini en doğru şekilde nasıl kullanırız konusunu inceledik.

Bir sonraki yazıda görüşmek üzere,