Erhan Ballıeker

WebAssembly ve Microsoft Asp.Net Core Blazor

Selamlar,

Bu yazımda sizlere Mono, Docker vb çılgınlık seviyesinde bir yenilikten bahsetmek istiyorum;

WebAssembly

Tarihi çok da eskilere gitmemekle beraber öncesinde bir grubun poc olarak başlattığı fakat sonradan çarşının karışabileceği belli olduğundan birçok dünya devininde geliştirilmesine yatırımlar yaptığı bir teknoloji haline geldi webassembly.

Geçmişi tam olarak şöyle;

wasmhistory.PNG

2017 yılında işe Apple, Google, Mozilla, Facebook ve benzeri devlerin de devreye girmesiyle hayalin gerçeğe dönüşmesi işi başladı.

Ne olduğunu tek bir cümlede anlatmak zor, ama özetle şu;

  • Modern browserlar üzerinde hiçbir plugin e gerek kalmadan (Flash, Silverlight vb.) çalışacak olan, client side (– server side desteği ile de,  ki .NetCore 3.0 içerisinde bu şekilde gelecek malesef, signalr destekli olarak. -) web yazılımına yeni özellikler ve performans katmayı amaçlayan yeni bir binary kod tipi.

Kendi sitelerinde söyledikleri de tam olarak şu aslında;

  • Binary formata sahip, stack-based bir virtual machine. High-Level dillerin (C#, Java vb.) kendisine compile edilebilmesi ve client ve server uygulamaları için web üzerinden deploy edilmesi ile asıl farkı yaratıyor.

Bu tek cümle yetersiz kaldığı kadar güçsüz de kalıyor. Az daha örneklersek şöyle oluyor;

  • High-Level bir dilde yadığınız kodu, .wasm a compile edip (bu kısma aşağıda web assembly yi nasıl yazarız kısmında değineceğim) web e deploy ederek, client side kodu gibi kullanabiliyorsunuz.

Aslında nasıl node.js ile front-end yazan arkadaşlarımız bir anda backendci ve fullstack oluverdilerse, bu sayede backend ci arkadaşlar da artık javascript yazmadan frontend ci olabilecekler (html + css hariç :)).

Peki o zaman ilk soru şu; Hangi browserlarda çalışır?

Bunun için şu adresten herhangi bir t anında hangi browserlarda çalışabilir olduğunu kontrol edebilirsiniz.

WebAssembly i anlamak için aslında önce javascript in nasıl çalıştığını anlamak gerekiyor. Modern browserlarda server-client arası işleyiş aşağıdakine benzer şekilde oluyor.

jvinanutshell

Html ve css ile browserların design tooları web sitenizi gösteriyor. Kullanıcı ile bu sayfalar arasında etkileşim oluşturabilmek için javascript kodları yazıyoruz. Browser http üzerinden server e istek gönderdiğinde, browser ın websitemizi göstermesi için gerekli olan tüm dosyalar server dan browser a iletiliyor yukarıda ki resimde de görebileceğiniz üzere.

Javascript bildiğiniz üzere tüm client-side tarafta(node.js ile beraber server tarafında da) işlemlerimizi yapmak için kullandığımız bir dil. Interpreted (yorumlanan) bir dil javascript, bir derlenme sürecinden geçip hedef makina nın doğrudan anlayıp çalıştırabileceği bir kod a dönüşmüyor önceden, yani ilgili cihaz da ilgili runtime da bir intertpreter(yorumlayıcı) ile yorumlanıp sonrasında runtime ın anlayıp execute edebileceği bir hale geliyor.

Tüm browserlarda (desktop, mobil vs) javascript çalıştırmak için aşağıdaki gibi bir Javascript Runtime a sahip. Bu sandbox olarak browser için de bulunan runtime da javascript kodları çalışırken bir yandan da browser tarafında bazı api lara ulaşma hakkı oluyor. Herşeye ulaşma imkanı yok, aksi halde bu ciddi güvenlik sorunları doğururdu. Aşağıdaki resimde özetle Javascript in bir browser üzerinde javascript runtime ile çalışıp browser üzerinde ulaşabildiği api ları görüyoruz.

jvruntime

Buraya kadar bu günlere geldik, yazılan tüm web projeleri client-side gereksinimlerini javascript ve yukarıdaki model ile çözebildi. Halen de çözebilir, gelecek zaman için javascript adına kötü haberler yok tabii ki.

Ama bu resme artık şöyle bir arkadaş eklendi;

jvwwa

Javascript ile aynı güvenlik prensiplerine sahip, aynı javascript runtime ı içerisinde çalışan ama interpreted olmayan, doğrudan runtime ın anlayıp çalıştırabileceği yeni bir binary kod tipi olan ve üstelik başka high-level dillerden kendisine compile edilebilir olan WASM (WebAssembly)” oyuna resmi olarak dahil oldu.

WebAssembly kodunun tipi aşağıdaki gibi birşey;

jsvsawasm

Sol tarafta bildiğiniz javascript, high-level yani insanların okuyup anlayabileceği kolayca yazabileceği bir dil, sağ tarafta ise bir wasm (web assembly) kodu görüyoruz. Ve önceden söylediğim gibi ikisi de aynı runtime içerisinde çalışarak, aynı güvenlik sistemleri içerisinde hem browser tarafında hemde node.js gibi teknolojilerle server tarafında çalışabilir halde.

 

WebAssembly nin gelecek planları içerisinde kendi runtime ın da çalıştırabilmek javascript runtime ına bağlı kalmamak gibi bir hedefi de var. Browser larda yine javascript runtime kullanılacak fakat, örneğin bir android cihaz da browser a gerek kalmadan doğrudan kendi runtime içerisinde çalıştırılabilmesi gibi bir hedef de söz konusu.

Wasm’ı tahmin edebilebileceğiniz gibi doğrudan yazmak biraz zor gibi duruyor 🙂 ama isteyen yazabilir, diğer farklı yazım olanaklarına bakmadan önce hızlıca “neden wasm kullanmalıyım?” sorusunu bir düşünelim.

