© Adam Freeman 2020
A. FreemanPro ASP.NET Core 3https://doi.org/10.1007/978-1-4842-5440-0_33

33. Using Blazor Server, Part 1

Adam Freeman1 
(1)
London, UK
 
Blazor is a new addition to ASP.NET Core that adds client-side interactivity to web applications. There are two varieties of Blazor, and in this chapter, I focus on Blazor Server. I explain the problem it solves and how it works. I show you how to configure an ASP.NET Core application to use Blazor Server and describe the basic features available when using Razor Components, which are the building blocks for Blazor Server projects. I describe more advanced Blazor Server features in Chapters 3436, and in Chapter 37, I describe Blazor WebAssembly, which is the other variety of Blazor. Table 33-1 puts Blazor Server in context.
Table 33-1.

Putting Blazor Server in Context

Question

Answer

What is it?

Blazor Server uses JavaScript to receive browser events, which are forwarded to ASP.NET Core and evaluated using C# code. The effect of the event on the state of the application is sent back to the browser and displayed to the user.

Why is it useful?

Blazor Server can produce a richer and more responsive user experience compared to standard web applications.

How is it used?

The building block for Blazor Server is the Razor Component, which uses a syntax similar to Razor Pages. The view section of the Razor Component contains special attributes that specify how the application will respond to user interaction.

Are there any pitfalls or limitations?

Blazor Server relies on a persistent HTTP connection to the server and cannot function when that connection is interrupted. Blazor Server is not supported by older browsers.

Are there any alternatives?

The features described in Part 3 of this book can be used to create web applications that work broadly but that offer a less responsive experience. You could also consider a client-side JavaScript framework, such as Angular, React, or Vue.js.

Table 33-2 summarizes the chapter.
Table 33-2.

Chapter Summary

Problem

Solution

Listing

Configuring Blazor

Use the AddServerSideBlazor and MapBlazorHub methods to set up the required services and middleware and configure the JavaScript file

3–6

Creating a Blazor Component

Create a .blazor file and use it to define code and markup

7

Applying a component

Use a component element

8, 9

Handling events

Use an attribute to specify the method or expression that will handle an event

10–15

Creating a two-way relationship with an element

Create a data binding

16–20

Defining the code separately from the markup

Use a code-behind class

21–23

Defining a component without declarative markup

Use a Razor Component class

24, 25

Preparing for This Chapter

This chapter uses the Advanced project from Chapter 32. No changes are required to prepare for this chapter.

Tip

You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/apress/pro-asp.net-core-3. See Chapter 1 for how to get help if you have problems running the examples.

Open a new PowerShell command prompt, navigate to the folder that contains the Advanced.csproj file, and run the command shown in Listing 33-1 to drop the database.
dotnet ef database drop --force
Listing 33-1.

Dropping the Database

Select Start Without Debugging or Run Without Debugging from the Debug menu or use the PowerShell command prompt to run the command shown in Listing 33-2.
dotnet run
Listing 33-2.

Running the Example Application

Use a browser to request http://localhost:5000/controllers, which will display a list of data items. Pick a city from the drop-down list and click the Select button to highlight elements, as shown in Figure 33-1.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig1_HTML.jpg
Figure 33-1.

Running the example application

Understanding Blazor Server

Consider what happens when you choose a city and click the Select button presented by the example application. The browser sends an HTTP GET request that submits a form, which is received by either an action method or a handler method, depending on whether you use the controller or Razor Page. The action or handler renders its view, which sends a new HTML document that reflects the selection to the browser, as illustrated by Figure 33-2.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig2_HTML.jpg
Figure 33-2.

Interacting with the example application

This cycle is effective but can be inefficient. Each time the Submit button is clicked, the browser sends a new HTTP request to ASP.NET Core. Each request contains a complete set of HTTP headers that describe the request and the types of responses the browser is willing to receive. In its response, the server includes HTTP headers that describe the response and includes a complete HTML document for the browser to display.

The amount of data sent by the example application is about 3KB on my system, and almost all of it is duplicated between requests. The browser only wants to tell the server which city has been selected, and the server only wants to indicate which table rows should be highlighted; however, each HTTP request is self-contained, so the browser must parse a complete HTML document each time. The root issue that every interaction is the same: send a request and get a complete HTML document in return.

Blazor takes a different approach. A JavaScript library is included in the HTML document that is sent to the browser. When the JavaScript code is executed, it opens an HTTP connection back to the server and leaves it open, ready for user interaction. When the user picks a value using the select element, for example, details of the selection are sent to the server, which responds with just the changes to apply to the existing HTML, as shown in Figure 33-3.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig3_HTML.jpg
Figure 33-3.

Interacting with Blazor

The persistent HTTP connection minimizes the delay, and replying with just the differences reduces the amount of data sent between the browser and the server.

Understanding the Blazor Server Advantages

The biggest attraction of Blazor is that it is based on Razor Pages written in C#. This means you can increase efficiency and responsiveness without having to learn a new framework, such as Angular or React, and a new language, such as TypeScript or JavaScript. Blazor is nicely integrated into the rest of ASP.NET Core and is built on features described in earlier chapters, which makes it easy to use (especially when compared to a framework like Angular, which has a dizzyingly steep learning curve).

Understanding the Blazor Server Disadvantages

Blazor requires a modern browser to establish and maintain its persistent HTTP connection. And, because of this connection, applications that use Blazor stop working if the connection is lost, which makes them unsuitable for offline use, where connectivity cannot be relied on or where connections are slow. These issues are addressed by Blazor WebAssembly, described in Chapter 36, but, as I explain, this has its own set of limitations.

Choosing Between Blazor Server and Angular/React/Vue.js

Decisions between Blazor and one of the JavaScript frameworks should be driven by the development team’s experience and the users’ expected connectivity. If you have no JavaScript expertise and have not used one of the JavaScript frameworks, then you should use Blazor, but only if you can rely on good connectivity and modern browsers. This makes Blazor a good choice for line-of-business applications, for example, where the browser demographic and network quality can be determined in advance.

If you have JavaScript experience and you are writing a public-facing application, then you should use one of the JavaScript frameworks because you won’t be able to make assumptions about browsers or network quality. (It doesn’t matter which framework you choose—I have written books about Angular, React, and View, and they are all excellent. My advice for choosing a framework is to create a simple app in each of them and pick the one whose development model appeals to you the most.)

If you are writing a public-facing application and you don’t have JavaScript experience, then you have two choices. The safest option is to stick to the ASP.NET Core features described in earlier chapters and accept the inefficiencies this can bring. This isn’t a terrible choice to make, and you can still produce top-quality applications. A more demanding choice is to learn TypeScript or JavaScript and one Angular, React, or Vue.js—but don’t underestimate the amount of time it takes to master JavaScript or the complexity of these frameworks.

Getting Started with Blazor

The best way to get started with Blazor is to jump right in. In the sections that follow, I configure the application to enable Blazor and re-create the functionality offered by the controller and Razor Page. After that, I’ll go right back to basics and explain how Razor Components work and the different features they offer.

Configuring ASP.NET Core for Blazor Server

Preparation is required before Blazor can be used. The first step is to add the services and middleware to the Startup class, as shown in Listing 33-3.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Advanced.Models;
namespace Advanced {
    public class Startup {
        public Startup(IConfiguration config) {
            Configuration = config;
        }
        public IConfiguration Configuration { get; set; }
        public void ConfigureServices(IServiceCollection services) {
            services.AddDbContext<DataContext>(opts => {
                opts.UseSqlServer(Configuration[
                    "ConnectionStrings:PeopleConnection"]);
                opts.EnableSensitiveDataLogging(true);
            });
            services.AddControllersWithViews().AddRazorRuntimeCompilation();
            services.AddRazorPages().AddRazorRuntimeCompilation();
            services.AddServerSideBlazor();
        }
        public void Configure(IApplicationBuilder app, DataContext context) {
            app.UseDeveloperExceptionPage();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseEndpoints(endpoints => {
                endpoints.MapControllerRoute("controllers",
                    "controllers/{controller=Home}/{action=Index}/{id?}");
                endpoints.MapDefaultControllerRoute();
                endpoints.MapRazorPages();
                endpoints.MapBlazorHub();
            });
            SeedData.SeedDatabase(context);
        }
    }
}
Listing 33-3.

Adding Services and Middleware in the Startup.cs File in the Advanced Folder

The “hub” in the MapBlazorHub method relates to SignalR, which is the part of ASP.NET Core that handles the persistent HTTP request. I don’t describe SignalR in this book because it is rarely used directly, but it can be useful if you need ongoing communication between clients and the server. See https://docs.microsoft.com/en-gb/aspnet/core/signalr for details. For this book—and most ASP.NET Core applications—it is enough to know that SignalR is used to manage the connections that Blazor relies on.

Adding the Blazor JavaScript File to the Layout

Blazor relies on JavaScript code to communicate with the ASP.NET Core server. Add the elements shown in Listing 33-4 to the _Layout.cshtml file in the Views/Shared folder to add the JavaScript file to the layout used by controller views.
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <base href="~/" />
</head>
<body>
    <div class="m-2">
        @RenderBody()
    </div>
    <script src="_framework/blazor.server.js"></script>
</body>
</html>
Listing 33-4.

Adding Elements in the _Layout.cshtml File in the Views/Shared Folder

The script element specifies the name of the JavaScript file, and requests for it are intercepted by the middleware added to the request pipeline in Listing 33-3 so that no additional package is required to add the JavaScript code to the project. The base element must also be added to specify the root URL for the application. The same elements must be added to the layout used by Razor Pages, as shown in Listing 33-5.
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <base href="~/" />
</head>
<body>
    <div class="m-2">
        <h5 class="bg-secondary text-white text-center p-2">Razor Page</h5>
        @RenderBody()
    </div>
    <script src="_framework/blazor.server.js"></script>
</body>
</html>
Listing 33-5.

Adding Elements in the _Layout.cshtml File in the Pages Folder

Creating the Blazor Imports File

Blazor requires its own imports file to specify the namespaces that it uses. It is easy to forget to add this file to a project, but, without it, Blazor will silently fail. Add a file named _Imports.razor to the Advanced folder with the content shown in Listing 33-6. (If you are using Visual Studio, you can use the Razor View Imports template to create this file, but ensure you use the .razor file extension.)
@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Microsoft.EntityFrameworkCore
@using Advanced.Models
Listing 33-6.

The Contents of the _Imports.razor File in the Advanced Folder

The first five @using expressions are for the namespaces required for Blazor. The last two expressions are for convenience in the examples that follow because they will allow me to use Entity Framework Core and the classes in the Models namespace.

Creating a Razor Component

There is a clash in terminology: the technology is Blazor, but the key building block is called a Razor Component. Razor Components are defined in files with the .razor extension and must begin with a capital letter. Components can be defined anywhere, but they are usually grouped together to help keep the project organized. Create a Blazor folder in the Advanced folder and add to it a Razor Component named PeopleList.razor with the content shown in Listing 33-7.
<table class="table table-sm table-bordered table-striped">
    <thead>
        <tr>
            <th>ID</th><th>Name</th><th>Dept</th><th>Location</th>
        </tr>
    </thead>
    <tbody>
        @foreach (Person p in People) {
            <tr class="@GetClass(p.Location.City)">
                <td>@p.PersonId</td>
                <td>@p.Surname, @p.Firstname</td>
                <td>@p.Department.Name</td>
                <td>@p.Location.City, @p.Location.State</td>
            </tr>
        }
    </tbody>
