Blazor Web App Template, selecting 'None' for the Interactive Render mode.
To begin my understanding of Static Server-Side Rendering (SSR), as it is implemented in Blazor compared to that of MVC or Razor pages, I thought I might implement the missing Counter component, similar to the interactive versions of the template.
This code seems to work fine, without query string params, cookies or JS interop for using browser storage (all approaches I considered)...
@page "/counter"
<PageTitle>Counter - BlazorSSR</PageTitle>
<h1>Counter</h1>
<p>Current count: @CurrentCount</p>
<EditForm Enhance FormName="counterForm" Model="CurrentCount" OnSubmit="IncrementCount">
<InputNumber class="d-none" @bind-Value="CurrentCount" />
<button class="btn btn-primary" type="submit">Click me</button>
</EditForm>
@* Thanks to Ruikai Feng's answer, this is interchangeable with the above EditForm:
<form data-enhance method="post" @formname="counterForm" @onsubmit="IncrementCount">
<AntiforgeryToken />
<input type="number" @bind-value="@CurrentCount" name="CurrentCount" hidden />
<button class="btn btn-primary" type="submit">Click me</button>
</form>
*@
@code {
[SupplyParameterFromForm]
public int CurrentCount { get; set; }
private void IncrementCount()
{
CurrentCount += 1;
}
}
My questions:
Why does this work? It appears to me the IncrementCount() method is running per the OnSubmit, and the resulting form-submission is being supplied as a parameter back to the same page, successfully incrementing the CurrentCount. Is that correct?
Is this the correct and simplest approach for implementing the Counter page? Just because it works, doesn't mean it's correct.
For the InputNumber, I would have preferred a standard HTML input with type="hidden", but couldn't find the correct way to bind the CurrentCount. I realize I'm not using a traditional 'model' here, and I'm just using a native int. Seemed silly to create an entire model just for a single int.
Having the understanding of MVC, with POST-Redirect-Get, what's the workflow here? I'm not injecting the NavigationManager, so after the OnSubmit runs IncrementCount(), is it just reloading the same page, and somehow taking the CurrentCount from my form submission and feeding it back into itself?
So what's the pattern here? If I redirected to another page, can I pass the form submission data into the other page, or do I have to stay on the current page, do something with the data, and then redirect to another page?
Because this is SSR, I expect, in terms of DI, that Scoped and Transient mean the same thing. So had I injected a service to keep track of the CurrentCount, only a Singleton would work, at the expense of the Counter value being the same for everyone.
Update:
While Ruikai Feng's answer is helpful, I need to address my fundamental misunderstanding for the Blazor SSR form submission workflow, as with static server-side rendering, the workflow is quite different compared with a page having interactivity.
To that end, I created a page slightly more complex than the Counter page, albeit a simple example to demonstrate the workflow.
@page "/FormLifecycle"
<PageTitle>Form Lifecycle - BlazorSSR</PageTitle>
<div class="row">
<div class="col-12 col-lg-6 mx-auto">
<h1>Form Lifecycle Testing</h1>
<EditForm class="mb-2" Enhance Model="Person" FormName="personForm" OnValidSubmit="SubmitPerson">
<DataAnnotationsValidator />
<div class="input-group mb-3">
<span class="input-group-text" for="firstName">First Name</span>
<InputText id="firstName" class="form-control" @bind-Value="Person.FirstName" />
</div>
<ValidationMessage For="@(() => Person.FirstName)" class="mb-3 text-danger" style="margin-top: -16px;" />
<div class="input-group mb-3">
<span class="input-group-text" for="lastName">Last Name</span>
<InputText id="lastName" class="form-control" @bind-Value="Person.LastName" />
</div>
<ValidationMessage For="@(() => Person.LastName)" class="mb-3 text-danger" style="margin-top: -16px;" />
<button class="btn btn-primary" type="submit">Submit</button>
</EditForm>
@if (PersonModel.IsModelValid(Person, out _))
{
<p>Submitted: @Person.FirstName @Person.LastName</p>
}
</div>
</div>
@code {
[SupplyParameterFromForm(FormName = "personForm")]
public PersonModel Person { get; set; } = new();
protected override void OnInitialized()
{
Console.WriteLine("OnInitialized()");
}
protected override void OnParametersSet()
{
Console.WriteLine("OnParametersSet()");
}
protected override void OnAfterRender(bool firstRender)
{
Console.WriteLine("OnAfterRender()");
}
private void SubmitPerson()
{
Console.WriteLine("SubmitPerson()");
if (PersonModel.IsModelValid(Person, out _))
{
Console.WriteLine($"Submitted: {Person.FirstName} {Person.LastName}");
}
}
}
The output is as follows:
OnInitialized()
OnParametersSet()
[I filled in the form and pressed submit]
OnInitialized()
OnParametersSet()
SubmitPerson()
Submitted: Homer Simpson
I expected the SubmitPerson() method to be run before reloading the page, not after.
Suppose I was displaying a list of people from a database on the same page as the form. It seems to me, this would reload the same page over again, then submit the new person... leaving it to me to reload the page a third time, OR fake it by manually adding the submitted person to the list of people from the database, without actually reloading the page or reloading database results.
I may have answered my own question with this little experiment, just it feels the answer is not as intuitive as I would have thought. My aim is to utilize static server-side rendering as much as possible, to avoid/minimize the need for web sockets or client-side activity, as much as possible.


PersonModel? What are the limitations in using SSR? For example, can I use Blazor component? Is there any example of a Blazor application created only using SSR?