I never really liked writing JavaScript and doing web browser DOM interaction. For me, dealing with JavaScript has always been a pain, a-must when developing web applications . In contrast, I always enjoyed to write C# code.
Back in Silverlight times, C# was – for a relative short period – one possibility to write web client apps. With introduction of HTML5 days were numbered for web browser plugins like Flash and Silverlight. So, everything came back to the vanilla JavaScript (an assembler language of the web) or some JavaScript frameworks like React, Angular, Vue.js and related.
With WebAssembly and Microsoft implementation Blazor WebAssembly, maybe this time C# has chance to be back in game for writing rich web client side applications. Why I think this is right time: WebAssembly-Wasm (technology/specification on which Blazor is based on) is under W3C Consortium (https://www.w3.org, World Wide Web Consortium). Wasm working group at W3C is made of major players, like: Apple, Microsoft, Google, Mozilla Foundation, Facebook, Huawei, etc…
So what is WebAssembly: a size- and load-time-efficient format and execution environment. To be more plastic, WebAssembly provides a way to run code written in multiple languages on the web at near native speed, with client apps running on the web that previously couldn’t have done so.
WebAssembly is by no mean designed to replace JavaScript, it’s designed to complement and run alongside JavaScript – to enrich client side web development experience!
In general this means that compact binary format application can run inside web browser. This – assembly like binary format – can be created with different programming languages, like C/C++, Rust, C#. WebAssembly(ies) can be created with various programming languages, and one of the implementation is from Microsoft. Its name is BLAZOR!
Enter Blazor
Blazor is a client web UI framework based on .NET and WebAssembly. It basically allows to execute .NET code inside the browser. It is part of ASP.NET Core web stack.
Blazor can run on client side or on server side. Both hosting models share the same programming model.
- Blazor Server runs on the server on top of SignalR.
- Blazor WebAssembly runs client-side on WebAssembly.
Server side was included in .NET Core 3.0, client side Blazor WebAssembly is still in preview and will be included in the next versions of .NET.
In this blog post I will focus on client side Blazor WebAssembly. I will address Blazor Server with real-time SignalR in one of the next blog posts.
If you need more details, here is the official site: https://blazor.net
Let’s try some basic experiments
I was eager to try some C# code inside web browser, so I made some experiments. I created Blazor App in Visual Studio 2019.
Example #1: Security – symmetric encryption and hash function.
My first experiment is some basic security stuff, like playing around with asymmetric encryption and hash calculation with Blazor. All processing is done on the client side, .i.e. inside web browser. Normally, for average .NET (web) developer this would be relative difficult task to do. It would required good knowledge of JavaScript in order to complete the task. But with Blazor, .NET developers can reuse existing C# code. Let’s take a look.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
@page "/crypto" @using System.Security.Cryptography; @using System.IO; @using System.Text; <div class="row"> <div class="column"> <h2>Symetric Encryption</h2> <div> <textarea name="PlainText" cols="40" @bind="PlainText" rows="5"></textarea> </div> <div> <textarea name="Key" cols="40" rows="5" @bind="Key"></textarea> </div> <div> <button class="btn btn-primary" @onclick="EncryptText">Encrypt</button> </div> <div> <textarea id="test" cols="40" rows="5" @bind="CryptedText"></textarea> </div> <div> <button class="btn btn-primary" @onclick="DecryptText">Decrypt</button> </div> <div> <textarea name="DecryptedText" cols="40" rows="5" @bind="DecryptedText"></textarea> </div> </div> <div class="column"> <h2>Hash</h2> <div> <textarea name="PlainTextForHash" cols="40" @bind="PlainTextHash" rows="5"></textarea> </div> <div> <textarea name="Hash" cols="40" @bind="Hash" rows="5"></textarea> </div> <div> <button class="btn btn-primary" @onclick="CalculateHash">Hash</button> </div> </div> </div> @code { private string Key = "xo9dm2xdb2zmh3e0clj32vyn3s8z4f6b"; private string PlainText = "jenx.si"; private string CryptedText = ""; private string DecryptedText = ""; private string PlainTextHash = "jenx.si"; private string Hash = ""; private void EncryptText() { EncryptString(); } void DecryptText() { DecryptString(); } void CalculateHash() { CalculateHashInternal(); } private void EncryptString() { try { byte[] iv = new byte[16]; byte[] array; using (var aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(Key); aes.IV = iv; ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (var memoryStream = new MemoryStream()) { using (var cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write)) { using (var streamWriter = new StreamWriter((Stream)cryptoStream)) { streamWriter.Write(PlainText); } array = memoryStream.ToArray(); } } } CryptedText = Convert.ToBase64String(array); } catch (Exception ex) { CryptedText = ex.Message + Key.Length; } } private void DecryptString() { byte[] iv = new byte[16]; byte[] buffer = Convert.FromBase64String(CryptedText); using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(Key); aes.IV = iv; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream(buffer)) { using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read)) { using (StreamReader streamReader = new StreamReader((Stream)cryptoStream)) { DecryptedText = streamReader.ReadToEnd(); } } } } } private void CalculateHashInternal() { using (var sha256Hash = SHA256.Create()) { byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(PlainTextHash)); var builder = new StringBuilder(); for (int i = 0; i < bytes.Length; i++) { builder.Append(bytes[i].ToString("x2")); } Hash = builder.ToString(); } } } |
The working application looks like this:
I was shocked how fine this works and how easy task this is with C# and Blazor. Keep in mind that all this is executed inside web browser sandbox. Awesome.
Example #2: Blazor to JavaScript interop
Every good abstraction layer or framework must enable easy and full access to the underlying native runtime. Blazor WebAssembly has very nice interop with JavaScript. Developer just need to include JavaScript file (or include JavaScript code in script block), and inject JSRuntime
in razor page.
For demo, I put two JavaScript functions inside hosting html page: AlertFunction()
and BrowserInfoFunction()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>Jenx.Blazor.Experiments</title> <base href="/" /> <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> <link href="css/site.css" rel="stylesheet" /> <script src="js/client.min.js"></script> <script src="_content/Cloudcrate.AspNetCore.Blazor.Browser.Storage/Storage.js"></script> </head> <body> <app>Loading Jenx Blazor Experiments...</app> <script src="_framework/blazor.webassembly.js"></script> <script> function AlertFunction() { alert('jenx.si was here!'); } function BrowserInfoFunction() { var client = new ClientJS(); var result = "AGENT:" + client.getUserAgentLowerCase(); + "\r\n"; result += "BROWSER: " + client.getBrowserVersion(); return result; } </script> </body> </html> |
I extended razor page and injected (@inject
directive) IJSRuntime
and called InvokeAsync()
function with correct attributes, e.g.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
@page "/blazor-to-js" @inject IJSRuntime JsRuntime; <div> <button class="btn btn-primary" @onclick="RaiseJavaScriptAlert">Invoke Javascript Alert</button> </div> <br /> <div> <button class="btn btn-primary" @onclick="GetBrowserInfo">Get Browser Info</button> </div> <br /> <div> @Info </div> @code { private string Info { get; set; } private async void RaiseJavaScriptAlert() { var text = await JsRuntime.InvokeAsync<object>("AlertFunction", null); } private async void GetBrowserInfo() { var text = await JsRuntime.InvokeAsync<string>("BrowserInfoFunction", null); Console.WriteLine(text); Info = text; StateHasChanged(); } } |
The output is as follows:
Pretty awesome, right? This way I can extent or reuse existing JavaScript code with Blazor.
Experiment #3: Using Html5 LocalStorage from Blazor
With web storage (local storage and session storage) web applications can store data locally within the user’s browser. Same is with Blazor – C# code can use the same mechanism to persists some data. Let’s take a look.
For this, I used external library. I referenced NuGet package Cloudcrate.AspNetCore.Blazor.Browser.Storage
then I was able to use local storage in my razor page, e.g.:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@page "/local-storage" @inject Cloudcrate.AspNetCore.Blazor.Browser.Storage.LocalStorage Storage <input type="text" @bind="value"> <button @onclick="SetValue">Set</button> <button @onclick="GetValue">Get</button> @code { string value; void SetValue() { Storage.SetItem("storageKey", value); } void GetValue() { value = Storage.GetItem("storageKey"); } } |
The browser output is as follows:
Experiment #4: Reusable components
Sharing and reusing code is very nice feature for any development environment. Blazor use component based model to share code. Let’s take a look. I created TimerComponent
and ParameterComponent
razor components:
Timer component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<p>Timer component</p> <div> Current client-side time is: @time </div> <h5>Timer component (with mono bug, UTC/localtime problem)</h5> @code { private string time = DateTime.Now.ToLongTimeString(); private System.Timers.Timer timer; protected override void OnInitialized() { timer = new System.Timers.Timer(1000); timer.Elapsed += (a, b) => { time = DateTime.Now.ToLongTimeString(); StateHasChanged(); }; timer.Start(); } } |
Component with input parameter:
1 2 3 4 5 6 7 8 9 |
<div> Parent valu displayed inside compoent: @Parameter </div> @code { [Parameter] public string Parameter { get; set; } } |
Integration into hosting razor page:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
@page "/components" <h1>Components</h1> <TimerComponent /> <br /> <h2>Component with parameter</h2> <div> <p>Parameter:</p> <textarea id="test" cols="40" rows="5" @bind="@PageParameter"></textarea> </div> <div> <button class="btn btn-primary" @onclick="UpdateComponentParameter">Send Parameter to component</button> </div> <ParameterComponent Parameter="@ComponentParameter" /> @code { private string PageParameter = ""; private string ComponentParameter = ""; void UpdateComponentParameter() { ComponentParameter = PageParameter; } } |
and browser output:
This way complex applications can be decomposed to smaller, more manageable parts.
Experiment #5: State management
Blazor apps are by design stateless, meaning that if you navigate from one page to another state is not persisted. State maintenance can be simple implemented this way: First define state holder, in my case I created StateDto
class in Models
folder/namespace:
1 2 3 4 5 6 7 |
namespace Jenx.Blazor.Experiments.Data { public class StateDto { public int Counter { get; set; } } } |
In Startup class I used built in dependency injection mechanism by calling AddScoped()
function.
1 2 3 4 5 6 7 8 9 10 |
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<Data.StateDto>(); // ... code removed for brevity } // ... code removed for brevity } |
AddScoped()
instantiate singleton object per user session. So, if i put this into hosting razor page, like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@page "/state" @inject Data.StateDto State <h1>State management</h1> Counter: @State.Counter <div> <button class="btn btn-primary" @onclick="CounterIncrease">Count up</button> </div> @code { void CounterIncrease() { State.Counter++; } } |
state maintenance will be preserved even I navigate from this page. Super awesome.
In this blog post I presented just some simple experiments with Blazor WebAssembly. Possibilities are enormous. You must give it a try, and play around with Blazor.
You can find source code here: https://github.com/josipx/Jenx.Experiments
Important links
Just for the record, I put some links related to this debate here:
- Official Blazor site: https://blazor.net
- Awesome Blazor: https://github.com/AdrienTorris/awesome-blazor
- WebAssembly (WASM) home page: https://webassembly.org
- WASM Working Group: https://www.w3.org/wasm
- WASM Core specification: https://webassembly.github.io/spec/core/bikeshed/index.html
- WebAssembly Working Group Status: https://www.w3.org/2004/01/pp-impl/101196/status
- Daniel Roth blog: https://devblogs.microsoft.com/aspnet/author/danroth27
- Blazor source code: https://github.com/aspnet/AspNetCore/tree/master/src/Components
Conclusion
At first, I was a bit skeptic about client side Blazor WebAssembly. Let’s face it: web is (was) all about HTML5 and JavaScript. I hope this will no longer be the only option, and web developers will have more possibilities to create rich web client applications.
Now, I am more optimistic about WebAssembly technology, why?
- WebAssembly – underlying technology used by Blazor is under W3C umbrella. Technical Working Group consist from all big players: Google, Apple, Microsoft, Mozilla, etc…
- Microsoft already put release version of server-side Blazor (based on SignalR) and it’s planning to put also client side Blazor as part upcoming of .NET version.
- Community is interest in WebAssembly.
- Long-missing good all-around runtime/abstraction for web client side runtime/development.
- Several development possibilities/ecosystems for client side web development.
- Popular component vendors like Telerik, DevExpress, and Syncfusion also actively participate in development of Blazor UI components.
- Everything is open source, similar as everything related to .NET Core.
If you haven’t tried Blazor yet, you should give it a try.
One thing is sure: I will follow Blazor and the Asp.Net team around Daniel Roth. Interesting times for .NET developers ahead.
Happy coding!