Blazor – on the server or on the client

With ASP.NET Core 3.0, the first version of Blazor will be released. Client-side Blazor allows writing .NET Code that runs in the browser – using WebAssembly. Server-side Blazor allows writing the same .NET code, but it runs in the server. You still don’t need to write JavaScript code for the client as communication between the client and the server is done with SignalR behind the scenes. Server-side Blazor is part of ASP.NET Core 3.0 and released in September-2019. Client-side Blazor is not released with ASP.NET Core 3.0, but in a future version of .NET. Using Blazor today you should know about the limitations of server-side Blazor, and how to write your Blazor components to use it both with server-side and client-side Blazor. This is what you’ll learn from this article.

Woman in blazer

Blazor

Blazor is based on the syntax of ASP.NET Core Razor Pages. Blazor allows to create components using C# code that can be referenced from other components or with links defined using Razor syntax.

Blazor = Browser + Razor

The first Blazor preview allowed writing .NET Code running in a standardized way in the browser – all modern browsers. Behind the scenes, WebAssembly is used.

WebAssembly (WASM) defines a binary format to run code in the browser. This is possible in all modern browsers. caniuse.com lists Microsoft Edge, Firefox, Chrome, Safari, and iOS Safari, and shows it in development with the Opera browser developer.microsoft.com.

WebAssembly is defined by a W3C specification. Currently, the spec is already a candidate recommendation dated 18-Jul-2019. It’s very new, and there’s a lot of progress going on.

A restriction we have with the first version is to interact with the DOM tree of the HTML elements, JavaScript is needed. Using binary WASM code it’s not possible to directly interact with the HTML elements. Using Blazor, you don’t need to write JavaScript code – this is done behind the scenes. However, this costs performance. On pages with many HTML elements and a lot of integration going on, this can be too slow.

Server-side Blazor part of .NET Core 3.0 allows writing the same code you would create for the client to run on the server.

Server-Side Blazor

Writing client components to run on the server is a concept we’ve done with ASP.NET using WebForms. Nowadays still many applications built with WebForms are out there. Now we have a new concept with some similarities: .NET components running on the server. As WebForms is not supported with .NET Core, the best way to move existing Web Forms applications can be server-side Blazor.

Using WebForms, view-state was send from the client to the server to allow firing events in server side code for button clicks or selection changes. With server-side Blazor, behind the scenes SignalR is used as a communication mechanism between the client and the server-side components. A connection from the client to the server is kept open to allow fast communication. This already gives some restrictions using this technology:

  • The server needs more resources – a connection stays open with every connected client.
  • Having offline clients is not possible with server-side Blazor. The code runs on the server, so an important part is missing to have this with offline functionality.

Before using this technology with your application, you should check on the expected number of clients, the resources needed with the server, the bandwidth available with all clients… Applications using Web Forms today typically don’t have these constraints because then a different technology would already be in use instead of WebForms. One example where server-side Blazor can be a fully valid scenario: a company-internal application with a limited number of users (could be several thousands) and developers knowledgeable of .NET but don’t like JavaScript.

Starting with Server-Side Blazor

Creating a server-side Blazor project, you can select the Visual Studio template Blazor App, and select the option Blazor Server App. This creates an application with a Counter component where a counter is incremented server-side when the user clicks a button client-side, and a component showing actual weather information.

Running the application and checking the network communication you can see that a WebSocket connection is initiated and communication between the client-side part and the server-side .NET Code is done using WebSockets. WebSockets is used behind the scenes with SignalR – if WebSockets is available. Otherwise SignalR can also switch to other ways for communication.

Server-side Blazor with WebSockets

Adding EF Core

As the components are now running server-side it’s an easy way to add information from a database. In the first step of the custom code in the example, I’m creating a .NET Standard library with a model and an EF Core context, and inject this context in a Blazor component.

This library just consists from a POCO Book class with a few properties:

public class Book
{
  public int BookId { get; set; }
  public string Title { get; set; } = string.Empty;
  public string? Publisher { get; set; }
}

The BooksContext class is defined with the ASP.NET Core project to be used with dependency injection. This class makes use of EF Core seeding to define some initial data on creating the database programmatically.

public class BooksContext : DbContext
{
  public BooksContext(DbContextOptions<BooksContext> options)
    : base(options)
  {
  }

  public DbSet<Book> Books { get; set; } = default!;

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.Entity<Book>().Property(b => b.Title).IsRequired().HasMaxLength(50);
    modelBuilder.Entity<Book>().Property(b => b.Publisher).IsRequired(false).HasMaxLength(30);

    modelBuilder.Entity<Book>().HasData(
      new Book { BookId = 1, Title = "Professional C# 6", Publisher = "Wrox Press" },
      new Book { BookId = 2, Title = "Professional C# 7", Publisher = "Wrox Press" },
      new Book { BookId = 3, Title = "Professional C# 8", Publisher = "Wrox Press" });
  }
}

With the configuration of the ASP.NET Core middleware, the database is created if it doesn’t exist yet.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  using IServiceScope scope = app.ApplicationServices.CreateScope();
  var context = scope.ServiceProvider.GetService<BooksContext>();
  context.Database.EnsureCreated();
  //...
}

The BooksContext class now needs to be added to the DI container. This is not different to other ASP.NET Core web applications. With the ConfigureServices method of the Startup class you can also see the invocation of the method AddServerSideBlazor. This method is implemented in the library Microsoft.AspnetCore.Components.Server and registers the interfaces needed for server-side Blazor.

public void ConfigureServices(IServiceCollection services)
{
  services.AddRazorPages();
  services.AddServerSideBlazor();
  services.AddSingleton<WeatherForecastService>();
  services.AddDbContext<BooksContext>(options =>
  {
    options.UseSqlServer(Configuration.GetConnectionString("BooksConnection"));
  });
}

With this in place, a component can be created to inject and use the BooksContext class. Blazor components have the razor file extension and are built using Razor syntax. The page directive defines the link that can be used to access the component. With inject, the BooksContext is injected on creation of the component, and a property BooksContext is created to access this injected object. In the code block you can see an override of the method OnInitAsync. This method is called on initialization of the component. Here, the database is accessed to return a list of all books and assigns this to the books variable. In turn, this books variable is used with the foreach statement to display all ids, titles, and publishers:

@page "/bookslist1"
@using BooksLib
@using ServerSideBlazor.Services;
@using Microsoft.EntityFrameworkCore;
@inject BooksContext BooksContext

<h1>Books</h1>

<p>This component demonstrates using the EF Core context.</p>

@if (books == null)
{
  <p><em>Loading...</em></p>
}
else
{
  <table class="table">
    <thead>
      <tr>
        <th>Id</th>
        <th>Title</th>
        <th>Publisher</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var book in books)
      {
        <tr>
          <td>@book.BookId</td>
          <td>@book.Title</td>
          <td>@book.Publisher</td>
        </tr>
      }
    </tbody>
  </table>
}

@code {
  Book[]? books;

  protected override async Task OnInitAsync()
  {
    books = await BooksContext.Books.ToArrayAsync();
  }
}

This new component can now be referenced using the NavLink component passing the link similar to the other components listed:

<li class="nav-item px-3">
  <NavLink class="nav-link" href="bookslist1">
    <span class="oi oi-book" aria-hidden="true"></span> Show books
  </NavLink>
</li>

Running the application, the books show up clicking the link:

Show books

Instead of using the link, the component can also directly be used within another component. Here, BooksList1 is used in the Index component in the file Index.razor:

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<BooksList1 />

Adding a local service

The books list component has an issue. Changing the application to client-side Blazor instead of server-side Blazor, the information from the books should come from the server. Injecting the EF Core context to the client component, you can’t access the database on the server. Client-side Blazor runs in the sandbox offered by the browser, and thus has the same limits as JavaScript code running in the browser.

Accessing databases in the client is possible using IndexedDB. The community is already working on an EF Core provider for client-side Blazor making use of IndexedDB. See the links on the bottom of this page for more information.

To resolve this, and allow using the page both with server-side and with client-side Blazor, an abstraction layer can be added. All the page needs is a contract defined by the interface IBooksService:

public interface IBooksService
{
  Task<IEnumerable<Book>> GetBooksAsync();
}

The implementation for server-side Blazor, the class BooksDataService implements this interface and injects the BooksContext:

public class BooksDataService : IBooksService
{
  private readonly BooksContext _booksContext;
  public BooksDataService(BooksContext booksContext)
  {
    _booksContext = booksContext;
  }

  public async Task<IEnumerable<Book>> GetBooksAsync()
  {
    return await _booksContext.Books.ToListAsync();
  }
}

With the Startup class, the contract IBooksService needs to be mapped to the implementation BooksDataService:

public void ConfigureServices(IServiceCollection services)
{
  services.AddRazorPages();
  services.AddServerSideBlazor();
  services.AddSingleton<WeatherForecastService>();
  services.AddScoped<IBooksService, BooksDataService>();
  services.AddDbContext<BooksContext>(options =>
  {
    options.UseSqlServer(Configuration.GetConnectionString("BooksConnection"));
  });
}

The Blazor component can now be changed to inject IBooksService instead of the EF Core context:

@page "/bookslist2"
@using BooksLib
@inject IBooksService BooksService

<h1>Books</h1>

<p>This component demonstrates using the same component for client-side and server-side Blazor</p>

@if (books == null)
{
  <p><em>Loading...</em></p>
}
else
{
  <table class="table">
    <thead>
      <tr>
        <th>Id</th>
        <th>Title</th>
        <th>Publisher</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var book in books)
      {
        <tr>
          <td>@book.BookId</td>
          <td>@book.Title</td>
          <td>@book.Publisher</td>
        </tr>
      }
    </tbody>
  </table>
}

@code {
  Book[]? books;

  protected override async Task OnInitAsync()
  {
    books = (await BooksService.GetBooksAsync()).ToArray();
  }
}

With this in place, the same Blazor component can be used with client-side and server-side Blazor.

Moving to Client-Side Blazor

Client-side Blazor will not be released with .NET Core 3. However, it’s already in the state of being an official product from Microsoft, and let’s hope for a release with the next major version of .NET Core – .NET 5.0 in November-2020.

The sample project is created using the Blazor App template, selecting Blazor WebAssembly App with the option ASP.NET Core hosted selected. ASP.NET Core hosted offers services built with ASP.NET Core, and this will be done to return the books from the server.

Instead of hosting client-side Blazor with ASP.NET Core, client-side Blazor can also be hosted with Azure Storage – and using APIs implemented with Azure Functions. This is shown in the article Hosting Blazor on Azure Storage with Azure Functions Backend.

Creating a hosted Blazor WebAssembly App project creates three projects. One project makes use of a .NET Standard library – this library will be used both with the client- and the server-part of the application. The project with the postfix .Client contains the code for the WebAssembly. Here you’ll have the Blazor components. The BooksList component looks the same as before with the server-side Blazor project:

@page "/bookslist"
@using BooksLib
@inject IBooksService BooksService

<h1>Books</h1>

<p>This component demonstrates using the same component for client-side and server-side Blazor</p>

@if (books == null)
{
  <p><em>Loading...</em></p>
}
else
{
  <table class="table">
    <thead>
      <tr>
        <th>Id</th>
        <th>Title</th>
        <th>Publisher</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var book in books)
      {
        <tr>
          <td>@book.BookId</td>
          <td>@book.Title</td>
          <td>@book.Publisher</td>
        </tr>
      }
    </tbody>
  </table>
}

@code {
  Book[]? books;

  protected override async Task OnInitializedAsync()
  {
    books = (await BooksService.GetBooksAsync()).ToArray();
  }
}

Because the database running on the server cannot be directly accessed, the implementation of the interface IBooksService now need to look different – accessing an API service running on the server. The interface is implemented in the class BooksApiClient. Here, the HttpClient class is injected. From a Blazor WebAssembly app you cannot create a new instance of the HttpClient class and use this to access the server. The code running in the WebAssembly runs in a sandbox in the browser – and is restricted in what the browser allows it to do. JavaScript clients can only make use of the XMLHttpRequest object or the newer fetch API to make calls to a server. With Blazor, a HttpClient is registered in the dependency injection container that makes use of a handler making calls using XMLHttpRequest. Using this injected HttpClient, you can use it similar to other .NET applications. In the sample code, the extension method GetJsonAsync is used. This extension method is defined in the assembly Microsoft.AspnetCore.Blazor.HttpClient which in turn makes use of the class JsonSerializer to deserialize the received JSON string.

public class BooksApiClient : IBooksService
{
  private readonly HttpClient _httpClient;
  public BooksApiClient(HttpClient httpClient)
  {
    _httpClient = httpClient;
  }

