Building a Software Survey using Blazor - Part 4 - Render Mode
Tags: blazor, csharp
Published: 2020-10-05 17:27:47

I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.

As I learn with Blazor I will blog about it as a series of articles.

This series of articles is not intended to be a training course for Blazor - rather my thought process as I go through learning to use the product.

Earlier articles in this series:

* [Part 1 - State](https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state)
* [Part 2 - Better State](https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state)
* [Part 3 - Components](https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components)

---

Your Blazor server side app will be initiated something like this in _Hosts.cshtml:

### _Hosts.cshtml
```
        <app>
            <component type="typeof(App)" render-mode="ServerPrerendered" />
        </app>
```

I believe by default, the `render-mode` will be set to `ServerPrerendered` - and I think most examples will show `render-mode` with that option - and I have to admit I didn't really pay much attention to it until I struggled with my automated end-to-end tests.

## My problem
I had built an end-to-end happy path test using Selenium and orchestrated it to run every hour using Azure DevOps.  The test was fairly simple; it ran through every page of the survey with defined responses - then checked against CosmosDB to ensure that the survery results had been recorded.

I could run the test locally and it worked perfectly everytime.

And it would *mostly* work correctly when being run on the hourly schedule - but it wasn't consistent.

Occassionally I'd get failures.  When I investigated it looked as if my Selenuim test simple wasn't pressing the "Next" button on the home page:

![Home Page](https://dev-to-uploads.s3.amazonaws.com/i/8osfqosx8ed8ufjq9gfh.png)

I experimented with various techniques to track down the problem - but eventually I discovered the cause.

It was the `render-mode`.

## ServerPrerendered
What I hadn't taken the time to fully appeciate was how the `ServerPrerendered` worked.

From the [Microsoft Docs page](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.rendering.rendermode?view=aspnetcore-3.1):

> "Renders the component into static HTML and includes a marker for a Blazor server-side application. When the user-agent starts, it uses this marker to bootstrap a blazor application."

The important part here is the "Renders the component into static HTML".

On first request, the `ServerPrerendered` will return the the browser a static HTML representation to the page requested (with all its components).  The browser will display this HTML - then run the Blazor Javascript to establish a SignalR connection.  Once the SignalR connection is established THEN the Blazor page is re-rendered - this time with all the Blazor goodness.

What you are effectively getting it a two stage render to the browser:

1. Cosmetic render of the component - all the HTML - but none of the functionality
2. Functional render of the components - replaces the cosmetic only HTML with all the intended functionality

This is a techniques called hydration that has been popular in Javascript Single-Page-Apps (SPAs) for a while.  The intention is to provide the user with valuable content (the HTML) while the app is downloading, initializing and starting in the background.  Once ready the app would "take over" - replacing the HTML on screen.

Done right it would look to the user that the app is ready much quicker that it actually is (plus the technique provides a considerable benefit for SEO - the clawler doesn't need to execute the Javascript to understand the content).

## Back to my problem
So my end-to-end tests where failing due to recieving the cosmetic-only HTML and attempting to use it BEFORE the app had rehydrated and became functional.

The test would open the website - get the HTML for the first page and click on the Next button.

But this was BEFORE the app had rehydrated - thus there was no click handler behind the Next button.  Thus clicking on it did nothing.

Thus when the end-to-end test asserted that the next page had been loaded, it failed because of course none of the logic had been run.

## The fix
I could have changed the `render-mode` (see below) but I chose to implement a loading spinner while in that pre-rehydrated state;

![Prerender Loading Page](https://dev-to-uploads.s3.amazonaws.com/i/fzaxe5q11cb66avs0vcw.png)

I felt this was best both for the user and for Selenium.  The user wouldn't become frustrated by a button that did nothing and Selenium would wait until the Next button appeared.

For this I created the `PreRenderLoadingMessage` component:

### PreRenderLoadingMessage.razor
```
@if (HasRendered)
{
    @ChildContent
}
else
{
    <LoadingSpinner Title="Loading ..." />
}


@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool HasRendered { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            HasRendered = true;
            StateHasChanged();
        }
    }
}
```

Then I wrapped the entire app within it:

### App.razor
```
<PreRenderLoadingMessage>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
                @{ Is404 = true; }
            </LayoutView>
        </NotFound>
    </Router>
</PreRenderLoadingMessage>
```

Thus the first render (the prerender) shows the `LoadingSpinner`, then subsequent (hydrated) renders would show the `@ChildContent`.

This technique was taken from [this Microsoft Docs page](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-3.1#detect-when-the-app-is-prerendering).

This approach solved my problem nicely.  The end-to-end tests worked like a charm once I implemented it.

## Other modes
So there are two other modes available to `render-mode` - `Server` and `Static`.

`Static` will just render static HTML - it doesn't provide any of the functionality.  I've not seen a good reason to use this mode - although I do wonder if it could be used with a static site generator - something like GatsbyJs.  I suspect its there for future use.

`Server` will operate the same as `ServerPrerendered` but without the static HTML version on the first page request.  Rather it leaves the `app` content empty until the SignalR connection is established and Blazor is running fully.

The problem with `Server` mode was that it can take a few seconds to establish that SignalR connection and to get everything "started".  I didn't want to provide an empty page to the user.  I'd end up adding some form of loading message in the _host.cshtml - only to then have to remove once the Bazlor app was ready.

Thus it made much more sense to use the functionality baked into Blazor with the `ServerPrerendered` - and I think that the `PreRenderLoadingMessage` is a good way to handle that.