</table>
<div class="form-group">
    <label for="city">City</label>
    <select name="city" class="form-control" @bind="SelectedCity">
        <option disabled selected>Select City</option>
        @foreach (string city in Cities) {
            <option value="@city" selected="@(city == SelectedCity)">
                @city
            </option>
        }
    </select>
</div>
@code {
    [Inject]
    public DataContext Context { get; set; }
    public IEnumerable<Person> People =>
        Context.People.Include(p => p.Department).Include(p => p.Location);
    public IEnumerable<string> Cities => Context.Locations.Select(l => l.City);
    public string SelectedCity { get; set; }
    public string GetClass(string city) =>
        SelectedCity == city ? "bg-info text-white" : "";
}
Listing 33-7.

The Contents of the PeopleList.razor File in the Blazor Folder

Razor Components are similar to Razor Pages. The view section relies on the Razor features you have seen in earlier chapters, with @ expressions to insert data values into the component’s HTML or to generate elements for objects in a sequence, like this:
...
@foreach (string city in Cities) {
    <option value="@city" selected="@(city == SelectedCity)">
}
...

This @foreach expression generates option elements for each value in the Cities sequence and is identical to the equivalent expression in the controller view and Razor Page created in Chapter 32.

Although Razor Components look familiar, there are some important differences. The first is that there is no page model class and no @model expression. The properties and methods that support a component’s HTML are defined directly in an @code expression, which is the counterpart to the Razor Page @functions expression. To define the property that will provide the view section with Person objects, for example, I just define a People property in the @code section, like this:
...
public IEnumerable<Person> People =>
    Context.People.Include(p => p.Department).Include(p => p.Location);
...
And, because there is no page model class, there is no constructor through which to declare service dependencies. Instead, the dependency injection sets the values of properties that have been decorated with the Inject attribute, like this:
...
[Inject]
public DataContext Context { get; set; }
...
The most significant difference is the use of a special attribute on the select element.
...
<select name="city" class="form-control" @bind="SelectedCity">
    <option disabled selected>Select City</option>
...

This Blazor attribute creates a data binding between the value of the select element and the SelectedCity property defined in the @code section.

I describe data bindings in more detail in the “Working with Data Bindings” section, but for now, it is enough to know that the value of the SelectedCity will be updated when the user changes the value of the select element.

Using a Razor Component

Razor components are delivered to the browser as part of a Razor Page or a controller view. Listing 33-8 shows how to use a Razor Component in a controller view.
@model PeopleListViewModel
<h4 class="bg-primary text-white text-center p-2">People</h4>
<component type="typeof(Advanced.Blazor.PeopleList)" render-mode="Server" />
Listing 33-8.

Using a Razor Component in the Index.cshtml File in the Views/Home Folder

Razor Components are applied using the component element, for which there is a tag helper. The component element is configured using the type and render-mode attributes. The type attribute is used to specify the Razor Component. Razor Components are compiled into classes just like controller views and Razor Pages. The PeopleList component is defined in the Blazor folder in the Advanced project, so the type will be Advanced.Blazor.PeopleList, like this:
...
<component type="typeof(Advanced.Blazor.PeopleList)" render-mode="Server" />
...
The render-mode attribute is used to select how content is produced by the component, using a value from the RenderMode enum, described in Table 33-3.
Table 33-3.

The RenderMode Values

Name

Description

Static

The Razor Component renders its view section as static HTML with no client-side support.

Server

The HTML document is sent to the browser with a placeholder for the component. The HTML displayed by the component is sent to the browser over the persistent HTTP connection and displayed to the user.

ServerPrerendered

The view section of the component is included in the HTML and displayed to the user immediately. The HTML content is sent again over the persistent HTTP connection.

For most applications, the Server option is a good choice. The ServerPrerendered includes a static rendition of the Razor Component’s view section in the HTML document sent to the browser. This acts as placeholder content so that the user isn’t presented with an empty browser window while the JavaScript code is loaded and executed. Once the persistent HTTP connection has been established, the placeholder content is deleted and replaced with a dynamic version sent by Blazor. The idea of showing static content to the user is a good one, but it can be confusing because the HTML elements are not wired up to the server-side part of the application, and any interaction from the user either doesn’t work or will be discarded once the live content arrives.

To see Blazor in action, restart ASP.NET Core and use a browser to request http://localhost:5000/controllers. No form submission is required when using Blazor because the data binding will respond as soon as the select element’s value is changed, as shown in Figure 33-4.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig4_HTML.jpg
Figure 33-4.

Using a Razor Component

When you use the select element, the value you choose is sent over the persistent HTTP connection to the ASP.NET Core server, which updates the Razor Component’s SelectedCity property and rerenders the HTML content. A set of updates is sent to the JavaScript code, which updates the table.

Razor Components can also be used in Razor Pages. Add a Razor Page named Blazor.cshtml to the Pages folder and add the content shown in Listing 33-9.
@page "/pages/blazor"
<script type="text/javascript">
    window.addEventListener("DOMContentLoaded", () => {
        document.getElementById("markElems").addEventListener("click", () => {
            document.querySelectorAll("td:first-child")
                .forEach(elem => {
                    elem.innerText = `M:${elem.innerText}`
                    elem.classList.add("border", "border-dark");
                });
        });
    });
</script>
<h4 class="bg-primary text-white text-center p-2">Blazor People</h4>
<button id="markElems" class="btn btn-outline-primary mb-2">Mark Elements</button>
<component type="typeof(Advanced.Blazor.PeopleList)" render-mode="Server" />
Listing 33-9.

