May 24, 2024 by CodeFlowerHorn
Creating a Web Application with C# Blazor Wasm App
In addition to Blazor Server, there is also Blazor WebAssembly (Wasm), a client-side rendering web application framework. Blazor Wasm enables developers to write C# code that runs directly in the browser, leveraging WebAssembly for high performance. This approach allows for a more interactive and dynamic user interface, as the application logic is executed on the client-side. Client-Side Rendering (CSR) excels in dynamic, interactive content, providing a more seamless user experience with real-time updates.
Prerequisite
Install .NET SDK for Ubuntu
Open a terminal and enter this command(s)
sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0
dotnet tool install --global dotnet-ef
Create a project solution
Open a terminal and enter this command(s)
dotnet new blazorwasm -o BlazorWasmApp
Create a .gitignore file
Open a terminal and enter this command(s)
cd BlazorWasmApp
dotnet new gitignore
Create a Model under Models/BookModel.cs
using System.ComponentModel.DataAnnotations;
namespace BlazorWasmApp.Models
{
public class BookModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Title is required")]
public string Title { get; set; } = "";
[Required(ErrorMessage = "Author is required")]
public string Author { get; set; } = "";
[Required(ErrorMessage = "Genre is required")]
public string Genre { get; set; } = "";
}
}
Create a Http client under Client/BookHttpClient.cs
using System.Net.Http.Json;
using BlazorWasmApp.Models;
namespace BlazorWasmApp.Client
{
public class BookHttpClient(HttpClient http)
{
public async Task<bool> CreateBookAsync(BookModel book)
{
var response = await http.PostAsJsonAsync("/api/book", book);
return response.IsSuccessStatusCode;
}
public async Task<IEnumerable<BookModel>> GetBooksAsync()
{
return await http.GetFromJsonAsync<IEnumerable<BookModel>>("/api/book") ?? [];
}
public async Task<BookModel> GetBookAsync(int id)
{
return await http.GetFromJsonAsync<BookModel>("/api/book/id=" + id) ?? new BookModel();
}
public async Task<bool> UpdateBookAsync(BookModel book)
{
var response = await http.PutAsJsonAsync("/api/book", book);
return response.IsSuccessStatusCode;
}
public async Task<bool> DeleteBookAsync(int id)
{
var response = await http.DeleteAsync("/api/book/id=" + id);
return response.IsSuccessStatusCode;
}
}
}
Create a page under Pages/Book.razor
@page "/books"
@using Models
@using System.Text.Json
@using System.Text.Json.Serialization
@using Client
@inject BookHttpClient Http
@inject IJSRuntime js
<PageTitle>Book</PageTitle>
<h1>Books</h1>
<div>
<button type="button" class="btn btn-success" data-bs-toggle="modal"
data-bs-target="#createBookModal">Create</button>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Genre</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
@{
int i = 0;
}
@foreach (var book in books)
{
i++;
var index = i;
<tr>
<th>@index</th>
<th>@book.Title</th>
<th>@book.Author</th>
<th>@book.Genre</th>
<th>
<button type="button" class="btn btn-warning" data-bs-toggle="modal"
data-bs-target="#updateBookModal" @onclick="(() => onClickUpdate(book.Id))">Update</button>
<button type="button" class="btn btn-danger" data-bs-toggle="modal"
data-bs-target="#deleteBookModal" @onclick="(() => DeleteBook(book.Id))">Delete</button>
</th>
</tr>
}
</tbody>
</table>
</div>
<!-- Modal Create book-->
<div class="modal fade" id="createBookModal" tabindex="-1" aria-labelledby="createBookModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createBookModalLabel">Create a book</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
@onclick="OnClickCloseModal"></button>
</div>
<EditForm Model="forCreateBookModel" OnValidSubmit="CreateBook">
<DataAnnotationsValidator />
<div class="modal-body">
<ValidationMessage For="@(() => forCreateBookModel.Title)" style="display: block !important" />
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Title</span>
<label for="Title" hidden></label>
<InputText id="Title" @bind-Value="forCreateBookModel.Title" type="text" class="form-control"
aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" />
</div>
<ValidationMessage For="@(() => forCreateBookModel.Author)" style="display: block !important" />
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Author</span>
<label for="Author" hidden></label>
<InputText id="Author" @bind-Value="forCreateBookModel.Author" type="text" class="form-control"
aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" />
</div>
<ValidationMessage For="@(() => forCreateBookModel.Genre)" style="display: block !important" />
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Genre</span>
<label for="Genre" hidden></label>
<InputText id="Genre" @bind-Value="forCreateBookModel.Genre" type="text" class="form-control"
aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
@onclick="OnClickCloseModal">Close</button>
<button type="submit" class="btn btn-success">Create</button>
</div>
</EditForm>
</div>
</div>
</div>
<!-- Modal Update book-->
<div class="modal fade" id="updateBookModal" tabindex="-1" aria-labelledby="updateBookModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="updateBookModalLabel">Update a book</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
@onclick="OnClickCloseModal"></button>
</div>
<EditForm Model="forUpdateBookModel" OnValidSubmit="UpdateBook">
<DataAnnotationsValidator />
<div class="modal-body">
<ValidationMessage For="@(() => forUpdateBookModel.Title)" style="display: block !important" />
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Title</span>
<label for="Title" hidden></label>
<InputText id="Title" @bind-Value="forUpdateBookModel.Title" type="text" class="form-control"
aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" />
</div>
<ValidationMessage For="@(() => forUpdateBookModel.Author)" style="display: block !important" />
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Author</span>
<label for="Author" hidden></label>
<InputText id="Author" @bind-Value="forUpdateBookModel.Author" type="text" class="form-control"
aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" />
</div>
<ValidationMessage For="@(() => forUpdateBookModel.Genre)" style="display: block !important" />
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Genre</span>
<label for="Genre" hidden></label>
<InputText id="Genre" @bind-Value="forUpdateBookModel.Genre" type="text" class="form-control"
aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
@onclick="OnClickCloseModal">Close</button>
<button type="submit" class="btn btn-success">Update</button>
</div>
</EditForm>
</div>
</div>
</div>
<!-- Modal Delete book-->
<div class="modal fade" id="deleteBookModal" tabindex="-1" aria-labelledby="deleteBookModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteBookModalLabel">Delete a book</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h6>Do you want to delete the "@forDeleteBookModel.Title" book?</h6>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
@onclick="OnClickCloseModal">No</button>
<button type="submit" class="btn btn-success" data-bs-dismiss="modal"
@onclick="confirmDeleteBook">Yes</button>
</div>
</div>
</div>
</div>
@code {
private IEnumerable<BookModel> books = [];
private BookModel forCreateBookModel = new BookModel();
private BookModel forUpdateBookModel = new BookModel();
private BookModel forDeleteBookModel = new BookModel();
private IJSObjectReference JsObjectRef { get; set; }
protected override async Task OnInitializedAsync()
{
books = await Http.GetBooksAsync();
}
protected override async Task OnAfterRenderAsync(bool first)
{
JsObjectRef = await js.InvokeAsync<IJSObjectReference>("import", "/js/site.js");
}
private async void DeleteBook(int id)
{
forDeleteBookModel = await Http.GetBookAsync(id);
StateHasChanged();
}
private async void CreateBook()
{
await Http.CreateBookAsync(forCreateBookModel);
books = await Http.GetBooksAsync();
forCreateBookModel = new BookModel();
await JsObjectRef.InvokeVoidAsync("CloseModal", "#createBookModal");
StateHasChanged();
}
private void OnClickCloseModal()
{
forCreateBookModel = new BookModel();
forUpdateBookModel = new BookModel();
forDeleteBookModel = new BookModel();
}
private async void UpdateBook()
{
await Http.UpdateBookAsync(forUpdateBookModel);
books = await Http.GetBooksAsync();
forUpdateBookModel = new BookModel();
await JsObjectRef.InvokeVoidAsync("CloseModal", "#updateBookModal");
StateHasChanged();
}
private async void onClickUpdate(int id)
{
forUpdateBookModel = await Http.GetBookAsync(id);
StateHasChanged();
}
private async void confirmDeleteBook()
{
await Http.DeleteBookAsync(forDeleteBookModel.Id);
books = await Http.GetBooksAsync();
StateHasChanged();
}
}
Bootstrap Javascript
Open the link below and copy & paste it under wwwroot/js/bootstrap.bundle.min.js
https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js
Jquery Javascript
Open the link below and copy & paste it under wwwroot/js/jquery-3.7.1/js
https://code.jquery.com/jquery-3.7.1.js
Jquery Javascript
Open the link below and copy & paste it under wwwroot/js/jquery-3.7.1.js
export function CloseModal(id) {
$(id).modal('hide');
}
wwwroot/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BlazorWasmApp</title>
<base href="/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="css/app.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="BlazorWasmApp.styles.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script type="module" src="js/site.js"></script>
<script type="" src="js/bootstrap.bundle.min.js"></script>
<script type="" src="js/jquery-3.7.1.js"></script>
</body>
</html>
Program.cs
Modify your Program.cs
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using BlazorWasmApp;
using BlazorWasmApp.Client;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped(sp => new BookHttpClient(new HttpClient { BaseAddress = new Uri("http://localhost:5184") }));
await builder.Build().RunAsync();
Run the application
dotnet run