Build your first Blazor client-side application

In the previous article you learned to build your first Blazor server-side application. In this article you will develop the same data entry form using Blazor client-side application. Blazor client-side applications use WebAssembly (Wasm) to run. A WebAssembly capable browser downloads the Blazor application on the client-side and then executes it within the boundaries of the browser.

Let's get going.

The data entry form you will build in this article is shown below:

 

As you can see, the page displays a list of CustomerIDs from Customers table of Northwind database. Selecting a CustomerID and clicking on the Show Data button displays CompanyName, ContactName, and Country values of that customer. You can modify these values and click on Update button to save the changes back to the database.

Begin by creating a new Blazor client-side application in Visual Studio.

There are two project templates in Visual Studio that create a Blazor client-side project - Blazor (client-side) and Blazor (ASP.NET Core hosted). The former template creates a standalone Blazor client-side application that doesn't talk with the server. The latter project template creates a Blazor client-side application that is served by an ASP.NET Core application. Since the Blazor application is hosted inside an ASP.NET Core application, you can call server-side code such as Web APIs from the Blazor code. In our example you use a Web API to perform the database update. So, you need to pick Blazor (ASP.NET Core hosted) project template.

Take a look at the Solution Explorer once Visual Studio finishes creating the projects. You will find three projects in the solution. The Client project is a Blazor application that represents the client side UI of the application. The Server project is an ASP.NET Core application that serves the Client application to the browser. This project can also contain EF Core model, Web APIs and other server side code. The Shared project contains code that can be shared between the Client and the Server projects. In our specific example you don't need this project as such.

Once the projects gets created. Add NuGet package for Entity Framework Core to the Server project - Microsoft.EntityFrameworkCore.SqlServer.

Then add two classes that represent DbContext and Customer entity. These classes are added to the Data folder and are shown below:

public class Customer
{
    [Key]
    [Required]
    [StringLength(5, MinimumLength = 5)]
    public string CustomerID { get; set; }

    [Required]
    [StringLength(30)]
    public string CompanyName { get; set; }

    [Required]
    [StringLength(30)]
    public string ContactName { get; set; }

    [Required]
    [StringLength(15)]
    public string Country { get; set; }

}
public class AppDbContext:DbContext
{

    public AppDbContext(
DbContextOptions<AppDbContext> options) : 
base(options)
    {

    }
    public DbSet<Customer> Customers { get; set; }
}

These classes are quite straightforward. Notice that the Customer class uses data annotations for the sake of model validation.

To perform database update operations you need a Web API. So, add a Web API controller class to the Server project's Controller folder. This class is shown below:

[Route("api/[controller]")]
public class CustomersController : ControllerBase
{

    private AppDbContext db = null;

    public CustomersController(AppDbContext db)
    {
        this.db = db;
    }


    [HttpGet]
    public async Task<IActionResult> GetAsync()
    {
        List<Customer> data = await 
db.Customers.ToListAsync();
        return Ok(data);
    }


    [HttpGet("{id}")]
    public async Task<IActionResult> GetAsync
(string id)
    {
        Customer cust = await 
db.Customers.FindAsync(id);
        return Ok(cust);
    }


    [HttpPut("{id}")]
    public async Task<IActionResult> PutAsync
(string id, [FromBody]Customer cust)
    {
        if (ModelState.IsValid)
        {
            db.Customers.Update(cust);
            await db.SaveChangesAsync();
            return Ok
("\"Customer updated successfully!\"");
        }
        else
        {
            return BadRequest
("\"Error while updating customer!\"");
        } 
    }
}

