Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Creating an SPA Using Razor Pages With Blazor

DZone's Guide to

Creating an SPA Using Razor Pages With Blazor

In this article, we are going to create a Single Page Application (SPA) using Razor pages in Blazor with the help of Entity Framework Core database first approach.

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

In this article, we are going to create a Single Page Application (SPA) using Razor pages in Blazor with the help of the Entity Framework Core database first approach. Single-Page Applications are web applications that load a single HTML page and dynamically update that page as the user interacts with the app. We will be creating a sample Employee Record Management System and perform CRUD operations on it.

We will be using Visual Studio 2017 and SQL Server 2014.

Take a look at the final application.

Prerequisites

  • Install .NET Core 2.1 Preview 2 SDK from here.
  • Install Visual Studio 2017 v15.7 or above from here.
  • Install ASP.NET Core Blazor Language Services extension from here.
  • SQL Server 2008 or above.

The Blazor framework is not supported by versions below Visual Studio 2017 v15.7.

Source Code

You can get the source code from GitHub.

Creating a Table

We will be using a DB table to store all the records of employees.

Open SQL Server and use the following script to create Employee table.

CREATE TABLE Employee (  
EmployeeID int IDENTITY(1,1) PRIMARY KEY,  
Name varchar(20) NOT NULL ,  
City varchar(20) NOT NULL ,  
Department varchar(20) NOT NULL ,  
Gender varchar(6) NOT NULL   
)

Create a Blazor Web Application

Open Visual Studio and select File >> New >> Project.

After selecting the project, a "New Project" dialog will open. Select .NET Core inside the Visual C# menu from the left panel. Then, select "ASP.NET Core Web Application" from available project types. Set the name of the project to BlazorSPA and press OK.

After clicking on OK, a new dialog will open asking you to select the project template. You can observe two drop-down menus at the top left of the template window. Select ".NET Core" and "ASP.NET Core 2.0" from these dropdowns. Then, select "Blazor (ASP .NET Core hosted)" template and press OK.

Now, our Blazor solution will be created. You can observe the folder structure in Solution Explorer as shown in the below image.

You can observe that we have three project files created inside this solution.

  1. BlazorSPA.Client - It has the client-side code and contains the pages that will be rendered on the browser.
  2. BlazorSPA.Server - It has the server-side code, such as DB related operations and the web API.
  3. BlazorSPA.Shared - It contains the shared code that can be accessed by both client and server. It contains our model classes.

Scaffolding the Model to the Application

We are using the Entity Framework core database first approach to create our models. We will create our model class in the BlazorSPA.Shared project so that it can be accessible to both client and server projects.

Navigate to Tools >> NuGet Package Manager >> Package Manager Console. Select "BlazorSPA.Shared" from Default project drop-down. Refer to the image below:

First, we will install the package for the database provider that we are targeting which is SQL Server in this case. Hence, run the following command:

Install-Package Microsoft.EntityFrameworkCore.SqlServer

Since we are using Entity Framework Tools to create a model from the existing database, we will install the tools package as well. Hence, run the following comman

Install-Package Microsoft.EntityFrameworkCore.Tools

After you have installed both the packages, we will scaffold our model from the database tables using the following command,

Scaffold-DbContext "Your connection string here" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Tables Employee

Do not forget to put your own connection string (inside " "). After this command is executed successfully, you will see that a Models folder has been created and it contains two class files, myTestDBContext.cs and Employee.cs. Hence, we have successfully scaffolded our Models using the EF core database first approach.

At this point in time, the Models folder will have the following structure.

Creating Data Access Layer for the Application

Right-click on BlazorSPA.Server project and then select Add >> New Folder and name the folder, DataAccess. We will be adding our class to handle database related operations inside this folder only.

Right-click on the DataAccess folder and select Add >> Class. Name your class EmployeeDataAccessLayer.cs.

Open EmployeeDataAccessLayer.cs and put the following code into it.

using BlazorSPA.Shared.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorSPA.Server.DataAccess
{
    public class EmployeeDataAccessLayer
    {
        myTestDBContext db = new myTestDBContext();

        //To Get all employees details   
        public IEnumerable<Employee> GetAllEmployees()
        {
            try
            {
                return db.Employee.ToList();
            }
            catch
            {
                throw;
            }
        }

        //To Add new employee record     
        public void AddEmployee(Employee employee)
        {
            try
            {
                db.Employee.Add(employee);
                db.SaveChanges();
            }
            catch
            {
                throw;
            }
        }

        //To Update the records of a particluar employee    
        public void UpdateEmployee(Employee employee)
        {
            try
            {
                db.Entry(employee).State = EntityState.Modified;
                db.SaveChanges();
            }
            catch
            {
                throw;
            }
        }

        //Get the details of a particular employee    
        public Employee GetEmployeeData(int id)
        {
            try
            {
                Employee employee = db.Employee.Find(id);
                return employee;
            }
            catch
            {
                throw;
            }
        }

        //To Delete the record of a particular employee    
        public void DeleteEmployee(int id)
        {
            try
            {
                Employee emp = db.Employee.Find(id);
                db.Employee.Remove(emp);
                db.SaveChanges();
            }
            catch
            {
                throw;
            }
        }
    }
}

