Module : Programmation .Net C#
Niveau : 4ème Génie Informatique
Objectif : Nettoyer le code ! Séparer la logique métier
(Services) de l'interface
graphique (Vues). Comprendre le pattern Injection de Dépendance (DI) et la
programmation asynchrone (async/await).
DashboardData fonctionnel (issu du TP 3).interface IMonService).Dans le TP 3, notre liste de capteurs était définie directement dans la page
MyDashboard.razor.
C'est une mauvaise pratique (couplage fort). Nous allons déplacer cette logique dans une classe
dédiée.
1. Dans le dossier Data (ou créez un dossier Services à la
racine), créez une
nouvelle classe SensorService.cs.
2. Déplacez la logique de création de données ici. Nous allons ajouter quelques capteurs supplémentaires pour rendre les calculs intéressants :
using DashboardData.Models; // Import du namespace où se trouve SensorData
namespace DashboardData.Services;
public class SensorService
{
// Notre "Base de données" en mémoire pour l'instant
private List<SensorData> _sensors = new()
{
new SensorData { Name = "Temp_Salon", Value = 22.5 },
new SensorData { Name = "Hum_Cuisine", Value = 45.0 },
new SensorData { Name = "CO2_Bureau", Value = 800 },
new SensorData { Name = "Temp_Bureau", Value = 24.0 },
new SensorData { Name = "Temp_Ext", Value = 12.0 }
};
// Méthode pour récupérer les données
public List<SensorData> GetSensors()
{
return _sensors;
}
// Méthode pour ajouter une donnée (simulée)
public void AddSensor(SensorData sensor)
{
_sensors.Add(sensor);
}
}
Une Interface est un contrat. Elle définit ce que le service fait, mais pas comment. Cela permet de changer l'implémentation (ex: passer d'une liste en mémoire à une vraie base de données) sans modifier le code de la page.
1. Dans le même dossier Services, créez une interface
ISensorService.cs :
using DashboardData.Models;
namespace DashboardData.Services;
public interface ISensorService
{
List<SensorData> GetSensors();
void AddSensor(SensorData sensor);
}
2. Modifiez SensorService pour implémenter cette interface :
public class SensorService : ISensorService
{
// ... le reste du code ne change pas
}
C'est dans Program.cs que nous disons au moteur Blazor : "Quand une page demande
un
ISensorService, donne-lui une instance de SensorService".
1. Ouvrez Program.cs et ajoutez cette ligne avant
builder.Build() :
// Enregistrement du service avec une durée de vie "Scoped"
builder.Services.AddScoped<ISensorService, SensorService>();
| Durée de Vie | Description | Cas d'usage typique |
|---|---|---|
Singleton |
Une seule instance pour toute l'application. | Configuration, cache partagé. |
Scoped |
Une instance par "circuit" (connexion utilisateur Blazor Server). | Données utilisateur, contexte de base de données. |
Transient |
Une nouvelle instance à chaque injection. | Services légers, calculs sans état. |
Maintenant, notre page MyDashboard.razor ne doit plus créer la liste
elle-même. Elle la
demande.
1. En haut de MyDashboard.razor, ajoutez la directive d'injection :
@page "/dashboard"
@rendermode InteractiveServer
@using DashboardData.Services // Import du namespace
@inject ISensorService SensorService // "Injecte-moi le service !"
2. Modifiez le bloc @code pour utiliser le service :
@code {
private List<SensorData> _sensors = new();
protected override void OnInitialized()
{
// Au lieu de créer la liste ici, on la demande au service
_sensors = SensorService.GetSensors();
}
}
3. Lancez l'application (dotnet watch). L'affichage doit être identique, mais le
code est
bien mieux organisé.
En situation réelle, les données viennent d'une API ou d'une base de données. Ces opérations prennent du temps. Il est impératif de les rendre asynchrones pour ne pas bloquer l'interface.
1. Dans ISensorService.cs, modifiez la signature de GetSensors :
// Avant :
List<SensorData> GetSensors();
// Après :
Task<List<SensorData>> GetSensorsAsync();
2. Implémentez-la dans SensorService.cs avec un délai simulé
:
public async Task<List<SensorData>> GetSensorsAsync()
{
// Simule 2 secondes de latence réseau
await Task.Delay(2000);
return _sensors;
}
3. Mettez à jour MyDashboard.razor pour utiliser cette méthode
asynchrone :
@code {
private List<SensorData> _sensors = new();
private bool _isLoading = true;
// OnInitializedAsync au lieu de OnInitialized
protected override async Task OnInitializedAsync()
{
_sensors = await SensorService.GetSensorsAsync();
_isLoading = false; // Le chargement est terminé
}
}
4. Ajoutez un indicateur de chargement dans le HTML :
@if (_isLoading)
{
<p><em>Chargement des données...</em></p>
}
else
{
<!-- Le tableau de données -->
<table> ... </table>
}
5. Relancez. Vous devriez voir le message "Chargement..." pendant 2 secondes avant l'affichage des données !
Objectif : Créer une page dédiée pour l'ajout d'un capteur, avec navigation.
AddSensor.razor avec la route
@page "/add-sensor".
MyDashboard.razor, ajoutez un bouton pour naviguer vers cette page.AddSensor.razor, créez le formulaire (inputs + bouton de validation).
NavigationManager pour gérer la redirection.<!-- En haut du fichier -->
@inject NavigationManager NavManager
@code {
private void NavigateBack()
{
// Retour au tableau de bord
NavManager.NavigateTo("/dashboard");
}
}
Pour mieux comprendre, créez un nouveau service simple :
UserCounterService.cs avec une propriété Count
et une méthode
Increment().
Program.cs avec différentes durées de vie.Utilisez builder.Services.AddSingleton<UserCounterService>().
Ouvrez votre dashboard dans deux onglets différents (ou un en navigation privée). Incrémentez dans l'un, rafraîchissez l'autre.
Changez pour builder.Services.AddScoped<UserCounterService>() et
redémarrez.
Utilisez builder.Services.AddTransient<UserCounterService>().
Injectez le service deux fois dans la même page :
@inject UserCounterService S1 et @inject UserCounterService S2. Affichez
les
compteurs de S1 et S2.
@inject).Module: .Net C# Programming
Level: 4th Year Computer Engineering
Objective: Clean up the code! Separate business logic (Services) from the UI
(Views).
Understand the Dependency Injection (DI) pattern and asynchronous programming
(async/await).
DashboardData project (from LAB 3).interface IMyService).In LAB 3, our sensor list was defined directly in the MyDashboard.razor page. This is
bad
practice (tight coupling). We will move this logic into a dedicated class.
1. In the Data folder (or create a Services folder at the root), create a new
class
SensorService.cs.
2. Move the data creation logic here. We will add a few extra sensors to make calculations interesting:
using DashboardData.Models; // Import namespace where SensorData is located
namespace DashboardData.Services;
public class SensorService
{
// Our "database" in memory for now
private List<SensorData> _sensors = new()
{
new SensorData { Name = "Temp_LivingRoom", Value = 22.5 },
new SensorData { Name = "Hum_Kitchen", Value = 45.0 },
new SensorData { Name = "CO2_Office", Value = 800 },
new SensorData { Name = "Temp_Office", Value = 24.0 },
new SensorData { Name = "Temp_Outdoor", Value = 12.0 }
};
// Method to retrieve data
public List<SensorData> GetSensors()
{
return _sensors;
}
// Method to add data (simulated)
public void AddSensor(SensorData sensor)
{
_sensors.Add(sensor);
}
}
An Interface is a contract. It defines what the service does, but not how. This allows changing the implementation (e.g., from an in-memory list to a real database) without modifying the page code.
1. In the same Services folder, create an interface
ISensorService.cs:
using DashboardData.Models;
namespace DashboardData.Services;
public interface ISensorService
{
List<SensorData> GetSensors();
void AddSensor(SensorData sensor);
}
2. Modify SensorService to implement this interface:
public class SensorService : ISensorService
{
// ... rest of code remains the same
}
It's in Program.cs that we tell the Blazor engine: "When a page requests an
ISensorService, give it an instance of SensorService".
1. Open Program.cs and add this line before
builder.Build():
// Register service with "Scoped" lifetime
builder.Services.AddScoped<ISensorService, SensorService>();
| Lifetime | Description | Typical Use Case |
|---|---|---|
Singleton |
A single instance for the entire application. | Configuration, shared cache. |
Scoped |
One instance per "circuit" (Blazor Server user connection). | User data, database context. |
Transient |
A new instance on every injection. | Lightweight services, stateless computations. |
Now, our MyDashboard.razor page no longer creates the list itself. It
requests it.
1. At the top of MyDashboard.razor, add the injection directive:
@page "/dashboard"
@rendermode InteractiveServer
@using DashboardData.Services // Import namespace
@inject ISensorService SensorService // "Inject the service!"
2. Modify the @code block to use the service:
@code {
private List<SensorData> _sensors = new();
protected override void OnInitialized()
{
// Instead of creating the list here, we request it from the service
_sensors = SensorService.GetSensors();
}
}
3. Run the app (dotnet watch). Display should be identical, but the code is
much better
organized.
In real situations, data comes from an API or database. These operations take time. It is imperative to make them asynchronous to avoid blocking the UI.
1. In ISensorService.cs, modify the GetSensors signature:
// Before:
List<SensorData> GetSensors();
// After:
Task<List<SensorData>> GetSensorsAsync();
2. Implement it in SensorService.cs with simulated delay:
public async Task<List<SensorData>> GetSensorsAsync()
{
// Simulate 2 seconds of network latency
await Task.Delay(2000);
return _sensors;
}
3. Update MyDashboard.razor to use this asynchronous method:
@code {
private List<SensorData> _sensors = new();
private bool _isLoading = true;
// OnInitializedAsync instead of OnInitialized
protected override async Task OnInitializedAsync()
{
_sensors = await SensorService.GetSensorsAsync();
_isLoading = false; // Loading is complete
}
}
4. Add a loading indicator in HTML:
@if (_isLoading)
{
<p><em>Loading data...</em></p>
}
else
{
<!-- The data table -->
<table> ... </table>
}
5. Restart. You should see the "Loading..." message for 2 seconds before the data displays!
Goal: Create a dedicated page for adding a sensor, with navigation.
AddSensor.razor with the route
@page "/add-sensor".
MyDashboard.razor, add a button to navigate to this page.AddSensor.razor, create the form (inputs + submit button).NavigationManager to handle redirection.<!-- At the top of the file -->
@inject NavigationManager NavManager
@code {
private void NavigateBack()
{
// Return to dashboard
NavManager.NavigateTo("/dashboard");
}
}
To better understand, create a simple new service:
UserCounterService.cs with a Count property and an
Increment() method.
Program.cs with different lifetimes.Use builder.Services.AddSingleton<UserCounterService>().
Open your dashboard in two different tabs. Increment in one, refresh the other.
Change to builder.Services.AddScoped<UserCounterService>() and
restart.
Use builder.Services.AddTransient<UserCounterService>().
Inject the service twice in the same page:
@inject UserCounterService S1
and @inject UserCounterService S2. Display counters for S1 and S2.
@inject).