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. 
In this article we will implement the Blazor Server App. The following figure demonstrate the structure of the application.




Figure 2: The Blazor Server App

Prerequisites for the implementation

  1. Visual Studio 2019
  2. .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

CREATE TABLE [dbo].[Product](
[ProductRowId] [int] IDENTITY(1,1) PRIMARY KEY,
[ProductId] [varchar](50) NOT NULL,
[ProductName] [varchar](100) NOT NULL,
[CategoryName] [varchar](100) NOT NULL,
[Manufacturer] [varchar](100) NOT NULL,
[Description] [varchar](200) NOT NULL,
[BasePrice] [int] NOT NULL
)

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 Core


 dotnet ef dbcontext "Server=.;Initial Catalog=ProductCatalog;Persist Security Info=False;User ID=MyUser;Password=MyPassword;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" Microsoft.EntityFraameworkCore.SqlServer -Models         

Listing 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
There are several other directives in Blazor app, the above directives I will be using in the applcation code. Blazor also has various standard components as follows

  • 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.     


Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach