Creating Blazor Server App for Performing CRUD Operations using EntityFramework Core
In this article we will understand an implementation of the Blazor Server App for performing CRUD Operations using EntityFrameworkCore.
The modern web applications required rich and interactive UI. To implement such UI, we uses various JavaScript Libraries and Frameworks e.g. Angular, React.js, Vue.js etc. Although JavaScript is the powerful language for browser based front-end apps, the developer must learn it properly and use it carefully and accurately for front-end application development. Considering you are a .NET Developer and having good experience of C# programming language and ASP.NET Web Forms / ASP.NET MVC, if you are expected to work on rich and interactive web UI, then learning JavaScript (or TypeScript) will take some time (learning curve) and then taking the decision for choosing the JavaScript library or framework for the implementation will be time consuming process. So what if that, you use your existing knowledge and expertise on C# and .NET to develop such interactive UI? Will it not save time and additional learning curve? Yes!!! This is where we can think of using Blazor.
What is Blazor?
Blazor is a feature of ASP.NET for building interactive Web User Interfaces (UIs) using C# programming language instead of JavaScript. Blazor provides a real .NET application running in the browser on WebAssembly. Blazor can run the client-side C# code of the application directly in the browser using WebAssembly. The WebAssembly (aka Wasm) is an open-standard that defines a portable binary code for an executable programs, we can also call it as a byte code for Web. If the application code can be compiled to the WebAssembly then it can run on any browser on any platform with great speed. With .NET code running in Browser we can enjoy .NET full-stack application development experience.
If you have an experience of ASP.NET Web Forms, then you know that WebForms are purely based on request-reply pattern. The page is submitted (or posted back) to the server, the web server accepts the request then process the page and generates new HTML rendered output that is responded to the browser. In this case the posted page is not similar to the newly received page (although you post-back same page again and again).
Unlike ASP.NET Web Forms, the Blazor is not based on request-reply model. In Blazor, user interaction are handled as events but they does not falls in the context of HTTP request. So what is the technology behind it? Well, Blazor UI is consist of various Components. The component is an autonomous object that has it own UI, data and events. In the .NET concept, the component is. .NET class that has re-usable UI with data (aka state) and events. One component can contain other components as child. When an event is handled by the component, the state can be changed. Once the state is changed, Blazor renders the component and it also keep track of what is the rendered output. Here, we have to keep one important in mind that, the component does not rendered directly in the browser's DOM instead it uses RenderTee, this is an in-memory representation of the DOM. This plays very important role in tracking the rendering changes. When the component handles an event again and if the state is changed instead of re-rendering the whole component again the changed differences are calculated and those differences are applied to DOM to show output. The following figure shows the rendering behaviour of the Blazor component
Figure 1: The component rendering
Blazor App Hosting Models
Blazor application can be hosted on IIS as like ASP.NET Web Form applications. Blazor apps can also be hosted as
- Server-Side ASP.NET Core Apps
- In this case components run on the server. All the UI events handled in the browser are send on the server over a real-time connection. The components instances running on the server are notified by these events. The components is rendered based on state changes and the differences are calculated. These UI differences are serialized and send back to the browser so that these changes are applied on DOM.
- Client-Side in the browser on WebAssembly
- Blazor WebAssembly apps are directly executed in browser. This execution is handled on the browser by WebAssembly-based .NET runtime. This .NET runtime is downloaded with the app.
Prerequisites for the implementation
- Visual Studio 2019
- .NET Core 3.1 SDK / .NET 5 SDK
Step 1: Create a new Database in SQL Server and name it as ProductCatalog. In this database, create a new table named Product using the script as shown in the listing 1
Listing 1: The Product creation Table script
Step 2: Open Visual Studio 2019 (I am using Visual Studio 2019 for Mac) and create Blazor Server App application as shown in following figure
Figure 3: Creating Blazor Server App
Click on Next button and select the target framework, I have selected .NET 5 (visual studio 2019 on windows provides the .NET framework selection on the same window where the Blazor Server App template is chosen.) Note that I have chosen No Authentication. I will post the security features in future articles.
Click on Next and the provide the project name. In my case I have named the project as Blazor_App. Once the project is created, we can see following folders in the project
- Data, this folder contains sample WEB API.
- Pages, this is the most important folder. This folder contains pre-created Blazor components by the template. These components are as follows
- Counter
- FetchData
- Index
- The _Host.cshtml, contains HTML markup for importing components from pages folder using @namespace directive (these will be covered in forthcoming steps). This files loads bootstrap for CSS and refer the most important blazor.server.js file. This script file is used to establish SignalR connection with the server. This connection is responsible for receiving the serialized UI updated in the component based on events and update the DOM as explained in figure 1.
- The Error.cshtml is used to render the UI if any error occurs while processing the request.
- Shared, this folder contains components for rendering the MainLayout, Navigation menu, etc. This Navigation menu contains code for routing across components
- wwwroot folder contains the client side rendered css files e.g. bootstrap
- The _Imports.razor, contains all standard imports of .NET packages e.g. Authorization, JSInterop, Routing, etc.
- App.razor, contains code for rendering the components, based on the route match this file will decide if the components is rendered or not.
- appaettings.json, file contains all app level configurations e.g. connection string.
- Startup.cs , file contains Startup class for registering all dependencies and the middlewares.
- Program.cs is an entry-point of the application.
Step 3: In the project add following packages to use EntityFrameworkCore as shown in Figure 4
Figure 4: The packages for EntityFrameworkCore
Step 4: Open the command prompt and navigate to the project folder and run the command as shown in listing 2. This command generates the entity classes from the database first approach of EntityFramework CoreListing 2: The Code First Approach using EntityFramework Core
Once the command is executed successfully, the Models folder is created in the project. This folder contains Product.cs. This contains the Product class with properties mapped with columns with the Product class. Modify the product class for data validations as shown in listing 3
public partial class Product
{
public int ProductRowId { get; set; }
[Required(ErrorMessage ="Product Id is Must")]
public string ProductId { get; set; }
[Required(ErrorMessage = "Product Name is Must")]
public string ProductName { get; set; }
[Required(ErrorMessage = "Category Name is Must")]
public string CategoryName { get; set; }
[Required(ErrorMessage = "Manufacturer is Must")]
public string Manufacturer { get; set; }
[Required(ErrorMessage = "Description is Must")]
public string Description { get; set; }
[Required(ErrorMessage = "Base Price is Must")]
public int BasePrice { get; set; }
}
Listing 3: The Data Validation annotations
The ProductCatalogContext.cs class is derived from the DbContext class. This class manages the mapping with database and tables from the database for performing CRUD operations. The ProductCatalogContext class contains OnConfiguring() method. This method contains the Connection String of the database. Copy this connection string and paste it in appsettings.json as shown in the listing 4
"ConnectionStrings": {
"AppConnectionString": "Server=tcp:msit.database.windows.net,1433;Initial Catalog=ProductCatalog;Persist Security Info=False;User ID=MaheshAdmin;Password=P@ssw0rd_;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
Listing 4: The connection string in the appsetings.json
Step 5: In the project add a new folder and add name it as Services. In this folder add a new interface and name it as IService.cs. In this interface add the code as shown in the listing 5. This is generic interface. The TEntity parameter will be typed to the Entity class to generate CRUD methods and in TPk will be the primary key based on which Product will be updated and deleted.
public interface IService<TEntity, in TPk> where TEntity: class
{
Task<List<TEntity>> GetAsync();
Task<TEntity> GetAsync(TPk id);
Task<TEntity> CreateAsync(TEntity entity);
Task<TEntity> UpdateAsync(TPk id, TEntity entity);
Task<bool> DeleteAsync(TPk id);
}
Listing 5: The Interface with methods for generic interface methods
Step 6: In this folder add a new class file and name it as ProductServce.cs. In this file add code for as shown in listing 6 for performing CRUD operations with database
public class ProductService : IService<Product,int>
{
private readonly ProductCatalogContext _context;
public ProductService(ProductCatalogContext context)
{
_context = context;
}
public async Task<Product> CreateAsync(Product entity)
{
var record = await _context.Products.AddAsync(entity);
await _context.SaveChangesAsync();
return record.Entity;
}
public async Task<bool> DeleteAsync(int id)
{
var prd = await _context.Products.FindAsync(id);
if (prd == null)
{
return false;
}
_context.Products.Remove(prd);
await _context.SaveChangesAsync();
return true;
}
public async Task<List<Product>> GetAsync()
{
var records = await _context.Products.ToListAsync();
return records;
}
public async Task<Product> GetAsync(int id)
{
var prd = await _context.Products.FindAsync(id);
return prd;
}
public async Task<Product> UpdateAsync(int id, Product entity)
{
var prd = await _context.Products.FindAsync(id);
if (id != entity.ProductRowId || prd == null)
{
return null;
}
var record = _context.Update<Product>(entity);
await _context.SaveChangesAsync();
return record.Entity;
}
}
Listing 6: The ProductService class with CRUD Operations
Please note that the ProductService class is injected with ProductCatalogContext class. This class implements IService interface which is typed with Product class to perform CURD operations with the Product table.
In the models folder add a new class file and name it as Constants.cs and add the code in this file as shown in listing 7
using System;
using System.Collections.Generic;
namespace Blazor_App.Models
{
public static class Constants
{
public static List<string> Categories = new List<string>()
{
"Elctronics",
"Electrical",
"Food"
};
public static List<string> Manufacturers = new List<string>()
{
"MS-Electronics",
"MS-Electrical",
"MS-Food",
"TS-Electronics",
"TS-Electrical",
"TS-Food"
};
}
}
Listing 7: The constants
Step 7: Modify the ConfigurationServices() method of the Startup class in Startup.cs file to register the ProductCatalogContext class and ProductService class as shown in listing 8
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ProductCatalogContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("AppConnectionString"));
});
.....
services.AddScoped<IService<Product,int>,ProductService>();
}
Listing 8: Registration of the classes in DI
Directives used in Blazor Application
- @page, specifies the route for the Blazor component
- @using, referring the namespace reference in the Blazor component
- @namespace, importing the namespace
- @inject, injecting the dependency in the Blazor component
- @code, add the class members to the Blazor component
- EditForm, the form that contains UI elements for accepting data
- InputTest, the input element for accepting text data
- InputNumber, the input element for accepting numeric input
- InputSelect, the UI element to show the DropDown
- DataAnnotationValidator, the component to show error messages
- ValidationSummary, the component to show error messages in summary
- NavLink, the component that renders <a> anchor tag to navigate to the route link
Step 8: In the pages folder add a new file and name it as CreateRazor.razor. Add the code in it as shown in listing 9
@page "/createproduct"
@using Blazor_App.Models
@using Blazor_App.Services
@inject NavigationManager uriHelper;
@inject IService<Product, int> productService
<EditForm OnValidSubmit="@saveProduct" Model="@product">
<DataAnnotationsValidator/>
<ValidationSummary/>
<h2>Create new Product</h2>
<div class="container">
<div class="form-group">
<label for="ProductId">Product Id</label>
<InputText @bind-Value="product.ProductId" class="form-control"></InputText>
</div>
<div class="form-group">
<label for="ProductName">Product Name</label>
<InputText @bind-Value="product.ProductName" class="form-control"></InputText>
</div>
<div class="form-group">
<label for="CategoryName">Category Name</label>
<InputSelect @bind-Value="@product.CategoryName" class="form-control">
<option>Select Category Name</option>
@foreach (var cat in Constants.Categories)
{
<option value="@cat">@cat</option>
}
</InputSelect>
</div>
<div class="form-group">
<label for="Manufacturer">Manufacturer</label>
<InputSelect @bind-Value="@product.Manufacturer" class="form-control">
<option>Select Manufacturer</option>
@foreach (var man in Constants.Manufacturers)
{
<option value="@man">@man</option>
}
</InputSelect>
</div>
<div class="form-group">
<label for="Description">Description</label>
<InputText @bind-Value="product.Description" class="form-control"></InputText>
</div>
<div class="form-group">
<label for="BasePrice">Base Price</label>
<InputNumber @bind-Value="product.BasePrice" class="form-control"></InputNumber>
</div>
<div class="form-group">
<input type="submit" class="btn btn-success" value="Save">
<input type="button" class="btn btn-danger" value="Cancel"
@onclick="@cancelOperation">
</div>
</div>
</EditForm>
@code {
private Product product;
protected override Task OnInitializedAsync()
{
product = new Product();
return base.OnInitializedAsync();
}
async void saveProduct()
{
product = await productService.CreateAsync(product);
if (product.ProductRowId > 0)
{
uriHelper.NavigateTo("/listproducts");
}
}
void cancelOperation()
{
uriHelper.NavigateTo("/listproducts");
}
}
Listing 9: The CreateProduct component
The code in the listing 9, uses directives for using Models and Services namespaces. The NavigationManager class is injected that provides NavigateTo() method to route to a component. The IService<Product,int> is injected in the component. This injection will provide an instance of the ProductService class so that its methods are accessible to the component for performing save operation. The CreateProduct component has the @page directive to define routing /createproduct.
The component implement OnInitializeAsync() lifecycle method. This method is invoked when the component is initialized. The InputSelect component is populated using the Categories and Manufacturers constants to generate options to display Categories and Products. The saveProduct() method invokes CreateAsync() method of the ProductService to create a new Product. This method is bound with the Save button using @onclick event binding. The InputValue, InputNumber and InputSelect components are bound with the Product class using @bind-Value two-way binding. This means that when value is entered in these components the corresponding property of the Product model will be set.
Add a new file in EditProduct.razor. In this file add the code as shown in listing 10
@page "/editproduct/{id:int}"
@using Blazor_App.Models
@using Blazor_App.Services
@inject NavigationManager uriHelper;
@inject IService<Product, int> productService
<h2>Edit Product with Id as @id</h2>
<EditForm OnValidSubmit="@saveProduct" Model="@product">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="container">
<div class="form-group">
<label for="ProductId">Product Id</label>
<InputText @bind-Value="@product.ProductId" class="form-control"></InputText>
</div>
<div class="form-group">
<label for="ProductName">Product Name</label>
<InputText @bind-Value="@product.ProductName" class="form-control"></InputText>
</div>
<div class="form-group">
<label for="CategoryName">Category Name</label>
<InputSelect @bind-Value="@product.CategoryName" class="form-control">
<option>Select Category Name</option>
@foreach (var cat in Constants.Categories)
{
<option value="@cat">@cat</option>
}
</InputSelect>
</div>
<div class="form-group">
<label for="Manufacturer">Manufacturer</label>
<InputSelect @bind-Value="@product.Manufacturer" class="form-control">
<option>Select Manufacturer</option>
@foreach (var man in Constants.Manufacturers)
{
<option value="@man">@man</option>
}
</InputSelect>
</div>
<div class="form-group">
<label for="Description">Description</label>
<InputText @bind-Value="@product.Description" class="form-control"></InputText>
</div>
<div class="form-group">
<label for="BasePrice">Base Price</label>
<InputNumber @bind-Value="@product.BasePrice" class="form-control"></InputNumber>
</div>
<div class="form-group">
<input type="submit" class="btn btn-success" value="Save">
<input type="button" class="btn btn-danger" value="Cancel"
@onclick="@cancelOperation">
</div>
</div>
</EditForm>
@code {
[Parameter]
public int id { get; set; }
private Product product;
protected override Task OnInitializedAsync()
{
product = new Product();
product = productService.GetAsync(id).Result;
return base.OnInitializedAsync();
}
async void saveProduct()
{
product = await productService.UpdateAsync(product.ProductRowId, product);
uriHelper.NavigateTo("/listproducts");
}
void cancelOperation()
{
product = new Product();
product = productService.GetAsync(id).Result;
uriHelper.NavigateTo("/listproducts");
}
protected override bool ShouldRender()
{
return base.ShouldRender();
}
}
Listing 10: The EditProduct component
The EditProeuct component uses @page directive for route value as "/editproduct/{id:int}". This route expression contains the route parameter as {id:int} of the type integer. The component defines 'id' property. This property is applied with [Parameter] attribute. This is the route parameter using which the OnInitializedAsync() method loads the product to be updated. Like CreateProduct component, the EditProduct Component also generate options for InputSelect component. The saveProduct() method invokes UpdateAsync() method of the ProductService to update the product. The EditProduct component has the same data-binding as explained for CreateProduct Component.
In the pages folder add a new file and name it as ListProducts.razor. In this file add code as shown in the listing 11
@page "/listproducts"
@inject NavigationManager uriHelper;
@using Blazor_App.Models
@using Blazor_App.Services
@using System.Reflection
@using Microsoft.EntityFrameworkCore
@inject IService<Product, int> productService
<div class="container">
<NavLink href="/createproduct" @onclick="@createnew">Create New</NavLink>
<table class="table table-bordered table-striped">
<thead>
<tr>
@foreach (var header in headers)
{
<th>@header</th>
}
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var prd in Products)
{
<tr>
@foreach (var header in headers)
{
<td>@prd.GetType().GetProperty(header).GetValue(prd, null)</td>
}
<td>
<input type="button" value="Edit" class="btn btn-warning"
@onclick="((evt)=>navigateToEdit(prd.ProductRowId))" />
</td>
<td>
<input type="button" value="Delete" class="btn btn-danger"
@onclick="((evt)=>delete(prd.ProductRowId))" />
</td>
</tr>
}
</tbody>
</table>
</div>
@code {
private Product product = new Product();
private List<string> headers = new List<string>();
private List<Product> Products = new List<Product>();
protected override async Task OnInitializedAsync()
{
PropertyInfo[]properties= product.GetType().GetProperties(System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (var property in properties)
{
headers.Add(property.Name);
}
Products = await productService.GetAsync();
}
void navigateToEdit(int id)
{
uriHelper.NavigateTo("/editproduct/" + id);
}
async void delete(int id)
{
var res = await productService.DeleteAsync(id);
if (res)
{
Products = await productService.GetAsync();
}
}
void createnew()
{
uriHelper.NavigateTo("/createproduct");
}
// implement this to make sure that the component re-renders
protected override bool ShouldRender()
{
return base.ShouldRender();
}
}
Listing 11: The ListProducts component
The ListProducts component declares instance of the Product class. The headers are of the type List of string. This list contains all properties of the Product class to generate Table Column headers to show the Product List. The OnInitialilizedAsync() method reads all properties of the Product class and store these properties in headers list. This method invokes GetAsync() method of the ProductService to read all products. The HTML table is generated based on the headers and the Products. Each row of the table generates Edit and Delete button. The Edit button is bound to the navigateToEdit() method of the component. This method will navigate to the EditComponent with the route parameter as ProductRowId for the product to be edited. The Delete button is bound with delete() method. This method invokes DeleteAsync() method of the Product service to delete the Product. The ListProducts component uses the NavLink component. This component is bound with the createnew method using event-binding to navigate to the CreateProduct component.
You must have noticed that in ListProducts and EditProduct components I have implemented the ShouldRender() method. This method will make sure that the when the routing takes place to this component or any changes occurs the component will re-render. This will make sure that when all async operations are executed and the state of the component is updated the component is re-rendered.
Step 9: Modify the NavMenu.razor file from the Shared folder. Modify the <div> with class as @NavMenuCssClass as shown in listing 12
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="listproducts">
<span class="oi oi-list-rich" aria-hidden="true"></span> Product List
</NavLink>
</li>
</ul>
</div>
Listing 12: The navigation for the ListProducts component
Run the application, the Home page will be displayed with Navigation link for the ListProducts as shown in Figure 5
Figure 5: The Home page
Click on the List Products link. This will navigate to the ListProducts Component as shown in the figure 6
Figure 6: List of Products
Click on the Create New link provided on top of the List of Products. This will navigate to the Create Product Component as shown in figure 7
Figure 7: Create Product
Click on the Save button without entering and data in component, the validation error will be shown as shown in the figure 8
Figure 8: Validation error
Enter valid data in the Create Product component and click on the Save button, the record will be added and then the routing will be executed to render List Products component as shown in the figure 9
Figure 9: List Products with newly added record
Likewise click on Edit button on any of the row of ListProducts component, the EditProduct component will be routed which will show product information to be edited as shown in figure 10 (I have selected ProductRowId as 3)
Figure 10: The Product to be edited
Update Product Information e.g. update Base Price from 55000 to 70000 and click on the Save button, the product information will be updated and List Products component is navigated with updated Product as shown in figure 12
Figure 11: The Updated Product Information in List of Products
Similarly click on Delete button, the record will be deleted.
The code can be downloaded from this repository.
Conclusion: Using Blazor feature with its ready to use component makes the Interactive Web UI development vary easy. The Data-Binding and Event-Binding feature reduces the code complexity of Code-Behind which we were having in ASP.NET WebForms. Although the Blazor Server Apps runs on the server and manage the component rendering on the server the Postback does not take place. This will make sure that the UI is interactive. So with these features we can conclude that Blazor will definitely rule the Modern Web UI development.