The Contents of the Blazor.cshtml File in the Pages Folder

The Razor Page in Listing 33-9 contains additional JavaScript code that helps demonstrate that only changes are sent, instead of an entirely new HTML table. Restart ASP.NET Core and request http://localhost:5000/pages/blazor. Click the Mark Elements button, and the cells in the ID column will be changed to display different content and a border. Now use the select element to pick a different city, and you will see that the elements in the table are modified without being deleted, as shown in Figure 33-5.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig5_HTML.jpg
Figure 33-5.

Demonstrating that only changes are used

UNDERSTANDING BLAZOR CONNECTION MESSAGES

When you stop ASP.NET Core, you will see an error message in the browser window, which indicates the connection to the server has been lost and prevents the user from interacting with the displayed component. Blazor will attempt to reconnect and pick up where it left off when the disconnection is caused by temporary network issues, but it won’t be able to do so when the server has been stopped or restarted because the context data for the connection has been lost; you will have to explicitly request a new URL.

There is a default reload link in the connection message, but that goes to the default URL for the website, which isn’t useful for this book where I direct you to specific URLs to see the effect of examples. See Chapter 34 for details of how to configure the connection messages.

Understanding the Basic Razor Component Features

Now that I have demonstrated how Blazor can be used and how it works, it is time to go back to the basics and introduce the features that Razor Components offer. Although the example in the previous section showed how standard ASP.NET Core features can be reproduced using Blazor, there is a much wider set of features available.

Understanding Blazor Events and Data Bindings

Events allow a Razor Component to respond to user interaction, and Blazor uses the persistent HTTP connection to send details of the event to the server where it can be processed. To see Blazor events in action, add a Razor Component named Events.razor to the Blazor folder with the content shown in Listing 33-10.
<div class="m-2 p-2 border">
    <button class="btn btn-primary" @onclick="IncrementCounter">Increment</button>
    <span class="p-2">Counter Value: @Counter</span>
</div>
@code {
    public int Counter { get; set; } = 1;
    public void IncrementCounter(MouseEventArgs e) {
        Counter++;
    }
}
Listing 33-10.

The Contents of the Events.razor File in the Blazor Folder

You register a handler for an event by adding an attribute to an HTML element, where the attribute name is @on, followed by the event name. In the example, I have set up a handler for the click events generated by a button element, like this:
...
<button class="btn btn-primary" @onclick="IncrementCounter">Increment</button>
...

The value assigned to the attribute is the name of the method that will be invoked when the event is triggered. The method can define an optional parameter that is either an instance of the EventArgs class or a class derived from EventArgs that provides additional information about the event.

For the onclick event, the handler method receives a MouseEventArgs object, which provides additional details, such as the screen coordinates of the click. Table 33-4 lists the event description events and the events for which they are used.
Table 33-4.

The EventArgs Classes and the Events They Represent

Class

Events

ChangeEventArgs

onchange, oninput

ClipboardEventArgs

oncopy, oncut, onpaste

DragEventArgs

ondrag, ondragend, ondragenter, ondragleave, ondragover, ondragstart, ondrop

ErrorEventArgs

onerror

FocusEventArgs

onblur, onfocus, onfocusin, onfocusout

KeyboardEventArgs

onkeydown, onkeypress, onkeyup

MouseEventArgs

onclick, oncontextmenu, ondblclick, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onmousewheel, onwheel

PointerEventArgs

ongotpointercapture, onlostpointercapture, onpointercancel, onpointerdown, onpointerenter, onpointerleave, onpointermove, onpointerout, onpointerover, onpointerup

ProgressEventArgs

onabort, onload, onloadend, onloadstart, onprogress, ontimeout

TouchEventArgs

ontouchcancel, ontouchend, ontouchenter, ontouchleave, ontouchmove, ontouchstart

EventArgs

onactivate, onbeforeactivate, onbeforecopy, onbeforecut, onbeforedeactivate, onbeforepaste, oncanplay, oncanplaythrough, oncuechange, ondeactivate, ondurationchange, onemptied, onended, onfullscreenchange, onfullscreenerror, oninvalid, onloadeddata, onloadedmetadata, onpause, onplay, onplaying, onpointerlockchange, onpointerlockerror, onratechange, onreadystatechange, onreset, onscroll, onseeked, onseeking, onselect, onselectionchange, onselectstart, onstalled, onstop, onsubmit, onsuspend, ontimeupdate, onvolumechange, onwaiting

The Blazor JavaScript code receives the event when it is triggered and forwards it to the server over the persistent HTTP connection. The handler method is invoked, and the state of the component is updated. Any changes to the content produced by the component’s view section will be sent back to the JavaScript code, which will update the content displayed by the browser.

In the example, the click event will be handled by the IncrementCounter method, which changes the value of the Counter property. The value of the Counter property is included in the HTML rendered by the component, so Blazor sends the changes to the browser so that the JavaScript code can update the HTML elements displayed to the user. To display the Events component, replace the contents of the Blazor.cshtml file in the Pages folder, as shown in Listing 33-11.
@page "/pages/blazor"
<h4 class="bg-primary text-white text-center p-2">Events</h4>
<component type="typeof(Advanced.Blazor.Events)" render-mode="Server" />
Listing 33-11.

Using a New Component in the Blazor.cshtml File in the Pages Folder

Listing 33-11 changes the type attribute of the component element and removes the custom JavaScript and the button element I used to mark elements in the previous example. Restart ASP.NET Core and request http://localhost:5000/pages/blazor to see the new component. Click the Increment button, and the click event will be received by the Blazor JavaScript code and sent to the server for processing by the IncrementCounter method, as shown in Figure 33-6.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig6_HTML.jpg
Figure 33-6.

Handling an event

Handling Events from Multiple Elements

