Erhan Ballıeker

Microsoft Build 2019 Türkiye Etkinliği

Selamlar,

Geçtiğimiz 18 haziran günü Microsoft Türkiye’de Build 2019′ un Türkiye ayağını gerçekleştirdik. Çok değerli konuşmacı ve katılımcı dostlarımızla çok faydalı bir etkinlik olduğunu düşünüyorum. Birçok konuya değindiğimiz bu etkinlikte ben de WebAssembly ve Blazor hakkında bilgiler paylaştım.

Bir önceki yazımda WebAssembly ile alakalı yazımı bulabilirsiniz. Bunun üzerine bu yazımda da sizlere Blazor bahsetmek istiyorum.

WebAssembly’yi .Net dünyasında kullanmak için henüz official pre-relase halinde bulunan Blazor a başvuracağız. Blazor ile Visual Studio da 3 farklı şekilde geliştirme yapmak mümkün. Bunların detayından aşağıda bahsedip örnekler göstereceğim.

  1. Client-Side Blazor
  2. Asp.Net Core Hosted Blazor
  3. Server-Side Blazor

Client-Side Blazor

Bir önceki yazımda bahsettiğim gibi high-level dillerde yazılan kodun wasm a dönüşüp, browser içerisinde javascript sandbox ı içerisinde çalıştırılabilmesi mümkün. Hatta bu zaten WebAssembly nin en güçlü özelliklerinden biri.

Blazor için bu senaryo aşağıdaki şekilde gerçekleşiyor.

yazdığımız C#/Razor dosyaları compile olup .dll haline geliyor. daha sonra bu .dll dosyalarımız mono.wasm runtime ın da çalışabilir hale geliyor. mono.wasm da, javascript runtime üzerinde çalışıyor. Yani özetle şuan bir .dll dosyamız wasm olarak browserda çalışmak için mono ya biraz muhtaç. Ama bunlar arka planda olan şeyler, kullanırken mono vs uğraşmıyorsunuz. Ama yine de bu mono dan itibaren başlayan xamarin in gücünü ve arka planındaki güzelliği de bilmek açısından değerli 🙂

compiletowasm

Projemizde bir mvc projesinde kullandığımız .cshtml sayfalarına benzer olarak, Client-side Blazor da .razor uzantılı dosyaları kullanıyoruz. Bu dosyalar derlendiğinde elde ettiğmiz dll, mono-runtime ile browser içerisinde javascript runtime ın da çalışabilir bir wasm a dönüşüyor.

Tamamen javascript kadar güvenli olduğundan tekrar söz etmek istiyorum, çünkü aşağıdaki resme baktığımızda bir .net web developer ın çok da alışık olmadığı bir takım dosyaların browser tarafına yüklendiğini göreceğiz. Bir çok yazılımcının aklına, browsera a yüklenen .net dll lerini gördüğünde güvenlik ile alakalı sıkıntılar yaratıp yaratmayacağı geliyor. Ama dediğimiz gibi herşey en sonunda javascript kendisinin çalıştığı, sandbox environment ında çalıştığı için en az javascript kadar güvenli.

Capture

Yukarıda ki resme baktığınızda, C# kodumuzun browser içerisinde wasm olarak çalışması için ne kadar fazla dll in de browser a yüklendiğini görüyoruz.

Mono.Security.dll, Mono.WebAssembly.Interop.dll, mono.wasm, solutionname..dll, System.dll, System.Core.dll gibi hem mono hem de .net e bağlı birçok dll in browser a yükleniyor. Bu sayede yazdığımız C# kodları, client side tarafında çalıır hale geliyorlar.

Peki direk filmin sonunu göstermiş olduk ama biraz daha başa sararak herşeye daha detaylıca bakalım.

Öncelikle  ister Blazor ile ister diğer pre-release olan tüm yeni feature ları denemek için neler yapacağımıza bir bakalım.

Blazor .net core 3.0 ile beraber sadece Server-Side desteği ile official olarak gelecek. Client-Side ve Core Hosted taraflarının release olmasına biraz daha vari ama denemekten hatta basit projeleri bu yöntemler ile de yapmaktan çekinmenize hiçbir sebep yok.

Yeni .net core feature larını denemek için aşağıdaki yöntemleri izlemenizi tavsiye ederim.

  1. Öncelikle şuradan Visual Studio 2019 Preview ı download edin. Yeni featureları mevcut stabil VS 2019 da da aktif edebilirsiniz ama, preview ı indirip tüm yeni-henüz release olmamış özellikleri buradan daha hızlı update ler ile takip etmenizde fayda var.
  2. Sonrasında şuradan son güncel .net-core 3.0 sdk sını indirmeniz gerekiyor.

Bu adımlar aslında birçok feature ı deneminiz için yeterli ama Blazor için iki adımımız daha var.

  1. Buradan Asp.Net Core Blazor template ini download edip kurmanız gerekiyor. Oldukça basit bir işlem, bir VSIX dosyası (Visual Studio Extension) kurmaktan farklı değil
  2. Son olarak da, aşağıdaki komutu çalıştırmanı yeterli olacaktır.
    1. dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0-preview6.19307.2

Yukarıdaki tüm adımları tamamladığımızda, Visual Studio 2019 Preview ı açtığımızda, File-> New Project seçip yeni bir Asp.Net Core Project seçtiğimizde karçımıza aşağıdaki gibi bir ekran çıkacak. Yeni gelen, ve gelecek olan tüm proje template lerini burada görebilirsiniz.

Captureas.PNG

yukarıdaki 4 adımı da tamamladığımızda tüm yeni Blazor template leri karçımıza çıkıyor.

Blazor dışında, gRPC service, Worker Service (eski IHostedServices veya Background Services ın yeni hali) gibi yeni templateleri de denemenizde fayda var. Bunlar ile ilgili de en kısa sürede yazmayı planlıyorum.

Şimdilik Blazor a geri dönelim. Seçeneklerden Blazor (Client-Side) seçip Create e bastığımızda karşımıza aşağıdaki gibi bir solution açılacak.

Captureasasa.PNG

Yukarıda gördüğünüz gibi, Asp.Net Core projelerinde olduğu gibi bir Startup.cs, ve Program.cs dosyalarımız mevcut. Değişik gelecek ilk şey, .razor uzantılı dosyalar.Bunlar aslında klasik MVC projelerindeki .cshtml lerden çok da farklı değil. Gerisi tamamen bildiğiniz bir ASp.Net Core proje yapısı.

App.razor dosyasının içerisine baktğımızda aşağıdaki gibi tek bir satır kod görüyoruz.

Burada bir Router tanımlı. Assembly olarak da sadece projedeki Program.cs dosyasının bulunduğu assembly gösterilmiş. Sayfalar arasındaki geçişler sırasında postback olmadan geçişler olduğunu göreceksiniz. Bu router ın tanımlandığı yer.

Router AppAssembly="typeof(Program).Assembly" 

Program.cs dosyasının içerisi de aşağıdaki gibi. Normal bir Asp.Net Core projesi geliştirmesinde bulunduysanız, burada WebHostBuilder yada, HostBuilder kullanıldığını görmüşsünüzdür. Burada fark olarak kullandığımız HostBuilder, BlazorWebAssemblyHost oluyor. Use Startup yerine de yine Blazor için olan, UseBlazorStartup extension metodunu görüyoruz.

 public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
            BlazorWebAssemblyHost.CreateDefaultBuilder()
                .UseBlazorStartup();
    }

Yukarıda ki Program.cs tarafında kullanılmasını söylediğimiz Blazor Startup dosyasıda aşağıdaki gibi. Yine klasik bir core projesinden çok farklı değil. Sadece app ismindeki component imiz, builder a ekleniyor. App.Razor tarafında ki routing tarafının eklendiği ksım. Bir çok hali hazırda yazılmış komponentler mevcut, sizler kendi component lerinizi yazabilirsiniz. Bunlara başka bir yazıda detaylıca değineceğim.

   public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IComponentsApplicationBuilder app)
        {
            app.AddComponent("app");
        }
    }

Projede ki en temel kısımları gördükten sonra, .razor dosyalarının içlerine bir bakalım. Örneğin sol taraftaki Navigasyon menüsü için, proje içerisinde NavMenu.razor adında bir dosya mevcut. Bunun içeriği aşağıdaki gibi.