Cevabını aşağıdaki gibi listeyelebiliriz.

  • Interpreted bir dil olmayıp, doğrudan runtime tarafından execute edilebilecek olmasından dolayı neredeyse native hız da çalışabilecek bir kod tipi olması.
  • Diğer High-Level kodların(C#, Java, Python, C/C++ ..) WebAssembly(wasm) a derlenebilir olması.
  • Browserlar tarafından doğrudan desteklenir olması, kullanmak için hiçbir plugin e gerek olmaması.
  • Javascript Runtime Sandbox ı içerisinde çalıştığı için, javascript yazarken ki güvenliğin birebir aynısına sahip olması.
  • Javascript kodu ile beraber çalışabilir olması. Javascript tarafından webassembly modüllerini çağırıp parametreler geçebiliriz, aynı şekilde wasm modülleri tarafından da javascript fonksiyonları çağırabiliriz.

İki büyük yanlış anlaşılmayı da gidermek adına burada da söylemem de fayda var.

  • WebAssembly javascript yerine gelmedi. Aksine onu tamamlayıcı, onunla beraber çalışabilir bir dil. Javascript hayatına olduğu gibi devam edecek tabii ki.
  • WebAssembly ile yazmak demek artık server side ihtiyacı yok demek değil asla.Bunun yerine şunu söylersek doğru olur, önceleri javascript ile client side tarafında yapamayacağımız yoğun hesaplama işlemleri içeren bir sistemi artık server side a ihtiyaç duymadan client-side tarafta yapabiliriz. Örneklersek;
    • Video/Audio Editing, Streaming, Calling
    • Game
    • Virtual/Augmented Reality
    • AI (Image Recognition vs..)

WebAssembly nin performansı sayesinde yukarıdaki örneklerden oyun a örnek olarak aşağıdaki oyuna bir göz atabilirsiniz.

https://www.funkykarts.rocks/demo.html 

Bu oyun yerçekimini simülasyonu hesaplamalarını tamamen client-side tarafında webassembly ile yapan bir oyun.

Diğer yapılanlar için buraya bakabilirsiniz.

WebAssembly Yazmanın Yolları

WebAssembly ile kod geliştirmek için farklı yöntemler mevcut. Bunların neler olduğuna bakalım.

WAT Formatında Kod

Doğrudan wasm formatında kod yazmak çok gerçekçi ve mümkün olmadığından, bununla eş düzeyde wat formatında, insanın okuyup yazmasına çok daha müsait formatta wat kodumuzu yazıp wasm a derleyebiliriz.

İşleyiş şu şekilde oluyor.

writewat

Sol tarafta gördüğünüz gibi WAT formatında kodunuzu yazıp The WebAssembly Binary Toolkit ile runtime ın anlayabileceği WASM formatına dönüştürebilirsiniz.Bu dönüşüm işlemleri için hali hazırda başka tool larda yazılmakta fakat şu an en popülerlerinden birisi bu.

Peki bunu gerçekten deneyelim.

https://webassembly.studio/ sitesine giderek, yeni bir proje oluşurup bunu web ortamında build edip javascript içerisinde çağırıp sonuçları gözlemleyebilirsiniz.

webassembly.studio sitesine gittiğinizde karşınıza bir popup da istediğiniz proje tipini soran bir popup çıkacak.Burada bir çok highlevel dilin yanında Empty Wat Project seçerek, wat formatında kodunuzu yazabilirsiniz.

emptywat

Empty Wat Project seçtiğinizde karşınıza aşağıdaki gibi bir solution yapısı çıkıyor. src altında main.html, main.js ve main.wat dosyaları olduğunu görüyoruz. Burada .wat dosyasında wat formatında kodumuzu yazıp, Build&Run diyerek sonuçları aynı ekran üzerinde gözlemlememiz mümkün.

Aşağıdaki $add fonksiyonu basitce iki adet sayıyı ekleyip geri dönen bir function. Burada kod yazmak istediğiniz de inetllisense bile mevcut. sytnax a alışmak biraz vakit alıcak olsa da, doğrudan wasm yazmayla zorluk derecesi kıyaslanamaz bile 🙂

mainwat

Aşağıda ise, javascript doyasından bir wasm modülünün nasıl çağırıldığını görüyoruz. Bu da aslında WebAssembly yazmanın bir başka yolu diyebiliriz. Modülü instantiate ettikten sonra ilgili fonksiyonu export ederek kullanabiliriz.

mainjs

main.html tarafında ise bildiğimizi dışında hiçbirşey yok. aşağıdaki gibi basit bir html koduna sadece main.js eklenmiş.

mainhtml

Build&Run diyerek bu kodları çalıştırdığımızda çıktı aşağıdaki gibi ekranın sağ alt köşesinde gözüküyor.

Capture.PNG

Ek olarak bir de solution tarafında bir output folder ı görüyoruz.Burada wat kodumuzun dönüşmüş olduğu wasm kodunun nasıl olduğuna bakmak için, main.wasm doyasına sağ tıklayarak alttaki seçeneklerden View as Binary yi seçersek, ekran da bize yazdığımız wat kodumuzun wasm a çevrilmiş halini gösterecektir.

wasmbinary

Microsoft Blazor Tool Chain

Higl-level bir kodun wasm a derlenerek js runtime da çalışabildiğini söylemiştim, ki zaten bu javascripte göre en büyük artılarından bir tanesi webassembly nin.

Bir çok dil için farklı Tool lar mevcut. Biz bir C# / Razor kodumuzun nasıl webassembly e dönüştüğü kısmına bakacağız.

Aşağıdaki resimde ana fikri görüyoruz. Detaylarına değineceğim.

compiletowasm

Burada anlatılan aslında şu;

  • Yazdığımız C# kodu normal hayatta olduğu gibi bir .dll dosyasına dönüştürülür.
  • Bu .dll dosyası, wasm formatına dönüştürülerek mono.wasm runtime u üzerinde çalışır.
  • mono da, aslında js runtime u üzerinde çalışır.

Yani aslında yazdığımız kod .dll e dönüştükten sonra, mono sayesinde, ilgili formata dönüştürülüp mono runtime üzerinde çalışır. yani kodumuz direk js runtime üzerinde değil, mono runtime ında çalışır, mono runtime ı da js runtime üzerinde çalışabildiğinden, en nihayetinde yazdığımız C# kodu, mono sayesinde javascript runtime üzerinde wasm tipinde çalışan bir kod haline gelir.

Sadece bu bile Mono yu yani Xamarin i(mono project i başlatan ekip sonuçta, hatta başlatan kişi 🙂 ) çok fazla sevmek için bile tek başına yetebilecek bir sebep.

Blazor ın Client-side ve server side tarafında çalışma şekilleri farklı. Bunların detaylarına bir sonraki yazımda gireceğim. Asp.Net Core 3.0 ile beraber ne şekilde kullanabiliyor olacağız, bunları inceleyeceğiz.

WebAssembly tarafına ufak bir girişten ve Blazor için C#/Razor kodunun browser da çalışır hale gelmesi akışını sadece akış olarak inceledikten sonra, bir sonraki yazımda sizlere Visual Studio üzerinde Blazor ile neler yapabiliyoruz bunları da göstermek istiyorum.

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

 

Asp.Net Core API Backend ve Xamarin.Forms İle Kelime Oyunu Bölüm 2 (Asp.Net Core SignalR ve Azure SignalR Service)

Selamlar,

Xamarin Forms Mobile App ve Asp.Net Core backend tabanlı kelime oyunumuzdan bir önceki yazımda bahsetmiştim. Bu 1 aylık macerada yaşadıklarımızı ve kullandıklarımızı örneklerle anlatmaya devam ediyorum

Bu yazımda kullanıcıları aynı üzerinde oynatmak ve online olarak göstermek için faydalandığımız teknolojiden – ki herhangi bir .net projenizde bir şekilde bir realtime ihtiyacınız varsa kullanmanızı önereceğim teknoloji olan –  SignalR dan bahsedeceğim.

Aslında daha da doğrusu Asp.Net Signalr Core dan bahsedeceğim. SignalR ve SignalR Core farklı iki API. Asp.Net Core ile beraber yeniden geliştirilmeye başlandı.

Biliyorsunuz ki bir uygulama da realtime ihtiyacımız olduğunda kullanabileceğimiz birçok yöntem var, bunlar;

  • Web Socket
  • Server Sent Events (SSE)
  • Long Polling
  • Short Polling

vs gibi birden çok seçeneğimiz var. En tavsiye edilen en yeni yöntem tabii ki websocket kullanmak. Ama yazdığınız uygulama her ne ise (web-mobil-desktop vs) kullanacak olan clientları ve kullanacakları donanım ve yazılımları bilemeyeceğiniz için sadece kalkıpta tüm iletişimi WebSocket ile kurmak çok doğru olmayacaktır, size kullanıcılar kaybettirecektir.

Peki SignalR ne yapıyor?

SignalR önce gerçek zamanlı haberleşme isteyen client ın kontrolünü yapıyor. İlk denediği seçenek WebSocket kullanarak haberleşmek eğer client bu teknolojiyi desteklemiyorsa sırası ile SSE, LongPolling vs deneyerek en sonunda doğru yöntemi bulup iletişimi sağlıyor.

Bir asp.net core projemizde signalr kullanmakta çok basit artık. Biliyorsunuz ki Asp.Net core da artık koca MVC akışı bi pipeline olarak proje ekleniyor. SignalR da bu şekilde projeye ekleniyor ve kullanmaya başlıyorsunuz.

Öncelikle biz Asp.Ne Core API projemizde bunun için neler yaptık buna bakalım.

Microsoft.AspNetCore.SignalR paketini kullandık. Bu paketi eğer projenizde Microsoft.AspNetCore.App paketi varsa indirmenize gerek yok zaten bu paket mevcut halde gelmiş oluyor.

Bundan sonra yapmanız gereken Signalr ın projenizle ilişki kurduğu Hub sınıfını oluşturmak.

Bizim projemiz içerisinde GameHub adında bir hub ımız mevcut.Aşağıdaki gibi bir kısmını inceleyelim.

public class GameHub : Hub
    {
        public IServiceProvider Services { get; }
        public GameHub(IServiceProvider services)
        {
            Services = services;
        }

        public async void ConnectToHub(string username)
        {
            await Clients.Caller.SendAsync("ConnectedToHub", username, Context.ConnectionId);
            await Clients.AllExcept(Context.ConnectionId).SendAsync("NewUserConnectedToHub");
        }

        ...

Burada constructor tarafında kullandığımız ServiceProvider a çok takılmayın şimdilik. Bu aşağıda başka metodlar da application service ler tarafında yazdığımız ve container a register ettiğimiz serviceleri almak için kullandığımız yöntem. constructor injection yapamazdık çünkü Hub sınıfı parametresiz Constructor ı bulup çağıracaktır.

ConnectToHub metodunda iki şey yaptık.

  • Bu metodu çağıran Client ın kendisin deki –Clients.Caller diyerek-  Client tarafta ki ConnectedToHub metodunu çalıştırmasını söyledik. Parametre olarak ta signalr ın kendi oluşturmuş olduğu Context.ConnectionId yi geçtik, çünkü kullanıcıyı kendi db mizde online olarak update etmek ve bu kullanıcıyla doğrudan haberleşmelerde bu ContextId yi kullanmamız gerekeceği için o bağlantı boyunca saklamak istedik.
  • Kullanıcı bir şekilde disconnected olduğunda signalr client tarafta zaten böyle bir event i tetikleyecek bizde kullanıcıyı offline olarak işaretleyip sakladığımı ConnectionId sini sileceğiz, taa ki yeniden bağlanıp yeni connectionId sini öğrenene kadar.
  • İkinci satırda ise, uygulamada zaten mevcut oturum açmış insanların telefonunda online user sayını arttırabilmek yani yeni birinin geldğini söyleyebilmek adına, Clients.AllExcept(Context.ConnectionId) diyerek yani bu metodu çağıran kişi dışında herkese bir sinyal göndererek client tarafta NewUserConnectedToHub metodunu çalıştırmak istediğimizi söyledik.

Hub tarafında bu ve benzeri başka metodların tanımı dışında birşey yok.

Gelelim bu signalr ı Asp.net core un request pipeline ına nasıl eklediğimize.

public void ConfigureServices(IServiceCollection services)
        {
            // CORS
            services.AddCors();

            // MVC
            var mvc = services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            //SignalR 
            services.AddSignalR()
                    .AddAzureSignalR("Endpoint=...;AccessKey=...;Version=...;");

              ........

Startup tarafındaki ConfigureServices metoduna yukarıda gördüğünüz gibi AddSignalR diyerek SignalR service lerini kullanacağımızı söyledik. Biz bu projede signalr ın backend tarafı scale edilme yönetmi olarak Azure SignalR Service i kullandığımız için AddAzureSignalR diyerek içerisine azure tarafında oluşturmuş olduğumuz service url i ve accesskey i verdik. Azure SignalService tarafına daha sonra gelicem. Şimdilik SignalR ile devam edelim.

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            ...
            app.UseMvc();
            app.UseAzureSignalR(routes =>
            {
                routes.MapHub("/game");
            });
            ...
        }

Configure metodunda ise UseAzureSignalR diyerek AzureSignalService kullanacağımızı ve hub ımızı register etmiş olduk. Eğer AzureSignalService kullanmadan sadece SignalR ı eklemek isteseydik kod aşağıdaki gibi olacaktı.

app.UseSignalR(builder => builder.MapHub("/game"));

SignalR Client (Xamarin Forms)

Api tarafında Signalr ı ne şekilde kurup kullandığımızdan bahsettim. Xamarin Forms projemizde de signalr kullanmak için yaptığımız ilk şey aşağıdaki paketi tüm forms projelerine indirmekti

Microsoft.AspNetCore.SignalR.Client (1.1.0)

Bundan sonra uygulamanın App.cs tarafında aşağıdaki gibi HubConnectionBuilder  ı kullanarak kullanıcıyı hub a bağladık.

 public App()
        {
            InitializeComponent();

            Connection = new HubConnectionBuilder()
               .WithUrl("http://appserversitename.net/game")
               .Build();

            Connection.Closed += async (error) =>
            {
                IsConnectedToHub = false;
                await Task.Delay(new Random().Next(0, 15) * 1000);
                MessagingCenter.Instance.Send(this, "Disconnected");

                var disconnectResult = await ApiClient.UserApi.DisconnectUser(new Models.Models.Dto.Request.User.DisconnectUserRequestModel { Username = InMemorySettings.GetCurrentUser().Username });

                await Connection.StartAsync();
            };

            RegisterSignalrEvents();

            ....

Connection ın kapanması durumunda signalr bizim için bir event tetikliyor burada da istediğimiz diğer işlemleri yazdık.

RegisterSignalREvents metodumuz da ise, API tarafında kullanıcı tarafında şu metodları tetikle dediğimiz metodları oluşturduk. örnek olarak API tarafı client tarafında ConnectedToHub metodu tetiklediğinde ne olacağını, Connection.On metodu ile parametreleri de vererek, içeriye yazdık.

private void RegisterSignalrEvents()
        {
            Connection.On<string, string>("ConnectedToHub", (user, connectionId) =>
             {
                 Xamarin.Forms.Device.BeginInvokeOnMainThread(async () =>
                 {
                     try
                     {
                         await ApiClient.UserApi.ConnectUser(new Models.Models.Dto.Request.User.ConnectUserReqModel { ConnectionId = connectionId, Username = user });

                         var onlineUserCount = await ApiClient.UserApi.GetOnlineUsers();

                         Device.BeginInvokeOnMainThread(() =>
                         {
                             MessagingCenter.Instance.Send(this, "OnlineUserCountReceived", onlineUserCount);
                         });

                         InMemorySettings.GetCurrentUser().ConnectionId = connectionId;
                         MessagingCenter.Instance.Send(this, "Connected");

                         IsConnectedToHub = true;
                     }
                     catch (Exception ex)
                     {
                           ...
                     }
                 });
             });

Burada basitçe kullanıcıyı db de online olarak set edip, signalrdan gelen connection id sini saklayıp, uygulama tarafında başka yerleri xamarin.forms un messaging center ını kullanarak tetikliyoruz. Aşağıdaki gibi o an kaş kişi oynuyor bunu görmemiz için kullandığımız yöntem bu idi.

WhatsApp Image 2019-05-31 at 22.10.45 (1)

ApiClient.. şeklindeki haberleşme yapısına benzer yapıyı nasıl kurduğumuza bakmak için aşağıdaki yazılarımı okuyabilirsiniz.

Client tarafında bundan fazla bir numara yok.

Gelelim Azure SignalR Service i neden kullandığımıza. Daha önceleri SignalR kullandıysanız bir gerçek hayat senaryosunda ve büyük bir projede signalr ı ya azure service bus ile ya redis ile yada mssqlserver ile desteklemişsinizdir. Bunun temel sebepleri hem signalrı scalable kılmak hemde daha persistant bir yapı oluşturmak diyebiliriz. İşte artık Azure Signalr Service ile bunları düşünmekten kurtuluyorsunuz. Bu şöyle oluyor. Artık kullanıcılar eskiden olduğu gibi doğrudan sizin appserver ına bağlanmıyorlar, sizin appserver ınızdan bir url alıp (negotiation url) Signalr Service e bağlanıyorlar, sizin app server ınızda buraya bağlanıyor ve nihayetinde client ile appserver ınız arasında realtime persistant bir connection kurulmuş oluyor.

Aşağıdaki görsel bunu anlatıyor.

Capture.PNG

Asp.net Core SignalR için, Signalr Service, hub başına 5 adet websocket açıyor. Bu service i ücretsiz olarak maximum 20 concurrent user için deneyebilirsiniz. Bu da zaten test etmeniz için yeterli bir sayı. Daha sonra concurrent kullanıcınız arttıkça paralı tarifeye geçip binlerce kullanıcıyı concurrent birşekilde yönetme ve realtime birbirini bağlama işini Azure a bırakabilirsiniz.

Azure portal e girdiğiniz de All Services den Web sekmesini tıklayarak ücretsiz bir SignalR Service oluşturun ve en kısa sürede denemeye çalışın derim.

Capture.PNG

Bu yazımda da yazmış olduğumuz kelime oyunu için realtime kısmını hem server hem de client tarafta nasıl çözdük ve azure signal service i neden nasıl kullandığımızı anlattım.

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

Asp.Net Core API Backend ve Xamarin.Forms İle Kelime Oyunu Bölüm 1 (Proje Yapısı, Teknolojiler ve Lottie)

Selamlar,

Eğlenceli bir seri ile karşınızdayım. Xamarin’i 2012 den beri aktif olarak takip eden ve gönülden destekleyen biri olarak her fırsatta gücünü göstermek için elimden geleni yapıyorum. Muhtemelen bir xamarin ekibini bir de DreamTheater ı bu kadar yürekten savunup reklamını yapmışımdır.  =)

Haziran ayının ilk haftası içerisinde store a atmaya hazırlandığımız ve 1 ay gibi kısa bir sürede (Normal mesai dışında) geliştirdiğimiz xamarin.forms ile yazılmış ve backend tarafında Asp.Net Core kullandığımız uygulamamızın arayüzlerinin bir kısmını, bu uygulamayı yaparken kullandığımız teknolojileri sizlerle örnekler yaparak paylaşmak istedim.

Uygulamanın ara yüz örnekleri aşağıdaki gibi.

Evet store da onlarcası olan bir kelime oyununu yaptık =) Maksat ya bu da tutarsa dan fazlası. İnsanlara xamarin ile kısa sürede yapılabilecekleri göstermek de sebepleri arasında. Tabi ekstra özellikler de koyacağız zamanla ama şuanki haliyle hem kendi kendinize hem de online kişilerle aynı anda oynayabilme seçenekleri ile storeda kilerden fonksiyonalite olarak pek bir fazlası yok, ama ui konusunda daha gösterişli olduğumuzu düşünüyoruz:)

Gelelim 1 ay gibi kısa bir sürede bu oyunu neler kullanarak tamamladık.

Solution tarafında temel projelerimiz şu şekilde idi.

  • 1 Asp.net core api projesi.
  • 1 Xamarin.Forms projesi. (Standard lib, ios ve android)
  • Geçen yazılarımda göstermiş olduğum gibi bir API client projesi
  • Boggle algoritmasını kullanarak oyunları oluşturup sakladığımız bir Console App
  • Gerisi de yardımcı Utils tadında class libraryler diyebilirim.

Daha detaylı olarak kullandığımız teknolojiler ise aşağıdak gibi;

  • Asp.Net Core SignalR
  • Azure SignalR Service
  • Azure MSSQL Database
  • EF Core
  • Asp.Net Core
  • Xamarin.Forms
  • Lottie

Evet temel seviyede bunlardan daha farklı kullandığımız birşey yoktu aslında. Klasik bir katmanlı yapı içerisinde data önden arkaya gidip geliyor 🙂

Uygulamadan bahsedecek olursak, projenin tamamlanması için önemli iki konu vardı

Birincisi parmağınızla harflerin üzerinde gezerek kelime oluşturucağınız komponent in yapımı

İkincisi de bu komponent te kelimelerin doğru şekilde yerleştirilemsi tabii ki 🙂

İkinci konuyu Boggle algoritması ile çözebiliyorsunuz. Detaylarına girmiyeceğim tabii ki ama araştırmak isteyen olursa şuradan başlayabilir.

İkinci sorunumuzun çözümü iOS tarafında basit idi, ama android tarafında 0 dan GridView oluşturup ekrana basmak durumunda kaldık. Detayına sonradan gireceğim ama şunu söylemeden edemeyeceğim CustomRenderer yazarken iOS tarafındaki şu sihirli metodu android tarafında çok arayacaksınız.

Xamarin.Forms.Platform.iOS dll i altındaki ;

protected internal virtual UIView GetControl();

Xamarin forms projelerinizde CustomRenderer yazarken artık Native taraftasınız bildiğiniz gibi. Bu GetControl, Xamarin Forms tarafındaki oluşturmuş olduğunuz componenti size ios tarafında komple UIView olarak veriyor. zaten herşey orada UIView olduğu için herşey çok güzel. Android tarafta ise 128198321 tane Widget ve Layout olduğu için(bu kadar olmasa da fazlaca)  işler daha da karışabiliyor.

Peki bazı sayfaların detaylarına girip nerede neyi nasıl kullandık kısmına gelmeden önce, yukarıda ayrıca yazmış olduğum Lottie kütüphanesinden bahsetmek istedim.

Bu o kadar başarılı bir kütüphane ki ister native ister crossplatform bir uygulamay yazıyor olun, herhangi bir yerde herhangi bir animasyon oynatmak en basit işleriniz arasına giriyor bunun sayesinde.

Bu projede bizde bolca kullandık. Bu yüzden bu yazıyı Lottie ye örneklendirme yaparak bitirmek istiyorum.

Xamarin.Forms projelerinizin tümüne yani hem kod paylaşımı yaptığınız .NetStandard hem de platform spesifik projelere projelere aşağıdaki kütüphaneyi nugetten indiriyorsunuz.

Com.Airbnb.Xamarin.Forms.Lottie

İster kendi tasarımcılarınızın çizdiği animasyonlar olsun, isterseniz hazır animasyon olsunlar Lottie ile bu animasyonu oynatmak için onun istediği formatta bir json dosyası olarak vermeniz yeterli. Bu formatı tasarımcılarınız nasıl çıkartacaklarını bilirler yada hazır bulduğunuz animasyonlar için zaten download ederken bu şekilde gelecektir.

Hazır animasyonlar için de bir ton güzel animasyonun olduğu aşağıdaki linki incelemenizi öneririm

https://lottiefiles.com/

Burada illa ki ihtiyacınız olana benzer bir animasyon bulup projenize ekleyebilirsiniz diye düşünüyorum

Peki devam edelim Lottie yi xamarin forms tarafında kullanmamıza. Nuget paketini indirdikten sonra yapacağımız şey şu;

Her iki platform spesifik projeye gidip yani iOS ta AppDelegate e, Android de ise MainActivity ye gidip normal bir plugin kullanımında yaptığımız gibi aşağıdaki kodu yazıyoruz.

AnimationViewRenderer.Init();

LoadApplication(new App()) den önce bu kodu da yerleştirdikten sonra artık kullanmaya hazırız.

Projenizde bir Xaml sayfasına açın. Namespace lerin olduğu kısma aşağıdaki namespace i ekleyin.

xmlns:forms=”clr-namespace:Lottie.Forms;assembly=Lottie.Forms”

Artık projede Lottie animasyonu kullanmaya hazırız. lottifiles sitesine gidip istediğiniz bir animasyonu indirin. Aşağıdaki gibi istediğiniz animasyonun detayına gittiğinizde bazı görsel ve hız özelliklerini de değiştirerek bilgisayarınıza download edin.

Capture

json formatında pc nize indirdiğiniz bu dosyayı iOS projenizde direk Root a (AppDelegate ile aynı yere), Android projenizde ise Assets Folder(yok ise kendiniz oluşturabilirsiniz) ı içerisine ekleyin.

Daha sonra namespaci eklediğiniz xaml sayfasına dönün ve animasyonun ekran içerisinde oynamasını istediğiniz yere aşağıdaki kodu yazın

  <forms:AnimationView WidthRequest="50" 
                       HeightRequest="20"
                       HorizontalOptions="Center" 
                       Scale="4"
                       x:Name="loadingAnimationView"
                       Animation="wave_loading.json" 
                       Loop="true" 
                       AutoPlay="true"/>      

Burada projenize eklediğiniz json dosyasının adını uzantısı ile birlikte Animation propertysine vermeniz yeterli. Burada Loop true diyerek ve AutoPlay true diyerek animasyonun ekran açılınca hemen oynamaya başlamasını ve bittiğinde baştan tekrar başlamasını söylemiş olduk. Bunların tamamını animasyonun oynamasını istediğiniz senaryonuza göre kendiniz customize edebilirsiniz.

OnClick, OnPause, OnPlay vs gibi bir çok yardımcı event ten bir çok propertysine kadar istediğiniz şekilde animasyonu evirip çevirmenize olanak sağlıyor Lottie.

Biz uygulamada ana sayfadan tutunda, günlük ödül verdiğimiz popup a kadar birçok yerde Lottie yi kullandık.

Bu uygulama da kullandıklarımızla ile ilgili şeylerden örnekler le bahsetmeye devam edeceğim.

Bir sonraki örneğimizde görüşmek üzere.

Tüm .Net Client Projelerimizde Ortak Kullanabileceğimiz Bir API Client yazalım.

Selamlar,

Daha önceden bahsetmiştim böyle bir konuya girmek istediğimi.

Yeni gelen HttpClientFactory ile de beraber daha önceden yazmış olduğum Resilient Network Services serisinin daha kısasını ve güncellenmiş halini kütüphanelerin detayların da çok fazla boğumladan yeniden incelemek istiyorum.

Amacımız şu;

Bir ApiClient Standard kütüphanesi oluşturalım. Solution ne kadar Client projemiz var ise Xamarin, Web, Console, UWP, WPF farketmez hepsi bu kütüphane üzerinden network haberleşmelerini yapsın(Http üzerinden)

Bu işlem sırasında da önceki yazılarımızda kullandığımız refit ve polly hatta akavache yi de kullanalım. ModernHttpClient a artık çok gerek yok, çünkü proje özelliklerinden Default mu yoksa platform spesifik sınıflarımı kullanmak istediğimizi zaten belirtebiliyoruz aşağıdaki gibi.Capture.PNG

Burada HttpClient implementation ın Managed mı Default mu yoksa Android mi olduğunu seçebiliyoruz. iOS tarafında da durum aynı şekilde.

Peki çok uzatmadan bu kütüphanemizi yazmaya başlayalım ve Asp.Net Core Web App ve Xamarin projelerimizde de kullanalım bakalım.

Bir adet boş solution açıp içerisine önce bir adet Asp.Net Core Web App ve bir adet de xamarin.forms projeleri ekleyelim.

Daha sonra Add New Project diyerek Bir adet .netstandard class library ekleyelim.

Capture.PNG

Bu kütüphanenin diğer tüm client projeleri tarafından kullanılabileceğine eminiz çünkü .netstandard kütüphanesi ekledik.

Projede kullanmak istediğimiz ve resilient network services kısmında bize yardımcı olacak 3 temel şey var.

  • Polly
  • Refit
  • HttpClientFactory

Tüm bunların kullanımı için öncelikle aşağıdaki paketleri projeye teker teker ekliyoruz.

  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Http.Polly
  • Refit.HttpClientFactory
  • Xamarin.Essentials (xamarin tarafında kullanmak üzere şimdilik çok önemi yok)

Projede kullanmak için bir api yazmaktansa yine open apilardan randomuser  kullanalım.

Bunun için daha önceki yazılarımda çokça detayına girdiğim için burada konuyu uzatmadan hemen refit interface imi oluşturucam. Sonrasında da örnek bir tane delegating Handler oluşturucam

Bunlar aşağıdaki gibi;

Random user api si ile haberleşecek olan Refit Interface i;
Projede Endpoints adında bir klasör açıp içerisine aşağıdaki interface i atıyorum

Burada amaç tüm farklı endpointleri ayrı ayrı interfaceler altında toplayıp hepsini ayrı ayrı konfigüre edebilmek.

 [Headers("Content-Type : application-json")]
    public interface IRandomUserApi
    {
        [Get("/")]
        Task<string> GetUser(string results);
    }

DelegatingHandler ım.

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

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

Bu iki kod bloğu ile ilgili daha detaylara girmek isterseniz aşağıdaki yazılarımı okuyabilirsiniz.

Projenin temel kısımları hazır oldu. Bundan sonraki yazımda asıl sınıfımız olan ApiClient sınıfını yazıp client projelerimizde kullanacağız.

Bir sonraki yazımda 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.

Asp.Net Core HttpClientFactory + Polly

Selamlar,

HttpClientFactory ile ilgili yazılarıma bir yenisini de Polly ile kullanımını inceleyerek devam etmek istedim.

Polly, açıkçası farkında olmasanız bile kullanmak zorunda olduğunuz bir kütüphane diyecek kadar ileri gidebilirim =) eğer ki benzer işlevleri zaten zamanında siz kendiniz farklı kütüphaneler olarak yazmadıysanız.