The CustomersController class contains three methods - GetAsync(), GetAsync(id), and PutAsync(). These methods perform the respective operations. Once developed the AppDbContext needs to be registered with the DI container. This is done in the ConfigureServices() of the Startup class (make sure to do this in Server project).

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddDbContext<AppDbContext>(options => 
options.UseSqlServer("data source=.;
initial catalog=Northwind;integrated security=true;"));
}

You register AppDbContext with the DI container using AddDbContext() method.

Next, go to the Client project and add Customer class to its Data folder. We need this Customer class in the client application because the Web API from the Server application is going to return data wrapped in this data structure. You could have isolated the Customer class into Shared project but for the sake of simplicity you can add it to the Client project itself. Make sure that the Customer class has data annotations for performing model validation.

Then add a new Razor Component named Customers.razor to the Pages folder.

Open Customers.razor file and following code at the top:

@page "/customers"
@using BlazorDemo.Client.Data
@inject HttpClient http

The @page directive controls the route at which this component can be accessed. In this case the component is accessible at this URL : http://localhost:1234/customers. The @using directives uses BlazorDemo.Client.Data namespace so that model classes can be referenced in the code. Notice the @inject directive that injects an instance of HttpClient into the component. You need HttpClient object to invoke the Customers Web API.

Now, add the following code to the @code block.

@code {
    Customer cust = new Customer();
    List<Customer> customers = null;
    string Message { get; set; }
}

As you might have guessed the @code block is used to write C# code (members, properties, methods) that interacts with the user interface. Here, the code declares a member of type Customer. This member is used during data binding with the form (discussed shortly). The List<Customer> stores a list of customers returned from GetAsync() method of the Web API and is also used to fill the dropdown list. The Message property is used to notify user of the result of database update operation.

As soon as the component is initialized you need to call the GetAsync() method of the Web API so that the CustomerID dropdown list can be populated. To do this, you write OnInitAsync() method in @code block as shown below:

protected override async Task OnInitAsync() 
{
    customers = await http.GetJsonAsync
<List<Customer>>("/api/customers"); 
}

You are basically overriding OnInitAsync() method of ComponentBase class (Blazor components inherit from ComponentBase class). Inside, you call GetJsonAsync() method of HttpClient to invoke the GetAsync() action of the Web API. The data returned by the GetAsync() action is stored into customers member.

If you observe the user interface shown at the beginning of this article, you will find that there are two buttons - Show Data and Update. You need click event handlers for these buttons.

private async Task OnShowDataAsync()
{
    cust = await http.GetJsonAsync
<Customer>("/api/customers/" + cust.CustomerID);
}

private async Task OnUpdateAsync()
{
    Message = await http.PutJsonAsync
<string>("/api/customers/" + cust.CustomerID, cust);
}

You will be binding cust member with the data entry form. The data binding features of Blazor allow you to do one-way and two-way data binding. When you select a CustomerID the CustomerID property of cust member is automatically changed to reflect the selection. So, when you pass cust.CustomerID in the GetJsonAsync() call you are passing the selected value. The returned Customer object's values are reflected in the other textboxes. The  OnUpdateAsync() event handler simply calls the PutJsonAsync() method of HttpClient to save the changes to the database.

Below the @code block write the following Blazor markup.

<h2>Customer Manager</h2>

<EditForm Model="cust">
    <DataAnnotationsValidator>
    </DataAnnotationsValidator>

    <table border="0" cellpadding="20">
        <tr>
            <td>Customer ID :</td>
            <td>
                <InputSelect id="customerid" 
@bind-Value="@cust.CustomerID">
                    @if (customers != null)
                    {
                        foreach (var c in customers)
                        {
                            <option value="@c.CustomerID">
@c.CustomerID</option>
                        }
                    }
                </InputSelect>
                <button type="button" 
@onclick="@OnShowData">Show Data</button>
            </td>
        </tr>
        <tr>
            <td>Company Name :</td>
            <td>
                <InputText id="companyname" 
@bind-Value="@cust.CompanyName" />
                <ValidationMessage 
For="@(() => cust.CompanyName)" />
            </td>
        </tr>
        <tr>
            <td>Contact Name :</td>
            <td>
                <InputText id="contactname" 
@bind-Value="@cust.CompanyName" />
                <ValidationMessage 
For="@(() => cust.ContactName)" />
            </td>
        </tr>
        <tr>
            <td>Country :</td>
            <td>
                <InputText id="country" 
@bind-Value="@cust.Country" />
                <ValidationMessage 
For="@(() => cust.Country)" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="button" 
@onclick="@OnUpdate">Update</button>
            </td>
        </tr>
    </table>
    <ValidationSummary></ValidationSummary>
</EditForm>
<h5>@Message</h5>

The above markup shows several Blazor features including data binding, components, data annotations and validations.

The <EditForm> component is used to render a data entry <form>. The Model property sets the model for the form to an object (Customer object in this case). The <DataAnnotationsValidator> component enables form validations based on data annotation attributes such as [Required] and [StringLength].

The CustomerID dropdown list is rendered using an <InputSelect> component (same as <select> HTML element). To bind the dropdown list with model's property @bind-Value attribute performs two-way data binding (from cust to dropdown and from dropdown to cust). To populate all the CustomerIDs into the dropdown list C# foreach loop is used. The onclick event handler of Show Data button is wired to OnShowData() method written in the @code block.

To display CompanyName, ContactName, and Country <InputText> component is used. This component renders HTML <input> element. Two-way data binding is done as before. To display field level validation errors <ValidationMessage> component is used. The onclick  event of Update button is handled by OnUpdate() method. At the bottom, there is <ValidationSummary> component that displays a collective list of error messages.

Finally, the Message property is displayed so that use known the outcome of update operation.

Compile the project and run the Server application by pressing F5. Once the browser window loads the application, go to the address bar and change the URL to http://localhost:1234/customers. You should now see the UI of the Customers component. Check whether data can be updated or not. 

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant and trainer by profession specializing in Microsoft web development technologies. Having embraced the Yoga way of life he is also a meditation teacher and spiritual guide to his students. He is a prolific author and writes regularly about software development and yoga on his websites. He is programming, meditating, writing, and teaching for over 27 years. To know more about his ASP.NET online courses go here. More details about his Kriya and Meditation online course are available here.

Posted On : 22 July 2019