… @functions { bool collapseNavMenu = true; string NavMenuCssClass => collapseNavMenu ? “collapse” : null; void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } }

Yukarıdaki gibi normal bir html içerisine, tıpkı cshtml deik gibi C# kodlarımızıda ekleyebilyoruz. Burada ekstra olarak farklı olan şey sayfanın en sonunda function section ı içerisinde yazmış olduğumuz C# kodları. İşte bunlar tam olarak javascript metodları yazar gibi C# metodlarını yazığ sayfa içerisinde kullanacağımız kısım. Counter.razor dosyasına bakarken daha dikkatli inceleyeceğiz.

sayfaların en üst tarafında route ları belirtiyoruz. Örneğin aşağıda Index.razor ı görüyoruz, ekstra bir root a sahip olmadığından direkt olarak “/” ile işaretlenmiş durumda. bu route u verirken sadece başına @page yazmamız gerekiyor.

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

Sayfalar içerisinde başka sayfaları (componentleri – .razor dosyalarını) çağırmak da çok basit. yukarıda gördüğünü gibi Index içerisinde SurveyPrompt dosyası ayrı bir html tag i gibi veriliyor sadece bu kadar. Eğer komponentler içerisine parametre geçmek de istiyorsa bunu da yukarıdaki Title attribute u ile yağtığımız gibi, componenti tanımlarken ki Property adını yazıp verebiliyoruz atamak istediğimiz değeri.

Örneğin SurveyPromt.razor dosyasının alt tarafında yazılan function sekmesi şöyle;

@functions {
      // Demonstrates how a parent component can supply parameters
       [Parameter] string Title { get; set; }
}

Başında [Parameter] attribute ile işaretlenmiş bir Property mevcut. Bu parametreyide aynı isimle başka bir component içerisinden çağırırıken yukarı da index sayfasında yaptığımız gibi, bir html element ine attribute atarmış gibi tanımlıyoruz bu kadar.

Aşağıda bir de counter dosyasına bakalım. basit bir IncrementCount metodu tanımlanmış, currentCOunt değerini bir arttırıyor. Bu metodu html elementine atamak da sadece başına bir @ işareti koyup adını yazmak kadar basit. herşey normal cshtml dosyalarında razor engine kullanırken ki syntax gibi aslında. ama yapabildiklerimiz çok çok daha fazla =)

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>

@functions {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

Bu projeyi çalışıtırığp çıktısına baktığımızda da en üstte gösterdiğim gibi bir çok dll in browser tarafına yüklendiğini görüyoruz. Mono runtime ı sayesinde de dll lerimiz javascript runtime ında çalışabilen wasm a dönüşüyor. Benim gibi javascript i çok seven biri değilseniz, WebAssembly ve Blazor ın yeri sizde de bambaşka olacaktır. =)

Bir sonraki yazımda Server-Side ve Core-Hosted seçeneklerini inceleyeceğiz.

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

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.

 

Tüm .Net Client Projelerimizde Ortak Kullanabileceğimiz Bir API Client yazalım Bölüm 2

Selamlar,

Bir önceki yazımda hazırlığını yapmış olduğumuz projemizin asıl kısmına gelelim.

Projemize ApiClient adında bir sınıf ekliyorum ve içeriğini aşağıdaki gibi dolduruyorum. Bir göz gezdirip detaylarına değinelim.


 public class ApiClient
        {
            #region fields

            private static ApiClient shared;
            private static object obj = new object();

            #endregion

            #region properties

            internal static IServiceCollection Services { get; set; }
            internal static IServiceProvider ServiceProvider { get; private set; }
            private static ApiClient Shared
            {
                get
                {
                    if (shared == null)
                    {
                        lock (obj)
                        {
                            if (shared == null)
                            {
                                shared = new ApiClient();
                            }
                        }
                    }

                    return shared;
                }
            }

            #endregion

            #region api spesific properties

            private IRandomUserApi _IRandomUserApi { get => ServiceProvider.GetRequiredService(); }

            //Exposed public static props via ApiClient.Shared 
            public static IRandomUserApi RandomUserApi{ get => Shared._IRandomUserApi; }

            #endregion

            #region ctor

            private ApiClient()
            {
                if (Services == null)
                    Services = new ServiceCollection();

                Init();
            }

            #endregion

            #region internal methods

            private void Init()
            {
                ConfigureServices(Services);
                ServiceProvider = Services.BuildServiceProvider();
            }

            private void ConfigureServices(IServiceCollection services)
            {
                services.AddTransient<ITokenService, TokenService>();

                #region AnonymousApi Configuration

                services.AddRefitClient()
                .ConfigureHttpClient(c =>
                {
                    c.BaseAddress = new Uri("http://wordy.azurewebsites.net/");
                    c.Timeout = TimeSpan.FromSeconds(10);
                })
                .AddTransientHttpErrorPolicy(p => p.RetryAsync())
                .AddHttpMessageHandler(serviceProvider =>
                {
                    var token = serviceProvider.GetRequiredService().GetToken();
                    return new AuthTokenHandler(token);
                });

                #endregion

            }

            #endregion
        }

Yukarıdakilerle alakalı olarak şurayı da okumanızı öneririm.

Burada önceki yazımda projeme eklediğim kütüphaneleri kullanmaya başlıyorum artık.

Yukarıda dönen hikaye şu;

  • Singleton bir ApiClient objem var. Tüm refit interfacelerini birer property üzerinden dışarıya expose ediyorum.
  • ConfigureServices metodunu neredeyse Asp.Net Core daki gibi birebir aynı yapmaya çalıştım. IoC conteiner ı oluşturup, refit interfacelerimi ve polly policy lerimi ilgili enpoint e register ediyor
  • Son olarak aynı şekilde yazmış olduğum DelegatingHandler larıda client ıma ekliyorum ve herşey kullanıma hazır hale geliyor.

Bundan sonra herhangi bir clien projesinde şunu yapabilirim

ApiClient.RandomUserApi.GetUser(“3”);

dediğimde Client projem her ne olursa olsun ister bir core web app ister xamarin ister başka birşey,  httpclient factory üzerinden refit, polly, delegating handler lar da kullanarak güzel yapı kurmuş oluyorum.

Bunları istediğimiz gibi şekillendirip güncelleyip, istediğimiz gibi konfigüre edebiliriz ve hiçbir client projemize dokunmamış oluruz.

Ek olarak burada ITokenService ve TokenService diye bir service yazdım. Bunu da şuna örnek olarak kullanabiliriz. Örneğin kullanıcı mobil uygulama da login olduğunda aldığımız token Xamarin.Essentials s Preference paketi ile saklayarak AuthTokenDelegating handler a parametre olarak verebilmenin örneği olsun diye koydum.

Farkli client projeler de bu ITokenService implemenatasyonlarını ayrı yazarak başka türlü yerlerde saklayıp okuyabiliriz.

Xamarin projesi için bu işimizi görecektir.

   public interface ITokenService
    {
        string GetToken();
    }

Xamarin forms projesi için implementasyon;

    public class TokenService : ITokenService
    {
        public string GetToken()
        {
            if (Preferences.ContainsKey(Constants.AuthToken))
                return Preferences.Get(Constants.AuthToken, string.Empty);
            else
                return string.Empty;
        }
    }

Bunu muhtemelen bu ApiClient projesinde değil de client projelerde register etmek daha doğru olacaktır gerçek hayat senaryolarında.

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

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.

Xamarin.Forms ConfinstaApp Sample Bölüm 3

Selamlar,

Confinsta app in detaylarını incelemeye devam ediyoruz. Bu bölümde xamarin.forms un yeni gelen özellikleri ile de birlikte 1 gün gibi kısa bir sürede ne şekilde ui lar çıkartabileceğimizi örneklemeye çalıştığım Confinsta app de, kullanıcıların fotoğraflarının kimler tarafından beğenildiğini ve hangi fotoğraflarının beğenildiğini görecekleri ve kimlerin kendisini Follow ettiklerini görecekleri 2 tab lı sayfadan oluşan sayfamızı inceleyeceğiz.

Bu sayfa içerisinde kullandığım temel özellikler şöyle;

  • Syncfusion TabView
  • FlexLayout
  • ListView, GroupedListView
  • BindableSpan

gibi özellikler bulunuyor.

