Bir önceki yazıda Blazor ve ASP.NET Core ile ilgili bir şeyler karalamıştım. Blazor’ın ne olduğu, ASP.NET Core ile ilişkisi ve son durumunu özetlemeye çalışmıştım. Şimdi daha çok kod üzerinden biraz daha neler oluyor bakalım. Hem daha iyi anlaşılmasını sağlar belki, hem de kurcalamak, öğrenmek ve ilerleyen zamanlarda çözümler içerisinde katmak için heyecanlandırır belki de.

Yazıda paylaşacağım örnekler; .NET Core 3.0 Preview 4 ve Visual Studio 2019 üzerinde olacak. Deneyimlemek istiyorsanız bu versiyonları yüklemeniz daha sağlıklı olacaktır. Tabi ki Visual Studio Code da olur.

Öncelikle yeni bir ASP.NET Core Web projesi yarattığımızda, proje şablonu olarak Blazor(server-side)‘ı göreceğiz. Önceki versiyonlarda Razor Components‘dı… Bu proje şablonunu seçtiğimizde yandaki gibi bir proje yapısı oluşacaktır.

İlk defa deneyimleyecekler için *.razor uzantılı dosyalar dikkat çekecektir. Onlara direkt gelmeden önce, ilk olarak, standart ASP.NET Core uygulamalarındaki Startup.cs dosyasının içine bakalım. Bu arada bu yazıda bahsedeceğim konular Blazor(server-side) hosting modeli ile ilgili olacak.

Startup.cs

Herhangi bir ASP.NET Core Web uygulaması (MVC, Razor, Web API…) ile uğraştıysanız çok farklı bir şey görmeyeceksiniz aslında. Önemli olan kısımlar; ConfigureServices() kısmındaki .AddServerSideBlazor(); ve Configure() kısmındaki .MapBlazorHub();

.AddServerSideBlazor(); adından da çok net anlaşılacağı üzere Blazor’ın çalışma servislerinin uygulamamıza eklenmesini sağlıyor. Preview 3‘de AddRazorComponents olan metot Preview 4 ile asıl ismine kavuştu 😊 Bu satırı eklemeden Blazor uygulama modelini kullanmamız mümkün değil.

.MapBlazorHub(); da, Blazor’ın server-side modeli için tarayıcıdaki UI bileşenlerinin ASP.NET Core uygulaması ile SignalR üzerinden iletişim kurması için Hub oluşmasını sağlıyor.

Şimdi sıra geldi _Host.cshtml dosyamıza. Bu dosya uygulamanın statik olarak ilk oluşturulan içeriği diye düşünebilirsiniz. Zaten içeriğine baktığınızda çok tanıdık HTML içeriğini görüp, “aaaa bu mu, bildiğimiz HTML” diyeceksiniz. Ama burada önemli olan kısım RenderComponent()

Biraz daha derinlere inelim, ama çok değil. Blazor, temel olarak, bileşenler (components) ile oluşturulan küçük UI parçalarının çalışması/çalıştırılması üzerine ortaya çıkan bir uygulama modeli. Ön yüz tarafının parçalar halinde, bağımsız çalışması, kullanıcı etkileşimin ve UI durumlarının sağlıklı çalışmasını amaçlıyor. Angular, React…vs. tarzı diğer UI Framework’lerindeki gibi. Javascript(js) tabanlı UI Framework’lerine çok hâkim değilim ama onların da temel çıkış noktası, bileşen yani component kavramı…

Blazor’da da component yaklaşımı çok önemli. Proje şablonunda da gördüğünüz *.razor dosyalarının her biri aslında birer component yani bileşen. Bileşenler birer sayfa olabildiği gibi, UI tarafındaki küçük bir tablo ya da bir input formu olabilir. ASP.NET WebForms dönemini yaşayanlar için UserControl kavramını diyebilirim. Bileşen kavramının, günümüz standartları, ihtiyaçları ve HTTP pipeline’ına daha uygun olduğunu söyleyebilirim.Neyse…