To avoid code duplication, elements from multiple elements can be received by a single handler method, as shown in Listing 33-12.
<div class="m-2 p-2 border">
    <button class="btn btn-primary" @onclick="@(e => IncrementCounter(e, 0))">
        Increment Counter #1
    </button>
    <span class="p-2">Counter Value: @Counter[0]</span>
</div>
<div class="m-2 p-2 border">
    <button class="btn btn-primary" @onclick="@(e => IncrementCounter(e, 1))">
        Increment Counter #2
    </button>
    <span class="p-2">Counter Value: @Counter[1]</span>
</div>
@code {
    public int[] Counter { get; set; } = new int[] { 1, 1 };
    public void IncrementCounter(MouseEventArgs e, int index) {
        Counter[index]++;
    }
}
Listing 33-12.

Handling Events in the Events.razor File in the Blazor Folder

Blazor event attributes can be used with lambda functions that receive the EventArgs object and invoke a handler method with additional arguments. In this example, I have added an index parameter to the IncrementCounter method, which is used to determine which counter value should be updated. The value for the argument is defined in the @onclick attribute, like this:
...
<button class="btn btn-primary" @onclick="@(e => IncrementCounter(e, 0))">
...

This technique can also be used when elements are generated programmatically, as shown in Listing 33-13. In this example, I use an @for expression to generate elements and use the loop variable as the argument to the handler method. I have also removed the EventArgs parameter from the handler method, which isn’t being used.

AVOIDING THE HANDLER METHOD NAME PITFALL
The most common mistake when specifying an event handler method is to include parentheses, like this:
...
<button class="btn btn-primary" @onclick="IncrementCounter()">
...
The error message this produces will depend on the event handler method. You may see a warning telling you a formal parameter is missing or that void cannot be converted to an EventCallback. When specifying a handler method, you must specify just the event name, like this:
...
<button class="btn btn-primary" @onclick="IncrementCounter">
...
You can specify the method name as a Razor expression, like this:
...
<button class="btn btn-primary" @onclick="@IncrementCounter">
...
Some developers find this easier to parse, but the result is the same. A different set of rules applies when using a lambda function, which must be defined within a Razor expression, like this:
...
<button class="btn btn-primary" @onclick="@( ... )">
...
Within the Razor expression, the lambda function is defined as it would be in a C# class, which means defining the parameters, followed by the “goes to” arrow, followed by the function body, like this:
...
<button class="btn btn-primary" @onclick="@((e) => HandleEvent(e, local))">
...
If you don’t need to use the EventArgs object, then you can omit the parameter from the lambda function, like this:
...
<button class="btn btn-primary" @onclick="@(() => IncrementCounter(local))">
...

You will quickly become used to these rules as you start to work with Blazor, even if they seem inconsistent at first.

@for (int i = 0; i < ElementCount; i++) {
    int local = i;
    <div class="m-2 p-2 border">
        <button class="btn btn-primary" @onclick="@(() => IncrementCounter(local))">
            Increment Counter #@(i + 1)
        </button>
        <span class="p-2">Counter Value: @GetCounter(i)</span>
    </div>
}
@code {
    public int ElementCount { get; set; } = 4;
    public Dictionary<int, int> Counters { get; } = new Dictionary<int, int>();
    public int GetCounter(int index) =>
        Counters.ContainsKey(index) ? Counters[index] : 0;
    public void IncrementCounter(int index)  =>
        Counters[index] = GetCounter(index) + 1;
}
Listing 33-13.

Generating Elements in the Events.razor File in the Blazor Folder

The important point to understand about event handlers is that the @onclick lambda function isn’t evaluated until the server receives the click event from the browser. This means care must be taken not to use the loop variable i as the argument to the IncrementCounter method because it will always be the final value produced by the loop, which would be 4 in this case. Instead, you must capture the loop variable in a local variable, like this:
...
int local = i;
...
The local variable is then used as the argument to the event handler method in the attribute, like this:
...
<button class="btn btn-primary" @onclick="@(() => IncrementCounter(local))">
...
The local variable fixes the value for the lambda function for each of the generated elements. Restart ASP.NET Core and use a browser to request http://localhost:5000/pages/blazor, which will produce the response shown in Figure 33-7. The click events produced by all the button elements are handled by the same method, but the argument provided by the lambda function ensures that the correct counter is updated.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig7_HTML.jpg
Figure 33-7.

Handling events from multiple elements

Processing Events Without a Handler Method

Simple event handling can be done directly in a lambda function, without using a handler method, as shown in Listing 33-14.
@for (int i = 0; i < ElementCount; i++) {
    int local = i;
    <div class="m-2 p-2 border">
        <button class="btn btn-primary" @onclick="@(() => IncrementCounter(local))">
            Increment Counter #@(i + 1)
        </button>
        <button class="btn btn-info" @onclick="@(() => Counters.Remove(local))">
                Reset
        </button>
        <span class="p-2">Counter Value: @GetCounter(i)</span>
    </div>
}
@code {
    public int ElementCount { get; set; } = 4;
    public Dictionary<int, int> Counters { get; } = new Dictionary<int, int>();
    public int GetCounter(int index) =>
        Counters.ContainsKey(index) ? Counters[index] : 0;
    public void IncrementCounter(int index)  =>
        Counters[index] = GetCounter(index) + 1;
}
Listing 33-14.

Handling Events in the Events.razor File in the Blazor Folder

Complex handlers should be defined as methods, but this approach is more concise for simple handlers. Restart ASP.NET Core and request http://localhost:5000/pages/blazor. The Reset buttons remove values from the Counters collection without relying on a method in the @code section of the component, as shown in Figure 33-8.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig8_HTML.jpg
Figure 33-8.

Handling events in a lambda expression

Preventing Default Events and Event Propagation

