Home Our blog Blazor - part 1 - introduction
Blazor - part 1 - introduction
07/01/20
Petr Otych
One of the most interesting events of recent years in the field of web application development has been the release of Blazor. This framework and WebAssembly undoubtedly have a bright future ahead. But what is it, what are its foundations and what is it like developing with it?
WebAssembly
Let me begin with some brief introduction. The trend on the web nowadays are applications with some JavaScript framework on the front end. For several years, however, a new technology, which at the end of 2019 reached the stage of W3C Recommendation, has been worked on - WebAssembly. There is no point in extensively explaining what this standard (Wasm for short) describes, the essential goal is simple - bytecode in the browser. Wasm code can also be encoded in binary format and can be processed and executed faster than JavaScript code.
WebAssembly, however, will certainly not wipe JavaScript off the face of the earth. It is more a complement to it than an alternative. JavaScript is going to stay with us for a long time, especially considering its global popularity. In addition, WebAssembly cannot access the DOM directly right now, it must be accessed via JavaScript - this may change in the future, but the question is whether this really is a wrong design decision and a lacking feature. Wasm runs in the same security context as JavaScript, so you don't have to worry about security - it's not like Silverlight or Flash.
WebAssembly is supported in all modern browsers (so not in IE) and is already supported by many compilers. You can try it with code written in C ++, Rust and many more languages. But not only that - many interpreted languages already have an interpreter written in Wasm (e.g. Python).
What about .NET?
So, it is great that C++ code can run in a browser, but it does not help in developing a web application. However, Microsoft took this opportunity and decided to build upon it. The ideal solution would be to compile a .NET Core web application written in C# into Wasm, but the current state of CoreRT and general support of AOT compilation in .NET is lacking (we'll see at the time of .NET 5 release and beyond) so it was decided to pick a runtime that supports JIT compilation and compile into WebAssembly only that and the application itself remains in .NET IL. After the first experiments, Microsoft remembered it had acquired Mono and picked it as the runtime. Since then, the AOT compilation has taken a few steps forward, so one day it will hopefully be ready, but this is a truly challenging task in many ways.
Blazor
Now, finally, we are slowly getting to Blazor. .NET Core can run in the browser and we need something that glues that with the DOM and enables us to applications. And we have Blazor - a framework used in web application development using C# and Razor. And since WebAssembly does not allow direct access to the DOM, Blazor maintains a virtual DOM that synchronizes with the actual DOM via JavaScript.
Hosting models
Server-side hosting model
Known as Razor components, but also as Blazor Server or Blazor components (last year it was being renamed quite a bit, so choose). It reached its final version since the release of ASP .NET Core 3.0. Instead of WebAssembly, the application is running on the server and communication between it and the browser (which is only used for displaying elements and UI) is done via SignalR. Therefore, each user of the application has its own instance of that application on the server. It's not as performance-limiting as it would seem at first glance, but of course, latency has an impact and server availability should also be considered. On the other hand, the code always runs on the server, which might important for many people because of security reasons. However, it seems rather as a solution that came about because circumstances encouraged it – interactive components without the need of writing JavaScript code, originally uncertain future of Blazor on WebAssembly and the existing need to synchronize the virtual DOM with the real one. So, Microsoft added SignalR to the mix and Razor components were introduced.
Client-side hosting model
So far in preview, should be finished this year. It works as described above - the runtime is in WebAssembly, compiles the application from MSIL and it runs directly in the browser. Files needed to run the application can be statically hosted by the web server, but for any real-world application you will also need a server application, which will among other things also take care of hosting of the files. Since ASP .NET Core 3.1, Mono on WebAssembly supports .NET Standard 2.1. Previously, its support of only .Net Standard 2.0 lead to relatively wild hierarchies of project dependencies to deal with this limitation. Now you can literally use the entire .NET Core.
How to start with Blazor?
You should absolutely check out the Visual Studio sample applications and study the documentation. It would be pointless to write here about everything Blazor has to offer. Rather, I will pick a few topics in this and future articles.
Architecture of Blazor applications
Officially, there is no defined architectural pattern that Blazor application should follow, but we will not lie to each other - Blazor is certainly closer to MVC than MVVM (but it can be "forced" to MVVM too). If you want to adhere strictly to a pattern, there will certainly appear some libraries or steps to follow, but it is possible to develop in Blazor with a calm mind even now - just accept that it picks bits out of multiple patterns.
Components
If you have ever worked with a framework that uses components, Blazor will not surprise you in any way. It's all about nesting them and inserting their hierarchies into pages and layouts.
Components are written either directly in C# or Razor. Of course, it is not the exact Razor you know from Razor Pages or MVC, but the principle is identical - it is a template from which the component class is generated. Basically, all you have to understand is that the markup code block in the .razor file is used by the compiler to generate the method for rendering the component. Therefore, you can inherit your component from any existing component and override its rendering method. This is useful for modifying built-in components. In addition, the generated component classes are partial, which is ideal if you do not want to have the C# code mixed with the markup code - just use a second file.
Blazor is aiming to work as efficiently as possible when it comes to redrawing the DOM, but it requires something from you in exchange. All components and elements have ids that must be unique to their parent, and in addition when compared to their siblings in ascending sequence - you must follow to this if you write the component's rendering function manually. Thanks to the identifiers, Blazor can quickly detect changes in the DOM and reuse items without deleting them and creating new ones of the same type - it just sends the input parameters into them again. However, you may be unpleasantly surprised if the component has its own internal state that does not depend on the input parameters - the internal state will remain, but the parameters will change to the component's parameters that had this id in the last render batch (typically in collections). Fortunately, you can also assign a component unique key (using the “key” attribute) that ensures the parameters are always mapped to the same component.
I will not describe here all the stages of the component life cycle; I rather recommend finding some illustrative diagrams. Moreover, the names of these methods are quite indicative. However, let me mention just a few notes about some of them:
-
SetParametersAsync: the main function of the component; you must call the parent's implementation synchronously - there can be no await before the call. Therefore, it is useful mainly for throwing exceptions when some parameters are not set.
-
OnInitializedAsync and OnParametersSetAsync: it is crucial to realize if you are correctly working with the component as it is being created. Note that the input parameters may change must the component must handle such change correctly. If you perform operations that depend on the input parameters in OnInitializedAsync, it will eventually bite you back. It is also important to note that if the calls to these two functions are truly asynchronous and therefore return an unfinished Task, the component is rendered immediately. In such case, it is rendered one more time when compared to situation when the method completes synchronously and the entire startup stage of the life cycle of the component is completed.
-
OnAfterRenderAsync: The returned Task is not awaited by Blazor, it is a fire-and-forget, but fortunately Blazor catches and handles exceptions thrown in it.
Communication between components
Blazor has a neat for one-way binding parameters to nested components. You can either send parameters directly to child component, or to all nested components (using the so-called cascading parameters). The nested components capture them either by type or name. To me cascading parameters seem as a double-edged sword – firstly, they do not contribute to the readability of the code, and secondly, their use can easily turn into a situation where they actually replace the internal state of the application, which should be solved by something more sophisticated. So do not overuse them. Blazor itself does not implements a shared store/application state. Either you reach for a finished solution or using a singleton that sends events to components is sufficient for your scenario.
Basically, all events in Blazor are handled with the EventCallback structure, which can handle both synchronous and asynchronous operations and at it also notifies the component that its state has changed and consequently the component is rerendered. When a component is rendered, its nested components that use any of its variables as an input parameter are also rerendered.
Blazor can even handle two-way binding. However, in fact is just one-way binding combined with adding the above-mentioned EventCallback (by default with the same name as the variable with the suffix “Changed”). This callback contains action that sets the value of the variable in the parent component. You must call this callback manually - either in a function every time you change a value, which is not very intuitive, or in the setter, but you have to have a duplicated property to avoid infinite loop (the setter informs the parent, it redraws and wants to redraw the nested component (because binding exists), it sets the parameter, and this happens over and over). Moreover, this callback can only be invoked asynchronously (since it can also handle asynchronous operations, but it is kind of unpleasant in a setter), so you must choose between fire and forget or synchronous wait for this method. It doesn't really matter, because it will run synchronously anyway, so pick what seems more right to you.
So yes – there is two-binding in Blazor and you can use it to chain parameters over multiple components, but it's kind of unintuitive. However, what I must acknowledge is that in two-way binding, Blazor also sets another optional parameter. This parameter has the same name as the bound variable with the suffix “Expression”. This parameter must be of type Expression<Func<T>>. And what is it for? Well, because it is always an expression with the same lambda in which only the member variable is present, you can extract the name of that variable from it. This is useful in many cases (eg when working with forms) and you do not need to pass the variable name explicitly.
Blazor and threads
WebAssembly does not support multithreading yet and this means that Blazor has only one thread available. It does not report itself as a thread-pool thread, but it is called that and fortunately it behaves like it. If you had only UI thread and an empty threadpool in your application, you would not have much fun with asynchronous functions because they have nowhere to run. Therefore, you can use asynchronous functions in Blazor, but you can't wait for them synchronously (which might be just fine - at least you find out where your libraries have issues with async calls) because you simply don't have the two threads available where one could wait while the other is running. Consequently, all synchronous waits for async methods cause a deadlock.
In the server-side Blazor, the situation is a bit different. There, of course, you have no problem with the thread pool, but something else is returning from the times of ASP .NET - synchronization context. Blazor needs it to guarantee working DOM synchronization, and it has the same limitations as in ASP .NET - one thread at a time. Therefore, if you are performing an operation in a thread with this synchronization context, you cannot wait for another that needs it. I mean you can, but it will deadlock.
Conclusion
We quickly ran through the introduction to Blazor and its basic principles. I strongly encourage you to give it a try because it is undoubtedly an interesting technology. What is most interesting about it, however, is its impact on application development. In the following articles, we will talk about the impact of writing C# front end on the overall application design and what to focus on, including a quick reference to the other features of Blazor and its future.