Here we have defined methods to handle database operations. GetAllEmployees will fetch all the employee data from Employee Table. Similarly, AddEmployee will create a new employee record and UpdateEmployee will update the record of an existing employee. GetEmployeeData will fetch the record of the employee corresponding to the employee ID passed to it and DeleteEmployee will delete the employee record corresponding to the employee id passed to it.

Adding the Web API Controller to the Application

Right-click on the BlazorSPA.Server/Controllers folder and select Add >> New Item. An "Add New Item" dialog box will open. Select ASP.NET from the left panel, then select "API Controller Class" from templates panel and put the name as EmployeeController.cs. Click Add.

This will create our API EmployeeController class.

We will call the methods of EmployeeDataAccessLayer class to fetch data and pass on the data to the client-side.

Open the EmployeeController.cs file and put the following code into it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BlazorSPA.Server.DataAccess;
using BlazorSPA.Shared.Models;
using Microsoft.AspNetCore.Mvc;

namespace BlazorSPA.Server.Controllers
{
    public class EmployeeController : Controller
    {
        EmployeeDataAccessLayer objemployee = new EmployeeDataAccessLayer();

        [HttpGet]
        [Route("api/Employee/Index")]
        public IEnumerable<Employee> Index()
        {
            return objemployee.GetAllEmployees();
        }

        [HttpPost]
        [Route("api/Employee/Create")]
        public void Create([FromBody] Employee employee)
        {
            if (ModelState.IsValid)
                objemployee.AddEmployee(employee);
        }

        [HttpGet]
        [Route("api/Employee/Details/{id}")]
        public Employee Details(int id)
        {

            return objemployee.GetEmployeeData(id);
        }

        [HttpPut]
        [Route("api/Employee/Edit")]
        public void Edit([FromBody]Employee employee)
        {
            if (ModelState.IsValid)
                objemployee.UpdateEmployee(employee);
        }

        [HttpDelete]
        [Route("api/Employee/Delete/{id}")]
        public void Delete(int id)
        {
            objemployee.DeleteEmployee(id);
        }

    }
}

At this point, our BlazorSPA.Server project has the following structure.

We are done with our backend logic. Therefore, we will now proceed to code our client-side.

Adding Razor Page to the Application

We will add the Razor Page in BlazorSPA.Client/Pages folder. By default, we have "Counter" and "Fetch Data" pages provided in our application. These default pages will not affect our application but, for the sake of this tutorial, we will delete the fetchdata and counter pages from the BlazorSPA.Client/Pages folder.

Right-click on the BlazorSPA.Client/Pages folder and then select Add >> New Item. An "Add New Item" dialog box will open. Sselect "ASP.NET Core" from the left-hand panel, then select "Razor Page" from the templates panel and name it EmployeeData.cshtml. Click Add.

This will add an EmployeeData.cshtml page to our BlazorSPA.Client/Pages folder. This razor page will have two files, EmployeeData.cshtml and EmployeeData.cshtml.cs.

Now we will add some code to these pages.

EmployeeData.cshtml.cs

Open EmployeeData.cshtmfl.cs and put the following code into it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using BlazorSPA.Shared.Models;
using Microsoft.AspNetCore.Blazor;
using Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Blazor.Services;

namespace BlazorSPA.Client.Pages
{
    public class EmployeeDataModel : BlazorComponent
    {
        [Inject]
        protected HttpClient Http { get; set; }
        [Inject]
        protected IUriHelper UriHelper { get; set; }

        [Parameter]
        protected string paramEmpID { get; set; } = "0";
        [Parameter]
        protected string action { get; set; }

        protected List<Employee> empList = new List<Employee>();
        protected Employee emp = new Employee();
        protected string title { get; set; }

        protected override async Task OnInitAsync()
        {
            await FetchEmployee();
        }
        protected async Task FetchEmployee()
        {
            title = "Employee Data";
            empList = await Http.GetJsonAsync<List<Employee>>("api/Employee/Index");
        }

        protected override async Task OnParametersSetAsync()
        {
            if (action == "create")
            {
                title = "Add Employee";
                emp = new Employee();
            }
            else if (paramEmpID != "0")
            {
                if (action == "edit")
                {
                    title = "Edit Employee";
                }
                else if (action == "delete")
                {
                    title = "Delete Employee";
                }

                emp = await Http.GetJsonAsync<Employee>("/api/Employee/Details/" + Convert.ToInt32(paramEmpID));
            }
        }