Blazor provides two attributes that alter the default behavior of events in the browser, as described in Table 33-5. These attributes, where the name of the event is followed by a colon and then a keyword, are known as parameters.
Table 33-5.

The Event Configuration Parameters

Name

Description

@on{event}:preventDefault

This parameter determines whether the default event for an element is triggered.

@on{event}:stopPropagation

This parameter determines whether an event is propagated to its ancestor elements.

Listing 33-15 demonstrates what these parameters do and why they are useful.
<form action="/pages/blazor" method="get">
    @for (int i = 0; i < ElementCount; i++) {
        int local = i;
        <div class="m-2 p-2 border">
            <button class="btn btn-primary"
                @onclick="@(() => IncrementCounter(local))"
                @onclick:preventDefault="EnableEventParams">
                    Increment Counter #@(i + 1)
            </button>
            <button class="btn btn-info" @onclick="@(() => Counters.Remove(local))">
                    Reset
            </button>
            <span class="p-2">Counter Value: @GetCounter(i)</span>
        </div>
    }
</form>
<div class="m-2" @onclick="@(() => IncrementCounter(1))">
    <button class="btn btn-primary" @onclick="@(() => IncrementCounter(0))"
            @onclick:stopPropagation="EnableEventParams">Propagation Test</button>
</div>
<div class="form-check m-2">
    <input class="form-check-input" type="checkbox"
           @onchange="@(() => EnableEventParams = !EnableEventParams)" />
    <label class="form-check-label">Enable Event Parameters</label>
</div>
@code {
    public int ElementCount { get; set; } = 4;
    public Dictionary<int, int> Counters { get; } = new Dictionary<int, int>();
    public int GetCounter(int index) =>
        Counters.ContainsKey(index) ? Counters[index] : 0;
    public void IncrementCounter(int index)  =>
        Counters[index] = GetCounter(index) + 1;
    public bool EnableEventParams { get; set; } = false;
}
Listing 33-15.

Overriding Event Defaults in the Events.razor File in the Blazor Folder

This example creates two situations in which the default behavior of events in the browser can cause problems. The first is caused by adding a form element. By default, button elements contained in a form will submit that form when they are clicked, even when the @onclick attribute is present. This means that whenever one of the Increment Counter buttons is clicked, the browser will send the form data to the ASP.NET Core server, which will respond with the contents of the Blazor.cshtml Razor Page.

The second problem is demonstrated by an element whose parent also defines an event handler, like this:
...
<div class="m-2" @onclick="@(() => IncrementCounter(1))">
    <button class="btn btn-primary" @onclick="@(() => IncrementCounter(0))"
...

Events go through a well-defined lifecycle in the browser, which includes being passed up the chain of ancestor elements. In the example, this means clicking the button will cause two counters to be updated, once by the @onclick handler for the button element and once by the @onclick handler for the enclosing div element.

To see these problems, restart ASP.NET Core and request http://localhost:5000/pages/blazor. Click an Increment Counter button; you will see that the form is submitted and the page is essentially reloaded. Click the Propagation Test button, and you will see that two counters are updated. Figure 33-9 shows both problems.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig9_HTML.jpg
Figure 33-9.

Problems caused by the default behavior of events in the browser

The checkbox in Listing 33-15 toggles the property that applies the parameters described in Table 33-5, with the effect that the form isn’t submitted and only the handler on the button element receives the event. To see the effect, check the checkbox and then click an Increment Counter button and the Propagation Test buttons, which produces the result shown in Figure 33-10.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig10_HTML.jpg
Figure 33-10.

Overriding the default behavior of events in the browser

Working with Data Bindings

Event handlers and Razor expressions can be used to create a two-way relationship between an HTML element and a C# value, which is useful for elements that allow users to make changes, such as input and select elements. Add a Razor Component named Bindings.razor to the Blazor folder with the content shown in Listing 33-16.
<div class="form-group">
    <label>City:</label>
    <input class="form-control" value="@City" @onchange="UpdateCity" />
</div>
<div class="p-2 mb-2">City Value: @City</div>
<button class="btn btn-primary" @onclick="@(() => City = "Paris")">Paris</button>
<button class="btn btn-primary" @onclick="@(() => City = "Chicago")">Chicago</button>
@code {
    public string City { get; set; } = "London";
    public void UpdateCity(ChangeEventArgs e) {
        City = e.Value as string;
    }
}
Listing 33-16.

The Contents of the Bindings.razor File in the Blazor Folder

The @onchange attribute registers the UpdateCity method as a handler for the change event from the input element. The events are described using the ChangeEventArgs class, which provides a Value property. Each time a change event is received, the City property is updated with the contents of the input element.

The input element’s value attribute creates a relationship in the other direction so that when the value of the City property changes, so does the element’s value attribute, which changes the text displayed to the user. To apply the new Razor Component, change the component attribute in the Razor Page, as shown in Listing 33-17.
@page "/pages/blazor"
<h4 class="bg-primary text-white text-center p-2">Events</h4>
<component type="typeof(Advanced.Blazor.Bindings)" render-mode="Server" />
Listing 33-17.

Using a Razor Component in the Blazor.cshtml File in the Pages Folder

To see both parts of the relationship defined by the binding in Listing 33-16, restart ASP.NET Core, navigate to http://localhost:5000/pages/blazor, and edit the content of the input element. The change event is triggered only when the input element loses the focus, so once you have finished editing, press the Tab key or click outside of the input element; you will see the value you entered displayed through the Razor expression in the div element, as shown on the left of Figure 33-11. Click one of the buttons, and the City property will be changed to Paris or Chicago, and the selected value will be displayed by both the div element and the input element, as shown on the right of the figure.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig11_HTML.jpg
Figure 33-11.

