Skip to content

FernMiran/ComicApp_Blazor

Repository files navigation

1. Overall Project Architecture

This solution follows Clean Architecture principles and is divided into four main layers:

  • Domain Layer:
    Contains the core business entities (such as User, Comic, Character, etc.) and any business rules that are inherent to those entities. This layer is completely independent of any frameworks or external technologies.

  • Application Layer:
    Contains the application’s business logic. Here, you find the CQRS (Command Query Responsibility Segregation) implementation using MediatR. It holds:

    • Commands/Command Handlers: For write operations (e.g., creating a user).
    • Queries/Query Handlers: For read operations.
    • DTOs (Data Transfer Objects): Such as CreateUserDTO in the Contracts project.
    • Validations: Using FluentValidation (e.g., CreateUserValidator) and also pipeline behaviors (such as ValidationBehaviour) that ensure each command/query is valid before reaching its handler.
    • Common Classes: Such as OperationResult to standardize responses.
  • Infrastructure Layer:
    Implements all persistence logic and external integrations. It includes:

    • Entity Framework Core (EF Core) Context: In ComicDbContext, which is configured with Fluent APIs (in the DomainSettings folder) to set up entity configurations.
    • Generic Repositories: For commands and queries. For example, BaseCommandRepository<T> and BaseQueryRepository<T> provide generic operations. Specific repositories (e.g., UserCommandRepository and UserQueryRepository) inherit from these.
    • Unit of Work Pattern: Implemented in UnitOfWork to handle transaction management.
    • Extensions: Such as InfrastructureExtensions, which encapsulate dependency injection registrations.
  • Presentation Layer (Blazor Web UI):
    This is the front end built using Blazor. It interacts with the Application Layer (often via an API or directly through dependency-injected services) to send commands and fetch data. This layer is composed of Razor Components (which serve as the “pages” in a Blazor Server or WASM app).


2. How the Project Is Bootstrapped (Program.cs)

Take a look at the file ComicApp/Program.cs:

using ComicApp.Components;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();

// Map razor components with interactive rendering mode.
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

Key Points:

  • Service Registrations:

    • AddRazorComponents() / AddInteractiveServerComponents():
      These extension methods register the Blazor components. In Blazor Server apps, this allows for interactive (real-time) communication between the client and server.
  • Middleware Pipeline Configuration:

    • Exception Handling:
      For non-development environments, the app uses a global exception handler (UseExceptionHandler) that forwards to an /Error page.
    • HTTPS Redirection, Static Files, and Antiforgery:
      Standard middleware that ensures security (HTTPS, antiforgery tokens) and serves static content.
  • Mapping Razor Components:

    • app.MapRazorComponents():
      This line maps the root Blazor component (App) to the request pipeline and specifies the render mode, which in this case is interactive server render mode.

3. Understanding Razor Pages (Blazor Components) in the Project

In this project, “Razor pages” are implemented as Blazor components. Let’s examine the Users component:

File: ComicApp/Components/Pages/Users.razor

@page "/users"
@inject NavigationManager _navigationManager;

@inherits UsersBase

<PageTitle>Users</PageTitle>

<h3>@welcomeText</h3>

<table>
    <thead>
        <tr>
            <th>Id</th>
            <th>Username</th>
        </tr>
    </thead>
    <tbody>
        <!-- Data rows would be rendered here -->
    </tbody>
</table>

@code {
    // The component’s code logic can be added here (if not using code-behind)
}

Explanation:

  • @page "/users":
    This directive tells Blazor that the component should handle requests at the /users route.

  • Dependency Injection:
    The @inject NavigationManager _navigationManager; statement injects a service to manage navigation.

  • Inheritance:
    @inherits UsersBase links this component to its code-behind (found in Users.razor.cs), where additional logic can be maintained separately from the view markup.

  • PageTitle Component:
    <PageTitle>Users</PageTitle> sets the browser tab title.

  • HTML Markup:
    The <h3> tag displays a welcome text, and a basic table is prepared to eventually show user data.

File: ComicApp/Components/Pages/Users.razor.cs

using Microsoft.AspNetCore.Components;
using ComicApp.Contracts;

namespace ComicApp.Components.Pages
{
    public class UsersBase : ComponentBase
    {
        // You might inject services here for data retrieval. 
        // For example, an HttpClient or a custom IUserService.
        // private readonly HttpClient _httpClient;
        // private readonly NavigationManager _navigationManager;

        // Constructor injection is an option if you register these services.
        // public UsersBase(HttpClient http, NavigationManager navigationManager)
        // {
        //     _httpClient = http;
        //     _navigationManager = navigationManager;
        // }

        protected string welcomeText = "Welcome to 'Users'";

        // You could add methods here to call your API through MediatR.
        // For example:
        // public async Task CreateUser(CreateUserDTO createUserDTO)
        // {
        //     var result = await _httpClient.PostAsJsonAsync("api/user", createUserDTO);
        // }
    }
}

Explanation:

  • Separation of Concerns:
    The code-behind (or “base” class) encapsulates the logic for the component, keeping the Razor markup clean. This separation is useful for unit testing and for maintaining larger components.

  • Placeholder for Service Calls:
    Although commented out in the provided code, you can see that there is room to inject and use services (e.g., an HttpClient or a dedicated IUserService) to call the API endpoints (which, in this project, would interact with the Application layer through CQRS).


