Dans le monde du développement .NET moderne, l'application des principes SOLID est devenue incontournable pour créer des applications maintenables et évolutives. Blazor, le framework web de Microsoft, ne fait pas exception à cette règle. À travers cet article, je partage mon retour d'expérience sur l'implémentation des principes SOLID dans une application Blazor, en mettant en lumière les défis rencontrés et les solutions adoptées.
Les principes SOLID dans le contexte Blazor
Avant de plonger dans l'implémentation, rappelons brièvement les principes SOLID et leur pertinence dans Blazor :
- Single Responsibility Principle (SRP) : Chaque composant Blazor ne doit avoir qu'une seule raison de changer
- Open/Closed Principle (OCP) : Les composants doivent être ouverts à l'extension mais fermés à la modification
- Liskov Substitution Principle (LSP) : Les composants dérivés doivent pouvoir remplacer leurs composants de base
- Interface Segregation Principle (ISP) : Les interfaces doivent être spécifiques aux besoins des clients
- Dependency Injection Principle (DIP) : Les dépendances doivent s'appuyer sur des abstractions
Implémentation pratique
Single Responsibility Principle
Voici un exemple de composant Blazor respectant le SRP :
// UserProfileComponent.razor
@inject IUserService UserService
@inject ILogger Logger
@if (User != null)
{
}
@code {
[Parameter]
public string UserId { get; set; }
private UserModel User { get; set; }
protected override async Task OnInitializedAsync()
{
try
{
User = await UserService.GetUserAsync(UserId);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading user profile");
}
}
}
Open/Closed Principle
Pour respecter l'OCP, nous pouvons créer des composants extensibles :
// IDataDisplayComponent.cs
public interface IDataDisplayComponent
{
T Data { get; set; }
RenderFragment CustomTemplate { get; set; }
}
// GenericDataDisplay.razor
@typeparam T
@implements IDataDisplayComponent
@if (Data != null)
{
@if (CustomTemplate != null)
{
@CustomTemplate
}
else
{
@DefaultTemplate(Data)
}
}
@code {
[Parameter]
public T Data { get; set; }
[Parameter]
public RenderFragment CustomTemplate { get; set; }
private RenderFragment DefaultTemplate => (data) => @@data.ToString();
}
Dependency Injection et Services
L'utilisation correcte de l'injection de dépendances est cruciale dans Blazor :
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped();
services.AddScoped();
services.AddScoped();
}
// UserService.cs
public class UserService : IUserService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
public UserService(
IHttpClientFactory httpClientFactory,
ILogger logger)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
}
public async Task GetUserAsync(string userId)
{
try
{
var client = _httpClientFactory.CreateClient("API");
return await client.GetFromJsonAsync($"api/users/{userId}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching user data");
throw;
}
}
}
Patterns d'implémentation courants
Mediator Pattern
Le pattern Mediator s'intègre particulièrement bien avec Blazor et les principes SOLID :
// MediatorService.cs
public class MediatorService : IMediatorService
{
private readonly IServiceProvider _serviceProvider;
public MediatorService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task SendAsync(IRequest request)
{
var handlerType = typeof(IRequestHandler<,>).MakeGenericType(request.GetType(), typeof(TResponse));
var handler = _serviceProvider.GetService(handlerType);
if (handler == null)
throw new InvalidOperationException($"Handler not found for {request.GetType()}");
return await (Task)handlerType
.GetMethod("Handle")
.Invoke(handler, new object[] { request });
}
}
Tests et validation
Les tests unitaires sont essentiels pour valider l'implémentation des principes SOLID :
// UserProfileComponentTests.cs
public class UserProfileComponentTests
{
[Fact]
public async Task Should_Load_User_Profile_Successfully()
{
// Arrange
var mockUserService = new Mock();
mockUserService
.Setup(x => x.GetUserAsync(It.IsAny()))
.ReturnsAsync(new UserModel { Id = "1", Name = "Test User" });
var cut = new UserProfileComponent
{
UserId = "1"
};
// Act
await cut.OnInitializedAsync();
// Assert
Assert.NotNull(cut.User);
Assert.Equal("Test User", cut.User.Name);
}
}
Considérations de performance
L'application des principes SOLID ne doit pas se faire au détriment des performances :
- Utiliser le lazy loading pour les composants lourds
- Implémenter une stratégie de cache efficace
- Optimiser les rendus avec ShouldRender()
// LazyLoadedComponent.razor
@if (shouldRender)
{
@ChildContent
}
@code {
private bool shouldRender;
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override bool ShouldRender() => shouldRender;
public void Show()
{
shouldRender = true;
StateHasChanged();
}
}
Conclusion
L'application des principes SOLID dans Blazor nécessite une réflexion approfondie mais apporte des bénéfices significatifs en termes de maintenabilité et d'évolutivité. Les points clés à retenir sont :
- La séparation claire des responsabilités entre composants
- L'utilisation efficace de l'injection de dépendances
- La création de composants extensibles et réutilisables
- L'importance des tests unitaires
- L'équilibre entre principes SOLID et performances
En suivant ces principes et en utilisant les patterns appropriés, vous pourrez créer des applications Blazor robustes et maintenables sur le long terme.