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>
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_ordre);
return this.View(artistes);
}
/// <summary>
@@ -61,14 +59,6 @@ public class ArtisteController : Controller
[HttpPost]
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.
var artiste = new Artiste
{
@@ -115,11 +105,6 @@ public class ArtisteController : Controller
Biographie = model.Biographie,
};
if (!this.ModelState.IsValid)
{
return this.View(artiste);
}
this.artisteRepository.Update(artiste);
return this.RedirectToAction("Index");

View File

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

View File

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

View File

@@ -63,11 +63,6 @@ public class StyleController : Controller
[HttpPost]
public IActionResult Create(StyleCreateViewModel model)
{
if (!this.ModelState.IsValid)
{
return this.View(model);
}
var style = new Style
{
Libelle = model.Libelle,
@@ -124,7 +119,6 @@ public class StyleController : Controller
/// </summary>
/// <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>
[HttpGet]
public IActionResult Edit(int id)
{
var style = this.styleRepository.Find(id);
@@ -151,11 +145,6 @@ public class StyleController : Controller
[HttpPost]
public IActionResult Edit(StyleEditViewModel model)
{
if (!this.ModelState.IsValid)
{
return this.View(model);
}
var style = this.styleRepository.Find(model.IdStyle);
if (style == null)
{

View File

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

View File

@@ -21,6 +21,7 @@
/// </summary>
/// <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="titreRepository"></param>
public AccueilController(
ILogger<AccueilController> logger,
IConfiguration configuration,

View File

@@ -2,12 +2,14 @@ namespace Webzine.WebApplication.Controllers;
using Microsoft.AspNetCore.Mvc;
/// <summary>
/// Controller de version de l'API.
/// </summary>
public class ApiController : ControllerBase
{
private readonly ILogger<ApiController> logger;
/// <summary>
/// Initializes a new instance of the <see cref="ApiController"/> class.
/// Initialise une nouvelle instance de la classe <see cref="ApiController"/>.
/// </summary>
/// <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.
/// </summary>
/// <returns>Un objet JSON avec les propriétés "nom" et "version".</returns>
[HttpGet]
public IActionResult Version()
{
this.logger.LogInformation("Get Version was called");

View File

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

View File

@@ -9,12 +9,21 @@ namespace Webzine.WebApplication.Controllers
using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Recherche;
/// <summary>
///
/// </summary>
public class RechercheController : Controller
{
private readonly ILogger<RechercheController> logger;
private readonly ITitreRepository titreRepository;
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(
ILogger<RechercheController> logger,
ITitreRepository titreRepository,

View File

@@ -119,7 +119,7 @@ namespace Webzine.WebApplication.Controllers
this.titreRepository.IncrementNbLikes(titre);
}
return this.RedirectToAction("Details", new { id = model.IdTitre });
return this.RedirectToAction("Index", new { id = model.IdTitre });
}
/// <summary>
@@ -130,12 +130,6 @@ namespace Webzine.WebApplication.Controllers
[HttpPost]
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);
if (titre == null)
@@ -156,7 +150,7 @@ namespace Webzine.WebApplication.Controllers
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)

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.WebApplication.Configuration;
using Webzine.WebApplication.Extensions;
using Webzine.WebApplication.Filters;
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.
@@ -27,8 +28,11 @@ try
// Ajoute les services necessaires pour permettre l'utilisation des
// 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.
// Cela nous evite de recompiler l'application a chaque modification de vue.
// Necessite le package Nuget Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.
@@ -87,6 +91,9 @@ try
builder.Services.AddScoped<IDashboardService, DashboardService>();
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
// 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();

View File

@@ -133,8 +133,7 @@
<textarea name="Contenu"
rows="3"
class="form-control input-full"
placeholder="Votre commentaire..."
required></textarea>
placeholder="Votre commentaire..."></textarea>
</div>
</div>
@@ -156,7 +155,7 @@
<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))
{

View File

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

View File

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