Blazor WebAssembly with an Enterprise Twist
#

So, you’ve got your Blazor WebAssembly setup going from our last adventure in the world of .NET 9, and you’re ready to dive a bit deeper. Excellent choice! Today, we’re taking things up a notch by integrating a clean and scalable enterprise architecture, courtesy of my colleague Ben Abt’s AspNetCoreEnterprisePlatform repository. It’s a well-crafted, intuitive setup for applications that require solid backend architecture — perfect for those ready to go pro.

Let’s get started, shall we?

Forking the Enterprise Platform Repository
#

To kick things off, go ahead and fork Ben’s repository. This will give us a solid starting point, though we’ll be customizing it to fit our Blazor WebAssembly project. While the repository comes with a fully functional ASP.NET Core application, we won’t be using that particular part today. Instead, we’ll leverage some of the modular pieces Ben built to save us time and make our project way cooler (and a bit more manageable).

Versioning the Project
#

In Ben’s repo, you’ll find a global.json file with the .NET version. Let’s make sure it aligns with .NET 9 by updating it to the latest release version, which at the time of writing is 9.0.100-rc.2.24474.11. Just update it so we’re all speaking the same .NET language here.


Setting Up Your New Blazor WebAssembly Project
#

  1. In the solution folder 90_Apps, create a new Blazor WebAssembly (WASM) project called Web.
  2. Make sure to stick to the generation settings from our previous blog post, as they’ll work perfectly here. You should see the familiar client-server structure, with our new WASM client as the user-facing interface.

Just like last time, we’ll add a controller to our server project, tweak our Program.cs for dependency injection, and set up some basic HTTP client code in Home.razor to get us ready for action.


Meet the Mediator Pattern: Your New Best Friend
#

Now that we’ve got our setup, it’s time to introduce a little architectural magic: the Mediator pattern. Since I started working with Ben’s enterprise structure, I’ve been a big fan of how this pattern simplifies everything. Here, it serves as the backbone of our application, making communication between different parts smooth, scalable, and — most importantly — manageable.

To start using it, let’s register the core services in our dependency injection (DI) container in Program.cs of our server project. Simply add:

1
builder.Services.AddEngine();

Make sure to include the namespace:

1
using BenjaminAbt.MyDemoPlatform.Engine;

This will allow the AddEngine method to be recognized and the core services to be properly registered in your DI container.

This one line sets the stage for a much cleaner architecture by making the Mediator available across our app.


Adding Providers: Modular Architecture Done Right
#

Providers in Ben’s platform are reusable, independent units within the application that support different features without being features themselves. Think of them as supporting actors that help get the main actors (your features) ready for showtime.

  1. In our solution, create a new folder 60_Providers.
  2. Inside, add another folder called 01_Status, sticking with the Status example from our previous blog.
  3. Within 01_Status, we’ll create three new class library projects:
  • Providers.Status – Contains command and query handlers.
  • Providers.Status.Engine.Queries – Where our query classes live.
  • Providers.Status.DependencyInjection – Registers everything for dependency injection.

Crafting a Query: GetStatusQuery
#

Let’s create a query that will simply return a status message from our backend. We’ll start with a basic query record in Providers.Status.Engine.Queries:

1
public record class GetStatusQuery : IQuery<string>;

This query class is designed to receive a string response. It’s a clean, simple way of defining exactly what we want without a lot of extra baggage. Now, let’s head over to our Providers.Status project and start implementing this query.


The Query Handler with Best-Practice Logging
#

Inside Providers.Status, create a folder called Engine to keep our command and query handlers. Then, let’s create a handler for GetStatusQuery, which will manage the query request and return a simple “OK” status result. We’ll also integrate some of Microsoft’s logging best practices, because why not log like a pro?

Here’s what your query handler should look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class GetStatusQueryHandler : IQueryHandler<GetStatusQuery, string>
{
    private readonly ILogger<GetStatusQueryHandler> _logger;

    public GetStatusQueryHandler(ILogger<GetStatusQueryHandler> logger)
    {
        _logger = logger;
    }

    public Task<string> Handle(GetStatusQuery request, CancellationToken cancellationToken)
    {
        Log.HandleGetStatusQuery(_logger);
        try
        {
            string result = "OK Status Result via Event Dispatcher!";
            Log.HandleGetStatusQuerySuccess(_logger, result);
            return Task.FromResult(result);
        }
        catch (Exception e)
        {
            Log.HandleGetStatusQueryError(_logger, e);
        }

        return Task.FromResult("ERROR");
    }
}