Creating a two-way relationship between an element and a property

Two-way relationships involving the change event can be expressed as data bindings, which allows both the value and the event to be configured with a single attribute, as shown in Listing 33-18.
<div class="form-group">
    <label>City:</label>
    <input class="form-control" @bind="City" />
</div>
<div class="p-2 mb-2">City Value: @City</div>
<button class="btn btn-primary" @onclick="@(() => City = "Paris")">Paris</button>
<button class="btn btn-primary" @onclick="@(() => City = "Chicago")">Chicago</button>
@code {
    public string City { get; set; } = "London";
    //public void UpdateCity(ChangeEventArgs e) {
    //    City = e.Value as string;
    //}
}
Listing 33-18.

Using a Data Binding in the Bindings.razor File in the Blazor Folder

The @bind attribute is used to specify the property that will be updated when the change event is triggered and that will update the value attribute when it changes. The effect in Listing 33-18 is the same as Listing 33-16 but expressed more concisely and without the need for a handler method or a lambda function to update the property.

Changing the Binding Event

By default, the change event is used in bindings, which provides reasonable responsiveness for the user without requiring too many updates from the server. The event used in a binding can be changed by using the attributes described in Table 33-6.
Table 33-6.

The Binding Attributes for Specifying an Event

Attribute

Description

@bind-value

This attribute is used to select the property for the data binding.

@bind-value:event

This attribute is used to select the event for the data binding.

These attributes are used instead of @bind, as shown in Listing 33-19, but can be used only with events that are represented with the ChangeEventArgs class. This means that only the onchange and oninput events can be used, at least in the current release.
<div class="form-group">
    <label>City:</label>
    <input class="form-control" @bind-value="City" @bind-value:event="oninput"  />
</div>
<div class="p-2 mb-2">City Value: @City</div>
<button class="btn btn-primary" @onclick="@(() => City = "Paris")">Paris</button>
<button class="btn btn-primary" @onclick="@(() => City = "Chicago")">Chicago</button>
@code {
    public string City { get; set; } = "London";
}
Listing 33-19.

Specifying an Event for a Binding in the Bindings.razor File in the Blazor Folder

This combination of attributes creates a binding for the City property that is updated when the oninput event is triggered, which happens after every keystroke, rather than only when the input element loses the focus. To see the effect, restart ASP.NET Core, navigate to http://localhost:5000/pages/blazor, and start typing into the input element. The City property will be updated after every keystroke, as shown in Figure 33-12.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig12_HTML.jpg
Figure 33-12.

Changing the event in a data binding

Creating DateTime Bindings

Blazor has special support for creating bindings for DateTime properties, allowing them to be expressed using a specific culture or a format string. This feature is applied using the parameters described in Table 33-7.
Table 33-7.

The DateTime Parameters

Name

Description

@bind:culture

This attribute is used to select a CultureInfo object that will be used to format the DateTime value.

@bind:format

This attribute is used to specify a data formatting string that will be used to format the DateTime value.

Tip

If you have used the @bind-value and @bind-value:event attributes to select an event, then you must use the @bind-value:culture and @bind-value:format parameters instead.

Listing 33-20 shows the use of these attributes with a DateTime property.

Note

The formatting strings used in these examples are described at https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=netcore-3.1.

@using System.Globalization
<div class="form-group">
    <label>City:</label>
    <input class="form-control" @bind-value="City" @bind-value:event="oninput"  />
</div>
<div class="p-2 mb-2">City Value: @City</div>
<button class="btn btn-primary" @onclick="@(() => City = "Paris")">Paris</button>
<button class="btn btn-primary" @onclick="@(() => City = "Chicago")">Chicago</button>
<div class="form-group mt-2">
    <label>Time:</label>
    <input class="form-control my-1" @bind="Time" @bind:culture="Culture"
        @bind:format="MMM-dd" />
    <input class="form-control my-1" @bind="Time" @bind:culture="Culture" />
    <input class="form-control" type="date" @bind="Time" />
</div>
<div class="p-2 mb-2">Time Value: @Time</div>
<div class="form-group">
    <label>Culture:</label>
    <select class="form-control" @bind="Culture">
        <option value="@CultureInfo.GetCultureInfo("en-us")">en-US</option>
        <option value="@CultureInfo.GetCultureInfo("en-gb")">en-GB</option>
        <option value="@CultureInfo.GetCultureInfo("fr-fr")">fr-FR</option>
    </select>
</div>
@code {
    public string City { get; set; } = "London";
    public DateTime Time { get; set; } = DateTime.Parse("2050/01/20 09:50");
    public CultureInfo Culture { get; set; } = CultureInfo.GetCultureInfo("en-us");
}
Listing 33-20.

Using a DateTime Property in the Bindings.razor File in the Blazor Folder

There are three input elements that are used to display the same DataTime value, two of which have been configured using the attributes from Table 33-7. The first element has been configured with a culture and a format string, like this:
...
<input class="form-control my-1" @bind="Time" @bind:culture="Culture"
        @bind:format="MMM-dd" />
...
The DateTime property is displayed using the culture picked in the select element and with a format string that displays an abbreviated month name and the numeric date. The second input element specifies just a culture, which means the default formatting string will be used.
...
<input class="form-control my-1" @bind="Time" @bind:culture="Culture" />
...
To see how dates are displayed, restart ASP.NET Core, request http://localhost:5000/pages/blazor, and use the select element to pick different culture settings. The settings available represent English as it is used in the United States, English as it used in the United Kingdom, and French as it is used in France. Figure 33-13 shows the formatting each produces.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig13_HTML.jpg
Figure 33-13.

Formatting DateTime values

The initial locale in this example is en-US. When you switch to en-GB, the order in which the month and date appear changes. When you switch to en-FR, the abbreviated month name changes.

