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