UI Flashes after Migrating Blazor Projects To .NET 6

The Problem

I’ve noticed an issue recently after upgrading a few Blazor projects from .NET 5 to .NET 6 where the UI flashes after every call to StateHasChanged. Something like this:

A table with the title of "Vegetables". There are three columns (Id, Product and Quantity) and 3 rows of example data. The Id field disappears and reappears once a second causing the table to visibily shift back and forth
Flash. Flash. Flash. Flash…

Yeah, that’s not annoying at all. Unfortunately it was happening in quite a few different places and was pretty darn noticeable, so it looked like I was going to have to dive in and figure out what exactly was causing this to go wrong (despite working perfectly in .NET 5)!

The Cause

TL;DR: If you have an AuthorizeView that evaluates a policy that takes at least ~1ms to complete asynchronously, you’ll see this issue. If you want to get straight to potential fixes, feel free to jump to The Solution*!

While this took me a fair while to boil down into a Minimal, Reproducible Example, I managed it! I’ve even uploaded the resulting solution to GitHub for anyone who wanted to reproduce this themselves. The following examples are sourced from that repository!

So what’s going wrong here?

I haven’t read the source code so this is speculation based on behaviour, but it would seem that in .NET 5 the result of a policy evaluation is cached in some way after the first execution. This means that while there is an initial flash on page load, the policy continues to be considered successful until the result returns a different result. Here’s an example of a few different policies being displayed in .NET 5 while a timer loop runs StateHasChanged every second (bad practice in real life, but useful for reproducing the error visually):

A table with the headings "Policy" and "Authorized Text". There are four rows with policies of "None", "Instant", "Thread.Sleep(1)" and "Task.Delay(1)" respectively. All four rows show Authorized Text of "Example Text" that is solid and unchanging. A mouse cursor appears and circles the "Example Text" next to "Task.Delay(1)"
No flashing, all is right with the world

In .NET 6 however the policy is considered indeterminate every time it begins evaluating, meaning that the <Authorizing> block in <AuthorizeView> is rendered until a result is returned (I’ve been using empty <Authorizing> blocks, but including them would only mean that the text flashes between two different states rather than disappearing and reappearing). Here’s the same example as above running the same code with the same StateHasChanged loop, but running .NET 6:

A screen recording of a table with the headings "Policy" and "Authorized Text". There are four rows: Three rows (with policies "None", "Instant" and "Thread.Sleep(1)") show Authorized Text of "Example Text" that is solid and unchanging. The fourth row however has a Policy of "Task.Delay(1)" and the Authorized Text is "Example Text" but is visually flashing once every second

Since the effect can be hard to view on certain devices, here’s a view of the Firefox DOM inspector showing the block re-rendering every second:

The documentation states that “Blazor Server apps know the authentication state as soon as the state is established” so the <Authorizing> content is “never displayed”, but this doesn’t seem to take into account asynchronous policies where authentication may be based on a manual query to a database or API endpoint.

The Solution*

Uh-oh, an asterisk.

Although I have some approaches that you can take to hopefully rectify the problem, when I raised this as an issue on the ASP.NET Core GitHub project I was told that it is “expected and by design“. Although I’m personally of the opinion that it feels like a regression since it breaks existing behaviour and the issue is more to do with result caching than asynchronous operations, in the end it’s not my project and I’m not the one putting in the effort to maintain/develop it! As such we’ll need to figure out how to work around this design decision.

The below approaches may help fix/mitigate the issue, but the one (or combination) you choose to implement is highly specific to your project, how you’re using the affected sections and what requirements you have for how you evaluate your policies. For a quick overview of when I’d choose each option, here’s a handy-dandy table summarising the options:

SituationApproach
Not really affected by the issueDo Nothing
Require .NET 6, willing and able to implement a “proper” solutionCaching
AuthorizeView critical or recently migrated to .NET 6Use .NET 5
AuthorizeView only used in a couple of nice-to-have scenariosAvoid AuthorizeView
Require .NET 6, caching infeasible and AuthorizeView criticalUse a Synchronous Policy

Do Nothing

How’s that for a solution?

In all seriousness, how badly this affects you largely hinges on how often you’re refreshing page data and/or calling StateHasChanged. If your pages are pretty much static once they’ve been loaded or very rarely refresh you might not even notice any UI flashing or may consider it not much of a problem. If that’s the case you probably don’t need to change anything! Keep an eye out for this issue in the future if you expect to implement dynamic pages that use AuthorizeView in the future though!

Caching

Caching in this circumstance is what I would refer to as a “proper” solution since it resolves the core of the issue while allowing you to still use AuthorizeView and .NET 6. The issue with this approach however is that not only is it highly dependent on the circumstances of your project/whether caching is even feasible for your application, it can be a lot of extra work to implement and may lead to bugs later if not designed/implemented correctly.

Since caching is highly dependent on the specifics of your project I can’t go into details on exactly how to implement it, but the premise is that instead of performing an asynchronous request during every policy evaluation you store the result after the first run or after pre-emptively performing the query (such as on application load/on a timer). This result can then be stored in memory and loaded using Dependency Injection (for example with a custom Cache singleton) that can be queried directly, greatly reducing the time taken and meaning that the policy no longer needs to perform an asynchronous query.

As I mentioned earlier, I believe (based on speculation) that the implementation in .NET 5 essentially uses a caching layer for your policy results to avoid the error in the first place, but in .NET 6 you’ll need to manage this yourself.

Use .NET 5

If this issue is affecting your application in quite an integral way or you’ve only just migrated to .NET 6, rolling your project back to .NET 5 is a quick way of avoiding the issue with hopefully minimal code changes (sorry if you’ve already gotten used to all of the .NET 6 niceties)!

The issue is that it’s not really a long-term solution; .NET 5 is not an LTS (long term support) release while .NET 6 is and future versions of the framework may very well continue to behave in this manner. That being said, if you’re working on a project with tight deadlines you may find that this is the fastest fix to get you back to a stable state while you figure out an approach going forward.

Avoid AuthorizeView

Since the issue is to do with how AuthorizeView renders while using an asynchronous policy, one potential approach is to stop using AuthorizeView! This depends on how you’re currently using it; if it’s just for nice-to-have features (as in the “Vegetables” example at the top of this post that is showing IDs for administrators) you can look to remove the blocks entirely, but even if you rely on the functionality you may be able to rework your pages/components so that you have different copies that are loaded based on authorization state (although this may lead to code duplication).

Use a Synchronous Policy

*Shudder*

This is an approach that I really don’t like because rewriting asynchronous functions to only run synchronously will almost certainly make your system very slow/unresponsive. This approach should only be used as an absolute last-ditch effort and should be tested thoroughly to see if it is even feasible. Here be dragons!

Leave a comment

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