Create a Single Page App with Blazor Server and Entity Framework Core 3.0

Introduction

Blazor is a new framework built by Microsoft for creating interactive client-side web UI with a .NET codebase. We can write both client-side and server-side code in C#.NET itself. I have already written an article about Blazor on C# Corner. Please refer to the below article for more basics about the Blazor framework.

Create A Simple Blazor Server Application With .NET Core 3.0

We will create a simple Employee app using Blazor. In this app, we can add, edit, read, and delete employee information. We will use entity framework core 3.0 to store and retrieve data into an SQL database.

Create Blazor app in Visual Studio 2019

Visual Studio's latest version is shipped with .NET Core 3.0 SDK. We can choose a Blazor template and create a new Blazor project.

 .NET Core

We are going to use entity framework core and data migration commands to create an SQL database and table. We must install “Microsoft.EntityFrameworkCore.SqlServer” and “Microsoft.EntityFrameworkCore.Tools” libraries to perform these actions.

EntityFrameworkCore

Microsoft

Currently, Blazor doesn’t support Json methods like “GetJsonAsync”, “PostJsonAsync” and “PutJsonAsync” in HttpClient class. We will create a custom HttpClient class to add these methods. Hence, we must install the “Newtonsoft.Json” library also to the project.

GetJsonAsync

We can create an Employee class and SqlDbContext classes.

Employee. cs

namespace BlazorEmployeeEFCore.Data
{
    public class Employee
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Department { get; set; }
        public string Designation { get; set; }
        public string Company { get; set; }
    }
}

SqlDbContext.cs

using Microsoft.EntityFrameworkCore;
namespace BlazorEmployeeEFCore.Data
{
    public class SqlDbContext : DbContext
    {
        public SqlDbContext(DbContextOptions<SqlDbContext> options)
            : base(options)
        {
        }
        public DbSet<Employee> Employees { get; set; }
    }
}

SqlDbContext class inherits the DbContext class and contains an Employee property to perform all SQL-related operations.

We can add an SQL connection string in the appsettings.json file.

 SQL connection

We must register the SqlDbContext class in the startup class as well.

SqlDbContext

I have enabled the detailed error option also for the Blazor application. So that, we can see the detailed error messages in the developer console, if any occurs.

Create SQL database and table using DB migration

We are ready to create an SQL database and table using DB migration with the code-first approach. We have already added the “Microsoft.EntityFrameworkCore.Tools” library for this purpose.

Open “Package Manager Console” from the Tools menu and use the below command to create a migration script.

add-migration Initial

This command will create a new migration script class file under a new “Migrations” folder.

We can use the below command to execute the migration script class.

update-database

We have used the default local SQL server available with Visual Studio. If you check with SQL server object explorer, you can see a new database and a table created in the local server after DB migration.

We can create an Employee controller for Web API service. We will consume the web methods from this controller in our Blazor components for CRUD operations later.

EmployeesController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace BlazorEmployeeEFCore.Data
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeesController : ControllerBase
    {
        private readonly SqlDbContext _dbContext;

        public EmployeesController(SqlDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        [Route("Get")]
        public async Task<List<Employee>> Get()
        {
            return await _dbContext.Employees.ToListAsync();
        }

        [HttpPost]
        [Route("Create")]
        public async Task<bool> Create([FromBody]Employee employee)
        {
            if (ModelState.IsValid)
            {
                employee.Id = Guid.NewGuid().ToString();
                _dbContext.Add(employee);
                try
                {
                    await _dbContext.SaveChangesAsync();
                    return true;
                }
                catch (DbUpdateException)
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        [HttpGet]
        [Route("Details/{id}")]
        public async Task<Employee> Details(string id)
        {
            return await _dbContext.Employees.FindAsync(id);
        }

        [HttpPut]
        [Route("Edit/{id}")]
        public async Task<bool> Edit(string id, [FromBody]Employee employee)
        {
            if (id != employee.Id)
            {
                return false;
            }

            _dbContext.Entry(employee).State = EntityState.Modified;
            await _dbContext.SaveChangesAsync();
            return true;
        }

        [HttpDelete]
        [Route("Delete/{id}")]
        public async Task<bool> DeleteConfirmed(string id)
        {
            var employee = await _dbContext.Employees.FindAsync(id);
            if (employee == null)
            {
                return false;
            }

            _dbContext.Employees.Remove(employee);
            await _dbContext.SaveChangesAsync();
            return true;
        }
    }
}

We have added all the logic for CRUD actions inside the controller. All the methods are self-explanatory.

You must add the “MapControllers” endpoint in the “Configure” method inside the Startup class to get service calls from the Web API controller.

MapControllers

As I mentioned earlier, currently HttpClient class doesn’t contain the “GetJsonAsync”, “PostJsonAsync”, and “PutJsonAsyc” methods. I have already raised an issue in the Microsoft GitHub account for adding these methods. Please support, me if you can.

We can create a custom HttpClient class to add these missing methods to handle JSON data.

CustomHttpClient.cs

using Newtonsoft.Json;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace BlazorEmployeeEFCore.Data
{
    public class CustomHttpClient : HttpClient
    {
        public async Task<T> GetJsonAsync<T>(string requestUri)
        {
            HttpClient httpClient = new HttpClient();
            var httpContent = await httpClient.GetAsync(requestUri);
            string jsonContent = httpContent.Content.ReadAsStringAsync().Result;
            T obj = JsonConvert.DeserializeObject<T>(jsonContent);
            httpContent.Dispose();
            httpClient.Dispose();
            return obj;
        }

        public async Task<HttpResponseMessage> PostJsonAsync<T>(string requestUri, T content)
        {
            HttpClient httpClient = new HttpClient();
            string myContent = JsonConvert.SerializeObject(content);
            StringContent stringContent = new StringContent(myContent, Encoding.UTF8, "application/json");
            var response = await httpClient.PostAsync(requestUri, stringContent);
            httpClient.Dispose();
            return response;
        }

        public async Task<HttpResponseMessage> PutJsonAsync<T>(string requestUri, T content)
        {
            HttpClient httpClient = new HttpClient();
            string myContent = JsonConvert.SerializeObject(content);
            StringContent stringContent = new StringContent(myContent, Encoding.UTF8, "application/json");
            var response = await httpClient.PutAsync(requestUri, stringContent);
            httpClient.Dispose();
            return response;
        }
    }
}

In the GetJsonAsync method, we have fetched the data from the Web API service and converted string data to object. We can pass object type as a generic parameter. In our case, the Employee is the object type.

In PostJsonAsync and PutJsonAsync methods, we have serialized the object type data to string content and called PostAsync and PutAsync methods respectively. These methods are already available in the HttpClient class.

We must pass the base URL endpoint from each Blazor component to call the Web API methods. We can store the application base URL inside the appsettings.js file and create a service to get these values from the appsettings file. We can inject this service from each Blazor component.

AppSettingsService.cs

using Microsoft.Extensions.Configuration;
namespace BlazorEmployeeEFCore.Data
{
    public class AppSettingsService
    {
        private readonly IConfiguration _config;
        public AppSettingsService(IConfiguration config)
        {
            _config = config;
        }
        public string GetBaseUrl()
        {
            return _config.GetValue<string>("MySettings:BaseUrl");
        }
    }
}

We can store the base URL in the app settings configuration file.

Configuration file

For my application, the port number is 5000. In your case, it may be different. Please give the correct port number in the app settings.

We must register CustomHttpClient and AppSettingsService in the Startup class to inject from Blazor components.

Blazor components

We can create four Blazor components inside the “Pages” folder to perform all CRUD actions.

ListEmployees.razor

@using BlazorEmployeeEFCore.Data
@page "/listemployees"
@inject CustomHttpClient Http
@inject AppSettingsService AppSettingsService
<h2>Employee Details</h2>
<p>
    <a href="/addemployee">Create New Employee</a>
</p>
@if (employees == null)
{
    <img src="./basicloader.gif" />
}
else
{
    <table class='table'>
        <thead>
            <tr>
                <th>Name</th>
                <th>Department</th>
                <th>Designation</th>
                <th>Company</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var employee in employees)
            {
                <tr>
                    <td>@employee.Name</td>
                    <td>@employee.Department</td>
                    <td>@employee.Designation</td>
                    <td>@employee.Company</td>
                    <td>
                        <a href='/editemployee/@employee.Id'>Edit</a>
                        <a href='/deleteemployee/@employee.Id'>Delete</a>
                    </td>
                </tr>

            }
        </tbody>
    </table>
}

@code {
    Employee[] employees;
    string baseUrl;

    protected override async Task OnInitializedAsync()
    {
        baseUrl = AppSettingsService.GetBaseUrl();
        employees = await Http.GetJsonAsync<Employee[]>(baseUrl + "/api/employees/get");
    }
}

We have injected CustomHttpClient and AppSettingsService services in the above component. We have called the GetJsonAsync method inside the OnInitializedAsync event. This is the initial page lifecycle event of a Blazor application.

AddEmpoyee.razor

@using BlazorEmployeeEFCore.Data
@page "/addemployee"
@inject CustomHttpClient Http
@inject NavigationManager NavigationManager
@inject AppSettingsService AppSettingsService

<h2>Create Employee</h2>
<hr />
<form>
    <p class="row">
        <p class="col-md-8">
            <p class="form-group">
                <label for="Name" class="control-label">Name</label>
                <input for="Name" class="form-control" @bind="@employee.Name" />
            </p>
            <p class="form-group">
                <label for="Department" class="control-label">Department</label>
                <input for="Department" class="form-control" @bind="@employee.Department" />
            </p>
            <p class="form-group">
                <label for="Designation" class="control-label">Designation</label>
                <input for="Designation" class="form-control" @bind="@employee.Designation" />
            </p>
            <p class="form-group">
                <label for="Company" class="control-label">Company</label>
                <input for="Company" class="form-control" @bind="@employee.Company" />
            </p>
        </p>
    </p>
    <p class="row">
        <p class="col-md-4">
            <p class="form-group">
                <input type="button" class="btn btn-primary" @onclick="@CreateEmployee" value="Save" />
                <input type="button" class="btn" @onclick="@Cancel" value="Cancel" />
            </p>
        </p>
    </p>
</form>

@code {

    Employee employee = new Employee();
    string baseUrl;

    protected async Task CreateEmployee()
    {
        baseUrl = AppSettingsService.GetBaseUrl();
        await Http.PostJsonAsync(baseUrl + "/api/employees/create", employee);
        NavigationManager.NavigateTo("listemployees");
    }

    void Cancel()
    {
        NavigationManager.NavigateTo("listemployees");
    }
}

We have injected the NavigationManager class also in this component. We can control the page navigation with this class. We have also used the “@bind” directive to handle two-way data binding in Blazor. “@onclick” is an event handler used for calling a method.

EditEmployee.razor

@using BlazorEmployeeEFCore.Data
@page "/editemployee/{id}"
@inject CustomHttpClient Http
@inject NavigationManager NavigationManager
@inject AppSettingsService AppSettingsService

<h2>Edit Employee</h2>
<hr />
<form>
    <p class="row">
        <p class="col-md-8">
            <p class="form-group">
                <label for="Name" class="control-label">Name</label>
                <input for="Name" class="form-control" @bind="@employee.Name" />
            </p>
            <p class="form-group">
                <label for="Department" class="control-label">Department</label>
                <input for="Department" class="form-control" @bind="@employee.Department" />
            </p>
            <p class="form-group">
                <label for="Designation" class="control-label">Designation</label>
                <input for="Designation" class="form-control" @bind="@employee.Designation" />
            </p>
            <p class="form-group">
                <label for="Company" class="control-label">Company</label>
                <input for="Company" class="form-control" @bind="@employee.Company" />
            </p>
        </p>
    </p>
    <p class="row">
        <p class="form-group">
            <input type="button" class="btn btn-primary" @onclick="@UpdateEmployee" value="Update" />
            <input type="button" class="btn" @onclick="@Cancel" value="Cancel" />
        </p>
    </p>
</form>

@code {

    [Parameter]
    public string id { get; set; }
    string baseUrl;

    Employee employee = new Employee();

    protected override async Task OnInitializedAsync()
    {
        baseUrl = AppSettingsService.GetBaseUrl();
        employee = await Http.GetJsonAsync<Employee>(baseUrl + "/api/employees/details/" + id);
    }

    protected async Task UpdateEmployee()
    {
        await Http.PutJsonAsync(baseUrl + "/api/employees/edit/" + id, employee);
        NavigationManager.NavigateTo("listemployees");
    }

    void Cancel()
    {
        NavigationManager.NavigateTo("listemployees");
    }
}

DeleteEmployee.razor

@using BlazorEmployeeEFCore.Data
@page "/deleteemployee/{id}"
@inject CustomHttpClient Http
@inject NavigationManager NavigationManager
@inject AppSettingsService AppSettingsService

<h2>Delete</h2>
<p>Are you sure you want to delete this Employee with Id :<b> @id</b></p>
<br />
<p class="col-md-4">
    <table class="table">
        <tr>
            <td>Name</td>
            <td>@employee.Name</td>
        </tr>
        <tr>
            <td>Department</td>
            <td>@employee.Department</td>
        </tr>
        <tr>
            <td>Designation</td>
            <td>@employee.Designation</td>
        </tr>
        <tr>
            <td>Company</td>
            <td>@employee.Company</td>
        </tr>
    </table>
    <p class="form-group">
        <input type="button" value="Delete" @onclick="@Delete" class="btn btn-primary" />
        <input type="button" value="Cancel" @onclick="@Cancel" class="btn" />
    </p>
</p>

@code {
    [Parameter]
    public string id { get; set; }
    string baseUrl;
    Employee employee = new Employee();

    protected override async Task OnInitializedAsync()
    {
        baseUrl = AppSettingsService.GetBaseUrl();
        employee = await Http.GetJsonAsync<Employee>(baseUrl + "/api/employees/details/" + id);
    }

    protected async Task Delete()
    {
        await Http.DeleteAsync(baseUrl + "/api/employees/delete/" + id);
        NavigationManager.NavigateTo("listemployees");
    }

    void Cancel()
    {
        NavigationManager.NavigateTo("listemployees");
    }
}

We can modify the NavMenu component inside the “Shared” folder to add routing for Employee data.

NavMenu.razor

<p class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorEmployeeEFCore</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</p>

<p class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="listemployees">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Employee data
            </NavLink>
        </li>
    </ul>
</p>

@code {
    bool collapseNavMenu = true;

    string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

We have completed the entire coding. We can run the application now.

 Entire coding

We can click the Employee data menu link to navigate the employee list page.

Employee data

We have already added a simple spinner in this component. We can click the “Create New Employee” link to create a new employee record.

Create New Employee

We can perform the other CRUD operations like edit and delete as well in this application.

CRUD operations

I have noticed one interesting thing, even though we have called various Web API methods, these were not triggered in the network tab of the developer console.

Web API

We can see only one “negotiate” action in entire web requests. This will improve the performance of the entire application.

Conclusion

In this post, we have seen how to create a single-page application with Blazor and entity framework core 3.0. We have created the database and table using database migration with code code-first approach. We have also created four Blazor components to perform all CRUD actions. We can see more awesome features of the Blazor framework in upcoming articles. Please share your valuable feedback.


Similar Articles