Building Blazor WebAssembly Apps with Clean Architecture

You are currently viewing Building Blazor WebAssembly Apps with Clean Architecture

In one of my recent posts Building Blazor Server Apps with Clean Architecture, I demonstrated how to implement the clean architecture in Blazor server apps using CQRS, Mediatr, Entity Framework Core, and repository patterns. In this tutorial, we will learn how to implement clean architecture in Blazor WebAssembly applications. If you are not familiar with clean architecture then I will recommend you to read my previous posts A Guide for Building Software with Clean Architecture and Building ASP.NET Core Apps with Clean Architecture.

What is Blazor?

Blazor is an advanced web framework developed by Microsoft that allows developers to build interactive web applications using C# and .NET. By leveraging the power of WebAssembly, Blazor enables C# code to be executed directly in the browser, eliminating the need for JavaScript. There are many hosting models for Blazor, but the most popular are Blazor Server and Blazor WebAssembly.

What is Blazor Server App?

Blazor Server apps run on the server where they enjoy the support of .NET runtime. All the processing is done on the server and UI/DOM changes are transmitted back to the client over the SignalR connection. As your .NET code is already running on the server, you don’t need to create APIs for your front end. You can directly access services, databases, etc., and do anything you want to do on traditional server-side technology.

Blazor Server Apps

What is Blazor WebAssembly App?

In Blazor WebAssembly apps, the application logic runs directly in the browser, eliminating the need for server-side rendering. Blazor WebAssembly apps are deployed as static files, making them highly portable and easily hosted on any web server.

Blazor WebAssembly Apps

To learn more about Blazor Server and WebAssembly apps read my post A Beginner’s Guide To Blazor Server and WebAssembly Applications. If you want to learn Blazor in detail then read the series of posts, I wrote on Blazor.

What is Clean Architecture?

Clean Architecture is introduced by Robert C. Martin (also known as Uncle Bob) in 2012 and it emphasizes the separation of concerns and the independence of different layers within a system. It promotes a modular and highly maintainable codebase by enforcing clear boundaries and dependencies between components. In this architecture, the business logic is kept separate from the infrastructure and presentation layers, which allows developers to build scalable, testable, and maintainable software.  

Clean Architecture

The clean architecture is implemented by organizing the application code into multiple layers which can contain one or more projects each with a specific responsibility. These layers typically include:

Domain Layer – This layer is the backbone of the clean architecture and all other projects defined in other layers should depend on this layer. This layer is highly abstracted and it contains domain entities, events, value objects, aggregates, etc.   

Application Layer – The application business rules and use cases are available in this layer. Mostly it defines interfaces that are implemented by the outer layers. This layer contains business services, DTOs, mappers, validators, etc.

Infrastructure Layer – This layer contains the implementation of the interfaces defined in the Application layer. This layer is responsible for implementing the technical aspects of the system, such as data access, logging, security, etc. It can have multiple projects using different third-party libraries or frameworks.

Presentation Layer – This layer is responsible for presenting the user interface and handling user interaction with the system. It includes views, controllers, and other web components.

To learn more about clean architecture, you can read my posts A Guide for Building Software with Clean Architecture and Building ASP.NET Core Apps with Clean Architecture.

Getting Started with Clean Architecture in Blazor

Before we dive into the actual implementation details of the application, we need to decide what type of application we need to build. For this post, I have decided to use a demo application that will display the list of football stadiums used in the famous English Premier League in the United Kingdom. The database table with stadium information will look similar to the following screenshot.

Database Table for Blazor WebAssembly Clean Architecture

