Blazor Server:
Blazor Server is a single-page application. At the server, a pre-rendered HTML output of the page will be delivered to the user browsers. Any UI update, event handling, javascript calls will carry over by a SingnalR connection to the server. So application updates are depending on the continuous connection of the SignalR.
So Blazor server is a single-page application that can be made of C#. Since the blazor server only outputs the pre-rendered HTML to the client, so there is no c# code downloading into user browsers like in Blazor WebAssembly(c# code downloaded and run in the browser for blazor webassembly application).
SignalR Connection:
A Blazor Server application works over a SignalR connection. A Blazor Server application creates a UI State memory at the server which will be interacted by the SignalR connections. If a SignalR connection got interrupted, then the client tries to maintain the same state of the application by initiating a new SignalR connection and uses the existing UI state memory at the server. App routing changes, event changes, data changes everything will be carried out by the SignalR connection without any page reload.Each browser screen or browser tab has its own SignalR Connection channels and UI states at the application. So each browser screen or browser tab acts as an individual user request.
Blazor Components:
Blazor Server application built on top of the 'Blazor Components'. A Blazor component file will be created like 'Example.razor', the file extension is '.razor'. A blazor component file consists of both c# and razor syntax. Blazor component also provides an option to split the file like 'Example.razor'(contains all razor code) and 'Example.razor.cs'(contains all c#code).
Create A .NET6 Blazor Server Application:
Let's create a .Net6 Blazor Server sample project to accomplish our demo. We can use either Visual Studio 2022 or Visual Studio Code(using .NET CLI commands) to create any.Net6 application. For this demo, I'm using the 'Visual Studio Code'(using the .NET CLI command) editor.
CLI command
dotnet new blazorserver -o your_project_name
dotnet new blazorserver -o your_project_name
Let's understand a few key things about the project:
Route Middleware:-
Program.cs:
- The 'app.MapBlazorHub()' middleware is to enable the SignalR routing. The 'app.MapFallbackToPage()' middleware is to load the 'Pages/_Host.cshtml' so this is entry file gets loads.
- In Blazor Server '_Host.cshtml' is the entry file. We can observe here we configured the master layout that is '_Layout.cshtml'. Here 'App.razor' component is the entry component of the Blazor Sever.
- This is the blazor routing component. The 'RouteView' component to navigate to the respective Blazor Component.
The 'wwwroot' folder contains static files like 'js', 'css', 'images',etc.
Install And Configure MudBlazor UI Library:
Run the below command to install the MudBlazor UI library.
Package Manager:
Install-Package MudBlazor -Version 6.0.2
Install-Package MudBlazor -Version 6.0.2
.NET CLI Command:
dotnet add package MudBlazor --version 6.0.2
dotnet add package MudBlazor --version 6.0.2
Now add the MudBlazor namespace like '@using MudBlazor' into the '_Imports.razor' file.
Now add the below CSS links into 'Pages/_Layout.csthml' inside of the 'Head' HTML tag.
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /> <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />Now remove the existing CSS files inside of the 'Pages/_Layout.cshtml'.
Now add a Mudblazor js file into the 'Pages/cshtml' just above the closing body tag.
<script src="_content/MudBlazor/MudBlazor.min.js"></script>In .NET6 'Startup.cs' file is excluded all service registration added to the 'Program.cs' file. Now let's register the MudBlazor service into the 'Program.cs' file.
Program.cs:
builder.Services.AddMudServices();Now let's replace the content in 'Shared/MainLayout.razor' file.
Shared/MainLayout.razor:
@inherits LayoutComponentBase <PageTitle>Dot6.BlazorServer.Crud.Learn</PageTitle> <MudThemeProvider /> <MudDialogProvider /> <MudSnackbarProvider /> <MudLayout> <MudAppBar Color="Color.Primary"> <MudText Typo="Typo.h4">Cakes Wold</MudText> </MudAppBar> <MudMainContent class="mt-4"> @Body </MudMainContent> </MudLayout>
- (Line: 5) The 'MudThemeProvider' component was added as global registration. It helps apply styles or colors to our sample application.
- (Line: 6) The 'MudDialogProvider' component was added as global registration. It is required to work with MudBlazor dialog.
- (Line: 7) The 'MudSnackbarProvider' component was added as global registration. It is required to display notification messages.
- (Line: 9&16) The 'MudLayout' component was added as a parent of all other components.
- (Line: 10-12) The 'MudAppBar' component added display a nice header menu.
- (Line: 13-14) The '@Body' a razor syntax that helps to render the Blazor page components and it was wrapped by the 'MudMainContent' component. The 'class="mt-4"'(margin-top) applying some margin between the 'MudAppBar' and 'MudMainContent' components.
SQL Sample Script:
Run the below script to create a table into the SQL server.
CREATE TABLE [dbo].[Cake] ( [Id] INT IDENTITY (1, 1) NOT NULL, [Name] VARCHAR (MAX) NULL, [Price] DECIMAL (18, 2) NULL, [Description] VARCHAR (MAX) NULL );
Install EntityFramework Core And Configure Database Context:
Install entity framework core library.
Package Manager:
Install-Package Microsoft.EntityFrameworkCore -Version 6.0.0
Install-Package Microsoft.EntityFrameworkCore -Version 6.0.0
Package Manager:
dotnet add package Microsoft.EntityFrameworkCore --version 6.0.0
dotnet add package Microsoft.EntityFrameworkCore --version 6.0.0
Install EFCore dependant SQL library.
Package Manager:
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 6.0.0
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 6.0.0
Package Manager:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 6.0.0
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 6.0.0
Now let's create a table class like 'Cake.cs'.
Data/Entities/Cake.cs:
namespace Dot6.BlazorServer.Crud.Learn.Data.Entities; public class Cake { public int Id{get;set;} public string Name{get;set;} public decimal Price{get;set;} public string Description{get;set;} }Now let's create the Database Context model like 'MyWorldDbContext.cs'
Data/Entities:
using Dot6.BlazorServer.Crud.Learn.Data.Entities; using Microsoft.EntityFrameworkCore; namespace Dot6.BlazorServer.Crud.Learn.Data; public class MyWorldDbContext : DbContext { public MyWorldDbContext(DbContextOptions<MyWorldDbContext> options) : base(options) { } public DbSet<Cake> Cake{get;set;} }Add the 'Cake.cs' and 'MyWorldDbContext.cs' namespace into the '_Import.razor'.
_Import.razor:
@using Dot6.BlazorServer.Crud.Learn.Data @using Dot6.BlazorServer.Crud.Learn.Data.EntitiesAdd the database connection string into 'appsettings.Development.json' file.
appsettings.Development.json:
"ConnectionStrings":{ "MyWorldDbConnection":"your_connection" }Register the DatabaseContext.
Program.cs:
builder.Services.AddDbContext<MyWorldDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("MyWorldDbConnection")); });
Read Operation:
let's implement logic to read the records from the database and bind them to the UI. Let's update the Index.razor component as below
Index.razor:(Html Code)
@page "/" @inject MyWorldDbContext _myworldDbContext; <MudGrid Justify="Justify.Center" class="pr-4 pl-4"> @foreach (var cake in allCakes) { <MudItem xs="3"> <MudCard> <MudCardHeader> <CardHeaderContent> <MudText Typo="Typo.body1">@cake.Name</MudText> <MudText Typo="Typo.h6">@cake.Price $</MudText> </CardHeaderContent> </MudCardHeader> <MudCardMedia Image="images/sample-cake.jpg" Height="250" /> <MudCardContent> <MudText Typo="Typo.body2">@cake.Description</MudText> </MudCardContent> <MudCardActions> <MudIconButton Icon="@Icons.Filled.Edit" Color="Color.Primary" /> <MudIconButton Icon="@Icons.Filled.Delete" Color="Color.Error" /> </MudCardActions> </MudCard> </MudItem> } </MudGrid>
- (Line: 2) Injected the 'MyWorldDbContext'.
- (Line: 4) Rendered the 'MudGrid' component. Here applied padding right&left like 'class="pr-4 pl-4"'.
- (Line: 5-27) Looping the collection of records by rendering the 'MudItem' component, inside it renders the 'MudCard' component.
- (Line: 15) Added a dummy image for display purposes(image added in folders of 'wwwroot/images').
@code { List<Cake> allCakes = new List<Cake>(); protected override async Task OnInitializedAsync() { allCakes = await _myworldDbContext.Cake.ToListAsync(); } }
- (Line: 2)Initialized the collection of 'Cake' instance.
- (Line: 3-6) The 'OnInitialized' life cycle method invokes the database call to fetch all the records.
Create Operation:
Let's implement logic to add a new record to the database.
Now let's create dialog components like 'AddOrUpdateCakeDailog.razor'.
Pages/AddOrUpdateCakeDailog.razor:(HTML Part)
<MudDialog> <DialogContent> <MudTextField T="string" Label="Name" @bind-Value="cake.Name" /> <MudTextField T="decimal" Label="Price" @bind-Value="cake.Price" /> <MudTextField T="string" Label="Description" @bind-Value="cake.Description" Lines=4/> </DialogContent> <DialogActions> <MudButton OnClick="Cancel">Cancel</MudButton> <MudButton Color="Color.Primary" OnClick="Submit">Ok</MudButton> </DialogActions> </MudDialog>
- (Line: 1&11)Here added the 'MudDialog' component.
- (Line: 3&4) The 'MudTextField' component for the text fields and '@bind-value' for 2-way binding.
- (Line: 5) The 'MudTextField' component can be rendered as multi-line text filed by adding attributes like 'Lines'.
- (Line: 8) Added 'Cancel' button and its click event with 'Cancel' method.
- (Line: 9) Added 'Ok' button and its click event with 'Submit' method.
Pages/AddOrUpdateCakeDailog.razor:(c# Part)
@code { [CascadingParameter] MudDialogInstance MudDialog { get; set; } [Parameter] public Cake cake { get; set; } = new Cake(); private void Cancel() { MudDialog.Cancel(); } private void Submit() { MudDialog.Close(DialogResult.Ok<Cake>(cake)); } }
- (Line: 2) The 'MudDialogInstance' property gives control over the dialog.
- (Line: 4) The 'cake' property instance is used to bind the dialog fields and the instance of the property comes from the component that invokes the dialog.
- (Line: 6-9) The 'Cancel' method to close the dialog.
- (Line: 11-14) The 'Submit' method closes the dialog as well passe the dialog information to a component that invokes the component.
Index.razor:(HTML Part)
@page "/" @inject MyWorldDbContext _myworldDbContext; @inject IDialogService _dialogService; <MudContainer Class="d-flex justify-center mb-2"> <MudFab Color="Color.Primary" Icon="@Icons.Material.Filled.Add" Size="Size.Large" IconSize="Size.Large" Label="Add A New Cake" Class="ma-2" @onclick="(e => CreateAsync())" /> </MudContainer> <MudGrid Justify="Justify.Center" class="pr-4 pl-4"> <!-- Code hidden for display purpose --> </MudGrid>
- (Line: 3) Injected the 'IDialogService'.
- (Line: 5-8) Add button, its click event registered with 'CreateAsync()' method.
private async Task CreateAsync() { var parameters = new DialogParameters(); parameters.Add("cake", new Cake()); var dialog = await _dialogService.Show<AddOrUpdateCakeDialog>("Create A Post", parameters).Result; if (dialog.Data != null) { Cake newCake = dialog.Data as Cake; _myworldDbContext.Cake.Add(newCake); await _myworldDbContext.SaveChangesAsync(); allCakes.Insert(0, newCake); } }
- (Line: 3-4) The 'DialogParameters' instance needs to use for adding parameters that can be sent to the dialog.
- (Line: 5) Using the 'IDialogService' we can open dialog by calling 'Show<T>()' where T(type) will be the dialog component. The 'Show<T>' has 2 params where 1st parameter will be the title for the dialog and 2nd parameter will be the data that passed to the dialog component.
- (Line: 7) Checking for data coming from the dialog.
- (Line: 9-11) Using entity framework database context saves the new record to the database.
- (Line: 13) Now updating the 'allCakes' collection property with our newly created record.
Update Operation:
Let's implement our logic to update the record.
Index.razor:(HTML Part)
<MudCardActions> <MudIconButton Icon="@Icons.Filled.Edit" Color="Color.Primary" @onclick="(e => UpdateAsync(cake.Id))" /> <MudIconButton Icon="@Icons.Filled.Delete" Color="Color.Error" /> </MudCardActions>
- (Line: 2) The edit button registers with the 'UpdateAsync' click event.
private async Task UpdateAsync(int id) { var parameters = new DialogParameters(); var cakeNeedToUpdate = allCakes.FirstOrDefault(_ => _.Id == id); parameters.Add("cake", cakeNeedToUpdate); var dialog = await _dialogService.Show<AddOrUpdateCakeDialog>("Update A Item", parameters).Result; if (dialog.Data != null) { var updatedCake = dialog.Data as Cake; _myworldDbContext.Cake.Update(updatedCake); await _myworldDbContext.SaveChangesAsync(); allCakes.Remove(cakeNeedToUpdate); allCakes.Insert(0, updatedCake); } }
- (Line: 3-5) Adding the record that needs to be updated as a parameter to the 'DailogParamters'.
- (Line: 9-11) Saving modified changes to the database.
Delete Operation:
Let's implement our logic to remove the record from the database.
Index.razor:(Html Part)
<MudCardActions> <MudIconButton Icon="@Icons.Filled.Edit" Color="Color.Primary" @onclick="(e => UpdateAsync(cake.Id))" /> <MudIconButton Icon="@Icons.Filled.Delete" Color="Color.Error" @onclick="(e => DeleteAsync(cake.Id))" /> </MudCardActions>
- (Line: 3) The 'Delete' button registered with 'DeleteAsync' click event
private async Task DeleteAsync(int id) { bool? result = await _dialogService.ShowMessageBox( "Delete Confirmation", "Deleting can not be undone!", yesText: "Delete!", cancelText: "Cancel"); if (result ?? false) { var cakeToRemove = await _myworldDbContext.Cake.FindAsync(id); _myworldDbContext.Cake.Remove(cakeToRemove); await _myworldDbContext.SaveChangesAsync(); allCakes.Remove(cakeToRemove); } }
- (Line: 3-6) MudBlazor 'MessageBox' are simple popups that can be easily used like 'warning' or 'confirmation' popups. So 'MessageBox' is also invoked by using 'IDialogService'. MessageBox on click 'cancelText' button returns 'null', if we click 'yesText' button then it returns 'true' value.
- (Line: 10-12) Deleting the record from the database.
Hopefully, I think this article delivered some useful information on Blazor Server CRUD operations Using MudBlazor UI components. I love to have your feedback, suggestions, and better techniques in the comment section below.
Thank you for this wonderful and clear explanation tutorial
ReplyDeleteI received an error for the Pages/Index.razor: CS0103 The name 'allCakes' does not exist in the current context
ReplyDeleteIt is from "@foreach (var cake in allCakes)".
Could you help on this error? Thank you.
I thank you for this wonderful and brief explanation
ReplyDeleteI wish you would explain a more complex program that contains more than one table and some nested queries