Erhan Ballıeker

Microsoft Xamarin Türkiye Meetup – Xamarin Forms Shell

Selamlar,

Dün Microsoft Türkiye’de gerçekleştirmiş olduğumuz etkinlikte katılımcılara yeni gelen Xamarin Forms Shell den bahsetmişim. Bu yazımda buna değinmek istedim.

Öncelikle şunu belirtmek isterim ki Xamarin.Forms Shell şuan production da kullanılmaya hazır değil diye düşünüyorum. Geliştirmeler halen devam ediyor, üzerinde daha birçok değişiklik olacaktır. Ama tanımaktan denemekten zarar gelmez. Değişiklikleride hepberaber takip ederiz.

Xamarin Forms Shell i eğer VisualStudio 2019 kurduysanız bir extension paketi kurarak File-> NewProject dediğinizde Template olarak görebilirsiniz.

Template i şuradan indirebilirsiniz.

Bu visual studio extension paketini kurduğunuz da Yeni bir xamarin forms projesi açtığınızda aşağıdaki gibi karşınıza Shell Template i gelecektir.

Capture.PNG

Bunu seçip devam ettiğiniz deki kısma geleceğiz. Ama önce hali hazırda Visual Studio 2019 kurmamış olanların Shell i nasıl deneyeceklerine gelelim.

Visual Studio 2017 de yine bir xamarin forms projesi açıp başlayın.

Sonrasında yapmanız gereken ilk şey platform spesifik projelere gidip Xamarin.Forms.Forms.Init.. den önce

  • global::Xamarin.Forms.Forms.SetFlags(“Shell_Experimental”, “Visual_Experimental”, “CollectionView_Experimental”, “FastRenderers_Experimental”);

kodunu eklemeniz. Bu kod sayesinde şuan experimental olarak geçen tüm diğer özellikleri de test etmeye başlayabilirsiniz. Örneğin CollectionView uzun zamandır beklenen bir Layout idi, FlexLayout ile kısmen sorunlarımız çözüyorduk ama bunu da denemekte fayda olacaktır şimdiden.

Bundan sonra projesine bir Xaml sayfası ekleyip onu Shell sınıfından türetmeniz yeterli olacaktır. Artık sizde bu Shell dosyası üzerinde oynayabilir, çıktıları gözlemleyebilirsiniz.

Gelelim Shell in özelliklerine;

Shell in başlıca amacı şu;

  • Tüm uygulama genelindeki navigasyonları tek bir çatı altında toplayıp, sanki bir storyboard hazırlar gibi hazırlayabilmeniz.

Shell ile beraber web e yakın bir routing mekanizması da geldi. Ama varolan tüm navigaston mekanizması yani Navigaion.Push..Pop.. vs hepsi halen geçerli tabi

Şuanda temel 4 farklı tag imiz var.

Bunlar şunlar;

  • ShellItem
  • ShellSection
  • ShellContent
  • MenuItem

Bınların uygulamaya etkileri  şöyle oluyor.

Temel menülerin dizilimi yukarıda kullanacağınız tag lara göre 3 e ayrılıyor.

  • Her bir ShellItem Sol menüde ki bir Link e karşılık geliyor.
  • Bunların içinde tanımlanmış Herbir Shell Section bottombar olarak karşımıza çıkıyor.
  • Bunun da içerisinde bir yada birden çok Shell Content tanımlarsak bunlar da topbar olarak karşımıza geliyor.

Yani aşağıdaki gibi bir xaml ın menü hiyerarşisi şu şekilde oluyor.

 
<Shell xmlns="http://xamarin.com/schemas/2014/forms" 	
	   xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 	
	   xmlns:local="clr-namespace:TailwindTraders.Mobile.Features.Shell" 	
	   x:Class="TailwindTraders.Mobile.Features.Shell.TheShell" 	
	   Title="TailwindTraders"> 
<ShellItem Title="Home“ Route=“Home”> 
	<ShellSection Route=“Section1“>  
		<ShellContent Route=“index”> 
			<local:HomePage1 /> 
		</ShellContent>
		<ShellContent Route=“index”> 
			<local:HomePage2 /> 
		</ShellContent>
	 </ShellSection> 
	 	<ShellSection Route=“Section2“>  
		<ShellContent Route=“index”> 
			<local:HomePage3 /> 
		</ShellContent>
	 </ShellSection> 
</ShellItem> 
</Shell>

Sol tarafta tek bir menü linki. Buna tıkladığımızda, bu sayfada altta iki tab ı olan bir bottom tab. çünkü iki adet ShellSection konulmuş. İlk taba tıkladığımızda da yukarıda 2 tane tab ı olan topbar göreceğiz çünkü bunun içerisine de iki tane shell content konulmuş.

Bunun yanı sıra Flyout dediğimiz tag in Header ve Footer ile istediğimiz gibi oynayıp sol menünün görünümünü düzenleyebiliyoruz.

Routing mekanizması şu şekilde değişiyor.

RouteScheme: Url scheme such as “http” or “app”

RouteHost: The Domain portion of the URI

Route: The base segment of your application’s URI

▪Exp: “app://microsoft.com/myapp/home/section/index”

▪(App.Current.MainPage as Shell).GoToAsync(“app:///newapp/greeting?msg=Hello”);

İstersek sayfalar arasında data taşımak için querystring bile kullanabiliyoruz 🙂

[QueryProperty("Message", "msg"]
    public partial class GreetingPage : ContentPage
    {
        public GreetingPage()
        {
            InitializeComponent();
        }
 
        string _message;
        public string Message
        {
            get { return _message; }
            set
            {
                _message = value;
            }
        }
    }

Dediğim gibi şuan için yeni başlayan projelerinizde asla başlamamanızla birlikte deneysel olarak alışmanızda fayda olduğunu düşündüğümden çok fazla detaylarına girmeden Shell den bahsetmek istedim.

Daha fazla detaylara şuradan ulaşabilirsiniz.

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

Asp.Net Core Dışında Bir .NetStandard Kütüphanemizde (Ör: Xamarin.Forms Shared Projede) HttpClientFactory Kullanımı.

Selamlar,

Başlık konusunda biraz kararsız kalmıştım. Değinmek istediğim noktayı şöyle anlatayım. Asp.Net Core projesi dışında bir Client ınız olduğunu düşünelim (Console App, Xamarin Mobile App, WinForms, WPF, UWP vs vs..), bu projeden API call ları HttpClientFactory kullanarak nasıl yapacaksınız buna değinmek istiyorum.

Önceki yazılarımda Networking taraflarındaki konulara çokça değinmişimdir. Günümüzde de bir .net projesinde http üzerinden haberleşmek için artık elimizde en iyi sınıf HttpClientFactory gibi duruyor. Ama danışmanlıklarım sırasında şunu çok gördüm.

Asp.Net Core un genel olarak çalışma yapısı insanlarda oturmamış. Microsoft un tüm paketleri ayrı ayrı yapmasının sebebi çok anlaşılmamış. .NetStandard nedir, Core nedir, kafalarda karışıklıklar var.

Bunlar la ilgili konulara değindiğim yazılarıma bakmanızı tavsiye ederim.

İnernettki örneklerin birçoğu HttpClientFactory kullanımını bir Asp.Net Core projesi üzerinden anlatıyor. Asp.Net Core projesinde ki startup dosyasında IServiceCollection a httpclientfacory yi, named typed vs gibi farklı tiplerde istediğiniz gibi tanıtıp sonra da kullanıyorsunuz.

Ama bir xamarin projesi karşımıza çıktığında ve bu xamarin projesinden dışarıya yapacak olduğumuz requestleri HttpClient ile değilde HttpClientFactory ile olmasını istetdiklerinde, ortalıka ne bir Startup var, ne ServiceCollection vs vs.

Bu durumda yapıpyı kurmakta zorluk çekildiğini gördüm.

Önce şundan bahsedelim.

Asp.Net Core daki IServiceCollection ın Asp.Net Core projesine geldiği dll

Microsoft.Extensions.DependencyInjection paketi. Peki bu paketi ben başka bir .NetStandard kütüphaneme ekleyemez miyim? Evet ekleyebilirim.

.NetStandard ın ne olduğunu hatırlayalım kısaca;

  • Microsoft un .netframework ünün birden fazla implementasyonu var. Yani bu framework ü bir Interface olarak düşünürseniz farklı implementasyonlardan kastım şunlar olucaktır;
    • UWP
    • WPF
    • Asp.Net – Asp.Net Core
    • Xamarin
    • Mono
    • vs..
  • Tüm bu platformlar arasında kod paylaşımı için .netstandard a kadar iki farklı yönetmimiz var idi. Portable Class Library ler ve Shared Asset Project idi.
  • Microsoft artık tüm platformlar arasında kod paylaşımını kolaylaştırmak için ortaya bir standard koydu. .NetStandard. Artık her implementasyon bu standard içerisindeki API ları kendine eklemek zorunda idi
  • İlk başlarda çok fazla API içerisinde barındırmayan .NetStandard artık 2.0 versiyonu ile veraber tüm full .netframework 4.6.1 deki api lara bile sahip hale geldi. İçerisinde artık binlerce API var. Bu da şu demek, eğer nugetten indirdiğiniz bir kütüphane ya da sizin target olarak .NetStandard seçerek oluşturmuş olduğunuz bir kütüphane neredeyse üm .net implementasyonlarında çalışır olucak demek.

Peki bu özet bilgi ile şöyle bir işe kalkışalım. Bir solution ımız olsun. Bu solution içerisinde şunlar olsun.

  • Bir Asp.Net Core ile yazılmış API
  • Bir Asp.Net Core Web App (Client)
  • Bir  Xamarin iOS ve Android App (Client)
  • Bir Console App (Client)
  • Bir WPF App (Client)

Ve şunu istiyor olalım.

Tüm clienlar çoğunlukla olacağı gibi aynı Apı mız ile haberleşerek istediği işlemleri yapsın. Ve bu Api ile haberleşirken de HttpClient değil HttpClientFactory kullansın.

Hatta Eski yazılarımda olduğu gibi öyle bir Api Haberleşme sınıfı yazalım ki, buraya Refit, Polly gibi kütüphaneleri de kullanıp yine güzel bir Resilient Network Service altyapısı oluşturalım.

Tüm bunlaru yapmadan önce başlıkta dediğim gibi bir xamarin uygulamasında HttpClientFactory yi nasıl kullanırım bunu görelim.

Bunu yapmanın aslında ne kadar basit olduğunu görünce biraz şaşırabilirsiniz özellikle .Netstandard ve .Net Core ile beraber Microsoft un birçok paketi ayrı ayrı sunmasının faydalarını daha önce keşfetmediyseniz…

Bir xamarin uygulamasını neredeyse bir Asp.Net Core uygulaması gibi görünmesini sağlamak için yapmam gereken ilk şey 2 adet paketi .NetStandard kütüphaneme eklemek. Platforms spesifik projelerle şuan için işim yok. Boş bir xamarin.forms projesi açtıktan sonra (Empty template ile) ilk olarak aşağıdaki paketleri yüklüyorum nugetten.

bu iki paket şunlar;

  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Http

ilk paket ile uygulamama bir DI özelliği katıyorum, bunun içinde autofac, unity, ninject vs gibi bir IoC container kullanmadan Microsft un kendi extension paketi üzerinden yürüyorum. Bu noktada artık bir Asp.Net Core projesinde yaptığımız gibi her istediğimiz api ı bu container a ekleyip bunun üzerinden erişbilir olduk bile.

Diğer pakete de bana Asp.Net Core projesinde DI container a AddHttpClient dememi sağlayan ve HttpClientFactory devreye sokan extension metodun geldiği paket. Bu noktadan itibaren de artık bir core web app e eklediğim gibi ClientFactory yi ekleyebilirim.

Bunun için bir IServiceCollection field ını App.cs tarafında oluşturdum. Eğer null ise Constructor new ServiceCollection diyerek Container ı oluşturuyorum.

Daha sonrada uygulama sayfalarımdan burada register etmiş olduğum service lere ulaşmak için IServiceProvider property mi oluşturuyorum. Bunu ayağa kaldırmak içinde tüm serviceleri register ettikten sonra BuildServiceProvider() diyerek provider objemi oluşturuyorum. App.cs de yapacaklarım bu kadar.

 public partial class App : Application
    {
        IServiceCollection services;
        internal static IServiceProvider ServiceProvider { get; private set; }
        public App()
        {
            InitializeComponent();

            if (services == null)
            {
                services = new ServiceCollection();
            }
            services.AddHttpClient();


            ServiceProvider = services.BuildServiceProvider();
            

            MainPage = new MainPage();
        }

         .....

App.cs de gerekli işlemleri bir kaç satır da hallettikten sonra, ki bu birkaç satırda şunları yapmış olduk;

  • Bir DI mekanizması oluşurup IoC container kurduk
  • Bu Container a IHttpClientFactory yi register ettik

artık MainPage e geldiğimde, aşağıdaki gibi ServiceProvider üzerinden ilgili service ime ulaşabilirim. Burada ulaşmak istediğimiz service de IHttpClientFactory nin kendisi.

 public partial class MainPage : ContentPage
    {
        IHttpClientFactory _clientFactory;

        public MainPage()
        {
            InitializeComponent();

            _clientFactory = App.ServiceProvider.GetRequiredService<IHttpClientFactory>();

            Task.Run(async () =>
            {
                var result = await _clientFactory.CreateClient().GetStringAsync("http://randomuser.me/api?results=1");

            });
        }
    }

 _clientFactory = App.ServiceProvider.GetRequiredService(); dedikten sonra artık bu clienFactory normal bir asp.net core projemdeki gibi elimde. Burada basic usage olarak kullandığımız için hiçbir parametre verdeden CreateClient diyerek HttpClient ımızı oluşturup gerekli işlemlerimizi bunun üzerinden yapabiliriz.

Görmüş olduğunuz gibi Microsoft un Asp.Net Core ile beraber kurmuş olduğu yapı oldukça esnek ve birbiri ile kullanılmaya oldukça müsait. Bir xamarin projemizde HttpClientFactory yi ve hatta Microsoft kendi micro Container ını kullanmış olduk.

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.