I have decided to use Visual Studio 2022 with .NET 7.0 to build the demo application so let’s get started.

  • Open Visual Studio 2022 and create a new blank solution with the name BlazorWebAssemblyCleanArchitecture.
  • Next, create the following three solution folders inside the blank solution.
    • Core
    • Infrastructure
    • Presentation
  • Next, create the following two class library projects in the Core solution folder.
    • BlazorWebAssemblyCleanArchitecture.Domain
    • BlazorWebAssemblyCleanArchitecture.Application
  • Next, create the following class library project in the Infrastructure folder
    • BlazorWebAssemblyCleanArchitecture.Persistence
  • Blazor WebAssembly Apps run .NET code in the browser and they don’t have direct access to server-side libraries and code. To run .NET code on the server, it is a common practice to expose a Web API for the Blazor WebAssembly app. We will follow the same approach and we need to create an ASP.NET Core Web API project and a Blazor WebAssembly App project in the Presentation folder
    • BlazorWebAssemblyCleanArchitecture.API
    • BlazorWebAssemblyCleanArchitecture.WebUI
READ ALSO:  A Beginner's Guide To Blazor Server and WebAssembly Applications

To create a Blazor WebAssembly App project, you can follow these steps:

Right-click on the Presentation Solution folder and choose Add > New Project… option.

Select the Blazor WebAssembly App project template from the Add a new project dialog as shown in the screenshot below.

Add New Blazor WebAssembly App Project in Clean Architecture

In the Configure your new project dialog

  • Provide the Project name BlazorWebAssemblyCleanArchitecture.WebUI
  • Provide the location where you want to create the project
Configure Blazor WebAssembly App with Clean Architecture

In the Additional information dialog

  • Select .NET 7.0 (Standard Term Support) as the Framework
  • Select None as the Authentication type
  • Make sure all checkboxes are unchecked
Blazor WebAssembly App using .NET 7 and Clean Architecture

Once Blazor WebAssembly App is created, the project structure in Visual Studio solution explorer will look similar to the following screenshot. 

Blazor WebAssembly App in Visual Studio 2022 Solution Explorer

If you will run the Blazor WebAssembly App, you will see output similar to the following. This means the project is building and running fine and we are ready to move forward.

Running Blazor WebAssembly App in Browser

Next, we need to add the following NuGet packages in different projects of the solution.

BlazorWebAssemblyCleanArchitecture.Domain project needs the following NuGet packages

BlazorWebAssemblyCleanArchitecture.Application project needs the following NuGet packages

BlazorWebAssemblyCleanArchitecture.Persistence project needs the following NuGet packages

BlazorWebAssemblyCleanArchitecture.WebUI project needs the following NuGet packages

Implementing the Domain Layer

The domain layer is the core component of clean architecture and it usually includes domain entities, aggregates, value objects, etc. This layer represents the domain and use-case-independent business logic of the system and has no dependency on any technology, third-party library, or frameworks. For our demo project, let’s create the following two folders Common and Entities in the project. We will add different base objects and entities in these folders.

Domain-Layer-of-Blazor-WebAssembly-App-using-Clean-Architecture

Common Folder

The Common folder contains base classes and interfaces.

IEntity.cs

This file contains a base IEntity interface and all domain entities will implement this interface either directly or indirectly.

public interface IEntity
{
    public int Id { get; set; }
}

IAuditableEntity.cs

This file defines a child interface of the IEntity interfaces defined above. This interface adds additional properties to keep track of the entity’s audit trail information.

public interface IAuditableEntity : IEntity
{
    int? CreatedBy { get; set; }
    DateTime? CreatedDate { get; set; }
    int? UpdatedBy { get; set; }
    DateTime? UpdatedDate { get; set; }
}

BaseEntity.cs

This file contains BaseEntity class that implements the IEntity interface.

public abstract class BaseEntity : IEntity
{
    public int Id { get; set; }
}

BaseAuditableEntity.cs

This class is the child class of BaseEntity and it implements the IAuditableEntity interface defined above.

public abstract class BaseAuditableEntity : BaseEntity, IAuditableEntity
{
    public int? CreatedBy { get; set; }
    public DateTime? CreatedDate { get; set; }
    public int? UpdatedBy { get; set; }
    public DateTime? UpdatedDate { get; set; }
}

Entities Folder