Polly ile ilgili yazılarıma şuradan ulaşabilirsiniz;

Polly sizelere network haberleşmesi sırasında uygulamanızda olması gereken bir çok pattern i extension metodlar halinde sunan ve çok daha fazlasını yapmanıza olanak tanıyan bir kütüphane.

Polly ile ;

  • Error ve Result Handling
  • Retry, WaitAndRetry, ForeverRetry gibi senarto ve patternleri
  • Circuit Breaker ve Advanced Circuit Breaker gibi senaryo ve patternleri
  • Cache, Bulkhead Isolation gibi

birçok şeyi çok basit şekilde uygulamalarınıza implemente edebiliyorsunuz.

Detaylarına yukarıda ki yazılarımdan ve kendi github reposundan ulaşabilirsiniz.

Asp.Net Core projelerimizde HttpClientFactory ile beraber kullanmak için yapacağımız ilk şey nugetten aşağıdaki paketi indirmek

Microsoft.Extensions.Http.Polly

Daha sonrasında kullanım şekli önceki yöntemler ile çok benzer oluyor.

Uygulamanın startup dosyasında aşağıdaki gibi AddHttpHandler dedikten sonra o Client ile ilgili uygulamak istediğiniz policy leri fluent bir şekilde ekleyebiliyorsunuz.

  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.AddHttpClient("randomuserclient")
                .AddPolicyHandler(Policy.TimeoutAsync(TimeSpan.FromSeconds(10)))
                .AddTransientHttpErrorPolicy(p => p.RetryAsync(3));

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