Sayfaların  görüntülerini hatırlayalım.

Sayfanın en temel özelliği, parmak ile sağa sola swipe edilerek tabların geçişlerini de sağlayan Syncfusion ın TabView ı üzerine kurulu olması. syncfusion ın ChartBar ları dışında en çok kullandığım paketi bu paket. Bir çok uygulamada buna benzer swipe ile geçişi istenen tablı yapılar mevcut. Özellikle iOS native tarafta bile bunu yapmak için ayrı cocoaPod ihtiyaç duyacakken böyle bir yapıyı custom rendererlar ile yapmak çok akıl karı değil. Bu yüzden kullanmaya kaçınsam da, bazı durumlarda paralı paketleri kullanmak gerekebiliyor, özellikle zamanınız dar ise 🙂

Sayfa iki tab a ayrılmış durumda. Soldaki tab da kullanıcıların sizin resimlerinizden beğendiklerini listeliyoruz. Sağda ise sizi takip edilenler gösteriliyor. Özellikle sol taraftaki her bir kullanıcının altında “sizin şu resimleriniz beğendi” ile beraber resimlerin dinamil geldiğini görsem ve FlexLayout olmasa yine sinir olurdum. Ama bir önceki yazımda gördüğümüz gibi FlexLayout ile bu tarz ui lar çok basit hale geliyor.

Gelelim sayfanın xaml tarafına. Sayfada ki iki farklı ekranı da aslında aynı xaml dosyası içinde tanımlayabiliyoruz. Aslında tabii bunları ayrı ContentView lar olarak tanımlayıp TabView ın olduğu sayfada referanslarınıda verebilirdik (ilk yazıda Tabbed page template inde kullandığımız yapı gibi).

Sayfanın 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"
             xmlns:tabView="clr-namespace:Syncfusion.XForms.TabView;assembly=Syncfusion.SfTabView.XForms"
             xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
             x:Class="ConfinstaApp.Views.FeedActivityView">
    <ContentPage.Content>

        <tabView:SfTabView VisibleHeaderCount="2" TabHeaderBackgroundColor="White" x:Name="mainTabView" Margin="0,20,0,0">
            <tabView:SfTabItem Title="Following" TitleFontAttributes="Bold" TitleFontColor="Black" TitleFontSize="15">
                <tabView:SfTabItem.Content>
                    <StackLayout>
                        <ListView x:Name="FollowingFeedListView" 
                                  ItemsSource="{Binding FollowingItems}"
                                  SelectionMode="None"
                                  SeparatorVisibility="None"
                                  HasUnevenRows="true"
                                  RefreshCommand="{Binding LoadFollowingFeedListCommand}"
                                  IsPullToRefreshEnabled="true"
                                  IsRefreshing="{Binding IsBusy, Mode=OneWay}"
                                  CachingStrategy="RecycleElement">
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <ViewCell Appearing="ViewCell_Appearing">
                                        <Grid RowSpacing="5" Padding="10">
                                            <Grid.RowDefinitions>
                                                <RowDefinition Height="50"></RowDefinition>
                                                <RowDefinition Height="Auto"></RowDefinition>
                                            </Grid.RowDefinitions>
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="50"></ColumnDefinition>
                                                <ColumnDefinition Width="*"></ColumnDefinition>
                                            </Grid.ColumnDefinitions>

                                            <controls:CircleImage BorderThickness="2" 
                                                                  BorderColor="#ab423f" 
                                                                  WidthRequest="40"
                                                                  HeightRequest="40"
                                                                  Source="{Binding ProfileUrl}" 
                                                                  Aspect="AspectFit"
                                                                  Grid.Row="0"
                                                                  Grid.Column="0"></controls:CircleImage>

                                            <Label LineBreakMode="TailTruncation"
                                                   FontSize="Small"
                                                   Grid.Column="1"
                                                   Grid.Row="0"
                                                   VerticalOptions="Center"
                                                   TextColor="Black">
                                                <Label.FormattedText>
                                                    <FormattedString>
                                                        <Span Text="{Binding Name}" FontAttributes="Bold" FontSize="Small"></Span>
                                                        <Span Text=" liked some posts." TextColor="DarkGray" FontAttributes="None" FontSize="Small"></Span>
                                                    </FormattedString>
                                                </Label.FormattedText>
                                            </Label>

                                            <FlexLayout x:Name="imagesLayout" 
                                                        Wrap="Wrap" 
                                                        JustifyContent="Start"
                                                        Direction="Row"
                                                        Grid.Row="1"
                                                        Grid.Column="1"></FlexLayout>
                                        </Grid>
                                    </ViewCell>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </ListView>
                    </StackLayout>
                </tabView:SfTabItem.Content>
            </tabView:SfTabItem>
            <tabView:SfTabItem Title="You" FontIconFontAttributes="Bold" TitleFontColor="Black" TitleFontSize="15">
                <tabView:SfTabItem.Content>
                    <StackLayout>
                        <StackLayout>
                            <ListView x:Name="AboutYouFeedListView" 
                                  ItemsSource="{Binding GroupedItems}"
                                  SelectionMode="None"
                                  IsGroupingEnabled="True"
                                  GroupDisplayBinding="{Binding Key}"
                                  SeparatorVisibility="None"
                                  HasUnevenRows="true"
                                  RefreshCommand="{Binding LoadFollowingFeedListCommand}"
                                  IsPullToRefreshEnabled="true"
                                  IsRefreshing="{Binding IsBusy, Mode=OneWay}"
                                  CachingStrategy="RecycleElement">
                                <ListView.ItemTemplate>
                                    <DataTemplate>
                                        <ViewCell Appearing="ViewCell_Appearing">
                                            <Grid RowSpacing="5" Padding="10">
                                                <Grid.RowDefinitions>
                                                    <RowDefinition Height="Auto"></RowDefinition>
                                                </Grid.RowDefinitions>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="40"></ColumnDefinition>
                                                    <ColumnDefinition Width="*"></ColumnDefinition>
                                                    <ColumnDefinition Width="100"></ColumnDefinition>
                                                </Grid.ColumnDefinitions>

                                                <controls:CircleImage BorderThickness="2" 
                                                                  BorderColor="#ab423f" 
                                                                  WidthRequest="30"
                                                                  HeightRequest="30"
                                                                  Source="{Binding ProfileUrl}" 
                                                                  Aspect="AspectFill"
                                                                  Grid.Column= "0"></controls:CircleImage>

                                                <Label LineBreakMode="TailTruncation"
                                                   FontSize="Small"
                                                   Grid.Column="1"
                                                   VerticalOptions="Center"
                                                   MaxLines="3"
                                                   TextColor="Black">
                                                    <Label.FormattedText>
                                                        <FormattedString>
                                                            <Span Text="{Binding Name}" FontAttributes="Bold" FontSize="Small"></Span>
                                                            <Span Text=" lstarted following you" TextColor="DarkGray" FontAttributes="None" FontSize="Small"></Span>
                                                        </FormattedString>
                                                    </Label.FormattedText>
                                                </Label>

                                                <Button BackgroundColor="#3893e8" 
                                                        TextColor="White" 
                                                        Text="Follow" 
                                                        CornerRadius="10" 
                                                        WidthRequest="80" 
                                                        HeightRequest="40"
                                                        Grid.Column="2"></Button>

                                            </Grid>
                                        </ViewCell>
                                    </DataTemplate>
                                </ListView.ItemTemplate>
                            </ListView>
                        </StackLayout>
                    </StackLayout>
                </tabView:SfTabItem.Content>
            </tabView:SfTabItem>
        </tabView:SfTabView>
    </ContentPage.Content>
</ContentPage>

aslında ana yapı şu şekilde; Bir adet SFTabView. Bunun içerisine iki adet SfTabItem. Bunların içerisinde de yukarıda görmüş olduğunuz gibi sanki bir ContentPage in Content tag ının içini doldurur gibi sayfamızın xaml kodlarını dolduruyoruz.

        <tabView:SfTabView VisibleHeaderCount="2" TabHeaderBackgroundColor="White" x:Name="mainTabView" Margin="0,20,0,0">
            <tabView:SfTabItem Title="Following" TitleFontAttributes="Bold" TitleFontColor="Black" TitleFontSize="15">
                <tabView:SfTabItem.Content>

                </tabView:SfTabItem.Content>
            </tabView:SfTabItem>
            <tabView:SfTabItem Title="You" FontIconFontAttributes="Bold" TitleFontColor="Black" TitleFontSize="15">
                <tabView:SfTabItem.Content>
                    <StackLayout>

                </tabView:SfTabItem.Content>
            </tabView:SfTabItem>
        </tabView:SfTabView> 