The Entities folder contains domain entities such as Stadium, Player, Club, etc. All domain entities inherit from the above BaseAuditableEntity class. For this post, I will only implement the Stadium entity.

Stadium.cs

public class Stadium : BaseAuditableEntity
{ 
    public string Name { get; set; }
    public string City { get; set; }
    public int? Capacity { get; set; }
    public int? BuiltYear { get; set; }
    public int? PitchLength { get; set; }
    public int? PitchWidth { get; set; } 
}

The following diagram shows the relationships between different classes and interfaces defined in the Domain layer.

Clean Architecture Domain Layer

Implementing the Application Layer

The application layer depends on the Domain layer and acts as a bridge between the Domain layer and external layers such as Persistence or Presentations layer. This layer contains business services, DTOs, Commands, Queries, etc.

Application-Layer-of-Blazor-WebAssembly-App-using-Clean-Architecture

Repositories Folder

This folder contains interfaces such as IUnitOfWork, IGenericRepository, and other domain-specific interfaces such as IStadiumRepository, etc. These interfaces define methods to read and update data.

IGenericRepository.cs

This interface defines a generic repository and it contains generic CRUD methods.

public interface IGenericRepository<T> where T : class, IEntity
{
    IQueryable<T> Entities { get; }
  
    Task<T> GetByIdAsync(int id);
    Task<List<T>> GetAllAsync();
    Task<T> AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(T entity);
}

IUnitOfWork.cs

This interface defines a unit of work pattern that allows us to save changes made by multiple repositories at once.

public interface IUnitOfWork : IDisposable
{
    IGenericRepository<T> Repository<T>() where T : BaseAuditableEntity;
    Task<int> Save(CancellationToken cancellationToken);
    Task<int> SaveAndRemoveCache(CancellationToken cancellationToken, params string[] cacheKeys);
    Task Rollback();
}

IStadiumRepository.cs           

Most of the time, repositories will only use the generic methods defined in the IGenericRepository class but if they have some additional functionality and require a custom method implementation then these methods can be defined in specific repository interfaces. In the following example, IStadiumRepository is defining an additional method to get the list of all stadiums in a specific city.

public interface IStadiumRepository
{
    Task<List<Stadium>> GetStadiumByCityAsync(string cityName);
}

Features Folder

I am a big fan of organizing the application code by functional features. This makes it easier to find all the related classes in one place and to understand and maintain the codebase over time. If you want to have multiple features related to a particular entity or domain then you can create a separate subfolder for each feature e.g. Players, Clubs, Stadiums, etc. Each feature folder can contain subfolders such as Queries and Commands which can contain CQRS commands and queries. Let’s implement the GetAllStadiumsQuery class inside the Features > Stadiums > Queries > GetAllStadiums folder to implement the functionality of fetching all stadiums from the database.

READ ALSO:  A Developer’s Guide To Blazor Component Libraries

GetAllStadiumsQuery.cs

public record GetAllStadiumsQuery : IRequest<List<GetAllStadiumsDto>>;
 
internal class GetAllPlayersQueryHandler : IRequestHandler<GetAllStadiumsQuery, List<GetAllStadiumsDto>>
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;
 
    public GetAllPlayersQueryHandler(IUnitOfWork unitOfWork, IMapper mapper)
    {
        _unitOfWork = unitOfWork;
        _mapper = mapper;
    }
 
    public async Task<List<GetAllStadiumsDto>> Handle(GetAllStadiumsQuery query, CancellationToken cancellationToken)
    {
        return await _unitOfWork.Repository<Stadium>().Entities
               .ProjectTo<GetAllStadiumsDto>(_mapper.ConfigurationProvider)
               .ToListAsync(cancellationToken); 
    }
}

GetAllStadiumsDto.cs

The above query and handler are using the following DTO class also defined in the Features > Stadiums > Queries > GetAllStadiums folder.