Yukarıdaki örneğimizde projemize NamedClient olarak eklemiş olduğumuz bir Client ın Polly yi nasıl kullanabileceğini söylemiş olduk.

Nuget ten indirdiğimiz paket ile beraber bize birçok extension metod geliyor.

Bunlardan sadece iki tanesini yukarıdaki senaryoda kullanmış olduk.

  • Time out policy ekleyerek, gidecek requestlerin en fazla 10 sn bekledikten sonra eğer response alamazsa doğrudan polly tarafından hataya düşürülmesini sağladık
  • Haberleşme sırasında geçici sorunlar olduğunda 3 kere yeniden denenmesini istedik. Anlık internet kayıpları, server anlık olarak response verememesi available olamaması gibi durumlarda request 3 kere yeniden denenecek.(RetryPattern)

Kullanım şekline bakığımızda ise öncekilerden hiçbir farkı yok. NamedClient eklemiş olduğumuz için aşağıdaki şekilde kullandık. Typed veya Basic usage olarak ekleseydik de o şekilde kullanacaktık. Polly nin yazarken ki dikkat etmemiz gereken tek kısmı Startup tarafında Policyleri Clientlarımıza implemente ettiğimiz kısım.

 public class HomeController : Controller
    {
        private readonly IHttpClientFactory_randomUserApi;
        public HomeController(IHttpClientFactory randomUserApi)
        {
            _randomUserApi = randomUserApi;
        }

        public async Task Index()
        {
            var result = await IHttpClientFactory.CreateClient("randomuserclient").GetUser("10");
            return View();
        }

      ..........

Bir önceki yazımda Refit ile HttpClientFactory i nasıl kullanırız bundan bahsetmiştim. Bir de bu üçünü yani Refit + Polly + HttpClientFactory i nasıl kullanırız buna bakalım.

Aşağıda basit bir Refit interface i görüyoruz.

namespace HttpClientFactoryRefit
{
    [Headers("Content-Type : application-json")]
    public interface IRandomUserApi
    {
        [Get("/")]
        Task GetUser(string results);
    }
}

Nugetten aynı projemize ;

Refit.HttpClientFactory 

paketini de indirdikten sonra aşağıdaki şekilde hem refit i AddRefitClient diyerek services a ekleyebilir, hem de sonrasında yukarıda ki gibi Polly Policy lerini aynı Client ın devamında ekleyebiliriz.

  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.AddRefitClient()
                .ConfigureHttpClient(client =>
                {
                    client.BaseAddress = new Uri("https://randomuser.me/api");
                })
                .AddPolicyHandler(Policy.TimeoutAsync(TimeSpan.FromSeconds(10))) 
                .AddTransientHttpErrorPolicy(p => p.RetryAsync(3)); ;

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

Kullanım şeklinde de Refit ile HttpClientFactory i nasıl kullanıyor isek aynı şekilde kullanmaya devam ediyoruz.

public class HomeController : Controller
    {
        private readonly IRandomUserApi _randomUserApi;
        public HomeController(IRandomUserApi randomUserApi)
        {
            _randomUserApi = randomUserApi;
        }

        public async Task Index()
        {
            var result = await _randomUserApi.GetUser("10");
            return View();
        }
       

          .........

Bu yazımızda da hem Polly ile HttpClienFactory nasıl kullanacağımızı görmüş olduk hem de bir önceki yazımızda Refit ile kullandığımız gibi refit i de işin içine dahil ettik.

Refit + Polly + HttpClienFactory üçlüsü güçlü bir Api haberleşme altyapısı için çok büyük artılar sağlayacaktır projenize.

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

Asp.Net Core HttpClientFactory + Refit

Selamlar,

Son birkaç yazıdır üzerinde durduğum HttpClientFactory ile sevdiğim ve sıkça kullandığımı kütüphanelerden biri olan Refit in beraber kullanımı hakkında yazmak istedim.

Daha önceki konu ile alakalı yazılarıma aşağıdaki linklerden ulaşabilirsiniz.

 

Refit daha önce kullanmadıysanız hemen kullanmaya başlamanız için çok sebep var. Detaylarına girmeyeğim, önceki yazılarda değinmiştim, ama özetle söyleyebilirimki bir enpoint ile yapacağınız tüm haberleşmeyi tek bir interface üzerinde hazırlayıp geri kalanını refit e bırakıyorsunuz. O da size runtime bu interface nizin canlı ve çalışan bir halini sunuyor.

Refit ile;

  • Get,Post,Put,Delete .. her HttpVerb e karşılık gelen metodlar oluşturabilirsiniz
  • Querystring, dynamic url parameter s gibi birçok şekilde get request i atabilirsiniz
  • json ve formurlencoded gibi birçok farklı serialization yöntemi ile post lar yapabilirsiniz
  • multipart upload lar yapabilirsiniz
  • static veya dynamic header ları kolayca request lerinize ekleyebilirsiniz

ve daha nicesi..

Aşağıdaki basit bir refit interface i görüyoruz. Bu interface e baktığımız da şunu anlıyoruz.

Relative url i olmayan bir endpoint e GET request i atılacak string olarak results adında bir querystring parametresi geçilecek. Geriyede string bir result dönecek. Gidecek olan request e de Content-Type : application/json header ı eklenecek.

namespace HttpClientFactoryRefit
{
    [Headers("Content-Type : application-json")]
    public interface IRandomUserApi
    {
        [Get("/")]
        Task<string> GetUser(string results);
    }
}

Peki daha önceden 3 farklı yöntem de HttpClientFactory Kullanımı görmüştük. Hatta Typed Client kullanırken tam olmasada benzer bir şekilde Client sınıfımızı ayrıca yazmıştık. Peki Elimizde varolan bir proje var diyelim, önceden de refit kullanmıştık. Ama artık HttpClientFactoryde kullanmak istiyoruz dersek ne yapacağız.

Sadece projemize nugetten

Refit.HttpClientFactory paketini indirdikten sonra, Startup dosyamızdaki ConfigureServices metodunda aşağıdaki şekilde tanımlamamızı yapabiliriz.

Tek fark önceden olduğu gibi AddHttpClien değil, artık AddRefitClient metodunu kullanıyo olmamız. Bu extension metod, yukarıdaki paketi indirdiğimizde gelecek.

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.AddRefitClient()
                .ConfigureHttpClient(client =>
                {
                    client.BaseAddress = new Uri("https://randomuser.me/api");
                });

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

Peki kullanımı nasıl?

Kullanımının TypedClient kullanımından hiçbir farklı yok. Refit interface imizi sanki bir TypedClient mış gibi Controller ımızda tanımlıyoruz, ve constructor de bize inject edilecek olan interface olarak atamamızı yaptıktan sonra, dosya içerisinde istediğimiz yerde interface içerisinden yazdığımız istediğimiz metodu kolayca çağırıyoruz.

 public class HomeController : Controller
    {
        private readonly IRandomUserApi _randomUserApi;
        public HomeController(IRandomUserApi randomUserApi)
        {
            _randomUserApi = randomUserApi;
        }

        public async Task Index()
        {
            var result = await _randomUserApi.GetUser("10");
            return View();
        }

         ....

Bu kadar basit. Zaten refit kullandığınız ama HttpClientFactory e geçmediyseniz kodlarınızda çok az değişiklik yaparak kullanmaya başlayabilirsiniz.

Yeni başlayacağınız projelerde her ikisinide kullanmanızı öneririm.

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

 

Asp.Net Core da HttpClientFactory Kullanımı (Named and Typed Clients)

Selamlar,

HttpClient Factory nin Asp.Net Core tarafında kullanımı ile ilgili yazılarımın bu bölümünde NamedClient  ve TypedClient kullanımlarına bakacağız.

Önceki yazılarımda HttpClientFactory hakkında teorik olarak getirdiklerinden ve Basic seviye de kullanımından bahsetmiştim.

Şimdi ilk olarak NamedClients dediğimiz şekilde nasıl kullanırız buna bakalım.

Aslında Basic usage dan çok fazla farklı değil ama adı üzerinde bu sefer herbir httpclientfactory e bir isim vermeye başlıyoruz. Özellikle uygulamalarımızda birden çok endpoint ile haberleşeceğimiz düşünün (ki genelde böyle olur). Bunların hepsi ile haberleşirken aynı konfigürasyonu kullanmak yeterli olmayabilir. Dolayısı bu haberleşmeler için kullanacak olduğumuz client ları ayrı ayrı tanımlayıp ayrı ayrı konfigüre etmek daha doğru olacaktır.

Bu ayırt edebilmenin de en kolay yollarından biri her bir client a bir isim vermek 🙂

Aşağıdaki örnekte bir asp.net core startup dosyasında namesclient nasıl tanımlarız bunu görüyoruz. Önceki basic usage ile kıyasladığımız da tek fark sadece string bir isim vermiş olmamız. Bu örnekte isim olarak randomuserclient dedik.

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


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

Bundan sonrasında da yine Basic usage a oranla kullanımında da pek fark yok. Yine IHttpClienFactory interface imizi HomeController a otomatik olarak inject olan olacak şekilde atama yapıyoruz. Sonrasında da yine CreateClient metodu ile httpclient objemizi oluşturup işlemlerimizi yapabiliyoruz. Burada sadece tek fark CreateClient derken, Startup dosyasında vermiş olduğumuz isim ile çağırıyor olmamız.

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.GetAsync("?results=5");

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

Gerçek hayat senaryolarında Basic usage ve NamedClient ları kullanıyor olmanız çok olası değil aslında. Ama varlıklarını ve kullanım şekillerini bilmekten zarar gelmezdi. 🙂

Gelelim herbir client ımızı ayrı bir sınıf olarak tanımlayacağımız kullanım şekline. Daha gerçekçi bir senaryo olarak şunu düşünebiliriz. Örneğin projemizde “Customer” entityleri için yazılmış bir API var, ve CRUD işlemleri yapıyor. Bu Api ile haberleşecek yapıyı, Client projesinde bi HttpClientFactory typed client ı olarak tanımlayıp, Gidilecek her bir farklı relative url için (/create, /update, /delete, /get vb.)  ayrı ayrı metodlar olarak tanımlamak ve birarada tutmak hoş olmaz mıydı?

Bence de hoş olurdu:) Aşağıda randomuser api ile haberleşmesini istediğimiz Endpoint için tüm işlemlerimizi yazacağımız bir TypedClien oluşturalım.

Sınıf dikkat ederseniz hiçbir özel sınıftan veya interface den miras almıyor. En altta tanımladığımız bir interface miz var. tek bir metoda sahip, Concrete class ımız olan RandomUserClient buradan implemente oluyor ve ekstra bir metoda daha sahip.

Burada en önemli nokta Client içerisnde readonly olarak tanımladığımız ve Constructor ımıza otomatik olarak inject edilecek olan HttpClient objesi. Bu obje biz bu client ımızı startup dosyasında TypedClient olarak service lere eklerken bizim için inject olucak şekilde hazırlanıyor framework tarafından.

Yani bir typed client yazarken bir properymiz HttpClient tipinde olacak, ve bu TypedClient içerisindeki network haberleşmelerini, constructor ımızla bize inject edilcek olan client ile yapacağız.

namespace HttpClientFactoryTypedClient
{
    public class RandomUserClient : IRandomUserClient
    {
        public RandomUserClient(HttpClient client)
        {
            Client = client;
        }

        public HttpClient Client { get;  }

        public async Task GetStringContentAsync()
        {
            return  await Client.GetStringAsync("?results5");
        }

        public async Task GetStringDataLength()
        {
            var result = await Client.GetStringAsync("?results5");
            return result.Length;
        }
    }

    public interface IRandomUserClient
    {
        Task GetStringDataLength();
    }
}

Bu oluşturmuş olduğumuz typed client ı startup dosyasında aşağıdaki şekilde register edebiliriz.

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

            services.AddHttpClient<IRandomUserClient, RandomUserClient>(client =>
            {
                client.BaseAddress = new Uri("https://randomuser.me/api");
                client.DefaultRequestHeaders.Add("Accept", "application/json");
                client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting");
            })
            .SetHandlerLifetime(TimeSpan.FromMinutes(2));
            


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

IRandomUserClient interface ile e register ediyoruz ki, ileride bu implementasyonu değiştirmek istersek, projede burası dışında başka yerde kod değiştirmek zorunda kalmayalım burada sadece

services.AddHttpClient<IRandomUserClient, NewRandomUserClient>()….

diyerek hayatımıza devam edebilelim.

Peki bu typed client ımızı projede nasıl kullanacağımıza bakarsak, aşağıdaki gibi olacak.

Bu sefer basic ve named client kullanımında olduğu gibi IHttpClienFactory değil doğrudan kendi yazdığımız client ı field olarak oluşturuyoruz ve Constructorda da bize o inject ediliiyor. Aşağıda da yine eskiden olduğu gibi kullanabiliyoruz. Tek farkı, CreateClient metodu yerine Client propertysini kullanıyoruz. Bu da hemen yukarıda kendi TypedClient ımız içerisinde tanımlamış olduğumuz HttpClient tipindeki propertymiz idi.

public class HomeController : Controller
    {
        private readonly IRandomUserClient _randomUserClient;

        public HomeController(IRandomUserClient randomUserClient)
        {
            _randomUserClient = randomUserClient;
        }

        public async Task Index()
        {
            var result = await _randomUserClient.Client.GetAsync("?results=5");
            ViewBag.statusCode = result.StatusCode;

            var stringResult = await _randomUserClient.GetStringContentAsync();

            //var contentLength = await _randomUserClient.GetStringDataLength();

            return View();
        }

 

HttpClientFactory hakkında bayağı bilgi sahibi olmuş olduk. Daha yazacaklarım var bu konuda, örneğin Refit ve Polly ile kullanımı nasıl. asp.net core dışında bu httpclient factory i nasıl kullanırız vs vs..  Şimdilik bu kadar.

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

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.