Yukarıdaki Html.RenderComponentAsync() kısmına geri dönelim. Bu methot ile Blazor uygulamanın ilk bileşenini UI için belirtiyoruz. App sınıfı burada App.razor bileşenine denk gelmekte. App sınıfına “Go To Defination” dediğinizde App.razor.g.cs diye bir dosya açılacaktır. Dosyanın açılması için önce mutlaka projeyi Build etmeniz gerekmekte. Aksi takdirde açılmayacaktır.

Bu tarz *.g.cs dosyaları Blazor bileşenlerinin derlendiğinde oluşan kısımları. Bu dosyalar proje içeriğinde bulunmaz ama bileşene bir şekilde erişim olduğunda, dokunulduğunda bu şekilde oluşan dosyaların içindeki kodlar çalışmakta. App.razor’dan giriş yaptım ama tüm *.razor dosyaları için bu çalışma şekli geçerli.

Şimdi de proje içerisindeki App.razor dosyamıza gidelim. Bu dosyanın içeriği de oldukça yalın ve basit.

Router diye, HTML tag’ına benzer bir içerik göreceğiz. Bunu Blazor içinde built-in olan bir bileşen olarak düşünebiliriz. Blazor uygulamasındaki, routing yani yönlendirmelerin yapılmasını sağlayan bir bileşen. Bu bileşen ile diğer *.razor’lerine nasıl erişileceği yönetilmekte. Proje şablonundaki örnek diğer *.razor dosyalarına geçmeden önce projeyi bir çalıştıralım ve bakalım neler olacak.

Bu proje şablonu daha doğrusu örnek bize iki tane bileşen ile geliyor. Counter.razor ve FetchData.razor… Bu arada layout yapıları standart bir ASP.NET Core uygulamasındaki gibi, onlara çok girmeyeceğim bu yüzden. Ama onlarda Blazor uygulamalarında birer bileşen gibi yorumlanmakta…

Counter.razor bileşeninden devam edelim. Bu dosyayı açtığımız zaman aşağıdaki gibi bir içerikle karşılacağız.

Fark edeceğiniz üzere C# kodu ve HTML/JS/CSS içeriği bir arada. C# kodumuzda baktığımızda sadece bir metot ve bir değişken mevcut. Ama aslında Counter.razor yani Counter sınıfının birer metotu ve değişkeni bunlar. Counter.razor’ın derlendiği zaman ki Counter.razor.g.cs dosyasına baktığınız da; -ki bu dosyaları projenin /ojb/ klasöründen erişebilirsiniz, direkt C# yapısını görebilirsiniz.

Biz şimdilik buna çok girişmeden *.razor dosyamızdan devam edelim. <button /> HTML tag’i içerisinde, DOM event‘lerinden onclick karşısında dikkat ederseniz, C# metodu IncrementCount var. Yani UI tarafında <button /> ‘a tıkladığımızda bu C# kodu çalışacaktır. C# kodu da currentCount değişkenini +1 arttırıyor basit bir şekilde. <p> tag’i içinde de bu değişkenin değerini UI tarafında gösteriyoruz.

Projeyi çalıştırmadan bu dosyanın başındaki @page ifadesinden de bahsetmek istiyorum. Dikkat ederseniz @page “/counter” gibi bir ifade var. Bu ifade ile bu bileşenin bir sayfa olduğunu ve bu sayfaya http://localhost/counter şeklinde erişilebildiğini belirtiyoruz.

Şimdi projeyi çalıştıralım ve menüdeki Counter’a tıklayalım. Tıkladığımız zaman, adres çubuğundaki adres değişecek ve Counter.razor bileşeni karşımıza çıkacaktır. Dikkat ederseniz ki, tarayıcıda herhangi bir “load” olayı olmadı. Sanki bir ajax çağrısı gibi…

Aynı şekilde Counter sayfasını açıp, “Click Me” ‘ye tıkladığımızda sanki bir javascript kodu çalışıyormuş gibi UI tarafında değişikliği fark edeceksiniz. Peki bu nasıl oluyor? Gerçekten yazdığımız C# kodu browser’da mı çalışıyor?

Hayır… Blazor’ın server-side hosting modelinde yazdığımız C# kodu yine sunucu tarafındaki ASP.NET Core uygulamasında çalışıyor. Client-side hosting modelinde ise Evet… Ama ona şimdi girmiyoruz.