public class GetAllStadiumsDto : IMapFrom<Stadium>
{
    public int Id { get; init; }
    public string Name { get; set; }
    public string City { get; set; }
    public int? Capacity { get; set; }
    public int? BuiltYear { get; set; }
    public int? PitchLength { get; set; }
    public int? PitchWidth { get; set; }
}

Implementing the Infrastructure Layer

This layer contains the implementation of the interfaces defined in the Application layer. The project(s) defined in this layer communicate with external systems and technologies, such as databases, APIs, or cloud services. This layer should only interact with the domain layer through the application layer and should not contain any business logic or domain knowledge. The main goal of the infrastructure layer is to encapsulate the technical details of the application so that they can be easily changed or replaced without affecting the rest of the application.

Infrastructure-Layer-of-Blazor-WebAssembly-App-using-Clean-Architecture

Contexts Folder

There can be multiple database contexts in a large project so it is always a good idea to create a separate folder for all context classes. Currently, this folder will only contain ApplicationDbContext.cs file.

ApplicationDbContext.cs

This file contains the ApplicationDbContext that inherits from the EntityFramework DbContext.

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    { }
 
    public DbSet<Stadium> Stadiums => Set<Stadium>();
 
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
    }
 
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        return await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
    }
 
    public override int SaveChanges()
    {
        return SaveChangesAsync().GetAwaiter().GetResult();
    }
}

Repositories Folder

The Repositories folder contains the implementation of the repository and unit of work interfaces.

GenericRepository.cs

The implementation of GenericRepository is very straightforward. It uses ApplicationDbContext to perform standard CRUD operations.

public class GenericRepository<T> : IGenericRepository<T> where T : BaseAuditableEntity
{
    private readonly ApplicationDbContext _dbContext;
 
    public GenericRepository(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }
 
    public IQueryable<T> Entities => _dbContext.Set<T>();
 
    public async Task<T> AddAsync(T entity)
    {
        await _dbContext.Set<T>().AddAsync(entity);
        return entity;
    }
 
    public Task UpdateAsync(T entity)
    {
        T exist = _dbContext.Set<T>().Find(entity.Id);
        _dbContext.Entry(exist).CurrentValues.SetValues(entity);
        return Task.CompletedTask;
    }
 
    public Task DeleteAsync(T entity)
    {
        _dbContext.Set<T>().Remove(entity);
        return Task.CompletedTask;
    }
 
    public async Task<List<T>> GetAllAsync()
    {
        return await _dbContext
            .Set<T>()
            .ToListAsync();
    }
 
    public async Task<T> GetByIdAsync(int id)
    {
        return await _dbContext.Set<T>().FindAsync(id);
    }
}

StadiumRepository.cs

If any repository has any custom method, it can be defined in its respective repository. In our demo app, the IStadiumRepository has a custom method GetStadiumByCityAsync which is implemented in StadiumRespository below.

public class StadiumRepository : IStadiumRepository
{
    private readonly IGenericRepository<Stadium> _repository;
 
    public StadiumRepository(IGenericRepository<Stadium> repository) 
    {
        _repository = repository;
    }
 
    public async Task<List<Stadium>> GetStadiumByCityAsync(string cityName)
    {
        return await _repository.Entities.Where(x => x.City == cityName).ToListAsync();
    }
}

UnitOfWork.cs

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _dbContext;
    private Hashtable _repositories;
    private bool disposed;
 
    public UnitOfWork(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
    }
 
    public IGenericRepository<T> Repository<T>() where T : BaseAuditableEntity
    {
        if (_repositories == null)
            _repositories = new Hashtable();
 
        var type = typeof(T).Name;
 
        if (!_repositories.ContainsKey(type))
        {
            var repositoryType = typeof(GenericRepository<>);
 
            var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), _dbContext);
 
            _repositories.Add(type, repositoryInstance);
        }
 
        return (IGenericRepository<T>) _repositories[type];
    }
 
    public Task Rollback()
    {
        _dbContext.ChangeTracker.Entries().ToList().ForEach(x => x.Reload());
        return Task.CompletedTask;
    }
 
    public async Task<int> Save(CancellationToken cancellationToken)
    {
        return await _dbContext.SaveChangesAsync(cancellationToken);
    }
 
    public Task<int> SaveAndRemoveCache(CancellationToken cancellationToken, params string[] cacheKeys)
    {
        throw new NotImplementedException();
    }
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
 
    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
        {
            if (disposing)
            {
                //dispose managed resources
                _dbContext.Dispose();
            }
        }
        //dispose unmanaged resources
        disposed = true;
    }
}

