Blazor Client-Side Assembly Servicing
By: Shaun Walker
Blazor applications utilize a client/server architecture. This means that in the majority of Blazor hosting models, a client application is deployed to a specific client device or process which interacts with the server using standard HTTP requests. One of the biggest challenges in most client/server models is how to ensure the client stays updated with the latest fixes and improvements. Obviously the more automated and seamless this process can be from an end user perspective, the more successful it will be in facilitating a regular update cadence.
Oqtane is a modular application framework. Essentially, this means that all services and user interface components are developed and deployed independently from one another and are dynamically assembled at run-time to create a functional application. In order to provide this highly dynamic composite capability, the framework relies on advanced assembly loading techniques which were outlined in detail in an earlier article. Assembly loading is essential for both the server and client workloads, however the client has some unique requirements based on the aforementioned client application update challenge.
With the introduction of Blazor Hybrid, client applications can now run on web, mobile, and desktop. This is a lot of environments to manage - each with their own distinct set of capabilities. In order to satisfy the need for performance as well as serviceability, Oqtane has taken a unique approach in this area - explained in the diagram below.
When running on Blazor WebAssembly, the various module assemblies which will be utilized by the Oqtane client application at runtime need to be loaded. In order to accomplish this, logic is executed during client application startup which calls a server API to determine the list of client assemblies which should exist on the client. The list of assemblies utilizes a very specific naming convention containing a hash code of the assembly file name (for security purposes) as well as the Last Write Date of the assembly file on the server. The app then compares this with the list of assemblies which are stored locally in the browser's IndexedDB storage. The algorithm identifies any differences between the lists using the hash code and also compares the Last Write Date to determine if a newer version exists on the server. If any differences are identified it sends the list of differences to the server so that the actual assemblies can be downloaded as a compressed archive, unzipped on the client, stored in IndexedDB, and loaded into the browser's app domain.
When running on Blazor Hybrid, the process works in exactly the same manner, however instead of storing the assembly files in IndexedDB it stores them on the file system of the device using the FileSystem.Current.AppDataDirectory abstraction provided by .NET MAUI. And instead of loading the assemblies into the browsers app domain, it loads them into the device's app domain.
This approach is very efficient as it stores the assemblies locally in the client and only downloads them when they need to be updated.