Sayfanın backend tarafına bakığımızda durum aşağıdaki gibi. Sadece yine anasayfadakine benzer bir trick kullanıyoruz. sayfadaki ListView ın ViewCell_Appearing event i içerisinde, sayfaya bind etmiş olduğumuz model in içerisindeki, o anda ki satıra denk gelen model içerisinde ki fotoğraf sayısı kadar dönüp, bir image oluşturup flexlayou un Children property sine ekliyoruz. Gerisini resimde görmüş olduğunuz gibi kaç satır ve sütunda göstereceğine kendisi karar veriyor.

[XamlCompilation(XamlCompilationOptions.Compile)]
	public partial class FeedActivityView : ContentPage
	{
        FeedActivityViewModel viewModel;
		public FeedActivityView ()
		{
			InitializeComponent ();
            NavigationPage.SetHasNavigationBar(this, false);
            viewModel = new FeedActivityViewModel();
            BindingContext = viewModel;
		}

        private void ViewCell_Appearing(object sender, EventArgs e)
        {
            if (sender is ViewCell cell)
            {
                if (cell.BindingContext is FollowingItemModel viewModel)
                {
                    var flexLayout = cell.FindByName("imagesLayout");

                    if (flexLayout?.Children.Count == 0 && viewModel.IamgeUrls.Count > 0)
                    {
                        foreach (var url in viewModel.IamgeUrls)
                        {
                            flexLayout.Children.Add(new Image
                            {
                                Source = new UriImageSource
                                {
                                    Uri = new Uri(url),
                                    CacheValidity = TimeSpan.FromDays(1),
                                    CachingEnabled = true
                                },
                                WidthRequest = 40,
                                HeightRequest = 40,
                                Margin = new Thickness { Left = 5, Bottom = 0, Right = 0, Top = 5},
                                Aspect = Aspect.AspectFill
                            });
                        }
                    }
                }
            }
        }
    }

Sayfanın backendinde başka bir numara yok. Viewmodel e baktığımızda ise durum şöyle;

public class FeedActivityViewModel : BaseViewModel
    {
        public FeedActivityViewModel()
        {
            FollowingItems = new ObservableCollection();
            AboutYouItems = new ObservableCollection();
            LoadFollowingItems();
            LoadAboutYouItems();

            GroupedItems = AboutYouItems.GroupBy(x => x.GroupName);

            LoadFollowingFeedListCommand = new Command(() => { LoadMoreActivity(); });
        }

        private void LoadMoreActivity()
        {
            if (IsBusy)
                return;

            IsBusy = true;

            LoadFollowingItems();

            IsBusy = false;
        }

        private void LoadFollowingItems()
        {
            FollowingItems.Add(new FollowingItemModel
            {
                Name = "Ian Dooley",
                ProfileUrl = "https://randomuser.me/api/portraits/women/41.jpg",
                IamgeUrls = new List { "https://images.unsplash.com/photo-1539608170043-f55d83afe1c9?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89a54fa339be3dde93fa137a213655c7&auto=format&fit=crop&w=1189&q=80",
                "https://images.unsplash.com/photo-1539604880233-d282d9bac272?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=902aa0705d67ac390c0170c68aa4907f&auto=format&fit=crop&w=1051&q=80",
                "https://images.unsplash.com/photo-1539593608687-ccae798ff3ba?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=abbf25cb0a9e3f706263ff5fa81bf9d9&auto=format&fit=crop&w=1051&q=80" }
            });
            FollowingItems.Add(new FollowingItemModel
            {
                Name = "Christian Becker",
                ProfileUrl = "https://randomuser.me/api/portraits/women/81.jpg",
                IamgeUrls = new List { "https://images.unsplash.com/photo-1539553521736-053bd7e14cf5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=debb88414bb2ee774ce6229a72adac17&auto=format&fit=crop&w=500&q=60",
                "https://images.unsplash.com/photo-1539547018184-e5b1ce85fb07?ixlib=rb-0.3.5&s=1358c21c9ecdbbd65dd2993958ee4021&auto=format&fit=crop&w=500&q=60" }
            });
            FollowingItems.Add(new FollowingItemModel
            {
                Name = "Velizer Ivanov",
                ProfileUrl = "https://randomuser.me/api/portraits/women/69.jpg",
                IamgeUrls = new List { "https://images.unsplash.com/photo-1539572996946-c0665d491f04?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=3ed5d83abf5f65bfae42fec7c5e44dd2&auto=format&fit=crop&w=500&q=60",
                "https://images.unsplash.com/photo-1539550298564-8a06769aa728?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=5a3cdac18faf595762d48ac529233dd3&auto=format&fit=crop&w=500&q=60",
                "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539547018184-e5b1ce85fb07?ixlib=rb-0.3.5&s=1358c21c9ecdbbd65dd2993958ee4021&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539608170043-f55d83afe1c9?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89a54fa339be3dde93fa137a213655c7&auto=format&fit=crop&w=1189&q=80",
            "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60",
            }
            });
            FollowingItems.Add(new FollowingItemModel { Name = "Sam Dawson", ProfileUrl = "https://randomuser.me/api/portraits/women/20.jpg", IamgeUrls = new List { "https://images.unsplash.com/photo-1539607547234-e09cdb14d473?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=ccf5de3e1f25a87315d6201723d67d26&auto=format&fit=crop&w=500&q=600" } });
            FollowingItems.Add(new FollowingItemModel
            {
                Name = "Simon King",
                ProfileUrl = "https://randomuser.me/api/portraits/women/57.jpg",
                IamgeUrls = new List { "https://images.unsplash.com/photo-1539602783210-221ffbec8280?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=4b5bf2be9796a2d7b9b005a7cbf28372&auto=format&fit=crop&w=500&q=60"
                , "https://images.unsplash.com/photo-1539602783210-221ffbec8280?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=4b5bf2be9796a2d7b9b005a7cbf28372&auto=format&fit=crop&w=500&q=60", "https://images.unsplash.com/photo-1539602783210-221ffbec8280?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=4b5bf2be9796a2d7b9b005a7cbf28372&auto=format&fit=crop&w=500&q=60" }
            });
            FollowingItems.Add(new FollowingItemModel
            {
                Name = "Ian Dooley",
                ProfileUrl = "https://randomuser.me/api/portraits/women/23.jpg",
                IamgeUrls = new List { "https://images.unsplash.com/photo-1539587310936-afda09bd0dc7?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=daf5f114d2545336bcab3e53bc4568e1&auto=format&fit=crop&w=500&q=60",
                "https://images.unsplash.com/photo-1539602010674-1346135ab34e?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=eb91750f2ad63d82661fb76b0772d6fd&auto=format&fit=crop&w=500&q=60",
                "https://images.unsplash.com/photo-1539585173613-89e3967da7d5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=18ea6cc3988589adb75af4d8fe57d959&auto=format&fit=crop&w=500&q=60",
             "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539547018184-e5b1ce85fb07?ixlib=rb-0.3.5&s=1358c21c9ecdbbd65dd2993958ee4021&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539608170043-f55d83afe1c9?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89a54fa339be3dde93fa137a213655c7&auto=format&fit=crop&w=1189&q=80",
            "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539547018184-e5b1ce85fb07?ixlib=rb-0.3.5&s=1358c21c9ecdbbd65dd2993958ee4021&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539608170043-f55d83afe1c9?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89a54fa339be3dde93fa137a213655c7&auto=format&fit=crop&w=1189&q=80",
            "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60", "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539547018184-e5b1ce85fb07?ixlib=rb-0.3.5&s=1358c21c9ecdbbd65dd2993958ee4021&auto=format&fit=crop&w=500&q=60",
            "https://images.unsplash.com/photo-1539608170043-f55d83afe1c9?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89a54fa339be3dde93fa137a213655c7&auto=format&fit=crop&w=1189&q=80",
            "https://images.unsplash.com/photo-1539546978801-fbee5d0fe203?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f2df90670dbf90177e118ff434fd708&auto=format&fit=crop&w=500&q=60",}
            });
            FollowingItems.Add(new FollowingItemModel
            {
                Name = "Christian Becker",
                ProfileUrl = "https://randomuser.me/api/portraits/women/41.jpg",
                IamgeUrls = new List { "https://images.unsplash.com/photo-1539608170043-f55d83afe1c9?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89a54fa339be3dde93fa137a213655c7&auto=format&fit=crop&w=1189&q=80",
                "https://images.unsplash.com/photo-1539604880233-d282d9bac272?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=902aa0705d67ac390c0170c68aa4907f&auto=format&fit=crop&w=1051&q=80",
                "https://images.unsplash.com/photo-1539593608687-ccae798ff3ba?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=abbf25cb0a9e3f706263ff5fa81bf9d9&auto=format&fit=crop&w=1051&q=80" }
            });
            FollowingItems.Add(new FollowingItemModel
            {
                Name = "Sam Dawson",
                ProfileUrl = "https://randomuser.me/api/portraits/women/81.jpg",
                IamgeUrls = new List { "https://images.unsplash.com/photo-1539553521736-053bd7e14cf5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=debb88414bb2ee774ce6229a72adac17&auto=format&fit=crop&w=500&q=60",
                "https://images.unsplash.com/photo-1539547018184-e5b1ce85fb07?ixlib=rb-0.3.5&s=1358c21c9ecdbbd65dd2993958ee4021&auto=format&fit=crop&w=500&q=60" }
            });
        }

        private void LoadAboutYouItems()
        {
            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Ian Dooley",
                ProfileUrl = "https://randomuser.me/api/portraits/women/41.jpg",
                GroupName = "Yesterday"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Christian Becker",
                ProfileUrl = "https://randomuser.me/api/portraits/women/81.jpg",
                GroupName = "Yesterday"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Velizer Ivanov",
                ProfileUrl = "https://randomuser.me/api/portraits/women/69.jpg",
                GroupName = "Yesterday"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Sam Dawson",
                ProfileUrl = "https://randomuser.me/api/portraits/women/20.jpg",
                GroupName = "This Month"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Simon King",
                ProfileUrl = "https://randomuser.me/api/portraits/women/57.jpg",
                GroupName = "This Month"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Ian Dooley",
                ProfileUrl = "https://randomuser.me/api/portraits/women/23.jpg",
                GroupName = "This Month"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Christian Becker",
                ProfileUrl = "https://randomuser.me/api/portraits/women/41.jpg",
                GroupName = "This Month"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Sam Dawson",
                ProfileUrl = "https://randomuser.me/api/portraits/women/81.jpg",
                GroupName = "This Month"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Simon King",
                ProfileUrl = "https://randomuser.me/api/portraits/women/57.jpg",
                GroupName = "This Month"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Ian Dooley",
                ProfileUrl = "https://randomuser.me/api/portraits/women/23.jpg",
                GroupName = "This Month"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Christian Becker",
                ProfileUrl = "https://randomuser.me/api/portraits/women/41.jpg",
                GroupName = "This Month"
            });

            AboutYouItems.Add(new FollowingItemModel
            {
                Name = "Sam Dawson",
                ProfileUrl = "https://randomuser.me/api/portraits/women/81.jpg",
                GroupName = "This Month"
            });
        }

        public ObservableCollection<FollowingItemModel> FollowingItems { get; set; }
        public ObservableCollection<FollowingItemModel> AboutYouItems { get; set; }

        public IEnumerable<IGrouping<string, FollowingItemModel>> GroupedItems { get;  set; }

        public ICommand LoadFollowingFeedListCommand { get; set; }
    }

    public class FollowingItemModel
    {
        public string GroupName { get; set; }
        public string Name { get; set; }
        public string ProfileUrl { get; set; }
        public List IamgeUrls { get; set; }
    }