Implementing the Presentation Layer

The presentation layer is the outmost layer of the clean architecture and this layer should not contain business logic or domain knowledge and should only interact with the rest of the application through the application layer. We need to build two projects in the presentation layer.

READ ALSO:  Building ASP.NET Core Apps with Clean Architecture

BlazorWebAssemblyCleanArchitecture.API – An ASP.NET Core Web API project will communicate with the application layer and expose data to the Blazor WebAssembly App.

BlazorWebAssemblyCleanArchitecture.WebUI – A Blazor WebAssembly App will present the user interface to the end user.

Presentation-Layer-of-Blazor-WebAssembly-App-using-Clean-Architecture

Let’s add the following StatidumsController in the BlazorWebAssemblyCleanArchitecture.API project that is using Mediatr to send GetAllStatidumsQuery which returns the list of all stadiums from the backend application layer.

StadiumsController.cs

[ApiController]
[Route("api/[controller]")]
public class StadiumsController : ControllerBase
{ 
    private readonly IMediator mediator;

    public StadiumsController(IMediator _mediator)
    {
        mediator = _mediator;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        return Ok(await mediator.Send(new GetAllStadiumsQuery()));
    }
}

appsettings.json

We also need to add the database connection string in the appsettings.json file.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=MyServer;Database=PremierLeagueAppDb;User Id=MyUserId;Password=MyPassword;MultipleActiveResultSets=true;TrustServerCertificate=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Program.cs

We need to register application and persistence layer services using the following two commands in the Program.cs file.

builder.Services.AddApplicationLayer();
builder.Services.AddPersistenceLayer(builder.Configuration);

We will call the APIs of this project in Blazor WebAssembly App which will be hosted as a separate application on a separate domain or URL but Browser security prevents a web page from making requests to a different domain than the one that served the web page. In our case, we want to allow other sites to make cross-origin requests to our API project. We need to enable Cross-origin resource sharing (CORS) using the AddCors method in the Program.cs file.

builder.Services.AddCors(policy =>
{
    policy.AddPolicy("CorsPolicy", opt => opt
        .AllowAnyOrigin()
        .AllowAnyHeader()
        .AllowAnyMethod());
});

We also need to add the following UseCors method just before the UseAuthorization method.

app.UseCors("CorsPolicy");

Let’s test our API by running it in the browser and by navigating to /api/statidums URL. You should be able to see the list of stadiums as JSON data appearing in the browser.

Running Web API in Blazor WebAssembly App using Clean Architecture

Blazor WebAssembly apps call web APIs using a preconfigured HttpClient service. HttpClient uses Blazor JSON helpers or HttpRequestMessage objects to make API calls. The System.Net.Http.Json namespace provides extension methods for HttpClient that perform automatic serialization and deserialization using System.Text.Json. These extension methods send requests to a Web API URI and process the response accordingly. The common methods include:

  • GetFromJsonAsync: Sends an HTTP GET request and parses the JSON response body to create an object.
  • PostAsJsonAsync: Sends a POST request to the specified URI containing the value serialized as JSON in the request body.
  • PutAsJsonAsync: Sends an HTTP PUT request, including JSON-encoded content.

Let’s configure the HttpClient service in Program.cs file. Make sure to provide the base address of the Web APIs we want to call from Blazor WebAssembly Apps

builder.Services.AddScoped(sp => new HttpClient
{
    BaseAddress = new Uri("http://localhost:5080")
});

