Asp.Net Core Birden Fazla DelegatingHandler Kullanma ve Request LifeCycle
Selamlar,
Bir önceki yazımda tanıtmış olduğum DelegatingHandler ların kullanımı ve uygulamamızda clienttan gidecek olan requestlerin lifecycle ı arasına nasıl girdiğini görmüştük.
DelegatingHandler ların çalışma yapısı olarak Asp.Net Core Middleware lara benzediğinden bahsetmiştik. Yani request in lifecycle ına eklediğimi sıra ile request server tarafına gitmeden önce çalışırlar, response dönüşünde de ters sıra ile çalışıp client a geri dönerler. (Tabi herhangi bir delegatinghandler ile request akışını kesip bir sonrakilere ulaşmdan biz kendimiz de geriye bir httpresponsemessage dönebiliriz, bir önceki örneğimizde bunu görmüştük)
Aşağıdaki resimde görsel olarak bir request response akışı sırasında DelegatingHandler ların ne şekilde akışa dahil olduğuna bir bakalım.
Resimde gördüğümüz gibi asıl HttpHandler a request ulaşmadan önce, bizim request in pipeline ına eklediğimiz sıra ile tüm delegatinghandler lar dan request imiz geçerek ilerliyor, response alındıktan sonrada ilk önce HttpClientHandler lar dan sonra eklediğimiz sıra ters yönde geri dönüyor.
Peki biz de birden çok delegating handler örneği kullanımına bir bakalım.
Bir önceki örneğimizde AuthTokenHandler dan başka olarak bir de loglama ve Timing i kontrol etmemiz için bir de Timer Handler ekleyelim.
Timing DelegatingHandler ımız şu şekilde;
public class TimingHandler : DelegatingHandler { private readonly ILogger _logger; public TimingHandler(ILogger logger) { _logger = logger; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var sw = Stopwatch.StartNew(); _logger.LogInformation("Starting request"); var response = await base.SendAsync(request, cancellationToken); _logger.LogInformation($"Finished request in {sw.ElapsedMilliseconds}ms"); return response; } }
AuthTokenHandler ımız da aşağıdaki gibi
public class AuthTokenHandler : DelegatingHandler { private readonly string _token; private const string TOKEN = "Authorization"; public AuthTokenHandler(string token) { _token = token; } protected override async Task<HttpResponeMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (!request.Headers.Contains(TOKEN) && string.IsNullOrEmpty(_token)) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent("Missing auth token.") }; } else if (!request.Headers.Contains(TOKEN) && !string.IsNullOrEmpty(_token)) { request.Headers.Add(TOKEN, $"Bearer {_token}"); } var response = await base.SendAsync(request, cancellationToken); return response; } }
Log Handler ımız da aşağıdaki gibi.
public class LogHandler : DelegatingHandler { private readonly ILogger _logger; public LogHandler(ILogger logger) { _logger = logger; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var content = await request.Content.ReadAsStringAsync(); Debug.Write($"{Environment.NewLine} ApiLog Request Path: {request.RequestUri.AbsolutePath}"); Debug.Write($"{Environment.NewLine} ApiLog Request Uri: {request.RequestUri.AbsoluteUri}"); Debug.Write($"{Environment.NewLine} ApiLog Request Query: {request.RequestUri.Query}"); Debug.Write($"{Environment.NewLine} ApiLog Request Form Data: {content}"); var response = await base.SendAsync(request, cancellationToken); if (response.StatusCode == System.Net.HttpStatusCode.OK) { var responseContent = await response.Content.ReadAsStringAsync(); Debug.Write($"{Environment.NewLine} ApiLog Response Data: {responseContent}"); Debug.Write($"{Environment.NewLine} ApiLog Response status code: {response.StatusCode}"); } return response; } }
Tüm handlerlarımızı tanımladıktan sonra, hepsini AspNet Core un dependencyinjection container ına ekleyip aşağıdaki gibi request in pipeline ına çalışmasını istediğmiz sırası ile ekliyoruz.
public void ConfigureServices(IServiceCollection services) { services.Configure(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddTransient<AuthTokenHandler>(); services.AddHttpMessageHandler<TimingHandler>(); services.AddHttpMessageHandler<LogHandler>(); services.AddHttpClient("randomuserclient", client => { client.BaseAddress = new Uri("https://randomuser.me/api"); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting"); }) .AddHttpMessageHandler<TimingHandler>(); .AddHttpMessageHandler<AuhTokenHandler>(); .AddHttpMessageHandler<LogHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
Aşağıdaki gibi normal client ımızı kullanıyoruz.
public class HomeController : Controller { private readonly IHttpClientFactory _httpClientFactory; public HomeController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task Index() { var client = _httpClientFactory.CreateClient("randomuserclient"); var result = await client.GetStringAsync("?results=5"); return View(); }
Burada işleyiş nasıl olacağını bir özetleyelim
- Request önce TimingHandler ımıza gelecek, Burada stopwatch çalışacak ve base.SendAsync dediğimiz noktadan itibaren kodun devamı çalışmadan bir sonraki handler ımıza geçecek
- Pipeline eklediğimiz sıradaki handler ımız olan AuthToken Handler ımıza request gelecek. Burada Header kontrolü yapılıp yeni token değeri request in header ına eklenecek ve yine base.SendAsync dediğimiz anda bir sonraki handler ımıza geçecek
- Request in pipeline ına eklediğimiz son handler olan Loghandler a request gelecek. burada da base.SendAsync dediğimiz andan itibaren artık request HttpClientHandler a gelecek ve request artık bizden çıkıp ilgili API ye gidecek
- Sonrasonda HtpResponseMessage ı aldığımızda, HttpClientHandler response u karşıladıktan sonra ilk olarak pipelindaki son handler ımız olan LogHandler ın base.SendAsync kısmındaki sonraki kodlar çalışacak
- Buradaki kodlar tamamlandıktan sonra bir önceki handler olan AuthToken Handler a response gelecek ve yine base.SendAsync dan sonraki kodlar çalışacak.
- Bu kısım da bittikten sonra ilk handlerımıza response gelecek ve yine base.SendAsync kısmındaki sonraki kodlar çalışacak ve en nihayetinde request i attığımız caller metoda response ulaşacak.
Bir sonraki yazım da görüşmek üzere.