Erhan Ballıeker

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.

Capture

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.

Asp.Net Core DelegatingHandler

Selamlar,

Son yazılarımda Asp.Net Core 2.1 ile gelen HttpClientFactory ve bu api ın çeşitli kütüphaneler ile kullanımından bahsetmiştim.

Bu yazımda da yine clientfactory ile alakalı olarak DeletagingHandler dan bahsetmek istiyorum. Daha önce de çeşitli yollarla muhtemelen yapmış olduğunuz bir şeyi delegatinghandler lar ile nasıl daha kolay yapabiliriz bunu görücez.

DelegatingHandler için ilk söyleyeceğim şey Asp.Net Core daki Middleware yapısına olan benzerliğidir. Middleware yapısında nasıl ki browserdan server mıza kadar gelen request in yaşam döngüsü içerisinde, Kestret ayağa kalktıktan sonra, request in geçeceği middleware leri ayarlıyorsak, burada da aynı şekilde, HttpClientFactory ile request attığımız client ımız için, gidecek olan request in arasına istediğimiz şekilde ve farklı delegatinghandler la ile girip, request i yarı da kesmekten tutun da, response ı tamamen değiştirmeye kadar herşeyi yapabiliyoruz.

Aşağıda basit bir DelegatingHandler örneğine bakalım.

   public class AuthTokenHandler : DelegatingHandler
    {
        private readonly string _token;
        private const string TOKEN = "Authorization";
        public AuthTokenHandler(string token)
        {
            _token = token;
        }

        protected override async Task 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;
        }
    }

Yukarıda yazılan DelegatingHandler ı inceleyelim.

  • Yazdığımız DelegatingHandler ımız  DelegatingHandler sınıfından türemek durumunda
  • protected override async Task SendAsync metodunun override ederek istediğimiz işlemleri request in öncesi ve sonrasında yapabiliyoruz.
  • Bu metod içerisinde await base.SendAsync(request, cancellationToken); 
    • kodundan önceki kodlar request üzerinde yapacağımız işlemler
    • sonraki kodlar ise gelen response üzerinde yapmak isteyeceğimiz kısımlardır.
  • Bu handler ımız gelen request içerisinde Authorization isminde bir header arıyor. Eğer bu handler a paramere olarak geçilmiş olan token değerini bulamaz ise geriye request i bir sonraki aşamaya bile göndermeden(tıpkı middleware lardki shorcut yapısı gibi) geriye doğrudan response u dönüyor.
  • Eğer handler a geçilen parametre boş değilse bir token değeri varsa bu token değerini, Authorization header içerisine Bearer token olarak koyuyor.

Bu şekilde bir api ile haberleşirken sıkça karşılaşığımız gibi token based authantication yönetimi için her bir request ile header da token yollama işini çok basit şekilde halledebilmiş oluyoruz.

Peki bu yazmış olduğumuz Delegating handler ı request in pipeline ına nasıl ekleyeceğiz buna bakalım.

   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.AddHttpClient("randomuserclient", client =>
            {
                client.BaseAddress = new Uri("https://randomuser.me/api");
                client.DefaultRequestHeaders.Add("Accept", "application/json");
                client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting");
            })
            .AddHttpMessageHandler<AuthTokenHandler>();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

Geçtiğimiz yazılarımızda HttpClientFactory ve bunun asp.net core projelerinde kullanımına değindiğim için fazla detaya girmeyeceğim bu tarafta.

Buradan httpclientfactory ile ilgili yazdığım yazıların detaylarına ulaşabilirsiniz

Burada basitçe bir namedclient kullanıp services e randomuserclient adında bir client ekliyoruz, ve en son noktada, clientfactory ile ilgili tüm ayarlamalarımızı yaptıktan sonra

.AddHttpMessageHandler<AuthTokenHandler>();

diyerek request pipeline ına bu handler ı eklemiş oluyoruz.

Bir dikkat etmemiz gereken noktada şu;

AddHttpMessageHandler demeden önce bu handler ı ekleyebilmek için, handler ın kendisini de önceden asp.net core dependency injection mekanizmasına tanıtmamız gerekiyor, bunun için de;

services.AddTransient<AuthTokenHandler>();

diyerek, container a bu sınıfımızı da ekliyoruz.

Aşağıda da her zaman olduğu client factory mizi kullanabiliriz. Burada önceki kullanım şekillerine göre hiçbir fark yok (NamedClient olarak eklediğimiz için yine CreateClient derken randomuserclient şeklinde ismini vererek çağırı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();
        }

Bir sonraki yazımda birden fazla DelegatingHandler eklediğimiz durumlarda işler nasıl oluyor buna bakacağız.

Bir sonraki yazımda görüşmek üzere.