We already have Index.razor file in Blazor WebAssembly App, We need to add Index.razor.cs file as a code behind file of this component and inject HttpClient using the [Inject] attribute. Finally, we need to use the GetFromJsonAsync method to call the Web API that will give us the list of stadiums.

public partial class Index
{
    [Inject]
    private HttpClient Http { get; set; }

    private List<GetAllStadiumsDto> stadiums;

    protected override async Task OnInitializedAsync()
    {
        stadiums = await Http.GetFromJsonAsync<List<GetAllStadiumsDto>>("api/stadiums");
    }
}

If you want to learn more about HttpClient usage in Blazor Apps, you can read my posts Making HTTP Requests in Blazor WebAssembly Apps and Making HTTP Requests in Blazor Server Apps

The code in the Index.razor is very straightforward. We are iterating all stadiums using the foreach loop and displaying the information using a standard HTML table.

@page "/"

<PageTitle>Stadiums</PageTitle>
 
<h1>Stadiums</h1>

@if (stadiums == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Name</th>
                <th>City</th>
                <th>Capacity</th>
                <th>Built Year</th>
                <th>Pitch Length</th>
                <th>Pitch Width</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var stadium in stadiums)
            {
                <tr>
                    <td>@stadium.Name</td>
                    <td>@stadium.City</td>
                    <td>@stadium.Capacity</td>
                    <td>@stadium.BuiltYear</td>
                    <td>@stadium.PitchLength</td>
                    <td>@stadium.PitchWidth</td>
                </tr>
            }
        </tbody>
    </table>
}

Before we run our Blazor WebAssembly App, we need to make sure the Web API it is consuming is also up and running at the same time. Luckily, Visual Studio has a built-in feature that allows us to run multiple projects at once. Right click on the Solution Explorer and click Configure Startup Projects… menu option.

Configure multiple Startup Projects in Visual Studio 2022

From the Solution Proper Pages dialog, click the Multiple startup projects radio button and then select Start as Action for both API and WebUI projects. Also, make sure you move both projects to the top of the list using the arrow buttons on the right-hand side.

Configure Web API and Blazor WebAssembly App as Startup Projects

Let’s run the application by pressing F5 and you should see the output similar to the following screenshot.

Running Blazor WebAssembly App using Clean Architecture

Conclusion

In this article, I tried to give you a detailed example of implementing clean architecture and using CQRS, Mediatr, Entity Framework Core, ASP.NET Web APIs and Blazor WebAssembly App. I hope you have found this tutorial useful. The complete source code of our demo application is available on GitHub. If you have any comments or suggestions, please leave your comments below. Don’t forget to share this tutorial with your friends or community.

This Post Has 7 Comments

  1. Kwilingira

    Thanks for this – very insightful. How do you handle EF migrations using this architecture?

  2. Axel

    Nice tutorial !

    I guess you’d need a reference to the EF library in your Domain project in case of a DB first scenario ?

    What would change in such scenarios ?

  3. Shabbir

    Thanks Waqas, Really helpful tutorial.

    I manage use to Blazor server App using your tutorial, what do you recommend for large application whether to use Server App or WebAssembly App ?

  4. gunda ramesh

    I tried but I am getting bellow error.

    ) in GetAllStadiumsQuery.cs
    +
    return await _unitOfWork.Repository().Entities
    BlazorWebAssemblyCleanArchitecture.API.Controllers.StadiumsController.Get() in StadiumsController.cs
    +
    return Ok(await mediator.Send(new GetAllStadiumsQuery()));

  5. marcello

    Hi can you attach the db to the repository?
    Thank you

    1. Waqas Anwar

      You need to provide the database connection string in appsettings.json file in presentation layer.

  6. Kent

    Thanks for the great tutorial! I am having an issue starting the WebUi project though and cannot seem to get the debugger to kick in. The error box that pops up states: “Process with an Id of xxxxxx is not running.” Any clue how to debug or fix this?

Leave a Reply