This handler uses partial methods for logging, with different messages for starting, succeeding, and failing the GetStatusQuery. It keeps things clean and follows Microsoft’s recommendations for structured logging:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static partial class Log
{
    [LoggerMessage(1, LogLevel.Information, "GetStatusQueryHandler: Handle GetStatusQuery")]
    public static partial void HandleGetStatusQuery(ILogger logger);

    [LoggerMessage(2, LogLevel.Information, "GetStatusQueryHandler: Handle GetStatusQuery (Result: {Result}) succeeded")]
    public static partial void HandleGetStatusQuerySuccess(ILogger logger, string result);

    [LoggerMessage(3, LogLevel.Error, "GetStatusQueryHandler: Handle GetStatusQuery failed")]
    public static partial void HandleGetStatusQueryError(ILogger logger, Exception e);
}

By using structured logging, we’re making our application easier to debug and our log files friendlier to read — especially if things don’t go quite as planned.

Hint: You could also use primary constructors here to streamline the initialization.


Continuing our journey, let’s make our application more modular and scalable by adding a StatusProviderDependencyInjection class. With this, we can register services, commands, and queries, all while keeping the structure organized and extendable for future needs.


Adding the StatusProviderDependencyInjection Class
#

To begin, let’s set up the dependency injection configuration. In the Providers.Status.DependencyInjection project, add the following class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static class StatusProviderDependencyInjection
{
    public static IServiceCollection AddStatusProvider(this IServiceCollection services)
    {
        // register services
        
        // register commands
        
        // register queries
        services.AddEngineQuery<GetStatusQuery, string, GetStatusQueryHandler>();
        return services;
    }
}

This setup allows us to register everything needed for our Status provider, including our GetStatusQuery and its corresponding handler. This registration pattern is clean and concise, ensuring that all parts of our provider are bundled in a neat package.

Note: In a later blog post, we’ll look into configuring scoped settings from app configurations or secrets and injecting them into providers, adding even more versatility to this setup.

Adding the Provider to Our Server Project
#

Now that we have our provider class ready, we need to make it available in our server project. Head over to the server project and modify the Program.cs file to add the new provider:

1
2
builder.Services.AddEngine();
builder.Services.AddStatusProvider();

Tip: You may need to add a reference to the Providers.Status.DependencyInjection project in your server project to access the AddStatusProvider method.


Integrating the Mediator Pattern with Our Status Controller
#

With everything set up, we’re ready to start using the Mediator pattern in our controller to trigger queries from the Blazor WebAssembly frontend.

  1. Go to your StatusController.
  2. Inject the IEventDispatcher in the constructor.
  3. Use it to handle the GetStatusQuery query and return the result.

Here’s what the updated controller will look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[ApiController]
[Route("api/[controller]")]
public class StatusController : ControllerBase
{
    private readonly IEventDispatcher _eventDispatcher;
    public StatusController(IEventDispatcher eventDispatcher)
    {
        _eventDispatcher = eventDispatcher;
    }
 
    [HttpGet]
    public async Task<IActionResult> Get(CancellationToken cancellationToken)
    {
        var status = await _eventDispatcher.Get(new GetStatusQuery(), cancellationToken);
        return Ok(status);
    }
}

And that’s it! Thanks to the Mediator pattern, this StatusController efficiently handles the GetStatusQuery using the IEventDispatcher, allowing queries to remain modular and well-encapsulated.


Bringing It All Together
#

From the frontend, everything stays just as we had it in the previous post — our Blazor WebAssembly app can now make requests to the StatusController, which in turn leverages our new, provider-based setup and the Mediator pattern to return a result.

And there you have it! We’ve successfully integrated a structured provider with our Blazor WASM app, tapping into Ben’s enterprise-ready architecture and a little dependency injection magic. Now, we’re ready to tackle more complex data flows and configurations in future posts.

So far, we’ve forked, adjusted, and integrated, but we’re only just getting started with Ben’s fantastic enterprise structure. In the next steps, we’ll show you how to connect this architecture to your Blazor WebAssembly front end and see it all in action. Until then, happy coding!


Wrapping Up and Resources
#

If you’re following along and ready to try this out, check out my GitHub repo here for a full reference. And, as always, let me know if you have any questions, suggestions, or just want to chat about Blazor!

Happy coding, and see you in the next post!