Blazor Gotchas

Blazor uses a different hosting model than traditional Asp.Net Core applications. Even though Asp.Net offers a wide variety of functionality to help you make your pages rich and dynamic, it still uses the traditional request/response model we all know and love. This means that when you navigate to a certain page, the Razor engine will essentially give back an html page back to the browser. Behind the scenes you may be generating this page dynamically, but the end result is static html as far as the browser is concerned. Blazor on the other hand is categorized as a SPA, a single page application. This means that when the user navigates via a button click or a hyperlink, the navigation is virtual and is done internally to the application. Blazor will simply swap in the UI elements of the page you are navigating to and hide the DOM elements of the old page. Many Javascript libraries like React also do this. But the SPA aspect is not what makes Blazor special. Instead, it's the hosting and runtime model. The developer must be aware of certain pitfalls and limitations, so as not to introduce unintended behavior. We will look at some of these below.

Razor and Blazor

For anyone who has previously worked with Razor pages, it should come as no surprise that Blazor is built upon Razor. Microsoft's prototype name of Blazor used to be Razor Components. But while the code you write in a Blazor page or component looks like Razor, it is not the same as a Razor page. It uses the Razor engine to render the DOM elements, but that is done behind the scenes.

But there is one Razor page inside all Blazor Server side apps, and that is _Host.cshtml. This file is mapped in Startup.cs like this: endpoints.MapFallbackToPage("/_Host"); inside app.UseEndpoints. Looking at the documentation for this method, it says:

will match requests for non-file-names with the lowest possible priority

This means that any url that the routing engine was not able to match will be served by this fallback page. Looking inside this file we see that it is indeed a fully fleshed out Razor page, complete with html head, body, scripts, styles and so on. At the heart of it sits the Blazor app:

 <app>
        @(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
    </app>

HttpContext

Although the HttpContext object is technically available inside a Blazor page, it is not safe to use it, because Blazor code runs outside of the realm of the http request/response. Using it may lead to undefined behavior. However since _Host.cshtml is a true Razor page, it means we have access to the HttpContext there and can extract information from it and pass it down to the Blazor app by way of parameters. We discuss this more in dept this this blob article.

Entity Framework and DbContext

Entity Framework Core is a very popular way to access your SQL database. DbContext is the class that you use to make your database queries. This class is inherently not thread safe and special precaution must be taken when using inside a Blazor app. Therefore it is recommended that you instantiate a new DbContext every time you need to run a unit of work on your database. The class is by design very cheap to create and it uses connection pooling under the hood, so one need not worry about too many connections being open if you instantiate multiple contexts.

To address this problem, Microsoft introduced IDbContextFactory in .Net 5. Instead of injecting a DbContext into your Blazor page or component, you now inject an IDbContextFactory and then call its CreateDbContext method in any function where you may need to access the database. It is not recommended that you store these contexts as fields or properties, because you can easily end up in a situation where multiple threads are accessing the same context, which will lead to an exception.

To set up the factory, do the following in Startup.cs:

services.AddDbContextFactory<ApplicationDbContext>(options =>
{
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});

If you want to be able to inject a DbContext, do the following. But we recommend injecting the factory.

services.AddScoped<ApplicationDbContext>(p => 
    p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>()
    .CreateDbContext());

More information is available here.

Controllers

Just because you are running a Blazor application, does not mean that you can't have traditional controllers alongside it to serve up an api. To enable controllers, do the following. Inside your Startup.cs find the Configure method. Towards the end you will see a call to app.UseEndpoints. Depending on your hosting model (Server or WASM), there will already be certain calls inside the lambda. Simply add the following: endpoints.MapControllers();. Once controllers are mapped, the Asp.Net Core runtime will auto-discover any controller classes you have anywhere in your assembly, regardless of what namespace they are in. We cover this topic in more detail here.

Service Lifetimes

One thing Blazor developers have to be very careful with is dependency scoping. There are three service lifetimes: Scoped, Transient and Singleton. Transient is the easiest one to understand. When a service that has been configured to be transient is injected, the object received is always a new instance.

Singleton is fairly easy to understand as well, because as the name implies, whenever you inject a singleton service, the same instance is used over and over again across all clients/users. Be very careful with this one as it is truly global. Do not put any data in this services that could be specific to one particular user, lest you create a security hole.

Scoped is where it gets tricky. In the traditional Asp.Net world, the lifetime of scoped services is limited (scoped) to the request. Meaning that as soon as the http response has been sent, the service goes out of scope. This makes Scoped similar to Transient. However, in a Blazor Server app, Scoped means something very different. Because there is no notion of a traditional http request/response, Scoped services last the lifetime of the client connection. They go out of scope only when the Blazor SignalR connection is closed by way of the user closing the browser/tab or refreshing the page. Scoped services live on as the user navigates inside the Blazor app. Developers must be very careful with Scoped services, as they live much longer than one may expect.

Microsoft has more information on this topic here.

Table of Contents

  • Refit and Controllers

    We talk about how to create apis alongside your Blazor app and how to access those apis using Refit.

  • Blazor Gotchas

    This article explores various Blazor pitfalls that you should be aware of.

  • Blazor and HttpContext

    Here we discuss how to correctly use HttpContext in a Blazor app.

  • SignInManager and Blazor

    Blazor and SignInManager don't play well together. Let's see how to get around that.

  • One Year with Blazor

    We've switched over to using Blazor exclusively a year ago. Here are our thoughts.



An error has occurred. This application may no longer respond until reloaded. Reload 🗙