  public async Task<IEnumerable<Book>> GetBooksAsync()
  {
    return await _httpClient.GetJsonAsync<IEnumerable<Book>>("/api/Books");
  }
}

With the Main method of the client-side Blazor app, the BlazorWebAssemblyHost is used to define the initial interfaces and implementation in the DI container. The HttpClient is registered with the default builder.

public class Program
{
  public static void Main(string[] args)
  {
    CreateHostBuilder(args).Build().Run();
  }

  public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
    BlazorWebAssemblyHost.CreateDefaultBuilder()
      .UseBlazorStartup<Startup>();
}

The Startup class referenced from the UseBlazorStartup method looks similar to an ASP.NET Core server-side Startup class. In the ConfigureServices method, the local services can be registered in the dependency injection container. Similar to server-side code, Microsoft.Extensions.DependencyInjection is used. In the sample code, the implementation class BooksApiClient is specified for the contract IBooksService:

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddTransient<IBooksService, BooksApiClient>();
  }

  public void Configure(IComponentsApplicationBuilder app)
  {
    app.AddComponent<App>("app");
  }
}

Host Application for Client-Side Blazor

With the host of the client-side Blazor app, with the ASP.NET Core 3 Preview 8 version of the template, the old WebHost class is used. In the future this might change to the new Host class.

public class Program
{
  public static void Main(string[] args)
  {
    BuildWebHost(args).Run();
  }

  public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
      .UseConfiguration(new ConfigurationBuilder()
        .AddCommandLine(args)
        .Build())
      .UseStartup<Startup>()
      .Build();
  }
}

With the Startup class, the BooksContext is registered in the DI container in the same way it was registered with server-side Blazor for injection. With middleware configuration you can see some specifics for Blazor: in development mode, Blazor debugging is enabled, and the method UseClientSideBlazorFiles defines the Startup class from the client project to find the Blazor files.

public class Startup
{
  public Startup(IConfiguration configuration)
  {
    Configuration = configuration;
  }

  public IConfiguration Configuration { get; }

  public void ConfigureServices(IServiceCollection services)
  {
    services.AddMvc().AddNewtonsoftJson();
    services.AddResponseCompression(opts =>
    {
      opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/octet-stream" });
      });

    services.AddDbContext<BooksContext>(options =>
    {
      options.UseSqlServer(Configuration.GetConnectionString("BooksConnection"));
    });
  }

  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
    app.UseResponseCompression();

    if (env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
      app.UseBlazorDebugging();
    }

    app.UseStaticFiles();
    app.UseClientSideBlazorFiles<Client.Startup>();

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
      endpoints.MapDefaultControllerRoute();
      endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
    });
  }
}

The one thing that’s left is the API controller – but here’s nothing Blazor specific. You could also implement the API using Azure Functions as shown in another blog article (see below links list). With the controller, the EF Core context is injected:

[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
  private readonly BooksContext _booksContext;
  public BooksController(BooksContext booksContext)
  {
    _booksContext = booksContext;
  }

  public async Task<IEnumerable<Book>> GetBooks()
  {
    var books = await _booksContext.Books.ToListAsync();
    return books;
  }
}

Running the application, mono.wasm is returned to the client – this is the Mono runtime implemented as WebAssembly. The library of the application as well as libraries needed are returned to the client as well. .NET IL code runs in the Mono runtime.

Blazor with WebAssembly

Take away

With the release of ASP.NET Core 3.0, Blazor is ready to use – the server-side version of Blazor. You need to be aware on the limitations: server-side resources to keep connections opened for every client are needed, and you can’t create offline web applications (the code is running on the server). Switching later on to the WebAssembly client-side version of Blazor, the limits are very different. You can define your components now in a way for an easy move between server-side and client-side Blazor.

Enjoy coding and learning!

Christian

If you’ve read this far, consider buying me a coffee which helps me staying up longer and writing more articles.

Buy Me A Coffee

Extra Links

Complete code sample

Hosting Blazor on Azure Storage with Azure Functions Backend

Dependency injection

Hosting DI Container with .NET Core 3.0

IndexedDB in Blazor

Image: woman in blazer ID 142353886 © Vladimir Nikulin | Dreamstime.com

Advertisements

4 thoughts on “Blazor – on the server or on the client

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.