FollowintItems ve AboutYouItems adında iki adet ObservableCollection ımız var. Bunların içerisi bize gruplu data gösterirken yardımcı olması adına oluşturduğumuz bir sınıf ile dolu. Bu sınıf FollowingItemModel . Yukarıdaki gibi porpertyleri var. Bu propertyleri listview grouplu bir şekilde kullanmak istediğimiz de ilgili alanlara bind etmemiz yeterli oluyor.

ItemsSource=”{Binding GroupedItems}”
IsGroupingEnabled=”True”
GroupDisplayBinding=”{Binding Key}”

gibi. Constructor tarafında da linq sorgusu ile

GroupedItems = AboutYouItems.GroupBy(x => x.GroupName);

diyerek follow tarafına basacağımız itemları gruplamış oluyoruz. bu şekilde karşımıza resimdeki gibi sectionlara ayrılmış bir listview çıkıyor.

Ek olarak sadece ListView refresh olduğunda çağırdımız bir command imiz var. Bu command de ObservableCollection larımızı dolduran metodlarımızı çağırıyor bu kadar.

Bir sonraki yazımda uygulamanın detayları için son kısımlara değineceğiz. Herhangi bir ekran daki açılan kapanan menüler istediğimizde bunu en kolay ve hızlı bir şekilde nasıl yaparız buna bakacağız.

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

Xamarin.Forms ConfinstaApp Sample Bölüm 2

Selamlar,

Geçen yazımızda anlatmaya başladığım xamarin.forms ile kısa sürede ortaya çıkarabildiğim ve .NetConf da anlattığım Confinsta App in detaylarına devam ediyoruz.

En son uygulamanın temel navigasyon yapısını Tabbed template i ile nasıl oluşturuğumuzdan ve FeedView.xaml, .cs ve ViewModel tarafarından bahsetmiştik.

Bu yazımızda da xamarin yeni gelen özelliklerinden olan FlexLayout, SwipeGestureRecognizer gibi yeni özellikleri Instagramın Explore sayfasına benzer olarak bir sayfa yapmak için nasıl kullandığımıza bakalım.

Bu sayfada bir navigationbar bulunmuyor. Sayfa temel olarak 3 satırı olan bir Grid den oluşuyor. ilk satırında bir searchbar ve buton mevcut. İkinci satırında ise ana sayfadaki kulanıcı profil resimlerine benzer biraz daha eliptik imajlar üzerinden kategori isimlerinin olduğu bir scrollview var. üçüncü satır ise sadece bir FlexLayout tan oluşuyor. FlexLayout tan önce Native taraflardaki CollectionView görünümüne benzer olarak bir görüntü olutşturmak için GridView dinamil olarak satır ve sütunları kod tarafında oluşturup her bir içeriği onun ilgili satır ve sütununa basmak için uğraştırıcı böir yöntem kullanılıyorduk. FlexLayout da ise bir kaç property ile nasıl davramasını istediğinizi söylüyorsunuz ve içeriğini kod tarafta verdikten sonra gerisini o hallediyor. Yani FlexLayout un en güzel kullanım alanı bence, içeriği dinamil olarak değişecek olan UI larda ekranın yapısını ona bırakmak.

