
Deep Dive to Delegating Handlers
Http isteklerini yönetmek için yaygın kullanılan HttpClient
gelişmiş senaryolarda tek başına yetersiz kalabilir. Özellikle, istekleri merkezi bir şekilde yönetmek, öncesinde veya sonrasında müdahalelerde bulunmak gerektiğinde DelegatingHandler
devreye girer. DelegatingHandler
middleware gibidir. Ancak HttpClient
ile birlikte çalışır.
DelegatingHandler
sınıfı HttpMessageHandler
sınıfından türeyerek özelleştirilir. Birden fazla DelegatingHandler
zincirleme olaraka kullanılabilir, böylece isteklerin belirli aşamalardan geçmesini sağlayabiliriz.
DelegatingHandler
ile aşağıdaki gibi konuları merkezi bir noktada geliştirebiliriz:
- Logging
- Resiliency
- Authentication
- Caching
Configuring HttpClient
Basit bir typed client
oluşturarak yukarıda belirtilen senaryoları test edebiliriz.
public class XService(HttpClient client)
{
public async Task<string> GetAsync(string id)
{
var url = $"customers/{id}";
return await client.GetFromJsonAsync<string>(url);
}
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient<XService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.x.com");
});
var app = builder.Build();
app.MapGet("api/customers/{id}", async (
string id,
XService service) =>
{
var content = await service.GetAsync(id);
return Results.Ok(content);
});
app.Run();
XService
sınıfı, bir typed client
uygulamasıdır. Program.cs
içerisinde XService
eklenir ve BaseAddress
bilgisi eklenir. HttpClient
ile ilgili daha fazla bilgi için bakınız.
Logging DelegatingHandler
Http
isteği göndermeden önce ve sonra log tutmak isteyebilirsiniz. Bunun için LoggingDelegatingHandler
oluşturacağız. Ek davranış sunmak için SendAsync
metodunu override
edebilirsiniz.
public class LoggingDelegatingHandler(ILogger<LoggingDelegatingHandler> logger)
: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
try
{
logger.LogInformation("Before HTTP request");
var result = await base.SendAsync(request, cancellationToken);
result.EnsureSuccessStatusCode();
logger.LogInformation("After HTTP request");
return result;
}
catch (Exception e)
{
logger.LogError(e, "HTTP request failed");
throw;
}
}
}
Oluşturduğumuz bu delegate için Dependency Injection'a
eklememiz gerekir. Delegating Handler
Transient
olarak register edilmelidir.
builder.Services.AddTransient<LoggingDelegatingHandler>();
Daha sonra typed http client
nesnesine AddHttpMessageHandler
olarak eklemek gerekmektedir.
builder.Services.AddHttpClient<XService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.x.com");
}).AddHttpMessageHandler<LoggingDelegatingHandler>();
XService
her Http
request’i başlattığında LoggingDelegatingHandler
bir middleware
gibi araya girerek tanımlanan log
işlemlerini gerçekleştirecektir.
LoggingDelegatingHandler
içinde bulunan SendAsync
metodu bir sonraki Delegating Handler'a
yönlendirir. Eğer tanımlanan başka bir Delegating Handler
yoksa isteği tamamlayarak response döndürür.
Retry DelegatingHandler
Http
isteklerinde ağ hataları ve/veya geçici sorunlar nedeniyle başarısız olan istekleri tekrar belirli bir sayıda denemek isteyebiliriz.
public class RetryHandler : DelegatingHandler
{
private readonly int _maxRetries;
public RetryHandler(int maxRetries = 3)
{
_maxRetries = maxRetries;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
int retryCount = 0;
HttpResponseMessage response;
do
{
response = await base.SendAsync(request, cancellationToken);
if (response.IsSuccessStatusCode)
return response;
retryCount++;
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)), cancellationToken);
} while (retryCount < _maxRetries);
return response;
}
}
builder.Services.AddHttpClient<XService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.x.com");
}).AddHttpMessageHandler<RetryHandler>();
Caching DelegatingHandler
Sık yapılan Http
isteklerini cache kullanarak daha hızlı hale getirebilirsiniz.
public class CachingHandler : DelegatingHandler
{
private readonly IMemoryCache _cache;
public CachingHandler(IMemoryCache cache)
{
_cache = cache;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_cache.TryGetValue(request.RequestUri.ToString(), out HttpResponseMessage cachedResponse))
{
return cachedResponse;
}
var response = await base.SendAsync(request, cancellationToken);
_cache.Set(request.RequestUri.ToString(), response, TimeSpan.FromMinutes(5));
return response;
}
}
builder.Services.AddHttpClient<XService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.x.com");
}).AddHttpMessageHandler<CachingHandler>();
Request Manipulation DelegatingHandler
DelegatingHandler
bir middleware
gibi çalıştığı için request manipulation yapabilirsiniz. Örneğin her request için muhakkak bir header value barındırmak isteyebilirsiniz. Bu örnekte her request için bir token
ekleyen delegating handler
göstermeye çalışacağım.
public class AuthenticationHandler : DelegatingHandler
{
private readonly ITokenService _tokenService;
public AuthenticationHandler(ITokenService tokenService)
{
_tokenService = tokenService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = await _tokenService.GetTokenAsync();
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
builder.Services.AddHttpClient<XService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.x.com");
}).AddHttpMessageHandler<CachingHandler>();
Delegating Handler
yapısı birden çok kez kullanılabilir. Örneğin oluşturduğunuz herhangi bir client
için delegating handler
sayısını arttırabilirsiniz.
builder.Services.AddHttpClient<XService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.x.com");
}).AddHttpMessageHandler<CachingHandler>()
.AddHttpMessageHandler<LoggingHandler>()
.AddHttpMessageHandler<AuthenticationHandler>();
Tanımlanma sırasına göre delegating handler
çalışacaktır. Her delegating handler
içerisinde bulunan SendAsync
metodu base.SendAsync
diyerek bir sonraki delegate
‘e aktarım yapar. En son başka bir delegate
kalmayınca istek gerçekleştirilir ve response döner.
DelegatingHandler, HttpClient kullanımında merkezi kontrol ve işlem akışı sağlamak için güçlü bir mekanizmadır. Loglama, yetkilendirme, cacheleme ve retry mekanizması gibi kritik işlevleri merkezi bir noktadan yöneterek kod tekrarını önleyebiliriz. 🚀