LETTING THE BROWSER FORMAT DATES
Notice that the value displayed by the third input element in Listing 33-20 doesn’t change, regardless of the locale you choose. This input element has neither of the attributes described in Table 33-7 but does have its type attribute set to date, like this:
...
<input class="form-control" type="date" @bind="Time" />
...

You should not specify a culture or a format string when setting the type attribute to date, datetime-local, month, or time, because Blazor will automatically format date values into a culture-neutral format that the browser translates into the user’s locale. Figure 33-11 shows how the date is formatted in the en-US locale but the user will see the date expressed in their local convention.

Using Class Files to Define Components

If you don’t like the mix of code and markup that Razor Components support, you can use C# class files to define part, or all, of the component.

Using a Code-Behind Class

The @code section of a Razor Component can be defined in a separate class file, known as a code-behind class or code-behind file. Code-behind classes for Razor Components are defined as partial classes with the same name as the component they provide code for.

Add a Razor Component named Split.razor to the Blazor folder with the content shown in Listing 33-21.
<ul class="list-group">
    @foreach (string name in Names) {
        <li class="list-group-item">@name</li>
    }
</ul>
Listing 33-21.

The Contents of the Split.razor File in the Blazor Folder

This file contains only HTML content and Razor expressions and renders a list of names that it expects to receive through a Names property. To provide the component with its code, add a class file named Split.razor.cs to the Blazor folder and use it to define the partial class shown in Listing 33-22.
using Advanced.Models;
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Linq;
namespace Advanced.Blazor {
    public partial class Split {
        [Inject]
        public DataContext Context { get; set; }
        public IEnumerable<string> Names => Context.People.Select(p => p.Firstname);
    }
}
Listing 33-22.

The Contents of the Split.razor.cs File in the Blazor Folder

The partial class must be defined in the same namespace as its Razor Component and have the same name. For this example, that means the namespace is Advanced.Blazor, and the class name is Splt. Code-behind classes do not define constructors and receive services using the Inject attribute. Listing 33-23 applies the new component.
@page "/pages/blazor"
<h4 class="bg-primary text-white text-center p-2">Code-Behind</h4>
<component type="typeof(Advanced.Blazor.Split)" render-mode="Server" />
Listing 33-23.

Applying a New Component in the Blazor.cshtml File in the Pages Folder

Restart ASP.NET Core and request http://localhost:5000/pages/blazor, and you will see the response shown in Figure 33-14.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig14_HTML.jpg
Figure 33-14.

Using a code-behind class to define a Razor Component

Defining a Razor Component Class

Razor Components can be defined entirely in a class file, although this can be less expressive than using Razor expressions. Add a class file named CodeOnly.cs to the Blazor folder and use it to define the class shown in Listing 33-24.
using Advanced.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web;
using System.Collections.Generic;
using System.Linq;
namespace Advanced.Blazor {
    public class CodeOnly : ComponentBase {
        [Inject]
        public DataContext Context { get; set; }
        public IEnumerable<string> Names => Context.People.Select(p => p.Firstname);
        public bool Ascending { get; set; } = false;
        protected override void BuildRenderTree(RenderTreeBuilder builder) {
            IEnumerable<string> data = Ascending
                ? Names.OrderBy(n => n) : Names.OrderByDescending(n => n);
            builder.OpenElement(1, "button");
            builder.AddAttribute(2, "class", "btn btn-primary mb-2");
            builder.AddAttribute(3, "onclick",
                EventCallback.Factory.Create<MouseEventArgs>(this,
                    () => Ascending = !Ascending));
            builder.AddContent(4, new MarkupString("Toggle"));
            builder.CloseElement();
            builder.OpenElement(5, "ul");
            builder.AddAttribute(6, "class", "list-group");
            foreach (string name in data) {
                builder.OpenElement(7, "li");
                builder.AddAttribute(8, "class", "list-group-item");
                builder.AddContent(9, new MarkupString(name));
                builder.CloseElement();
            }
            builder.CloseElement();
        }
    }
}
Listing 33-24.

The Contents of the CodeOnly.cs File in the Blazor Folder

The base class for components is ComponentBase. The content that would normally be expressed as annotated HTML elements is created by overriding the BuildRenderTree method and using the RenderTreeBuilder parameter. Creating content can be awkward because each element is created and configured using multiple code statements, and each statement must have a sequence number that the compiler uses to match up code and content. The OpenElement method starts a new element, which is configured using the AddElement and AddContent methods and then completed with the CloseElement method. All the features available in regular Razor Components are available, including events and bindings, which are set up by adding attributes to elements, just as if they were defined literally in a .razor file. The component in Listing 33-24 displays a list of sorted names, with the sort direction altered when a button element is clicked. Listing 33-25 applies the component so that it will be displayed to the user.
@page "/pages/blazor"
<h4 class="bg-primary text-white text-center p-2">Class Only</h4>
<component type="typeof(Advanced.Blazor.CodeOnly)" render-mode="Server" />
Listing 33-25.

Applying a New Component in the Blazor.cshtml File in the Pages Folder

Restart ASP.NET Core and request http://localhost:5000/pages/blazor to see the content produced by the class-based Razor Component. When you click the button, the sort direction of the names in the list is changed, as shown in Figure 33-15.
../images/338050_8_En_33_Chapter/338050_8_En_33_Fig15_HTML.jpg
Figure 33-15.

Defining a component entirely in code

Summary

In this chapter, I introduced Blazor Server, explained the problem it solves, and described the advantages and disadvantages it presents. I showed you how to configure an ASP.NET Core application to enable Blazor Server and showed you the basic features that are available when using Razor Components, which are the Blazor building blocks. In the next chapter, I continue to describe the features provided by Blazor.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.41.214