Photo by Amy Lister on Unsplash

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. 🚀

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Furkan Güngör
Furkan Güngör

Written by Furkan Güngör

Solution Developer — I want to change the world, give me the source code.

No responses yet

Write a response