Bu ekranın xaml tarafı aşağıdaki gibi;

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
             x:Class="ConfinstaApp.Views.FeedCategoryView">
    
    <Grid Padding="0,20,0,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="50"></RowDefinition>
            <RowDefinition Height="70"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        
        <StackLayout Orientation="Horizontal" Grid.Row="0" HeightRequest="40" Padding="10" Spacing="10">
            <SearchBar Placeholder="Search" HorizontalOptions="FillAndExpand" WidthRequest="300" HeightRequest="30" BackgroundColor="Transparent"></SearchBar>
            <Image HorizontalOptions="EndAndExpand" Source="3ss.png" WidthRequest="20" HeightRequest="20"></Image>
        </StackLayout>

        <ScrollView Orientation="Horizontal" Grid.Row="1" HorizontalScrollBarVisibility="Never">
            <StackLayout Orientation="Horizontal">

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>

                    <controls:CircleImage Grid.Row="0" Grid.RowSpan="2" WidthRequest="100"  HeightRequest="80" Source="music.jpg" Aspect="AspectFill"></controls:CircleImage>
                    <Label Grid.Row="1"
                           HorizontalOptions="Center"
                           VerticalTextAlignment="Center"
                           Text="Music" 
                           FontSize="Small" 
                           FontAttributes="Bold"
                           TextColor="White"></Label>
                </Grid>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>

                    <controls:CircleImage Grid.Row="0" Grid.RowSpan="2" WidthRequest="100" HeightRequest="80" Source="nature.jpg" Aspect="AspectFit"></controls:CircleImage>
                    <Label Grid.Row="1"
                           HorizontalOptions="Center"
                           VerticalTextAlignment="Center"
                           Text="Nature" 
                           FontSize="Small" 
                           FontAttributes="Bold"
                           TextColor="White"></Label>
                </Grid>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>

                    <controls:CircleImage Grid.Row="0" Grid.RowSpan="2" WidthRequest="100" HeightRequest="80" Source="science.jpg" Aspect="AspectFill"></controls:CircleImage>
                    <Label Grid.Row="1"
                           HorizontalOptions="Center"
                           VerticalTextAlignment="Center"
                           Text="Science" 
                           FontAttributes="Bold"
                           FontSize="Small" 
                           TextColor="White"></Label>
                </Grid>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>

                    <controls:CircleImage Grid.Row="0" Grid.RowSpan="2" WidthRequest="100" HeightRequest="80" Source="sports.jpg" Aspect="AspectFill"></controls:CircleImage>

                    <Label Grid.Row="1"
                           HorizontalOptions="Center"
                           VerticalTextAlignment="Center"
                           Text="Sports" 
                           FontAttributes="Bold"
                           FontSize="Small" 
                           TextColor="White"></Label>
                </Grid>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>

                    <controls:CircleImage Grid.Row="0" Grid.RowSpan="2" WidthRequest="100" HeightRequest="80" Source="animals.jpg" Aspect="AspectFill"></controls:CircleImage>

                    <Label Grid.Row="1"
                           HorizontalOptions="Center"
                           VerticalTextAlignment="Center"
                           Text="Animals" 
                           FontAttributes="Bold"
                           FontSize="Small" 
                           TextColor="White"></Label>
                </Grid>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>

                    <controls:CircleImage Grid.Row="0" Grid.RowSpan="2" WidthRequest="100" HeightRequest="80" Source="fitness.jpg" Aspect="AspectFill"></controls:CircleImage>

                    <Label Grid.Row="1"
                           HorizontalOptions="Center"
                           VerticalTextAlignment="Center"
                           Text="Fitness" 
                           FontAttributes="Bold"
                           FontSize="Small" 
                           TextColor="White"></Label>
                </Grid>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>

                    <controls:CircleImage Grid.Row="0" Grid.RowSpan="2" WidthRequest="100" HeightRequest="80" Source="comics.jpg" Aspect="AspectFill"></controls:CircleImage>

                    <Label Grid.Row="1"
                           HorizontalOptions="Center"
                           VerticalTextAlignment="Center"
                           Text="Comics" 
                           FontAttributes="Bold"
                           FontSize="Small" 
                           TextColor="White"></Label>
                </Grid>
            </StackLayout>
            
        </ScrollView>

        <ScrollView  Grid.Row="2">
            <FlexLayout x:Name="flexLayout"
                        Wrap="Wrap"
                        JustifyContent="SpaceEvenly" />
        </ScrollView>
    </Grid>

</ContentPage>

Xaml tarafında yukarıda bahsettiğim gibi, dikkat edeceğim en önemli yer Grid View ın son satırına attığımız ScrollView içerisine koyduğumuz FlexLayout.

Sadece iki tane özellikle FlexLayout üzerinde, ekranda eşit aralıklarla dizilmiş bir koleksiyon göstermek istediğimi söylüyoruz.

  • Wrap:  Wrap olarak set ederek, tek bir satıra sığmayan itemları eşit aralıklarla alt alta dizmesini istediğimizi söylüyoruz. Örneğim iphone 6 ekranında test ettiğimiz de ekrana 3 kolon olacak şekilde diziyor. Telefon yan çevirdiğimiz de ise aralıkları biraz daha arttırarak (ama eşit olacak şekilde)  kolon sayısını 5 e çıkartıyor
  • JustifyContent: SpaceEvenly diyerek de yukarıda bahsettiğimiz gibi aralıkların eşit olarak bölünmesini istediğimizi söylüyoruz.

sayfanın c# tarafında neler yaptığımıza bakalım. Burada yaptığımız şey aslında sadece viewmodel i initialize edip sayfanın BindingContext ine atadıktan sonra, model içerisinde olan PhotoList içerisinde dönüp her biri için istediğimiz UI ı kod tarafında oluşturmuş oluyoruz. Bunun için bir content view oluşturup UI orada xaml tarafında da oluşturabilirdik, bu örnekte ben kod tarafında oluşturmayı seçtim. Aslında bu kod tarafından ui oluşturma alışkanlığım doğru mu yanlış mı bilmem ama, native developer lar arasında halen tartışılmakta olan şeyler (ör; native ios da storyboard kullanılmalı mı kullanılmama lı gibi).

UI için sadece bir imaj oluşturduk Width ve Height değerleri fix 120. Buna göre FlexLayout hangi boyutta ekran karşısına gelirse gelsin, yanyana sığdırabildiği kadar sığdırıp gerisini farklı satırlara bölüyor.

Her bir imaja instagram da olduğu gibi uzun basıldığında popup olarak açılma ve yukarı slide edildiğinde ActionSheet gösterip kullanıcıya seçenekler sunma kısmını da yapabilmek için şunu yaptım.

Xamarin forms a yeni gelen SwiperGestureRecognizer ile yukarı parmak hareketini algılayıp resmi azıcık yukarı doğru kaydırıyorum ve ActionSheet açıyorum. Ama buradan sırayla bahsetmek gerekirse adımlar şöyle;

  • Xamarin forms da LongTapGestureRecognizer olmadığından dolayı ben burada iki tıkla zorunlu bir TapGestureRecognizer koydum. Çift tap yapıldığında pop içerisinde resmi açıyorum.
  • Popup için Rg.Plugins.Popup plugini ni kullandım. Açılan popup tasarımı ve kod behind ı ayrı dosyalarda. Resmi yukarı kaydırma ve popup ı komple animate etme kısmı bu tarafta bulunuyor.
[XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class FeedCategoryView : ContentPage
    {
        FeedCategoryViewModel viewModel;
        public FeedCategoryView()
        {
            InitializeComponent();
            NavigationPage.SetHasNavigationBar(this, false);
            viewModel = new FeedCategoryViewModel();

            foreach (var item in viewModel.PhotoList)
            {
                var image = new Image()
                {
                    Source = new UriImageSource
                    {
                        Uri = new Uri(item.PhotoUrl),
                        CacheValidity = TimeSpan.FromHours(1),
                        CachingEnabled = true
                    },
                    WidthRequest = 120,
                    HeightRequest = 120,
                    Margin = new Thickness { Left = 0, Top = 3, Right = 0, Bottom = 0},
                    Aspect = Aspect.AspectFill,
                };

                image.GestureRecognizers.Add(new TapGestureRecognizer
                {
                    NumberOfTapsRequired = 2,
                    Command = new Command(async () =>
                    {
                        await Navigation.PushPopupAsync(new CategoryFeedPopupView(item));
                    })
                });

                flexLayout.Children.Add(image);
            }
        }
    }

Popup tarafına bakalım.

Eğer bu plugini daha önce hiç kullanmadıysanız hemen denemenizi tavsiye ederim. Popuplar la uğraşmak uygulama içerisindeki en kolay işlemlere dönüyor. Rg.Plugins.Popup nugetten indirip (hem standard hemde ios android projelerine de) uygulama spesifik taraflarda init etmeniz yeterli. Örneğin ios tarafına AppDelegate de aşağıdaki kodu LoadApplication dan önce yazmanız yeterli.

Rg.Plugins.Popup.Popup.Init();

sayfanın xaml tarafı aşağıdaki gibi. Birkaç farklı gelebilecek namespace var. Biri bu sayfanın bir popup sayfasına dair olduğu namespace, biri açılırken ki animasyon için olan diğerleride popup ın davranışlarını belirleyen özellikler

Örneğin;

CloseWhenBackgroundIsClicked=”False” diyerek popup ın arka kısmında ekranda bir yere basıldığına kapanmasın demiş oluyoruz. True dersek kapanır tıkladığımızda.

Tüm ekranı kenarları oval bir şekilde göstermek için Frame içine alıp BorderRadius özelliğini kullandım. Popup ın ekranın ortasında olması ve tüm ekranı kaplamaması içinde frame e VerticalOptions=”Center” dedim.

 <?xml version="1.0" encoding="UTF-8"?>
<pages:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
                 xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup" 
                  x:Class="ConfinstaApp.Views.Popups.CategoryFeedPopupView"
                 xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations;assembly=Rg.Plugins.Popup" 
                 xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
                 CloseWhenBackgroundIsClicked="False"
                 InputTransparent="False"
                 Padding="20,0,20,0">
    <pages:PopupPage.Animation>
        <animations:ScaleAnimation PositionIn="Center" PositionOut="Center" HasBackgroundAnimation="True" />
    </pages:PopupPage.Animation>

    <Frame BackgroundColor="#fff" VerticalOptions="Center" CornerRadius="15" x:Name="mainFrame">
        <Frame.GestureRecognizers>
            <SwipeGestureRecognizer Direction="Down" Swiped="SwipeGestureRecognizer_Swiped"></SwipeGestureRecognizer>
            <SwipeGestureRecognizer Direction="Up" Swiped="SwipeGestureRecognizer_Swiped"></SwipeGestureRecognizer>
        </Frame.GestureRecognizers>
        <StackLayout IsClippedToBounds="True" Spacing="0">
            <StackLayout Orientation="Horizontal" BackgroundColor="White" Padding="10" Spacing="0">
                <controls:CircleImage HorizontalOptions="StartAndExpand" Source="https://randomuser.me/api/portraits/women/72.jpg" Aspect="AspectFill" WidthRequest="50" HeightRequest="50"></controls:CircleImage>
                <Label FontSize="Large" TextColor="Black" FontAttributes="Bold" Text="Jojo Mayer" VerticalOptions="Center" HorizontalOptions="StartAndExpand"></Label>
            </StackLayout>
            <Image Source="{Binding PhotoUrl}" WidthRequest="300" HeightRequest="250"></Image>
        </StackLayout>
    </Frame>
</pages:PopupPage>

Dikkat ederseniz frame e xaml tarafında atanmış iki tane swipegesture var.

Up kısmında popup açıcaz, Down olarak set ettiğimizde ise popup ı kapayacağız.

Kod tarafı aşağıdaki gibi.İlk dikkat edeceğimiz şey sayfanın bir ContentPage sınıfından değil de PopupPage den türüyor olması. Sayfaya dışardan bir adet Photo sınıfı geçiyoruz bunu da BindingContext e set ediyoruz. Resim ve ismi bu şekilde xaml tarafına bind ediyoruz.

İkinci dikkat etmemiz gereken yer de, SwipeGesture ların aynı eventhandler ları kullanıyor olması. event e gelen EventArgument lardan Direction propertysi ile hangi gesture ile uğraşttığımızı anlayabiliyoruz.

Down ise sadece popup ı kapatıyoruz. Bu arada bu popup plugin inin Navigation üzerine yazılmış extension metodları mevcut. Yani normal xamarin.forms daki sayfa geçişlerinde Navigation sınıfını kullandığımız gibi popup lar içinde kullanabiliriz.

Örneğin popup ı kapatmak için;

await Navigation.PopPopupAsync(); diyebiliriz.

[XamlCompilation(XamlCompilationOptions.Compile)]
	public partial class CategoryFeedPopupView : PopupPage
	{
        Photos selecedPhoto;
		public CategoryFeedPopupView(Photos selectedPhoto)
		{
			InitializeComponent ();
            this.selecedPhoto = selectedPhoto;
            BindingContext = selecedPhoto;
		}

        private async void SwipeGestureRecognizer_Swiped(object sender, SwipedEventArgs e)
        {
            if (e.Direction == SwipeDirection.Down)
            {
                await Navigation.PopPopupAsync();
            }
            else if(e.Direction == SwipeDirection.Up)
            {
                mainFrame.TranslateTo(0, -140, 300, Easing.CubicOut);
                var res = await DisplayActionSheet(string.Empty, "Cancel", "Share", "Like");

                if (!string.IsNullOrEmpty(res))
                {
                    mainFrame.TranslateTo(0, 0, 150, Easing.CubicOut);
                    await Navigation.PopPopupAsync();

                }
            }
        }
    }

Up olarak slide ettiğimiz de ise en dıştaki Frame imize verdiğimiz name ile ona kod tarafından ulaşıp,

mainFrame.TranslateTo(0, 0, 150, Easing.CubicOut);

diyerek yukarı kaydırıyoruz.

Genel olarak sayfamıza dönecek olursak, sayfada kullandığımız viewmodel ise aşağıdaki gibi. Hiçbir numara yok, sadece dummy bir Photo sınıfı listesi oluşturuyoruz ObservableCollection olarak.

public class FeedCategoryViewModel
    {
        public FeedCategoryViewModel()
        {
            PhotoList = new ObservableCollection();
            LoadDummData();
        }

        private void LoadDummData()
        {
            for (int i = 0; i < 10; i++)
            {
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539578741486-e0d3a45e16c2?ixlib=rb-0.3.5&s=05b39cf560d156b0e5f7408c0ca05ccb&auto=format&fit=crop&w=1900&q=80" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539578903083-0d9bb0ed39d4?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=044382ada2f12060a3b4f28c120365a2&auto=format&fit=crop&w=634&q=80" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539604880233-d282d9bac272?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=902aa0705d67ac390c0170c68aa4907f&auto=format&fit=crop&w=1051&q=80" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539578839907-f463d05d7ad9?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=d7a43a0fdf7e74bb78e3413341a2598e&auto=format&fit=crop&w=634&q=80" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539572408025-26bebcbb750e?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=b3f3a1e1037df2e7adcf65ba2816d3db&auto=format&fit=crop&w=1050&q=80" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539609413529-1166774c3954?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=2c7b7da55abdaf0759143fbfc8e0b59b&auto=format&fit=crop&w=634&q=80" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539593758884-2e9d7b18e451?ixlib=rb-0.3.5&s=6e3b06927d46acb4071f7de4c8ba1a4c&auto=format&fit=crop&w=634&q=80" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539553281713-4f86b514e86f?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=33a4f691badb8c4b237727ef96c31414&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539535879069-3c148518f8c9?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=0e707059e2e9d16f94a682a189be8e42&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539553139747-e2ae5159d2e5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89e76f2a8cf3a81c8dba40f5dd01bb0f&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539576282236-40272d2dbe7e?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=9cee2739772eb498885974c2f2542a83&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539541364455-3a385c64f82d?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=503bce372f4b0071c629e5620a929bc1&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539601001507-887d25d5449e?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=4ee2cba1be085263937478f5f71803fc&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539609301259-1dd126206e5e?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=dcec9da5f439e073559530f679a1f23f&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539597583595-2069afd2b107?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=64b66dfa88a1b0f2ebffd20197857ed2&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539544048267-9744e3b84996?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=16959568ee70ac013296240aa5f7ced4&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539603584498-db314ea45182?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=a62e0d1be0a73a5030616caabd8b7cc7&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539598735229-e5918f5408b5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=3a526577df19d1ec423b2220532475e7&auto=format&fit=crop&w=500&q=60" });
                PhotoList.Add(new Photos { PhotoUrl = "https://images.unsplash.com/photo-1539553521736-053bd7e14cf5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=debb88414bb2ee774ce6229a72adac17&auto=format&fit=crop&w=500&q=60" });
            }
           
        }

        public ObservableCollection PhotoList { get; set; }
    }

bu sayfanın da sonuna geldik, birçok şeyin kullanım şeklini görmşü olduk, diğer detaylarla Confinsta App ve xamarin forms yazılarına devam edeceğim.

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

Resilient Network Services Bölüm 7 – Xamarin Projemizde Refit, Polly, Akavache, Fusillade ve ModernHttpClient Birlikte Kullanımı

Selamlar,

Bu seri boyunca bahsettiğim bu güzel kütüphaneleri artık iş üzerinde görme vakti geldi 🙂 Tüm bu kütüphanelerin bir xamarin projesinde Resilient Network services oluşturulması için nasıl bir arada kullanıcağımızı göreceğimiz bu yazımızda, kütüphanelerin detaylarını görmek için önceki yazıları da okumanızı öneririm. Buradan başlayabilirsiniz.