        protected async Task CreateEmployee()
        {
            if (emp.EmployeeId != 0)
            {
                await Http.SendJsonAsync(HttpMethod.Put, "api/Employee/Edit", emp);
            }
            else
            {
                await Http.SendJsonAsync(HttpMethod.Post, "/api/Employee/Create", emp);
            }
            UriHelper.NavigateTo("/employee/fetch");
            await FetchEmployee();
            this.StateHasChanged();
        }

        protected async Task DeleteEmployee()
        {
            await Http.DeleteAsync("api/Employee/Delete/" + Convert.ToInt32(paramEmpID));
            UriHelper.NavigateTo("/employee/fetch");
            await FetchEmployee();
            this.StateHasChanged();
        }

        protected void Cancel()
        {
            title = "Employee Data";
            UriHelper.NavigateTo("/employee/fetch");
            this.StateHasChanged();
        }
    }
}

Let us understand this code. We have defined a class (EmployeeDataModel)that will hold all our methods that we will use in the EmployeeData.cshtml page.

We are injecting the HttpClient service to enable web API calls and the IUriHelper service to enable URL redirections. After this, we have defined our parameter attributes - paramEmpID and action. These parameters are used in EmployeeData.cshtml to define the routes for our page. We have also declared a property, title, to display the heading to specify the current action that is being performed on the page.

The method FetchEmployeewill set the title to "Employee Data" and also fetch all the employee data by invoking our web API method. This method is called inside OnInitAsync so that the employee data is populated as the page loads.

The OnParametersSetAsync method is invoked when the parameter is set for the page. We will check if the action attribute of the parameter is "create," then we will set the title of the page to "Add Employee" and create a new object of type Employee. If the paramEmpID is not "0" then it is either an edit action or a delete action. We will then invoke our web API method to fetch the data for the employee id as it's set in the paramEmpID property.

The CreateEmployee method will check if it is invoked to add a new employee record or to edit an existing employee record. If the EmployeeId property is set then it is an "edit" request and we will send a PUT request to the web API. If EmployeeId is not set then it is a "create" request and we will send a POST request to the web API. We will then fetch the updated employee record by calling the FetchEmployee method and invoke StateHasChanged to display the updated changes on the UI.

The DeleteEmployee method will delete the employee record for the employee id as it's set in the paramEmpID property and display the updated records by invoking the FetchEmployee and StateHasChanged methods.

In the Cancel method, we will set the title property to "Employee Data," navigate to the fetch page, and call the StateHasChanged method to update the UI.

EmployeeData.cshtml

Open the EmployeeData.cshtml page and put the following code into it.

@page "/employee/{action}/{paramEmpID}"
@page "/employee/{action}"
@inherits EmployeeDataModel

<h1>@title</h1>

@if (action == "fetch")
{
    <p>
        <a href="/employee/create">Create New</a>
    </p>
}

@if (action == "create" || action == "edit")
{
    <form>
        <table class="form-group">
            <tr>
                <td>
                    <label for="Name" class="control-label">Name</label>
                </td>
                <td>
                    <input type="text" class="form-control" bind="@emp.Name" />
                </td>
                <td width="20">&nbsp;</td>
                <td>
                    <label for="Department" class="control-label">Department</label>
                </td>
                <td>
                    <input type="text" class="form-control" bind="@emp.Department" />
                </td>
            </tr>
            <tr>
                <td>
                    <label for="Gender" class="control-label">Gender</label>
                </td>
                <td>
                    <select asp-for="Gender" class="form-control" bind="@emp.Gender">
                        <option value="">-- Select Gender --</option>
                        <option value="Male">Male</option>
                        <option value="Female">Female</option>
                    </select>
                </td>
                <td width="20">&nbsp;</td>
                <td>
                    <label for="City" class="control-label">City</label>
                </td>
                <td>
                    <input type="text" class="form-control" bind="@emp.City" />
                </td>
            </tr>
            <tr>
                <td></td>
                <td>
                    <input type="submit" class="btn btn-success" onclick="@(async () => await CreateEmployee())" style="width:220px;" value="Save" />
                </td>
                <td></td>
                <td width="20">&nbsp;</td>
                <td>
                    <input type="submit" class="btn btn-danger" onclick="@Cancel" style="width:220px;" value="Cancel" />
                </td>
            </tr>
        </table>
    </form>
}
else if (action == "delete")
{
    <div class="col-md-4">
        <table class="table">
            <tr>
                <td>Name</td>
                <td>@emp.Name</td>
            </tr>
            <tr>
                <td>Gender</td>
                <td>@emp.Gender</td>
            </tr>
            <tr>
                <td>Department</td>
                <td>@emp.Department</td>
            </tr>
            <tr>
                <td>City</td>
                <td>@emp.City</td>
            </tr>
        </table>
        <div class="form-group">
            <input type="submit" class="btn btn-danger" onclick="@(async () => await DeleteEmployee())" value="Delete" />
            <input type="submit" value="Cancel" onclick="@Cancel" class="btn" />
        </div>
    </div>
}

