Erhan Ballıeker

Asp.Net Core da HttpClientFactory Kullanımı (Basic Usage)

Selamlar,

Bir önceki yazımda HttpClient kullanım hataları ve HttpClientHandler ile alakalı çıkan sıkıntılardan ve bunlara HttpClientFactory ile gelen düzeltmelerden bahsetmiştim. Bu yazımda HttpClientFactory nin bir asp.net core projesinde farklı kullanım şekillerini göreceğiz.

HttpClientFactory yi bir Asp.Net Core projenizde kullanabilmenizin 4 farklı yöntemi mevcut

Bunlar;

  • Basic Usage
  • Named Clients
  • Typed Clients
  • Generated Clients

Bunlardan ilki ile incelemeye başlayalım.

Haberleşeceğimiz url yine randomuser/me olsun.

Öncelikle Asp.Net Core Projemizdeki Startup dosyasında service lerimiz konfigüre ettiğimiz ConfigureServices metodu içerisine AddHttpClient diyerek, proje içerisinde HttpClientFactory kullanacağımızı söylemiş oluyoruz.

 public void ConfigureServices(IServiceCollection services)
        {
            services.Configure(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            //Basic Implementation
            services.AddHttpClient();

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

Bu işlemden sonra artık HttpClientFactory en basic hali ile kullanımımıza hazır hale geliyor.

Peki bir asp.net core projemiz deki bir controller içerisine gidelim ve orada bu HttpClientFactory yi nasıl kullanacağımıza bakalım.

 public class HomeController : Controller
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public HomeController(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        public async Task Index()
        {
            var client = _httpClientFactory.CreateClient();
            var result = await client.GetAsync("https://randomuser.me/api?results=5");

            ViewBag.statusCode = result.StatusCode;
            return View();
        }

          ....

Yukarı daki kod bloğunda gördüğümüz gibi en temel şekilde Basic Usage kullanımı olarak IHttpClientFactory sınıfını kullanabiliriz.

Asp.Net Core un kendi dependency injection paketi i, ConfigureServices metodunda AddHttpClient  dediğimiz anda, biz uygulamanın herhangi bir yerinde bu sınıfı istiyoruz dediğimizde bize istediğimizi veriyor olacak.

Burada HomeController ın constructor ında IHttpClientFactory bizim için inject edilmiş oluyor. Bizde bunu yukarıdaki fieldımızda saklayıp aşağıdaki  Index action ı içerisinde kullanabiliyoruz.

_httpClientFactory.CreateClient() dediğimiz her noktada aslında arkada yeni bir HttpClient instance ı oluşuyor. Bu, özellikle bir önceki yazımı okudu iseniz garip gibi gelebilir ilk başta ama aslında hatırlarsak sorun HttpClient ta değil HttpClient Handler da idi.

HttpClientFactory her CreateClient dediğimizde yeni bir HttpClient instance ı oluşturuyor fakat arka tarafta HttpClienthandler ları pool mekanizmasına soktuğu ve onların lifetime ını kendi yönettiği için normal httpclient kullanırken karşlılaştığımız sorunlarla karşılaşmıyoruz.

 

Asp.Net Core 2.1 İle Gelen HttpClientFactory ve HttpClient İle Kıyaslama.

Selamlar,

Üzerinden 3 ay kadar geçmiş olsa da, Eylül 2018 de Microsoft Türkiye tarafından düzenlenen etlinlikte bahsettiğim ve uzunca zamandır, hakkında konuşmak istediğim HttpClientFactory ve HttpClient hakkında yazmaya henüz başlayabiliyorum.

Öncelikle HttpClient dan, yanlış kullanımlarından ve doğru kullanım olsa dahi handikapları nelerdi bunlardan bahsedip, sonrasında HttpClientFactory hakkında yazacağım.

Özellikle uygulamalar arasındaki haberleşme konuları, ve bu alanda kullanılabilecek yeni teknolojiler ilgi alanımda olduğu için, (Resilient network services hakkında yazılarımı buradan başlayarak okumanızı öneririm) bu konuda yazmak istediğim çok şey var.

Önce kısa bir özet geçelim. Eğer bir .net developer iseniz ve dış dünya ile haberleşecek mekanizmalar yazmanız gerekiyor ise (http üzerinden), sırası ile .netframework tarafından bize sunulan API lar şu şekilde idi;

  • HttpWebRequest
  • WebClient
  • HttpClient

Özellikle son senelerde uygulamamlarımızda bir haberleşme olacak ise dış dünya ile(http protokolü ile), kolay kullanımı ve birçok isteri yerine getirebilmesinden, async desteğinden vs çokça sebepten ilk seçenek hemen hemen herkes için HttpClient oluyor.

Aşağıda yanlışları ile birlikte kolay kullanımıına bir örnek görüyoruz. Bir for döngüsü içerisinde, tıpkı microsoft un best-practice lerinde söylediği gibi using bloğu içerisinde bir endpoint e request atıyoruz. Burada diyebilirsiniz ki, – Client ı neden for un içerisinde yazdık dışında yazalım –  doğru diyorsunuz derim. Ama gelmek istediğim nokta başka.


for (int i = 0; i < 13; i++)
{
     using (var httpClient = new HttpClient())
      {
            var result = await httpClient.GetAsync("https://randomuser.me/api?results=5");
            Console.WriteLine($"{result.StatusCode} - {result.IsSuccessStatusCode}");
      }
}

Microsoft der ki;

“Eğer bir api IDisposable dan miras alıyor ise, onu using bloğu içerisinde kullanın”

Aşağıdaki resimde görmüş olduğunuz gibi, HttpClient, HttpMessageInvoker dan o da, IDisposable dan türüyen bir sınıf. Bu durumda bu HttpClient objesini using bloğu içerisinde kullanmakta bir sıkıntı yok gibi düşünebiliriz.

Capture

Lakin ki durum öyle değildir 🙂

Netstat.exe komutunu çalıştırarak pc mizden dış dünyaya açılan Socket lerin durumuna bir bakalım.

Bu arada socket dediğimiz de anlayacağımız şudur. İki bilgisayarın birbiri ile haberleşmesi sırasında birbirlerine verdikleri adres. Sadece IP adresi yeterli değildir bunun yanında bir de port numarası önemlidir.

Ör: browser dan google.com u açtınız. Client tarafındaki socket adresi:ClientIP+60432(dynamic port number)

Aradaki bağlantı ise : ClientIP+dynamicPort —- GoogleIP+80(standart port) şeklinde olucaktır. Session bittikten sonra aynı port yeniden kullanılabilir.

Peki dönelim netstat.exe nin sonuçlarına. Aşağıda ki resimde görebileceğiniz gibi. Ben Console uygulamamı durdurmuş olsam bile yine de HttpClient objelerinin dışarıya açmış olduğu socket lerin TIME_WAIT state inde bekliyor olduklarını görüyorum.

Capture.PNG

Yani aslında client tarafı connection ı kapamış ama eksik kalan paketler olabilmesi vs adına halen socket ler açık.Windows için global olarak bu bekleme süresi 240 sn.

Bu değeri değiştirmemiz mümkün;

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay

ile yeni istediğimiz değeri set edebiliriz.Peki bu şekilde bir kullanım sonunda uygulamanın herhangi bir t anında alabileceğimiz muhtemelen hata nedir?

System.Net.Sockets.SocketException

Peki önce bu genel HttpClient kullanım hatasının çözümüne gelelim. Çözüm aslında basit. Singleton bir HttpClient kullanmak.

private static HttpClient client = new HttpClient();

for (int i = 0; i < 13; i++)
{
	var result = await client.GetAsync("https://randomuser.me/api?results=5");
       Console.WriteLine($"{result.StatusCode} - {result.IsSuccessStatusCode}");
}

Peki client ı singleton yaparak farklı farklı httpclient objeleri oluşturmaktan ve dolayısı ile aslında kapanmış olan connectionların açtığı boşuna bekleyen açık socket israfından kurtulmuş oluyoruz. Fakat bu da beraberinde farklı sorunlar getiriyor.

Bu durumda longlived HttpClient lar yüzünden DNS değişikliği veya Azure tarafında otomatize edilmiş Staging-Production ortamları arasındaki geçişlerde halen eski adreslere gitme vs vs gibi farklı sorunlar karşımıza çıkıyor.

Bunlara da çözüm olarak; DefaultRequestHeaders. ConnectionClose true diyerek http keep-alive-header = false göndermek ve ya ServicePointManager api sini kullanarak kuracağımız bağlantı üzerinde daha low level ayarlar yapmak da mümkün. Fakat herkes tarafında net olarak evet çözüm budur denen bir durum yok.

Client.DefaultRequestHeaders.ConnectionClose = true;

var sp = ServicePointManager.FindServicePoint(new Uri(” https://randomuser.me/api?results=5 “)); 

sp.ConnectionLeaseTimeout = 60*1000;

aşağıdaki github issue sunda konuşulan sıkıntıları fikir sahibi olmanız açısında okumanızı öneririm

https://github.com/dotnet/corefx/issues/11224

Peki gelelim HttpClientFactory e. Bu Asp.net core 2.1 ile gelen api bize neler sunuyor bir bakalım.

HttpClientFactory

Bu api aslında şuan .net dünyasında bir haberleşme durumunda kullanmanız gereken en taze iyi api diyebilirim. Yani henüz başlamadı iseniz kullanmaya, bundan sonraki tüm Asp.Net Core projelerinizde kullanmaya başlamanızı şiddetle tavsiye ederim.

4 temel özelliğinden bahsedebiliriz;

  1. Tüm haberleşmenin merkezi bir şekilde tanımlanma ve konfigüre edilmesi imkanını sunuyor bize
  2. Refit, polly gibi .net foundation dünyasında önemli yeri olan kütüphanelerin extensionları yazıldı bile. Poly-based middleware extensionlar ve delegating handler lar ile request ve responselar üzerinde istediğimiz oynamaları yapabiliyoruz. Bir çeşit interceptor gibi düşünebilirsiniz.
  3. Yukarıd da bahsettiğim HttpClient görünümlü sıkıntıların asıl sebebi aslında HttpClientHandler.  HttpClienFactory de bu HttpClientHandler ın lifetime ını pooling mekanizması sayesinde daha doğru yönettiği için yukarıdaki sıkıntılardan da kurtulmuş oluyoruz.
  4. Kendi Factorysi tarafından oluşturulmuş tüm request ve responların loglanması otomatik olarak sağlanıyor.

 

Peki HttpClientFactory bu sorunlara nasıl çözüm getiriyor?

Şöyleki;

  • HttpClientHandler instance ları pool a alınır ve lifetime ları yönetilir. Default 2 dakika süre ile bu pool da yeni oluşturulmuş olan HttpClient instancelarının kullanımına hazır halde beklerler.HttpClient instance ları yaşadığı sürece, HttpClientHandler da yaşar.
  • 2 dakika sonra expired olarak işaretlenirler. Dolayısı ile, CreateClient ile yeni bir HttpClient instance I oluşturulduğunda, onun kullanabileceği durumda olmaz. Hemen dispose olmaz, çünkü başka dispose olmamış HttpClient ların önceden kullanımına alınmış olabilir.
  • HttpClientFactory, arkada bir background service kullanır ve expire olarak işaretlenmiş ClientHandler I takip eder. Artık referans edilmediklerinde de dispose eder ve connectionları kapar.
  • Pooling feature sayesinde, Socket exhaustion riskini azaltır. Refreshleme mekanizması da DNS update I gibi problemlere (long lived httpclienthandler lar yok artık =) ) çözüm getirir.

HttpClientFactory nin 4 farklı kullanım şekli mevcut.

  • Basic Usage
  • Named Clients
  • Typed Clients
  • Generated Clients

 

Teoril olarak HttpClient ve HttpClientFactory hakkında biraz bahsettikten sonra bundan sonraki yazılarımda bu farklı kullanım şekillerini göreceğiz.

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

Xamarin.Forms ConfinstaApp Sample Bölüm 4 – SlideOverKit, Swipable Menu

Selamlar,

Bu serimizin son yazısında yine çok faydalı bir plugin den bahsetmek istiyorum. Hem bu örnek boyunca ilerlemiş olduğumuz Confinsta app i sona erdirecek hem de uygulamalarımızda bizden ekranın farklı yerlerinde farklı şekillerde açılıp kapanan menüler istendiğinde bunu kısa ve hızlı bir şekilde nasıl hallederiz buna bakacağız.

Kullanacak olduğumuz pluginimiz SlideOverKit

Yine solutionımızdaki her projeye bunu ekledikten sonrai plaform spesifik taraflarda gerekli initialize işlemini yapıyoruz.

Detaylı kullanımı için SlideOverKit in kendi github sayfasını ve örneklerini incelemenizi öneririm. Buradan ulaşabilirsiniz.

Bu plugin confinsta app boyunca Customrenderer yazmış olmamı gerekitiren tek şey. Ama aslında yazacağınız custom renderer ın örneklerden alıp copy paste yapmaktan başka neredeyse değiştirmeniz gereken peki bir kısmı kalmıyor(class isimleri hariç) 🙂

Ama arka planda neler yaptığını bilmek isterseniz benim gibi github dan inceleyebilirsiniz.

Önce bir ekranı hatırlayalım, sonra da tek tek kod taraflarına bakalım.

Resimde gördüğünüz gibi, sağ üst köşede ki hamburger buton ikonuna basıldığında ekranın yarısı kadar ene sahip ve tam boyda bir menü sayfası açıyoruz. Tıpkı instagramda olduğu gibi.

Önce sayfanın kendisine bir bakalım.

Sayfada yukarıdaki gibi bir navigation bar kullanmak eskiden custom renderer yazmak gerektirirdi. Ama yeni gelen özellikler ile sayfanın NavigationView ını istediğimiz gibi design edebiliyoruz artık xaml tarafında. Ben sağ üst köşeye bir imaj koydum ve buna bir tapgesture ekledim. Basıldığında da menü yü toggle ediyorum. Bu kadar. Xaml tarafı şu şekilde.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ConfinstaApp.Views.ProfileView">

    <NavigationPage.TitleView>
        <StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal" Padding="10,0,10,0" Spacing="10">
            <Image Source="17.png" HorizontalOptions="StartAndExpand" WidthRequest="25" HeightRequest="25"></Image>
            <Label Text="erhanballieker" FontSize="Medium" FontAttributes="Bold" HorizontalOptions="CenterAndExpand" VerticalOptions="Center" Margin="0,0,25,0"/>
            <Image Source="11.png" HorizontalOptions="End" WidthRequest="25" HeightRequest="25">
                <Image.GestureRecognizers>
                    <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
                </Image.GestureRecognizers>
            </Image>
        </StackLayout>
    </NavigationPage.TitleView>
    
    <ContentPage.Content>
        <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
            <Label FontSize="Medium" FontAttributes="Bold" Text="erhanballieker Profile Page" HorizontalOptions="Center"></Label>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Yukarıda gördüğünüz gibi NavigationPage.TitleView içerisinde navigation design ım var. Sayfanın content ine hiçbir şey koymadım (Bir adet label ) dışında çünkü burada odaklandığımız yer sayfa içerisine eklediğimiz açılıp kapanan menü.

Sayfanın backend tarafı ise aşağıdaki gibi. Dikkat etmemiz gereken tek şey, Sayfanın ContentPage dışında bir de IMenuContainerPage interface ini implemente etmiş olması.

Bu interface den bize 3 adet property geliyor.

Bunlar;

  • SlideMenu Tipinde bir SlideMenu Prop u
  • Action tipinde bir ShowMenuAction prop u
  • Action tipinde bir HideMenuAction prop u

bunlar dışında sayfada sadece hamburger butonun gesturetapped event i var.

Bir diğer dikkat edeceğimiz nokta ise sayfanın constructor nda bana interface den gelen SlideMenu propertysine atadığım RightSideMenuContentView. 

	[XamlCompilation(XamlCompilationOptions.Compile)]
	public partial class ProfileView : ContentPage, IMenuContainerPage
        {
           public ProfileView()
	   {
	       InitializeComponent ();

               this.SlideMenu = new RightSideMenuContentView();
            }

        public SlideMenuView SlideMenu { get; set; }
        public Action ShowMenuAction { get; set; }
        public Action HideMenuAction { get; set; }

        private async void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            if (this.SlideMenu.IsShown)
            {
                HideMenuAction?.Invoke();
            }
            else
            {
                ShowMenuAction?.Invoke();
            }
        }
    }

RightSideMenuContentView ın xaml tarafına bir bakalım. Projemize bir adet ContentView ekleyip xaml tarafını aşağıdaki gibi düzenliyoruz.Basit bir StackLayout içerisinde bir kaç label var. Ama dikkat etmemiz gereken şey tüm sayfanın namespacinin SlideOverKit den geliyor olması.

<?xml version="1.0" encoding="utf-8" ?>
<t:SlideMenuView xmlns="http://xamarin.com/schemas/2014/forms" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
                 xmlns:t="clr-namespace:SlideOverKit" 
                 x:Class="ConfinstaApp.Views.SlideViews.RightSideMenuContentView">

    <StackLayout Padding="15,50,15,30" Spacing="20" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">

        <Label HeightRequest="30" Margin="0,0,0,80" Text="Menu" FontSize="20" XAlign="Center" TextDecorations="Underline" />

        <Label HeightRequest="30" Text="Insights" FontSize="20" XAlign="Start" YAlign="Center" TextDecorations="Underline" />
        <Label HeightRequest="30" Text="Your Activity" FontSize="20" XAlign="Start" YAlign="Center" TextDecorations="Underline"/>
        <Label HeightRequest="30" Text="Nametag" FontSize="20" XAlign="Start" YAlign="Center" TextDecorations="Underline"/>
        <Label HeightRequest="30" Text="Saved" FontSize="20" XAlign="Start" YAlign="Center" TextDecorations="Underline"/>
        <Label HeightRequest="30" Text="Discover People" FontSize="20" XAlign="Start" YAlign="Center" TextDecorations="Underline"/>
        <Image HeightRequest="40" Source="nature.png"></Image>
        <Label HeightRequest="30" Text="Settings" FontSize="20" XAlign="Start" YAlign="Center" VerticalOptions="EndAndExpand" TextDecorations="Underline"/>

    </StackLayout>
</t:SlideMenuView>

Slide menünün backend tarafına bakalım. ContentView dan değil de SlideMenuView dan miras alıyor olmamız ilk dikkat çeken nokta. Bu miras ın bize sunduğu bir kaç property ile de nasıl görüneceğini ayarlıyoruz.

IsFullScreen true diyerek tam sayfa açılır olmasını söylüyoruz.

WidthRequest ile istediğimiz genişliği söylüyoruz.

MenuOrienations ı RightToLeft diyerek sağdan sola açılmasını belirtiyoruz.

[XamlCompilation(XamlCompilationOptions.Compile)]
	public partial class RightSideMenuContentView : SlideMenuView
    {
		public RightSideMenuContentView()
		{
			InitializeComponent ();
                        this.IsFullScreen = true;
                        this.WidthRequest = 250;
                        this.MenuOrientations = MenuOrientation.RightToLeft;

                        this.BackgroundColor = Color.White;
                        this.BackgroundViewColor = Color.Transparent;
                }
    }

Daha öncede de söylediğim gibi tüm projenin içerisinde sadece bir adet custom renderer var. o da bu plugin i kullanılabilir kulmak için. iOS Custom Renderer Kısmı aşağıdaki gibi.

[assembly: ExportRenderer(typeof(ProfileView), typeof(ProfileViewRenderer))]

namespace ConfinstaApp.iOS
{
    public class ProfileViewRenderer : PageRenderer, ISlideOverKitPageRendereriOS
    {
        public Action ViewDidAppearEvent { get; set; }

        public Action OnElementChangedEvent { get; set; }

        public Action ViewDidLayoutSubviewsEvent { get; set; }

        public Action ViewDidDisappearEvent { get; set; }

        public Action<CGSize, IUIViewControllerTransitionCoordinator> ViewWillTransitionToSizeEvent { get; set; }

        public ProfileViewRenderer()
        {
            new SlideOverKitiOSHandler().Init(this);
        }

        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            OnElementChangedEvent?.Invoke(e);
        }

        public override void ViewDidLayoutSubviews()
        {
            base.ViewDidLayoutSubviews();
            ViewDidLayoutSubviewsEvent?.Invoke();

        }

        public override void ViewDidAppear(bool animated)
        {
            base.ViewDidAppear(animated);
            ViewDidAppearEvent?.Invoke(animated);

        }

        public override void ViewDidDisappear(bool animated)
        {
            base.ViewDidDisappear(animated);
            ViewDidDisappearEvent?.Invoke(animated);
        }

        public override void ViewWillTransitionToSize(CGSize toSize, IUIViewControllerTransitionCoordinator coordinator)
        {
            base.ViewWillTransitionToSize(toSize, coordinator);
            ViewWillTransitionToSizeEvent?.Invoke(toSize, coordinator);
        }
    }
}

Custom renderer kullanmak zorunda olmanın yanında bir iyi haber ise şu. SlideOverKit sample larından aldığınız bir ios yada android rendererlarda sadece class isimlerini düzeltip başka bir customization yapmasanız bile proje çalışır hale gelecektir. Yani aslında tam da custom renderer yazıyor sayılmazsınız.

Android Custom Renderer kısmı

[assembly: ExportRenderer(typeof(ProfileView), typeof(ProfileViewRenderer))]
namespace ConfinstaApp.Droid
{

    public class ProfileViewRenderer : PageRenderer, ISlideOverKitPageRendererDroid
    {
        public Action OnElementChangedEvent { get; set; }

        public Action<bool, int, int, int, int> OnLayoutEvent { get; set; }

        public Action<int, int, int, int> OnSizeChangedEvent { get; set; }

        public ProfileViewRenderer (Context context) : base (context)
        {
            new SlideOverKitDroidHandler ().Init (this, context);
        }

        protected override void OnElementChanged (ElementChangedEventArgs e)
        {
            base.OnElementChanged (e);
            OnElementChangedEvent?.Invoke (e);
        }

        protected override void OnLayout (bool changed, int l, int t, int r, int b)
        {
            base.OnLayout (changed, l, t, r, b);
            OnLayoutEvent?.Invoke (changed, l, t, r, b);
        }

        protected override void OnSizeChanged (int w, int h, int oldw, int oldh)
        {
            base.OnSizeChanged (w, h, oldw, oldh);
            OnSizeChangedEvent?.Invoke (w, h, oldw, oldh);
        }
    }
}

Evet tüm hikaye bu kadar. .NetConf 2018 de sunumunu yapmış olduğum ve yaklaşık 1 iş günümü almış olan (sadece ui olduğu için :)) bir app in, xamarin forms ile ve xamarin forms a yeni gelen özellikler ile de nasıl bir şey ortaya çıkarabildiğini gördük.

Ekranları bir hatırlayalım.

Xamarin.Forms özelinde faydalı örneklerle dolu olduğunu düşündüğüm bu Confinsta app in proje haline buradan ulaşabilirsiniz. Aynı şekilde aynı gün yapmış olduğum diğer sunum dosyaları ve projeler burada mevcut.

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