Refit, Fusillade, ModernHttpClient, Akavache ve Polly  kütüphanelerinin herbirinin birbirinden farklı ve aynı zamanda aynı işlevi gören özellikleri mevcut.
Öncelikle hangi kütüphaneden hangi özellikleri alıp nasıl bir yapı kuracağımızı ve bunu xamarin projelerinde nasıl kullanacağımıza bakacağız. Sırası ile kütüphanelerden alacağımız özellikler şunla olacak.

  1. Refit: Tüm Api haberleşmesini, tek bir interface altında toplayıp, HttpClient ve HttpMessageHandler yönetimini runtime da bu arkadaşa bırakacağız. Herhangi bir yerde biz HttpClient sınıfı ile ilgilenmeyeceğiz.
  2. Akavache: Api dan gelen responlarımız için tüm objelerimizi Akavache ile sqlite3, lokal veritabanında saklayacağız. Request atmaya ne zaman gitmeli, cache de varsa önce oradan okuma işlemi yada cache de olsa bile cache den sonucu hemen dönüp, yine de arka planda Api ye gidip cache deki dataları yenileme gibi işlemleri akavache ile yapacağız.AKavache de bize performanslı ve kolay bir SQLite yönetimi sunacak
  3. Fusillade: Bu güzel kütüphaneyi requestleri önceliklendirme işlemlerimiz için ve Request Limit işlemi için kullanacağız. Kullanıcı tetiklemesiyle mi birşeyler oldu, bir arka plan request imi atıyoruz, yoksa speculative bir request atıp verdiğimiz sınır boyunca belli bir data mı çekiyoruz bunu söyleyeceğiz.
  4. ModerHttpClient: En basit kullanımı olan bu kütüphanenin amacı zaten belli, daha performanslı http haberleşmesi sağlamak. kendi ardaka kullandığı native framework ler ile, bize sunduğu NativeMessageHandler ile daha performanslı network haberleşmesi sağlayacak.
  5. Polly: Bunu kullanın kullanabildiğiniz kadar 🙂 Çok fazla özelliği olan bu kütüphane de genellikle kullanılacak özellikler, api hanerleşmesinde istediğimiz senaryolara bağlı olduğundan, biz default olarak, Transient yani anlık hatalar için Retry pattern ini requestlerimizde uygulamak için kullanacağız.
    Daha da detaylarını eski yazılarımdan ve kütüphanelerin kendi github repo larından edinebilirsiniz.

Örnek olması açısından public api lardan biri randomuser.me  kullanalım. adrese gittiğinizde api ın neler sağladığını bulabilirsiniz. Biz basit bir get request i ile işe başlayalım. REquest atacağımız adres ;

https://randomuser.me/api/?results=5

Bu adres bize results querys string ile verdiğimiz sayı kadar user geriye dönecektir. Bu user a bağlı birden çok property ve ayrı class lar var. Postman üzerinden yada direk browserdan bu adrese request attığınızda karşınıza çıkacak olan response şu şekilde olacaktır.

Capture.PNG

bu json ın c# tipinde nasıl bir class a karşılık geldiğini anlamak ve yazmak için uzun uzun bakmak yerine sıkça kullandığım jsonutils.com adresine json ı yapıştıralım ve bize bu json olarak aldığımız response C# ile oluşturulmuş class lar olarak versin.

jsonutils.com a gidip aşağıdaki gibi json text alanına json string imizi girdiğimizde istediğimiz isimle ana sınıfı oluşturup buna bağlı sınıflarıda kendisi oluşturuyor.

Capture.PNG

bu endpoint e attığımız request in C# cası jsonutils in bize söylediğine göre tam olarak aşağıdaki gibi;

   public class Name
    {
        public string title { get; set; }
        public string first { get; set; }
        public string last { get; set; }
    }      
 
    public class Coordinates
    {
        public string latitude { get; set; }
        public string longitude { get; set; }
    }

    public class Timezone
    {
        public string offset { get; set; }
        public string description { get; set; }
    }

    public class Location
    {
        public string street { get; set; }
        public string city { get; set; }
        public string state { get; set; }
        public object postcode { get; set; }
        public Coordinates coordinates { get; set; }
        public Timezone timezone { get; set; }
    }

    public class Login
    {
        public string uuid { get; set; }
        public string username { get; set; }
        public string password { get; set; }
        public string salt { get; set; }
        public string md5 { get; set; }
        public string sha1 { get; set; }
        public string sha256 { get; set; }
    }

    public class Dob
    {
        public DateTime date { get; set; }
        public int age { get; set; }
    }

    public class Registered
    {
        public DateTime date { get; set; }
        public int age { get; set; }
    }

    public class Id
    {
        public string name { get; set; }
        public string value { get; set; }
    }

    public class Picture
    {
        public string large { get; set; }
        public string medium { get; set; }
        public string thumbnail { get; set; }
    }

    public class Result
    {
        public string gender { get; set; }
        public Name name { get; set; }
        public Location location { get; set; }
        public string email { get; set; }
        public Login login { get; set; }
        public Dob dob { get; set; }
        public Registered registered { get; set; }
        public string phone { get; set; }
        public string cell { get; set; }
        public Id id { get; set; }
        public Picture picture { get; set; }
        public string nat { get; set; }
    }

    public class Info
    {
        public string seed { get; set; }
        public int results { get; set; }
        public int page { get; set; }
        public string version { get; set; }
    }

    public class User
    {
        public IList results { get; set; }
        public Info info { get; set; }
    }

Bu class ları projemizde Models klasörüne (yoksa bir tane Model adında bir klasör açarak) içerisine koyalım. Diğer bir önz hazırlığımız da Fusillade tarafında bize önyüzde ve ortak kod tarafında yardımcı olması için, PriorityType adında bir enum oluşturalım.

public enum PriorityType
{
   Background,

   Speculative,

   UserInitiated
}

Hazırlığımıza devam edelim. Tüm request tiplerimizi bir arada tutacağımız bir ApiRequest interface i oluşturuyorum. Daha sonra uygulama üzerinden gidecek olan tüm requestleri bu interface i implemente eden ve başka türlü çalışmasını istediğimiz bir şekle çevirmek istersek bu şekilde ilerleek bize fayda sağlıyacaktır.

 public interface IApiRequest
    {
        T Speculative { get; }
        T UserInitiated { get; }
        T Background { get; }
    }

Interface in sadece üç adet readonly propertysi mevcut. Bundan sonra bu Interface i implemente edecek, concrete ApiRequest sınıfımızı oluşturacağız ve Fusillade,Refit ve ModernHttpClient kütüphanelerini kullanmaya başlayacağız.

Bu yazıyı burada bırakıyorum. Hazırlık kısmı olarak biraz fazla uzadı. Buradaki adımları yaptığımızda bir sonraki yazımda asıl implementasyonu benimle beraber siz de yapabilirsiniz.

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

 

Resilient Network Services – Bölüm 4 – ModernHttpClient

Selamlar,

Bu yazımda sizelere ModernHttpClient kütüphanesinden ve uygulamanıza performans olarak katkılarından bahsedeceğim.

ModernHttpClient kullanması belki de en basit kütüphane olabilir 🙂 ama etkileri fazlaca.

Bir xamarin uygulamanızda HttpClient ile beraber yapacağınız requestleri u kütüphane ile yaptığınızda çok hızlı çalıştığını göreceksiniz. Bunun sebebi ise bu kütüphanenin arkada platform spesifik taraflarda kullandığı native kütüphaneler. Bu kütüphaneler şunlar

Native development yapmış olanlarınız ya bu kütğphaneleri doğrudan kullanmıştır ya da bu kütüphaneleri arkaplanda kullanan başka kütüphaneler kullanmıştır.(AFNetwork gibi)

Bu iki kütüphane hakkında hiçbir fikriniz olmasa bile uygulamanızda bu ikisini kullanıp daha hızlı bir API haberleşmesi için yapmanız gereken sadece şu;

var httpClient = new HttpClient(new NativeMessageHandler());

Xamarin projenizde sadece Portable yada standard projenize ModernHttpClient ı referans ederek bu şekilde kullanıma başlayabilirsiniz.

Kütüphanenin kullanımı kadar kısa ama kendisi kadar faydalı bir yazı olduğunu umarım 🙂

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