In my 2019 A-Z series, I covered Blazor for ASP.NET Core while it was still experimental. As of ASP.NET Core 3.1, server-side Blazor has now been released, while client-side Blazor (currently in preview) is expected to arrive in May 2020. This post will cover server-side Blazor, as seen in NetLearner.
To see the code in action, open the solution in Visual Studio 2019 and run the NetLearner.Blazor project. All modern web browsers should be able to run the project.
NetLearner.Blazor web app in action
Entry Point and Configuration
Let’s start with Program.cs, the entry point of the application. Just like any ASP.NET Core web application, there is a Main method that sets up the entry point. A quick call to CreateHostBuilder() in the same file ensures that two things will happen: The Generic Host will call its own CreateDefaultBuilder() method (similar to how it works in a typical ASP.NET Core web application) and it will also call UseStartup() to identify the Startup class where the application is configured.
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Note that the Startup class doesn’t have to be called Startup, but you do have to tell your application what it’s called. In the Startup.cs file, you will see the familiar ConfigureServices() and Configure() methods, but you won’t need any of the regular MVC-related lines of code that set up the HTTP pipeline for an MVC (or Razor Pages) application. Instead, you just need a call to AddServerSideBlazor() in ConfigureServices() and a call to MapBlazorHub() in Configure() while setting up endpoints with UseEndPoints().
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddServerSideBlazor();
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
Note that the Configure() method takes in an app object of type IApplicationBuilder, similar to the IApplicationBuilder we see in regular ASP.NET Core web apps. A call to MapFallBackToPage() indicates the “/_Host” root page, which is defined in the _Host.cshtml page in the Pages subfolder.
Rendering the Application
This “app” (formerly defined in a static “index.html” in pre-release versions) is now defined in the aforementioned “_Host.cshtml” page. This page contains an <app> element containing the main App component.
<html>
...
<body>
<app>
<component type="typeof(App)" render-mode="ServerPrerendered" />
</app>
...
</body>
</html>
The HTML in this page has two things worth noting: an <app> element within the <body>, and a <component> element of type “App” with a render-mode attribute set to “ServerPrerendered”. This is one of 3 render modes for a Blazor component: Static, Server and ServerPrerendered. For more information on render modes, check out the official docs at:
According to the documentation, this setting “renders the component into static HTML and includes a marker for a Blazor Server app. When the user-agent starts, this marker is used to bootstrap a Blazor app.“
The App component is defined in App.razor, at the root of the Blazor web app project. This App component contains nested authentication-enabled components, that make use of the MainLayout to display the single-page web application in a browser. If the user-requested routedata is found, the requested page (or root page) is displayed. If the user-requested routedata is invalid (not found), it displays a “sorry” message to the end user.
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
The MainLayout.razor component (under /Shared) contains the following:
- NavMenu: displays navigation menu in sidebar.
- LoginDisplay: displays links to register and log in/out.
- _CookieConsentPartial: displays GDPR-inspired cookie message.
- @Body keyword: replaced by content of layout when rendered.
The _Layout.cshtml layout file (under /Pages/Shared) acts as a template and includes a call to @RenderBody to display its content. This content is determined by route info that is requested by the user, e.g. Index.razor when the root “/” is requested, LearningResources.razor when the route “/learningresources” is requested.
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
The NavMenu.razor component contains NavLinks that point to various routes. For more information about routing in Blazor, check out the official docs at:
LifeCycle Methods
A Blazor application goes through several lifecycle methods, including both asynchronous and non-asynchronous versions where applicable. Some important ones are listed below:
- OnInitializedAsync() and OnInitialized(): invoked after receiving initial params.
- OnParametersSetAsync() and OnParametersSet(): called after receiving params from its parent, after initialization.
- OnAfterRenderAsync() and OnAfterRender(): called after each render.
- ShouldRender(): used to suppress subsequent rendering of the component.
- StateHasChanged(): called to indicate that state has changed, can be triggered manually.
These methods can be overridden and defined in the @code section (formerly @functions section) of a .razor page, e.g. LearningResources.razor.
@code {
...
protected override async Task OnInitializedAsync()
{
...
}
...
}
Updating the UI
The C# code in LearningResources.razor includes methods to initialize the page, handle user interaction, and keep track of data changes. Every call to an event handler from an HTML element (e.g. OnClick event for an input button) can be bound to a C# method to handle that event. The StateHasChanged() method can be called manually to rerender the component, e.g. when an item is added/edited/deleted in the UI.
<div>
<input type="button" class="btn btn-primary" value="All Resources" @onclick="(() => DataChanged())" />
</div>
...
private async void DataChanged()
{
learningResources = await learningResourceService.Get();
ResourceLists = await resourceListService.Get();
StateHasChanged();
}
Note that the DataChanged() method includes some asynchronous calls to Get() methods from a service class. These are the service classes in the shared library that are also used by the MVC and Razor Pages web apps in NetLearner.
Parameters defined in the C# code can be used similar to HTML attributes when using the component, including RenderFragments can be used like nested HTML elements. These can be defined in sub-components such as ConfirmDialog.razor and ResourceDetail.razor that are inside the LearningResources.razor component.
<ResourceDetail LearningResourceObject="learningResourceObject"
ResourceListValues="ResourceLists"
DataChanged="@DataChanged">
<CustomHeader>@customHeader</CustomHeader>
</ResourceDetail>
Inside the subcomponent, you would define the parameters as such:
@code {
[Parameter]
public LearningResource LearningResourceObject { get; set; }
[Parameter]
public List<ResourceList> ResourceListValues { get; set; }
[Parameter]
public Action DataChanged { get; set; }
[Parameter]
public RenderFragment CustomHeader { get; set; }
}
For more information on the creation and use of Razor components in Blazor, check out the official documentation at:
Next Steps
Run the Blazor web app from the NetLearner repo and try using the UI to add, edit and delete items. Make sure you remove the restrictions mentioned in a previous post about NetLearner, which will allow you to register as a new user, log in and perform CRUD operations.
NetLearner.Blazor: Learning Resources
There is so much more to learn about this exciting new framework. Blazor’s reusable components can take various forms. In addition to server-side Blazor (released in late 2019 with .NET Core 3.1), you can also host Blazor apps on the client-side from within an ASP.NET Core web app. Client-side Blazor is currently in preview and is expected in a May 2020 release.
References