4. How Razor Pages (Blazor Components) Fit in the Overall Architecture

  • Presentation Interaction:
    The Blazor UI (Razor components) in the Presentation layer consumes services that eventually trigger application commands or queries via MediatR. For example, if you need to create a new user, your component could call a method (like the commented-out CreateUser in UsersBase) that would:

    1. Prepare a DTO (CreateUserDTO).
    2. Dispatch a command to the Application layer.
    3. The command handler (CreateUserCommandHandler) would perform business logic (e.g., checking for duplicates) and then use the repositories in the Infrastructure layer to persist the new user.
  • Data Flow:

    • From UI to Domain:
      The Razor component sends user input (perhaps via forms) to a service.
    • Through Application Layer:
      The service uses MediatR to send a command or query to the appropriate handler.
    • Persistence:
      The handler uses Generic Repositories and the Unit of Work pattern to interact with the SQL Server database through EF Core.
    • Response Handling:
      If an error occurs (for instance, during validation by FluentValidation), the Exception Middleware intercepts the exception and returns a standardized JSON error response.

5. How to Implement and Extend Razor Pages (Blazor Components)

Step-by-Step Guide to Create a New Razor Component (Page)

  1. Create the Razor File:

    • In the ComicApp/Components/Pages folder, add a new file named, for example, NewFeature.razor.
  2. Add the @page Directive:

    • At the top of your file, specify a route:
      @page "/new-feature"
  3. Inject Required Services:

    • Use @inject to include services. For instance:
      @inject NavigationManager NavigationManager
      @inject IUserService UserService
    • This allows you to use navigation and any custom service (which you would register via DI in your Program.cs or startup configuration).
  4. Define the Markup and Bind Data:

    • Build your UI with standard HTML and Blazor’s data-binding syntax. For example:
      <h1>New Feature</h1>
      <input @bind="newUser.Username" placeholder="Enter username" />
      <input @bind="newUser.Password" placeholder="Enter password" type="password" />
      <button @onclick="HandleCreateUser">Create User</button>
      
      @if (message != null)
      {
          <p>@message</p>
      }
  5. Create the Code Section or a Code-behind File:

    • You can either include an @code block at the bottom of the .razor file or use a code-behind file (e.g., NewFeature.razor.cs). For the inline version:
      @code {
          private CreateUserDTO newUser = new CreateUserDTO();
          private string message;
      
          private async Task HandleCreateUser()
          {
              // Assuming UserService.CreateUser returns a Task<OperationResult>
              var result = await UserService.CreateUser(newUser);
              if (result.Success)
              {
                  message = "User created successfully!";
              }
              else
              {
                  message = string.Join(", ", result.ErrorMessages);
              }
          }
      }
    • This method sends the user data to your backend via the injected service.
  6. Register Your Service:

    • In your DI configuration (possibly in the Program.cs or in a dedicated Extensions class), make sure that IUserService is registered so it can be injected into your component.
  7. Test and Debug:

    • Run your application. Navigate to /new-feature in your browser. Use browser developer tools and Blazor’s built-in error messages to debug any issues.

6. Integration with CQRS and Other Application Services

When implementing more interactive pages, you’ll likely want to trigger commands or queries that are handled by the Application layer. For example:

  • Dispatching a Command:

    • In your Razor component, you might inject an IMediator instance:
      @inject IMediator Mediator
    • Then, you could dispatch a command like this:
      var command = new CreateUser { Username = newUser.Username, Password = newUser.Password };
      var result = await Mediator.Send(command);
    • The CreateUserCommandHandler in the Application layer receives the command, performs validations (using the pipeline behavior ValidationBehaviour), checks business rules, and then uses the Infrastructure repositories to persist the user.
  • Handling Responses and Exceptions:

    • If the command fails validation (or any other exception occurs), the ExceptionMiddleware in the API layer (or similar middleware in your Blazor Server app) will catch the exception, wrap it in a standardized DTO (TaskResultDTO), and return it. Your UI can then display error messages accordingly.

7. Additional Best Practices and Considerations

  • Separation of Concerns:
    Keep UI logic (data binding, user interactions) separate from business logic. Let the Application layer handle complex scenarios (validation, business rules, persistence).

  • Validation:
    Leverage the FluentValidation library in the Application layer to ensure data integrity. The ValidationBehaviour in the MediatR pipeline automatically validates commands before they reach the handlers.

  • Generic Repositories and Unit of Work:
    These patterns help encapsulate data access logic, making the code easier to test and maintain. They also ensure that multiple operations can be grouped together in a single transaction.

  • Middleware for Exception Handling:
    Centralizing exception handling using custom middleware (as shown in ExceptionMiddleware) reduces code duplication and provides a consistent error response format across your application.

  • Blazor Specifics:
    When working with Blazor, remember that you have the flexibility to use code-behind classes (like UsersBase) to keep your markup clean. This approach not only improves maintainability but also simplifies unit testing of your component logic.


Conclusion

This project is a modern web application built with .NET 8 that leverages a layered, clean architecture. It combines Blazor for the interactive UI, CQRS via MediatR for clear separation between commands and queries, FluentValidation for robust input checking, and a generic repository/unit-of-work pattern for data access via EF Core.

Implementing and extending Razor pages (Blazor components) in this context involves:

  1. Creating new .razor files with routing and DI.
  2. Separating UI markup from business logic using code-behind classes.
  3. Wiring up user actions to dispatch commands/queries that flow through the Application layer and finally persist data via the Infrastructure layer.

By following these practices, you ensure that your application remains modular, testable, and maintainable as it scales.

Feel free to ask if you need further details on any specific part of the project!

About

A Comic App Using Blazor and Clean Architecture

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors