As I said, Blazor is experimental, but hopefully it will become an official part of the ASP.NET offer in due course. It relies on a technology called Web Assembly which is available in all modern browsers. You don't actually need to know anything about Web Assembly to use Blazor.
I have been watching Blazor since it was announced that it was being adopted by the ASP.NET team and have been playing with samples on and off since then. I have dabbled a tiny bit with Angular and React, and just like React, Blazor is based on components being the building blocks of UI. Unlike React, you don't use JSX or strings to build your components - you use Razor, just like in MVC or Razor Pages. What could possibly be simpler?
To demonstrate how this works currently - it's changing all the time - I thought I would have a go at implementing that old favourite among UI patterns - Cascading Dropdowns. If you want to play along, you should follow the setup instructions here to get Blazor working in your environment. Once you have done that, you should choose the Blazor (ASP.NET Core hosted) option which will generate a solution consisting of 3 projects: a <solution_name>.Server project that includes a Web API controller; a <solution_name>.Client project that holds the Blazor application and component files; and a <solution_name>.Shared project that holds class files for entities used in both of the other projects.
The Data
This example uses Authors and Books to populate dropdowns. The user will initially be presented with a list of authors, and when one is selected, the dependent dropdown will be populated with a list of books filtered by the selected author. So two classes are required to represent these entities. They are added to the Shared project:
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public ICollection<Book> Books { get; set; } } public class Book { public int BookId { get; set; } public string Title { get; set; } public Author Author { get; set; } }
Next we need a way of generating and exposing suitable data. This is best achieved by adding a Web API controller named BookController to the Controllers folder in the Server project with the following code:
using Blazor.Shared; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; namespace Blazor.Server.Controllers { [Produces("application/json")] [Route("api/book")] public class BookController : Controller { private static readonly List<Author> authors = new List<Author>{ new Author{ AuthorId = 1, Name = "Tom Clancy", Books = new List<Book> { new Book{BookId = 1, Title = "Sum of all Fears"}, new Book{BookId = 2, Title = "Rainbow Six"}, new Book{BookId = 3, Title = "Hunt for Red October"} } }, new Author{ AuthorId = 2, Name = "Stephen King", Books = new List<Book> { new Book{BookId = 4, Title = "Carrie"}, new Book{BookId = 5, Title = "The Stand"}, new Book{BookId = 6, Title = "The Black House"}, new Book{BookId = 7, Title = "It"} } }, new Author{ AuthorId = 3, Name = "Robert Ludlum", Books = new List<Book> { new Book{BookId = 8, Title = "The Bourne Ultimatum"}, new Book{BookId = 9, Title = "The Holcroft Covenant"}, new Book{BookId = 10, Title = "The Rhineman Exchange"} } } }; [HttpGet] public IEnumerable<Author> Get() { return authors; } } }
The data is hardcoded but it could just as easily come from a database.
The Blazor Component
The UI for displaying the dropdowns will be implemented as a Blazor component. This will be created in the Client project, which looks very similar in structure to a Razor Pages application:
Components are created as Razor files and added to the Pages folder. Tooling is still being worked on, so the workaround for adding a component at the moment is to add a Razor View. I named mine Books.cshtml. I'll show the finished code for the component first and then explain it in sections:
@using Blazor.Shared @page "/books" @inject HttpClient http <h1>Books</h1> <p>This component demonstrates fetching data from the server.</p> @if (authors == null) { <p><em>Loading...</em></p> } else { <select id="authors" onchange="@AuthorSelectionChanged"> <option></option> @foreach (var author in authors) { <option value="@author.AuthorId">@author.Name</option> } </select> } @if (books != null) { <select id="books"> <option></option> @foreach (var book in books) { <option value="@book.BookId">@book.Title</option> } </select> } @functions { Author[] authors; Book[] books; protected override async Task OnInitAsync() { authors = await http.GetJsonAsync<Author[]>("/api/book"); } void AuthorSelectionChanged(UIChangeEventArgs e) { if (int.TryParse(e.Value.ToString(), out int id)) { books = authors.First(a => a.AuthorId == id).Books.ToArray(); } else { books = null; } } }
Before explaining the component, it needs to be wired up to the example application. This is achieved by adding another link to the NavMenu component in the Shared folder in the Client project:
<li class="nav-item px-3"> <NavLink class="nav-link" href="/books"> <span class="oi oi-book" aria-hidden="true"></span> Books </NavLink> </li>
At the top of the component, a using
directive is added to bring the Shared
project into scope so that the entities declared in it can be referenced. Next,
a route is defined using an @page
directive. The syntax for this is very similar to
Razor Pages.
However, the @page
directive is required for a Razor Page, whereas it is only
needed in a Blazor Component if the component is to take part in routing. The @inject
directive is then used to provide an instance of HttpClient
from the
dependency injection container which will be used to call the Web API controller.
The middle section is largely plain HTML with a bit of C# embedded using Razor
syntax. If you are familiar with Razor, there is nothing daunting here at all.
If the authors collection is not null, the first dropdown is displayed,
populated with data. If the books collection is not null, the second dropdown is
displayed, populated with the books. The only new Blazor thing in this section
is the onchange
event handler in the first select element.
This is a Blazor event hander, not a standard Javascript event attribute. The
method that it points to, AuthorSelectionChanged
,
is declared in the @functions
block which is examined next.
The @functions
block is a block of C# code. It is where private fields and
methods are placed. It works in the same way as in Razor Pages and ASP.NET Web
Pages in the past. In this example, two private fields are declared - an array
of Authors and an array of Books. The OnInitAsync()
method (and it's synchronous
counterpart OnInit()
method) are provided by the BlazorComponent
class, which this component derives from. They can be overridden in derived
classes for processing that takes place after the component has been initialised
- a bit like the old OnInit
event handler in Web Forms or the
document.ready
function in jQuery. In this example,
an asynchronous call is made to the Web API controller using the injected
HttpClient
instance to obtain the collection of authors. The
resulting reponse is assigned to the authors
field, which results in the
dropdown of authors being populated and made visible:
Then the event handler for the onclick
event on the authors dropdown is
defined. The method accepts a UIChangeEventArgs
object as a parameter, which
contains information about the change event that was fired. Specifically, it
includes a Value
property, which holds the value of the selected option.
This is parsed into an int
and then used to filter the books according to the
selected author. The filtered books are assigned to the books dropdown which
is then displayed:
So what does this look like in the browser? Here's a peek at the Network tab in Chrome:
There are actual .NET dll files there. They are used by a special version of the NET runtime being developed by the Mono team to work with Web Assembly in the browser. The total download size in this example is 1.8Mb, but the team working on Blazor will be looking to optimise this kind of thing before it ever gets to a stage where the framework can be used in production.
Summary
Blazor is extremely promising. The team stress that it is an unsupported experimental web framework and that it should not be relied on for production use. There is a lot of work to be done but they seem to be committed to doing it, certainly for the time being. Personally, I'd love to see Blazor released.