@if (empList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class='table'>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Gender</th>
                <th>Department</th>
                <th>City</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var emp in empList)
            {
                <tr>
                    <td>@emp.EmployeeId</td>
                    <td>@emp.Name</td>
                    <td>@emp.Gender</td>
                    <td>@emp.Department</td>
                    <td>@emp.City</td>
                    <td>

                        <a href='/employee/edit/@emp.EmployeeId'>Edit</a>  |
                        <a href='/employee/delete/@emp.EmployeeId'>Delete</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

At the top, we have defined the routes for our page. There are two routes defined.

  1. /employee/{action}/{paramEmpID}: This will accept an action name along with the employee id. This route is invoked when we perform Edit or Delete operations. When we call edit or delete actions on a particular employee's data, the employee id is also passed as the URL parameter.
  2. /employee/{action}: This will only accept the action name. This route is invoked when we create a new employee data or we fetch the records of all the employees.

We are also inheriting the EmployeeDataModel class, which is defined in the EmployeeData.cshtml.cs file. This will allow us to use the methods defined in the EmployeeDataModel class.

After this, we are setting the title that will be displayed on our page. The title is dynamic and changes as per the action that is being executed currently on the page.

We will show the "Create New" link only if the action is "fetch." If the action is create or edit then "Create New" link will be hidden and we will display the form to get the user input. Inside the form, we have also defined two buttons, "Save" and "Cancel." Clicking on Save will invoke the CreateEmployee method whereas clicking on Cancel will invoke the Cancel method.

If the action is delete then a table will be displayed with the data of the employee on which the delete action is invoked. We are also displaying two buttons - "Delete" and "Cancel." On clicking the Delete button, the DeleteEmployee method will be invoked and clicking on Cancel will invoke the Cancel method.

In the end, we have a table to display all the employee data from the database. Each employee record will also have two action links: Edit to edit the employee record and Delete to delete the employee record. This table is always displayed on the page and we will update it after performing every action.

Adding the Link to the Navigation Menu

The last step is to add the link to our "EmployeeData" page in the navigation menu. Open BlazorSPA.Client/Shared/NavMenu.cshtml page and put the following code into it.

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

<div class=@(collapseNavMenu ? "collapse" : null) 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="/employee/fetch">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Employee data
            </NavLink>
        </li>
    </ul>
</div>

@functions {
    bool collapseNavMenu = true;

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

Hence, we have successfully created a Single Page Application (SPA) using Blazor with the help of the Entity Framework Core database first approach.

Execution Demo

Launch the application.

A web page will open as shown in the image below. The navigation menu on the left is showing a navigation link for the Employee data page.

Click on the "Employee data" link, it will redirect you to the EmployeeData view. Here you can see all the employee data on the page. Notice the URL has "employee/fetch" appended to it.

Since we have not added any data, it is empty. Click on CreateNew to open the "Add Employee" form to add a new employee's data. Notice the URL has "employee/create" appended to it.

After inserting data in all the fields, click on the "Save" button. The new employee record will be created and the Employee data table will get refreshed.

If we want to edit an existing employee record, then click on the Edit action link. It will open the Edit view as shown below. Here we can change the employee data. Notice that we have passed the employee id in the URL parameter.

Here we have changed the City of employee Swati from Mumbai to Kolkatta. Click on "Save" to refresh the employee data table to view the updated changes as highlighted in the image below:

Now, we will perform a Delete operation on the employee named Dhiraj. Click on the Delete action link which will open the Delete view asking for a confirmation to delete. Notice that we have passed the employee id in the URL parameter.

Once we click on the Delete button, it will delete the employee record and the employee data table will be refreshed. Here, we can see that the employee with the name Dhiraj has been removed from our record.

Deploying the Application

To learn how to deploy a Blazor application using IIS, refer to Deploying A Blazor Application On IIS

Conclusion

We have created a Single Page Application with Razor pages in Blazor using the Entity Framework Core database first approach with the help of Visual Studio 2017 and SQL Server 2014. We have also performed the CRUD operations on our application.

Please get the source code from GitHub and play around to get a better understanding.

You can check my other articles on Blazor here.

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:
web dev ,blazor ,single page application ,web application development

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}