UI tarafı, dinamik olarak oluşan javascript ile, SignalR özellikleri sunucudaki C# metotlarını “Invoke” ediyor… Özellikle “Invoke” kelimesini kullandım ki; UI’da kaynağı görüntüleyip, _framework/blazor.server.js dosyasının içeriğine arattığınızda invokeDotNetFromJS tarzı metotları görebilirsiniz. Bu arada .js minimized olduğu için direkt kör eder çok bakmayın 😊 Ama en azından çalışma mantığını az biraz anlamak için bakmakta fayda var. Özellikle hata mesajlarını görmek faydalı olabilir. Neyse… Şimdi biz Counter.razor dosyamıza geri dönelim.

Counter.razor içine baktığımızda C#, HTML vs. her şeyin olduğu bir kalabalık görmüştük. Hızlı ve kolaylıkla bazı çözümleri oluşturmak için birebir. Ama C# kodlarının çok olması durumlarında belli gereksinimlerin UI modelinden ayrıştırılarak yapılması tercih edilebilir. Şimdi buna da kendimiz bir bileşen yazarak bakalım.

Projemizi çok değiştirmeden aşağıdaki yapı içerisinde, SearchComponent.cs ve Search.razor ekleyelim… İçeriklerine geçmeden önce basit de bir senaryomuz olsun. Basit bir arama bileşeni yapalım, yazdığımız değeri Google’da arasın ve Google’dan dönen sonuç sayısını göstersin. Ek olarak klavyede bastığımız tuşların da ne olduğunu ekrana yazsın.

Dosya içeriklerimiz de aşağıdaki gibi olsun;

SearchComponent.cs

using Microsoft.AspNetCore.Components;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;

namespace BlazorDemo.Code
{
    public class SearchComponent : ComponentBase
    {
        public string SearchValue { get; set; }

        public string PressedKey { get; set; }

        public string SearchResult { get; set; }

        //[Inject]
        //IJSRuntime JSRuntime { get; set; }

        public SearchComponent()
        {
        }

        public async Task Search()
        {
            using (HttpClient client = new HttpClient())
            {
                var result = await client.GetAsync($"https://www.google.com/search?q={SearchValue}");

                if (result.IsSuccessStatusCode)
                {
                    var content = await result.Content.ReadAsStringAsync();

                    Match m = Regex.Match(content, $"<div class=\"sd\"{@"\s*(.+?)\s*"}</div>");

                    if (m.Success)
                    {
                        SearchResult = HttpUtility.HtmlDecode(m.Groups[0].Value.Replace("<div class=\"sd\" id=\"resultStats\">", "").Replace("</div>", ""));
                    }
                }
            }
        }

        public async Task OnKeyPressed(UIKeyboardEventArgs eventArgs)
        {
            PressedKey += eventArgs.Key;

        }
    }
}

Search.razor

@page "/search"
@inherits BlazorDemo.Code.SearchComponent


<input type="text" bind="@SearchValue" onkeydown="@OnKeyPressed" />

<button class="btn btn-primary" onclick="@Search">Search</button>
<br />
Google Search Result Count:@SearchResult
<br />
<br />
<br />
Pressed Key:@PressedKey
<br />

Burada önemli nokta Search.razor dosyamızda, ilk satırlardaki @inherits ifadesi. Böylece *.razor bileşenimizin SearchComponent sıfınından olduğunu belirtmiş oluyoruz. SearchComponent sınıfına geri döndüğümüzde de orada sınıfın ComponentBase‘den türetildiğini göreceksiniz.

Uygulamayı çalıştırdığımızda soldaki menüde gözüksün diye NavMenu.razor‘a da eklemeyi unutmayalım.

Çalıştırıp Search bileşenine eriştiğimiz zaman aşağıdaki ekran görüntümüz olacak.

Input alanında, klavyeden hangi tuşa basarsak ekrana bastığımız karakter yazılacaktır. Bu <input /> ‘un onkeydown event’ine karşılık gelen OnKeyPressed metodu ile olmakta. Yukarıda da bahsettiğim ile aynı aslında. Input alanına bir şeyler yazıp, “Search” dediğimizde de benzer şekilde yazdığımız ifadeyi Google’da arayıp, ne kadar sonuç dönüyor bunu yine UI tarafına aktarabiliyoruz. Burada önemli olan alanına yazdığımız değerleri “bind” özelliği ile C# tarafına aktarabiliyoruz basitçe.
Özetle UI tarafından, C# özelliklerine ve metotlarına erişmek bu şekilde oldukça kolay.

Aynı şekilde C# tarafından (server-side), UI tarafındaki javascript metotlarını çağırmakta mümkün. Bunun için SearchComponent sınıfına [Inject] ettiğimiz JSRuntime özelliğinden yararlanacağız.

Commentlediğimiz JSRuntime’ı açabiliriz artık. 🙂

Kolay olması için yine OnKeyPressed metodumuza aşağıdaki gibi bir ekleme yapalım.

        public async Task OnKeyPressed(UIKeyboardEventArgs eventArgs)
        {
            PressedKey += eventArgs.Key;

            if (eventArgs.Key == "1")
            {
              var someResult = await JSRuntime?.InvokeAsync<string>("DoSomeWarning");
            }

        }

Burada JSRuntime özelliğimizin InvokeAsync() metodu ile hangi javascript metodunu çalıştıracağımızı yazabiliyoruz. Generic bir metot ile dönüş değerini de belirtip, javascript metodundan bir sonuç döndürme ihtiyacımız olursa, o sonucu alabiliyoruz.

Bu arada örneğimizde ki javascript metodumuz da aşağıdaki gibi wwwroot altında olması gereken yerde.

Buradaki senaryomuz oldukça basit. UI tarafında 1’e bastığımız zaman DoSomeWarning() javascript metodu çalışacak. Burada önemli olan nokta, metodu C# tarafından tetikleyebilme imkanımız. SignalR ile uğraşanlar için oldukça tanıdık bir yöntem gelecektir, -ki zaten öyle de. Blazor (server-side) uygulama modelinde UI ve server-side tarafındaki iletişim SignalR sayesinde olmakta.

Debug…

Blazor uygulamalarını debug etmek de oldukça kolay. Aslında ekstra farklı bir özel bir durum söz konusu değil. Standart olarak Visual Studio’nun debug prosedürleri aynen geçerli. Örnek olarak daha demin ki senaryo ile devam edelim. UI tarafındaki DOM event’lerinin karşılığı ya da javascript metotlarındaki değerleri C# tarafında da yorumlayabiliyoruz bu şekilde.

javascript metodundan dönen değeri alabiliyoruz
UI tarafındaki DOM eventlerinin karşılıklarını C# tarafından erişebiliyoruz

Blazor(server-side)’ın temel özellikleri ile Blazor’ı biraz anlamaya çalıştık. Client-side modeli yani direkt tüm uygulama kodlarının tarayıca da çalışması da mümkün. Tarayıcıların UI thread‘inde Web Assembly(wasm) kavramı ile direkt C# kodlarımız çalışabilmekte. Yukarda bahsetmeye çalıştığım tüm özellikler, kodlar client-side çalışma modeli içinde geçerli. Tek farkı, uygulamanın tüm *.dll’leri ve gerekli .NET Core runtime’ı da tarayıcı tarafından indiriliyor. Bu şu aşamada boyut olarak büyük olduğu için web uygulaması için çok tercih edilebilecek bir durum olmayabilir. Wasm ve .NET Core’un daha da gelişmesi ve olgunlaşması ile orta vadede Blazor uygulama modeli güzel sonuçlar kazandırabilir. Ama tabi ki artı yönleri olduğu gibi önemli eksi yönleri de var. İhtiyaçlar doğrultusunda iyi analiz edilip, alternatif bir uygulama modeli olarak yaklaşmak en doğrusu.

Şu an Blazor ve ASP.NET Core 3.0 Preview olarak yayınlandığı için bir çok değişiklik ve yenilik karşımıza çıkabilir, tanıştığımız özellikler de gidebilir. Ama en azından bizi neler bekliyor görmek için bu tarz özellikleri takip etmek de fayda var.

Bir sonraki yazıda görüşmek üzere. 👨‍💻

Bu arada yazıdaki tüm örnekleri GitHub’a ekledim, buradan ulaşabilirsiniz.