Dotnet core veya .Net Framework uygulamalarında bir Web servisine erişmek için HttpClient sınıfından bir nesne oluşturulur ve kullanılır. Buraya kadar her şey normal. İşlem bittikten sonra kullanılan bu nesne yok edilir. İşte bu durumda işler biraz karışmaya başlar.
HttiClinet using blok ile kullanımı
public class ToDoClient : IToDoClient
{
public async Task<Todo> Get(int id)
{
using(var client = new HttpClient())
{
BaseAddress = new Uri(
"https://jsonplaceholder.typicode.com");
}
return await client.GetFromAsync<ToDo>(
$"/todos/{id}");
}
}
HttpClient sınıfı, IDisposible interface uyguladığı için, using bloğu ile kullanılabilir. Using bloğunda oluşturulan nesneler, blok sonunda ortadan kaldırılır. Ancak nesnenin kullandığı soket hemen serbest bırakılmaz. Ağır yük altında kullanılabilir soket sayısı azalır hatta tükenebilir. Burada konu ile ilgili bir yazı bulunmaktadır.
Yeniden kullanılabilir HttpClient nesnesi
Bu durumun önüne geçmek için tek bir kez singleton olarak oluşturulan ve paylaşılan bir HttpClient nesnesi kullanılabilir.
public class ToDoClient : IToDoClient
{
private readonly HttpClient client = new ()
{
BaseAddress = new Uri(
"https://jsonplaceholder.typicode.com");
};
public async Task<Todo> Get(int id)
{
return await client.GetFromAsync<ToDo>(
$"/todos/{id}");
}
}
Bu çözüm ise, az sayıda kullanılan kısa ömürlü console uygulamaları için uygun olabilir. Ancak geliştiricilerin karşılaştığı diğer bir sorun, uzun süren işlemlerde paylaşılan bir HttpClient örneği kullanırken ortaya çıkar. HttpClient’in tekil veya statik bir nesne olarak başlatıldığı bir durumda, DNS ip değişiklikleri yapılırsa uygulama hata alır. Örneğin load balancer ile bir DNS birden fazla ip de bulunan sunuculara sırayla yönlendiriliyor olabilir.
builder.Services.AddSingleton<IToDoClient, ToDoClient>();
Bu arada ToDoClient Program.cs içerisinde singleton olarak tanımlanabilir. Eğer Transient veya Scoped olarak kullanılırsa her request sırasında yeniden nesne oluşturacaktır. Bu durumun önüne geçmek için HttpCleint nesnesi static olarak tanımlanabilir.
public class ToDoClient : IToDoClient
{
private static readonly HttpClient client = new ()
{
BaseAddress = new Uri(
"https://jsonplaceholder.typicode.com");
};
public async Task<Todo> Get(int id)
{
return await client.GetFromAsync<ToDo>(
$"/todos/{id}");
}
}
Ancak bu durumda BaseAddress ile tanımlanan DNS TTL süresi bittiğinde domain adı yeni bir ip adresini işaret eder. Ancak kod restart olmadan yeni ip adresini bilemez. Çünkü default HttpClient içerisinde bunu yakalayan bir mekanizma bulunmaz. Bu durumun yakalayabilmek için SockeHttpHandler kullanılabilir.
public class ToDoClient : IToDoClient
{
private SocketsHttpHandler socketHandler = new()
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5)
};
private static readonly HttpClient client = new (socketHandler)
{
BaseAddress = new Uri(
"https://jsonplaceholder.typicode.com");
};
public async Task<Todo> Get(int id)
{
return await client.GetFromAsync<ToDo>(
$"/todos/{id}");
}
}
Ancak bu yöntem de en iyi çözüm yolu değildir. En iyi çözüm yolu .NET Core 2.1 ile birlikte gelen IHttpClientFactory interface kullanmaktır.
IHttpClientFactory Kullanımı
Bu interface kullanmadan önce Program.cs içerisinde bir tanımlama yapılması gerekmektedir.
builder.Services.AddSingleton<IToDoClient, ToDoClient>();
builder.Services.AddHttpClient<IToDoClient, ToDoClient>(client =>
{
client.BaseAddress = new Uri(
"https://jsonplaceholder.typicode.com");
});
Bu düzenleme ile artık HttpClient aşağıdaki gibi kullanılabilir.
public class ToDoClient : IToDoClient
{
private readonly HttpClient _httpClient;
public ToDoClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Todo> Get(int id)
{
return await _httpClient.GetFromAsync<ToDo>(
$"/todos/{id}");
}
}
Bu durumda HttpClient nesnesi IHttpClientFactory tarafından yönetilmektedir. Çünkü ortak bir havuzdaki HttpMessageHandler nesneleri, birden çok HttpClient örneği tarafından yeniden kullanılabilen nesnelerdir. Bu sayede DNS sorunu çözülmektedir.
Named Client kullanımı
Servis binding sırasında AddHttpClient() metoduna bir isim vererk kullanmak mümkündür.
builder.Services.AddSingleton<IToDoClient, ToDoClient>();
builder.Services.AddHttpClient("TodoApi",client =>
{
client.BaseAddress = new Uri(
"https://jsonplaceholder.typicode.com");
});
public class ToDoClient : IToDoClient
{
private readonly IHttpClientFactory _httpClientFactory;
public ToDoClient(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<Todo> Get(int id)
{
var client = _httpClientFactory.CreateClient("ToDoApi");
return await client.GetFromAsync<ToDo>(
$"/todos/{id}");
}
}
Bu sayede doğrudan IHttpClientFactory interface nesneleri ile HttpClient request’leri en verimli şekilde gerçekleştirilebilmektedir.
Kaynaklar
- https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
- https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/