refactor: simplifier les actions du contrôleur en supprimant les contrôles ModelState redondants et en améliorant la récupération des données

This commit is contained in:
mirage
2026-04-02 15:57:52 +02:00
parent 6ccb8173ee
commit 8acafbfe49
16 changed files with 158 additions and 61 deletions

View File

@@ -37,11 +37,9 @@ public class ArtisteController : Controller
/// <returns>Redirection.</returns> /// <returns>Redirection.</returns>
public IActionResult Index() public IActionResult Index()
{ {
IEnumerable<Artiste> artistes = this.artisteRepository.FindAll(); IEnumerable<Artiste> artistes = this.artisteRepository.FindAll().OrderBy(t => t.Nom);
var artistes_ordre = artistes.OrderBy(t => t.Nom).ToList(); return this.View(artistes);
return this.View(artistes_ordre);
} }
/// <summary> /// <summary>
@@ -61,14 +59,6 @@ public class ArtisteController : Controller
[HttpPost] [HttpPost]
public IActionResult Create(ArtisteCreateViewModel model) public IActionResult Create(ArtisteCreateViewModel model)
{ {
// vérifier si les données sont corrects.
if (!this.ModelState.IsValid)
{
// Passer model en paramètre afin que
// l'utilisateur conserve sa saissie.
return this.View(model);
}
// Créer un objet Artiste avecc les paramètres. // Créer un objet Artiste avecc les paramètres.
var artiste = new Artiste var artiste = new Artiste
{ {
@@ -115,11 +105,6 @@ public class ArtisteController : Controller
Biographie = model.Biographie, Biographie = model.Biographie,
}; };
if (!this.ModelState.IsValid)
{
return this.View(artiste);
}
this.artisteRepository.Update(artiste); this.artisteRepository.Update(artiste);
return this.RedirectToAction("Index"); return this.RedirectToAction("Index");

View File

@@ -5,6 +5,9 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
using Webzine.Repository.Contracts; using Webzine.Repository.Contracts;
using Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire; using Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire;
/// <summary>
///
/// </summary>
[Area("Administration")] [Area("Administration")]
public class CommentaireController : Controller public class CommentaireController : Controller
{ {

View File

@@ -31,7 +31,7 @@ public class DashboardController : Controller
/// <returns>La vue Index du tableau de bord.</returns> /// <returns>La vue Index du tableau de bord.</returns>
public IActionResult Index() public IActionResult Index()
{ {
DashboardDTO data = dashboardService.GetDashboardData(); DashboardDTO data = this.dashboardService.GetDashboardData();
return this.View(data); return this.View(data);
} }

View File

@@ -63,11 +63,6 @@ public class StyleController : Controller
[HttpPost] [HttpPost]
public IActionResult Create(StyleCreateViewModel model) public IActionResult Create(StyleCreateViewModel model)
{ {
if (!this.ModelState.IsValid)
{
return this.View(model);
}
var style = new Style var style = new Style
{ {
Libelle = model.Libelle, Libelle = model.Libelle,
@@ -124,7 +119,6 @@ public class StyleController : Controller
/// </summary> /// </summary>
/// <param name="id">L'identifiant du style a editer.</param> /// <param name="id">L'identifiant du style a editer.</param>
/// <returns>La vue d'edition ou une redirection vers l'index si le style n'existe pas.</returns> /// <returns>La vue d'edition ou une redirection vers l'index si le style n'existe pas.</returns>
[HttpGet]
public IActionResult Edit(int id) public IActionResult Edit(int id)
{ {
var style = this.styleRepository.Find(id); var style = this.styleRepository.Find(id);
@@ -151,11 +145,6 @@ public class StyleController : Controller
[HttpPost] [HttpPost]
public IActionResult Edit(StyleEditViewModel model) public IActionResult Edit(StyleEditViewModel model)
{ {
if (!this.ModelState.IsValid)
{
return this.View(model);
}
var style = this.styleRepository.Find(model.IdStyle); var style = this.styleRepository.Find(model.IdStyle);
if (style == null) if (style == null)
{ {

View File

@@ -95,13 +95,8 @@ public class TitreController : Controller
[HttpPost] [HttpPost]
public IActionResult Create(TitreAdminDTO model) public IActionResult Create(TitreAdminDTO model)
{ {
if (this.ModelState.IsValid) this.titreAdminService.CreerTitre(model);
{ return this.RedirectToAction("Index");
this.titreAdminService.CreerTitre(model);
return this.RedirectToAction("Index");
}
return this.View(model);
} }
/// <summary> /// <summary>
@@ -152,13 +147,8 @@ public class TitreController : Controller
[HttpPost] [HttpPost]
public IActionResult Edit(TitreAdminDTO model) public IActionResult Edit(TitreAdminDTO model)
{ {
if (this.ModelState.IsValid) this.titreAdminService.ModifierTitre(model);
{ return this.RedirectToAction("Index");
this.titreAdminService.ModifierTitre(model);
return this.RedirectToAction("Index");
}
return this.View(model);
} }
/// <summary> /// <summary>

View File

@@ -21,6 +21,7 @@
/// </summary> /// </summary>
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param> /// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
/// <param name="configuration">Service d'injection de configuration pour accéder aux paramètres de l'application.</param> /// <param name="configuration">Service d'injection de configuration pour accéder aux paramètres de l'application.</param>
/// <param name="titreRepository"></param>
public AccueilController( public AccueilController(
ILogger<AccueilController> logger, ILogger<AccueilController> logger,
IConfiguration configuration, IConfiguration configuration,

View File

@@ -2,12 +2,14 @@ namespace Webzine.WebApplication.Controllers;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
/// <summary>
/// Controller de version de l'API.
/// </summary>
public class ApiController : ControllerBase public class ApiController : ControllerBase
{ {
private readonly ILogger<ApiController> logger; private readonly ILogger<ApiController> logger;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ApiController"/> class.
/// Initialise une nouvelle instance de la classe <see cref="ApiController"/>. /// Initialise une nouvelle instance de la classe <see cref="ApiController"/>.
/// </summary> /// </summary>
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param> /// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
@@ -21,7 +23,6 @@ public class ApiController : ControllerBase
/// Endpoint de test pour vérifier que l'API fonctionne correctement. Retourne un objet JSON contenant le nom et la version de l'application. /// Endpoint de test pour vérifier que l'API fonctionne correctement. Retourne un objet JSON contenant le nom et la version de l'application.
/// </summary> /// </summary>
/// <returns>Un objet JSON avec les propriétés "nom" et "version".</returns> /// <returns>Un objet JSON avec les propriétés "nom" et "version".</returns>
[HttpGet]
public IActionResult Version() public IActionResult Version()
{ {
this.logger.LogInformation("Get Version was called"); this.logger.LogInformation("Get Version was called");

View File

@@ -5,6 +5,9 @@
using Webzine.Repository.Contracts; using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Artiste; using Webzine.WebApplication.ViewModels.Artiste;
/// <summary>
///
/// </summary>
public class ArtisteController : Controller public class ArtisteController : Controller
{ {
// Injection du logger via le constructeur // Injection du logger via le constructeur
@@ -16,6 +19,7 @@
/// Initialise une nouvelle instance du <see cref="ArtisteController"/>. avec un service de journalisation injecté. /// Initialise une nouvelle instance du <see cref="ArtisteController"/>. avec un service de journalisation injecté.
/// </summary> /// </summary>
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param> /// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
/// <param name="artisteRepository"></param>
public ArtisteController( public ArtisteController(
ILogger<ArtisteController> logger, ILogger<ArtisteController> logger,
IArtisteRepository artisteRepository) IArtisteRepository artisteRepository)

View File

@@ -9,12 +9,21 @@ namespace Webzine.WebApplication.Controllers
using Webzine.Repository.Contracts; using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Recherche; using Webzine.WebApplication.ViewModels.Recherche;
/// <summary>
///
/// </summary>
public class RechercheController : Controller public class RechercheController : Controller
{ {
private readonly ILogger<RechercheController> logger; private readonly ILogger<RechercheController> logger;
private readonly ITitreRepository titreRepository; private readonly ITitreRepository titreRepository;
private readonly IArtisteRepository artisteRepository; private readonly IArtisteRepository artisteRepository;
/// <summary>
/// Initialise une nouvelle instance de la classe <see cref="RechercheController"/>.
/// </summary>
/// <param name="logger"></param>
/// <param name="titreRepository"></param>
/// <param name="artisteRepository"></param>
public RechercheController( public RechercheController(
ILogger<RechercheController> logger, ILogger<RechercheController> logger,
ITitreRepository titreRepository, ITitreRepository titreRepository,

View File

@@ -119,7 +119,7 @@ namespace Webzine.WebApplication.Controllers
this.titreRepository.IncrementNbLikes(titre); this.titreRepository.IncrementNbLikes(titre);
} }
return this.RedirectToAction("Details", new { id = model.IdTitre }); return this.RedirectToAction("Index", new { id = model.IdTitre });
} }
/// <summary> /// <summary>
@@ -130,12 +130,6 @@ namespace Webzine.WebApplication.Controllers
[HttpPost] [HttpPost]
public IActionResult Comment(TitreComment model) public IActionResult Comment(TitreComment model)
{ {
if (!this.ModelState.IsValid)
{
this.logger.LogWarning("Echec de validation du modele de commentaire pour le titre ID {Id}.", model.IdTitre);
return this.RedirectToAction("Details", new { id = model.IdTitre });
}
var titre = this.titreRepository.Find(model.IdTitre); var titre = this.titreRepository.Find(model.IdTitre);
if (titre == null) if (titre == null)
@@ -156,7 +150,7 @@ namespace Webzine.WebApplication.Controllers
this.logger.LogInformation("Commentaire ajoute avec succes au titre ID {Id}.", model.IdTitre); this.logger.LogInformation("Commentaire ajoute avec succes au titre ID {Id}.", model.IdTitre);
return this.RedirectToAction("Details", new { id = model.IdTitre }); return this.RedirectToAction("Index", new { id = model.IdTitre });
} }
private static TitreStyleItem MapTitreItem(Titre titre) private static TitreStyleItem MapTitreItem(Titre titre)

View File

@@ -0,0 +1,42 @@
namespace Webzine.WebApplication.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
/// <summary>
/// Filtre d'exception global qui intercepte toute exception non gérée et la journalise automatiquement.
/// </summary>
public class GlobalExceptionFilter : IExceptionFilter
{
private readonly ILogger<GlobalExceptionFilter> logger;
/// <summary>
/// Initializes a new instance of the <see cref="GlobalExceptionFilter"/> class.
/// </summary>
/// <param name="logger">Service de journalisation injecté.</param>
public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
{
this.logger = logger;
}
/// <inheritdoc/>
public void OnException(ExceptionContext context)
{
this.logger.LogError(
context.Exception,
"Erreur non gérée dans {Action} : {Message}",
context.ActionDescriptor.DisplayName,
context.Exception.Message);
context.Result = new ObjectResult(new
{
erreur = "Une erreur inattendue est survenue.",
detail = context.Exception.Message,
})
{
StatusCode = StatusCodes.Status500InternalServerError,
};
context.ExceptionHandled = true;
}
}

View File

@@ -0,0 +1,73 @@
namespace Webzine.WebApplication.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
/// <summary>
/// Filtre d'action qui valide automatiquement le ModelState avant l'exécution du contrôleur.
/// Mesure également le temps d'exécution de chaque action (niveau Trace).
/// </summary>
public class ValidationActionFilter : IActionFilter
{
private readonly ILogger<ValidationActionFilter> logger;
/// <summary>
/// Initializes a new instance of the <see cref="ValidationActionFilter"/> class.
/// </summary>
/// <param name="logger">Service de journalisation injecté.</param>
public ValidationActionFilter(ILogger<ValidationActionFilter> logger)
{
this.logger = logger;
}
/// <inheritdoc/>
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var erreurs = context.ModelState
.Where(e => e.Value?.Errors.Count > 0)
.Select(e => $"{e.Key}: {string.Join(", ", e.Value!.Errors.Select(err => err.ErrorMessage))}")
.ToList();
this.logger.LogWarning(
"Validation échouée pour {Action} : {Erreurs}",
context.ActionDescriptor.DisplayName,
string.Join(" | ", erreurs));
string actionName = context.RouteData.Values["action"]?.ToString() ?? string.Empty;
// cas spécial: titre details
if (actionName.Equals("Index", StringComparison.OrdinalIgnoreCase))
{
context.Result = new RedirectResult("/");
return;
}
// Récupère le modèle soumis (premier argument de l'action, s'il existe)
object? model = context.ActionArguments.Values.FirstOrDefault();
if (context.Controller is Controller controller)
{
context.Result = new ViewResult
{
ViewName = actionName,
ViewData = new Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary(
controller.ViewData)
{
Model = model,
},
};
}
else
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
/// <inheritdoc/>
public void OnActionExecuted(ActionExecutedContext context)
{
}
}

View File

@@ -15,6 +15,7 @@ using Webzine.Repository;
using Webzine.Repository.Contracts; using Webzine.Repository.Contracts;
using Webzine.WebApplication.Configuration; using Webzine.WebApplication.Configuration;
using Webzine.WebApplication.Extensions; using Webzine.WebApplication.Extensions;
using Webzine.WebApplication.Filters;
using Webzine.WebApplication.Interceptors; using Webzine.WebApplication.Interceptors;
// Initiation du logger NLog pour la classe courante afin de pouvoir l'utiliser pour logger des messages d'information, d'erreur, etc avant la construction de l'application. // Initiation du logger NLog pour la classe courante afin de pouvoir l'utiliser pour logger des messages d'information, d'erreur, etc avant la construction de l'application.
@@ -27,8 +28,11 @@ try
// Ajoute les services necessaires pour permettre l'utilisation des // Ajoute les services necessaires pour permettre l'utilisation des
// controllers avec des vues. // controllers avec des vues.
builder.Services.AddControllersWithViews() builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add<ValidationActionFilter>();
options.Filters.Add<GlobalExceptionFilter>();
})
// Ajoute la compilation des vues lors de l'execution de l'application. // Ajoute la compilation des vues lors de l'execution de l'application.
// Cela nous evite de recompiler l'application a chaque modification de vue. // Cela nous evite de recompiler l'application a chaque modification de vue.
// Necessite le package Nuget Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation. // Necessite le package Nuget Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.
@@ -87,6 +91,9 @@ try
builder.Services.AddScoped<IDashboardService, DashboardService>(); builder.Services.AddScoped<IDashboardService, DashboardService>();
builder.Services.AddScoped<ITitreAdminService, TitreAdminService>(); builder.Services.AddScoped<ITitreAdminService, TitreAdminService>();
builder.Services.AddScoped<ValidationActionFilter>();
builder.Services.AddScoped<GlobalExceptionFilter>();
// https://learn.microsoft.com/fr-fr/aspnet/core/performance/response-compression?view=aspnetcore-10.0#configuration // https://learn.microsoft.com/fr-fr/aspnet/core/performance/response-compression?view=aspnetcore-10.0#configuration
// Ajoute le service de compression des réponses HTTP pour réduire la taille des données envoyées au client et améliorer les performances de l'application. // Ajoute le service de compression des réponses HTTP pour réduire la taille des données envoyées au client et améliorer les performances de l'application.
builder.Services.AddResponseCompression(); builder.Services.AddResponseCompression();

View File

@@ -133,8 +133,7 @@
<textarea name="Contenu" <textarea name="Contenu"
rows="3" rows="3"
class="form-control input-full" class="form-control input-full"
placeholder="Votre commentaire..." placeholder="Votre commentaire..."></textarea>
required></textarea>
</div> </div>
</div> </div>
@@ -156,7 +155,7 @@
<h4 class="mb-4">Commentaires</h4> <h4 class="mb-4">Commentaires</h4>
@if (Model.Details.Commentaires != null && Model.Details.Commentaires.Any()) @if (Model.Details.Commentaires.Any())
{ {
foreach (var comment in Model.Details.Commentaires.OrderByDescending(c => c.DateCreation)) foreach (var comment in Model.Details.Commentaires.OrderByDescending(c => c.DateCreation))
{ {

View File

@@ -26,7 +26,7 @@
<div class="d-flex align-items-start my-3"> <div class="d-flex align-items-start my-3">
<!-- Image --> <!-- Image -->
<a asp-action="Details" <a asp-action="Index"
asp-route-id="@titre.IdTitre" asp-route-id="@titre.IdTitre"
class="me-3 text-black"> class="me-3 text-black">
<img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70px" height="70px" class="object-fit-cover" loading="lazy"/> <img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70px" height="70px" class="object-fit-cover" loading="lazy"/>
@@ -41,7 +41,7 @@
@titre.ArtisteNom @titre.ArtisteNom
</a> </a>
- -
<a asp-action="Details" <a asp-action="Index"
asp-route-id="@titre.IdTitre"> asp-route-id="@titre.IdTitre">
@titre.Libelle @titre.Libelle
</a> </a>

View File

@@ -22,7 +22,7 @@
layout="${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|${aspnet-request-url:whenEmpty=NoRequest}" /> layout="${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|${aspnet-request-url:whenEmpty=NoRequest}" />
<!-- Console pour debug immédiat --> <!-- Console pour debug immédiat -->
<target xsi:type="Console" name="Info" <target xsi:type="Console" name="console"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}" /> layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
</targets> </targets>