#145 : merge de la branch dev.
This commit is contained in:
@@ -31,17 +31,14 @@ public class ArtisteController : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Affiche la liste des artistes. Pour l'instant, les artistes sont générés à partir de noms prédéfinis via la méthode SeedArtisteByName de la classe ArtisteFactory.
|
||||
/// Chaque artiste est ensuite ajouté à une liste d'artistes qui est passée à la vue.
|
||||
/// Affiche la liste des artistes.
|
||||
/// </summary>
|
||||
/// <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 +58,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 +104,6 @@ public class ArtisteController : Controller
|
||||
Biographie = model.Biographie,
|
||||
};
|
||||
|
||||
if (!this.ModelState.IsValid)
|
||||
{
|
||||
return this.View(artiste);
|
||||
}
|
||||
|
||||
this.artisteRepository.Update(artiste);
|
||||
|
||||
return this.RedirectToAction("Index");
|
||||
@@ -163,7 +147,6 @@ public class ArtisteController : Controller
|
||||
this.artisteRepository.Delete(artiste);
|
||||
}
|
||||
|
||||
// 3. Redirect back to the list (or wherever you want them to go after)
|
||||
return this.RedirectToAction("Index");
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
||||
using Webzine.Repository.Contracts;
|
||||
using Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire;
|
||||
|
||||
/// <summary>
|
||||
/// Contrôleur pour la gestion des commentaires dans l'administration du webzine. Ce contrôleur permet d'afficher la liste des commentaires, de supprimer un commentaire spécifique et de gérer les interactions liées aux commentaires dans l'interface d'administration.
|
||||
/// </summary>
|
||||
[Area("Administration")]
|
||||
public class CommentaireController : Controller
|
||||
{
|
||||
|
||||
@@ -2,29 +2,28 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Webzine.Repository.Contracts;
|
||||
using Webzine.WebApplication.Areas.Administration.ViewModels;
|
||||
using Webzine.Business.Contracts;
|
||||
using Webzine.Business.Contracts.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// Contrôleur pour gérer le tableau de bord de l'administration.
|
||||
/// </summary>
|
||||
[Area("Administration")]
|
||||
public class DashboardController : Controller
|
||||
{
|
||||
private readonly ILogger<DashboardController> logger;
|
||||
private readonly IStyleRepository styleRepository;
|
||||
private readonly IArtisteRepository artisteRepository;
|
||||
private readonly ITitreRepository titreRepository;
|
||||
private readonly IDashboardService dashboardService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DashboardController"/> class.
|
||||
/// Initialise une nouvelle instance de la classe <see cref="DashboardController"/>.
|
||||
/// </summary>
|
||||
/// <param name="logger">Service de journalisation injecté.</param>
|
||||
/// <param name="styleRepository">Repository des styles injecté.</param>
|
||||
public DashboardController(ILogger<DashboardController> logger, IStyleRepository styleRepository, IArtisteRepository artisteRepository, ITitreRepository titreRepository)
|
||||
/// <param name="dashboardService">Service de calcul des statistiques du tableau de bord.</param>
|
||||
public DashboardController(ILogger<DashboardController> logger, IDashboardService dashboardService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.styleRepository = styleRepository;
|
||||
this.artisteRepository = artisteRepository;
|
||||
this.titreRepository = titreRepository;
|
||||
this.dashboardService = dashboardService;
|
||||
|
||||
this.logger.LogInformation("Initialisation du contrôleur TitreController.");
|
||||
}
|
||||
@@ -35,42 +34,8 @@ public class DashboardController : Controller
|
||||
/// <returns>La vue Index du tableau de bord.</returns>
|
||||
public IActionResult Index()
|
||||
{
|
||||
var artisteLePlusChronique = this.titreRepository.FindAll()
|
||||
.GroupBy(t => t.Artiste)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.First();
|
||||
DashboardDTO data = this.dashboardService.GetDashboardData();
|
||||
|
||||
var albumLePlusChronique = this.titreRepository.FindAll()
|
||||
.GroupBy(t => t.Artiste)
|
||||
.OrderByDescending(g => g.Select(t => t.Album).Distinct().Count())
|
||||
.First();
|
||||
|
||||
var musiqueLaPlusJouee = this.titreRepository.FindAll()
|
||||
.OrderByDescending(t => t.NbLectures)
|
||||
.First();
|
||||
|
||||
var model = new DashboardViewModel
|
||||
{
|
||||
NombreArtistes = this.artisteRepository.FindAll().Count(),
|
||||
|
||||
ArtisteLePlusChronique = artisteLePlusChronique.Key.Nom,
|
||||
|
||||
AlbumLePlusChronique = albumLePlusChronique.Key.Nom,
|
||||
|
||||
NombreBiographies = this.artisteRepository.FindAll().Count(a => !string.IsNullOrEmpty(a.Biographie)),
|
||||
|
||||
IdMusiqueLaPlusJouee = musiqueLaPlusJouee.IdTitre,
|
||||
MusiqueLaPlusJouee = musiqueLaPlusJouee.Libelle,
|
||||
|
||||
NombreTitres = this.titreRepository.Count(),
|
||||
|
||||
NombreGenres = this.styleRepository.FindAll().Count(),
|
||||
|
||||
NombreLectures = this.titreRepository.FindAll().Sum(t => t.NbLectures),
|
||||
|
||||
NombreLikes = this.titreRepository.FindAll().Sum(t => t.NbLikes),
|
||||
};
|
||||
|
||||
return this.View(model);
|
||||
return this.View(data);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
||||
|
||||
using Business.Contracts;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
using Webzine.Business.Contracts.Dto;
|
||||
using Webzine.Entity;
|
||||
using Webzine.Repository.Contracts;
|
||||
using Webzine.WebApplication.Areas.Administration.ViewModels.Titre;
|
||||
@@ -17,6 +20,7 @@ public class TitreController : Controller
|
||||
private readonly ITitreRepository titreRepository;
|
||||
private readonly IArtisteRepository artisteRepository;
|
||||
private readonly IStyleRepository styleRepository;
|
||||
private readonly ITitreAdminService titreAdminService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TitreController"/> class.
|
||||
@@ -26,12 +30,14 @@ public class TitreController : Controller
|
||||
/// <param name="titreRepository">Repository des titres injecté pour accéder aux données des titres.</param>
|
||||
/// <param name="artisteRepository">Repository des artistes injecté pour accéder aux données des artistes, nécessaires pour les associations avec les titres.</param>
|
||||
/// <param name="styleRepository">Repository des styles injecté pour accéder aux données des styles, nécessaires pour les associations avec les titres.</param>
|
||||
public TitreController(ILogger<TitreController> logger, ITitreRepository titreRepository, IArtisteRepository artisteRepository, IStyleRepository styleRepository)
|
||||
/// <param name="titreAdminService">Service Titre Administration injecté gérant Edit et Crée.</param>
|
||||
public TitreController(ILogger<TitreController> logger, ITitreRepository titreRepository, IArtisteRepository artisteRepository, IStyleRepository styleRepository, ITitreAdminService titreAdminService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.titreRepository = titreRepository;
|
||||
this.artisteRepository = artisteRepository;
|
||||
this.styleRepository = styleRepository;
|
||||
this.titreAdminService = titreAdminService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,6 +87,18 @@ public class TitreController : Controller
|
||||
return this.View(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traite la soumission du formulaire de création d'un titre.
|
||||
/// </summary>
|
||||
/// <param name="model">Données saisies dans le formulaire.</param>
|
||||
/// <returns>Redirection vers Index en cas de succès, réaffichage du formulaire sinon.</returns>
|
||||
[HttpPost]
|
||||
public IActionResult Create(TitreAdminDTO model)
|
||||
{
|
||||
this.titreAdminService.CreerTitre(model);
|
||||
return this.RedirectToAction("Index");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Affiche le formulaire de modification d'un titre existant dans la vue Edit, en préremplissant les champs avec les données du titre sélectionné. Les listes déroulantes pour les artistes et les styles sont également remplies pour permettre à l'utilisateur de modifier ces associations.
|
||||
/// </summary>
|
||||
@@ -121,6 +139,18 @@ public class TitreController : Controller
|
||||
return this.View(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traite la soumission du formulaire de modification d'un titre.
|
||||
/// </summary>
|
||||
/// <param name="model">Données saisies dans le formulaire.</param>
|
||||
/// <returns>Redirection vers Index en cas de succès, réaffichage du formulaire sinon.</returns>
|
||||
[HttpPost]
|
||||
public IActionResult Edit(TitreAdminDTO model)
|
||||
{
|
||||
this.titreAdminService.ModifierTitre(model);
|
||||
return this.RedirectToAction("Index");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Affiche la vue de confirmation de suppression d'un titre, en récupérant les détails du titre à supprimer à partir de l'identifiant fourni. Le ViewModel contient les informations essentielles du titre, telles que le libellé et le nom de l'artiste, pour permettre à l'utilisateur de confirmer la suppression.
|
||||
/// </summary>
|
||||
@@ -154,11 +184,18 @@ public class TitreController : Controller
|
||||
public IActionResult Delete(AdminTitreDelete model)
|
||||
{
|
||||
var titre = this.titreRepository.Find(model.Id);
|
||||
|
||||
if (!this.ModelState.IsValid)
|
||||
{
|
||||
return this.View(model);
|
||||
}
|
||||
|
||||
if (titre != null)
|
||||
{
|
||||
this.titreRepository.Delete(titre);
|
||||
return this.RedirectToAction("Index");
|
||||
}
|
||||
|
||||
return this.RedirectToAction("Index");
|
||||
return this.View(model);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
namespace Webzine.WebApplication.Areas.Administration.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel pour le tableau de bord de l'administration du webzine.
|
||||
/// </summary>
|
||||
public class DashboardViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Définit le nombre total d'artistes chroniqués dans le webzine.
|
||||
/// </summary>
|
||||
public int NombreArtistes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit le nom de l'artiste le plus chroniqué dans le webzine.
|
||||
/// </summary>
|
||||
public string ArtisteLePlusChronique { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit le nom de l'album le plus chroniqué dans le webzine.
|
||||
/// </summary>
|
||||
public string AlbumLePlusChronique { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit le nombre total de biographies d'artistes dans le webzine.
|
||||
/// </summary>
|
||||
public int NombreBiographies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit l'identifiant de la biographie d'artiste la plus lue dans le webzine.
|
||||
/// </summary>
|
||||
public int IdMusiqueLaPlusJouee { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit le nom de la biographie d'artiste la plus lue dans le webzine.
|
||||
/// </summary>
|
||||
public string MusiqueLaPlusJouee { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit le nombre total de titres chroniqués dans le webzine.
|
||||
/// </summary>
|
||||
public int NombreTitres { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit le nombre total de genres musicaux chroniqués dans le webzine.
|
||||
/// </summary>
|
||||
public int NombreGenres { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit le nombre total de chroniques d'albums dans le webzine.
|
||||
/// </summary>
|
||||
public int NombreLectures { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Définit le nombre total de likes sur les chroniques d'albums dans le webzine.
|
||||
/// </summary>
|
||||
public int NombreLikes { get; set; }
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
{
|
||||
<tr class="align-middle">
|
||||
<td>
|
||||
<a asp-action="Details" asp-controller="Titre" asp-route-id="@commentaire.Titre.IdTitre">
|
||||
<a asp-controller="Titre" asp-action="Index" asp-route-id="@commentaire.Titre.IdTitre">
|
||||
@commentaire.Titre.Libelle
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.DashboardViewModel
|
||||
@using Webzine.Business.Contracts.Dto
|
||||
@model DashboardDTO
|
||||
|
||||
<h1 class="mb-4">Tableau de bord</h1>
|
||||
|
||||
@@ -94,7 +95,7 @@
|
||||
<div class="col-md-4">
|
||||
<a asp-area=""
|
||||
asp-controller="Titre"
|
||||
asp-action="Details"
|
||||
asp-action="Index"
|
||||
asp-route-id="@Model.IdMusiqueLaPlusJouee">
|
||||
<div class="ratio ratio-4x3">
|
||||
|
||||
|
||||
13
Webzine.WebApplication/Configuration/Middlewares.cs
Normal file
13
Webzine.WebApplication/Configuration/Middlewares.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Webzine.WebApplication.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Options de seuil pour la détection des opérations EF Core lentes.
|
||||
/// </summary>
|
||||
public class EfPerformanceOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Obtient ou définit le seuil en millisecondes au-delà duquel une commande SQL est journalisée.
|
||||
/// Valeur par défaut : 200 ms.
|
||||
/// </summary>
|
||||
public int SeuilMs { get; set; } = 200;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
using Webzine.Repository.Contracts;
|
||||
using Webzine.WebApplication.ViewModels.Artiste;
|
||||
|
||||
/// <summary>
|
||||
/// Contrôleur pour la gestion des artistes dans l'administration du webzine. Ce contrôleur gère les opérations de création, modification, suppression et affichage des artistes dans l'interface d'administration du webzine. Chaque action du contrôleur prépare un ViewModel spécifique pour la vue correspondante, permettant ainsi une séparation claire entre la logique métier et la présentation des données.
|
||||
/// </summary>
|
||||
public class ArtisteController : Controller
|
||||
{
|
||||
// Injection du logger via le constructeur
|
||||
@@ -12,10 +15,10 @@
|
||||
private readonly IArtisteRepository artisteRepository;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArtisteController"/> class.
|
||||
/// 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">Repository pour accéder aux données des artistes, injecté pour permettre les opérations de création, modification, suppression et affichage des artistes.</param>
|
||||
public ArtisteController(
|
||||
ILogger<ArtisteController> logger,
|
||||
IArtisteRepository artisteRepository)
|
||||
@@ -26,11 +29,10 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prend en paramètre le nom de l'artiste (ex: "fatal-bazooka"), utilise la factory pour trouver l'artiste correspondant, et affiche sa page dédiée.
|
||||
/// Affiche la liste des artistes.
|
||||
/// </summary>
|
||||
/// <param name="nom">Le nom de l'artiste à rechercher, formaté en kebab-case (ex: "fatal-bazooka").</param>
|
||||
/// <returns>La vue de l'artiste avec son ViewModel, ou une redirection vers l'accueil si le nom est vide, ou une erreur 404 si l'artiste n'est pas trouvé.</returns>
|
||||
[HttpGet("/artiste/{nom}")]
|
||||
public IActionResult Index(string nom)
|
||||
{
|
||||
this.logger.LogInformation("Tentative d'accès à l'artiste avec le nom : {NomArtiste}", nom);
|
||||
@@ -41,14 +43,12 @@
|
||||
return this.RedirectToAction("Index", "Accueil");
|
||||
}
|
||||
|
||||
// On transforme "fatal-bazooka" en "Fatal Bazooka" pour la factory
|
||||
// On transforme "fatal-bazooka" en "Fatal Bazooka"
|
||||
string nomPropre = System.Globalization.CultureInfo.CurrentCulture.TextInfo
|
||||
.ToTitleCase(nom.Replace("-", " "));
|
||||
|
||||
// On appelle la factory pour obtenir l'artiste unique
|
||||
var artiste = this.artisteRepository.FindByName(nomPropre);
|
||||
|
||||
// Check if artiste was found
|
||||
if (artiste == null)
|
||||
{
|
||||
this.logger.LogWarning("Artiste non trouvé avec le nom : {NomArtiste}", nomPropre);
|
||||
|
||||
@@ -9,12 +9,21 @@ namespace Webzine.WebApplication.Controllers
|
||||
using Webzine.Repository.Contracts;
|
||||
using Webzine.WebApplication.ViewModels.Recherche;
|
||||
|
||||
/// <summary>
|
||||
/// Controller de la page de recherche d'artistes et de titres.
|
||||
/// </summary>
|
||||
public class RechercheController : Controller
|
||||
{
|
||||
private readonly ILogger<RechercheController> logger;
|
||||
private readonly ITitreRepository titreRepository;
|
||||
private readonly IArtisteRepository artisteRepository;
|
||||
|
||||
/// <summary>
|
||||
/// Constructeur du controller de la page de recherche d'artistes et de titres.
|
||||
/// </summary>
|
||||
/// <param name="logger">Le logger pour enregistrer les événements.</param>
|
||||
/// <param name="titreRepository">Le repository pour gérer les opérations sur les titres.</param>
|
||||
/// <param name="artisteRepository">Le repository pour gérer les opérations sur les artistes.</param>
|
||||
public RechercheController(
|
||||
ILogger<RechercheController> logger,
|
||||
ITitreRepository titreRepository,
|
||||
@@ -29,7 +38,7 @@ namespace Webzine.WebApplication.Controllers
|
||||
/// Affichage de la page Recherche depuis le header de l'app.
|
||||
/// </summary>
|
||||
/// <param name="mot">Nom d'artiste ou de titre.</param>
|
||||
/// <returns>Page de recherche avec les résultats.</returns>
|
||||
/// <returns>Page de recherche avec les r<EFBFBD>sultats.</returns>
|
||||
public IActionResult Index(string mot)
|
||||
{
|
||||
// Logger la recherche.
|
||||
@@ -41,7 +50,7 @@ namespace Webzine.WebApplication.Controllers
|
||||
// Recherche des artistes.
|
||||
var artistes = this.artisteRepository.Search(mot);
|
||||
|
||||
// Paramètres a retourner à la vue.
|
||||
// Param<EFBFBD>tres a retourner <EFBFBD> la vue.
|
||||
var vm = new RechercheIndexViewModel
|
||||
{
|
||||
Mot = mot,
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Webzine.WebApplication.Controllers
|
||||
/// affichage des details, filtrage par style,
|
||||
/// ajout de likes, commentaires et recherche.
|
||||
/// </summary>
|
||||
[Route("titre")]
|
||||
public class TitreController : Controller
|
||||
{
|
||||
private readonly ILogger<TitreController> logger;
|
||||
@@ -40,8 +39,7 @@ namespace Webzine.WebApplication.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id">Identifiant du titre.</param>
|
||||
/// <returns>Vue des details ou 404 si introuvable.</returns>
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult Details(int id)
|
||||
public IActionResult Index(int id)
|
||||
{
|
||||
this.logger.LogInformation("Demande d'affichage du detail pour le titre ID {Id}.", id);
|
||||
|
||||
@@ -53,6 +51,8 @@ namespace Webzine.WebApplication.Controllers
|
||||
return this.RedirectToAction("Index");
|
||||
}
|
||||
|
||||
this.titreRepository.IncrementNbLectures(titre);
|
||||
|
||||
var vm = new TitreDetail
|
||||
{
|
||||
Details = new TitreContent
|
||||
@@ -83,7 +83,6 @@ namespace Webzine.WebApplication.Controllers
|
||||
/// </summary>
|
||||
/// <param name="style">Nom du style musical.</param>
|
||||
/// <returns>Vue contenant la liste filtree.</returns>
|
||||
[HttpGet("style/{style}")]
|
||||
public IActionResult Style(string style)
|
||||
{
|
||||
this.logger.LogInformation("Recherche des titres pour le style : {Style}.", style);
|
||||
@@ -104,7 +103,7 @@ namespace Webzine.WebApplication.Controllers
|
||||
/// </summary>
|
||||
/// <param name="model">Modele contenant l'identifiant du titre.</param>
|
||||
/// <returns>Redirection vers la page detail.</returns>
|
||||
[HttpPost("like")]
|
||||
[HttpPost]
|
||||
public IActionResult Like(TitreLike model)
|
||||
{
|
||||
this.logger.LogInformation("Ajout d'un like pour le titre ID {Id}.", model.IdTitre);
|
||||
@@ -114,12 +113,13 @@ namespace Webzine.WebApplication.Controllers
|
||||
if (titre == null)
|
||||
{
|
||||
this.logger.LogWarning("Impossible d'ajouter un like. Titre ID {Id} introuvable.", model.IdTitre);
|
||||
return this.RedirectToAction("Index");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.titreRepository.IncrementNbLikes(titre);
|
||||
}
|
||||
|
||||
titre.NbLikes++;
|
||||
|
||||
return this.RedirectToAction("Details", new { id = model.IdTitre });
|
||||
return this.RedirectToAction("Index", new { id = model.IdTitre });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -127,15 +127,9 @@ namespace Webzine.WebApplication.Controllers
|
||||
/// </summary>
|
||||
/// <param name="model">Donnees du commentaire.</param>
|
||||
/// <returns>Redirection vers la page detail.</returns>
|
||||
[HttpPost("comment")]
|
||||
[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)
|
||||
|
||||
@@ -7,6 +7,37 @@ public static class RouteConfiguration
|
||||
/// </summary>
|
||||
public static void MapCustomRoutes(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
// ----------- TITRE -----------
|
||||
endpoints.MapControllerRoute(
|
||||
name: "TitreStyle",
|
||||
pattern: "titres/style/{style}",
|
||||
defaults: new { controller = "Titre", action = "Style" });
|
||||
|
||||
endpoints.MapControllerRoute(
|
||||
name: "TitreIndex",
|
||||
pattern: "titre/{id}",
|
||||
defaults: new { controller = "Titre", action = "Index" });
|
||||
|
||||
endpoints.MapControllerRoute(
|
||||
name: "ArtisteIndex",
|
||||
pattern: "artiste/{nom}",
|
||||
defaults: new { controller = "Artiste", action = "Index" });
|
||||
|
||||
// ----------- ADMIN -----------
|
||||
var adminRoutes = new Dictionary<string, string>
|
||||
{
|
||||
{ "artistes", "Artiste" }, { "commentaires", "Commentaire" }, { "styles", "Style" }, { "titres", "Titre" },
|
||||
};
|
||||
|
||||
foreach (var route in adminRoutes)
|
||||
{
|
||||
endpoints.MapControllerRoute(
|
||||
name: $"Admin{route.Value}Index",
|
||||
pattern: $"administration/{route.Key}",
|
||||
defaults: new { area = "Administration", controller = route.Value, action = "Index" });
|
||||
}
|
||||
|
||||
// --- AUTRE PROUTES ---
|
||||
endpoints.MapControllerRoute(
|
||||
name: "areas",
|
||||
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
42
Webzine.WebApplication/Filters/GlobalExceptionFilter.cs
Normal file
42
Webzine.WebApplication/Filters/GlobalExceptionFilter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
73
Webzine.WebApplication/Filters/ValidationActionFilter.cs
Normal file
73
Webzine.WebApplication/Filters/ValidationActionFilter.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
146
Webzine.WebApplication/Interceptors/EfSlowQueryInterceptor.cs
Normal file
146
Webzine.WebApplication/Interceptors/EfSlowQueryInterceptor.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
namespace Webzine.WebApplication.Interceptors;
|
||||
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Configuration;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Intercepteur EF Core qui journalise uniquement les commandes SQL dépassant le seuil configuré.
|
||||
/// Remonte la pile d'appels pour identifier la méthode repository (<c>Webzine.Repository.*</c>) à l'origine de la requête.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Références :</b>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// EF Core interceptors (doc officielle) :
|
||||
/// <see href="https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors"/>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="DbCommandInterceptor"/> API :
|
||||
/// <see href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.diagnostics.dbcommandinterceptor"/>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Exemple de slow-query interceptor (SO) :
|
||||
/// <see href="https://medium.com/@sudipdevdev/how-to-detect-and-log-slow-queries-in-entity-framework-core-e2ab71024849"/>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="System.Diagnostics.StackTrace"/> pour remonter l'appelant :
|
||||
/// <see href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stacktrace"/>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Enregistrement via <c>AddInterceptors</c> :
|
||||
/// <see href="https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors#registering-interceptors"/>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class EfSlowQueryInterceptor : DbCommandInterceptor
|
||||
{
|
||||
private readonly ILogger<EfSlowQueryInterceptor> logger;
|
||||
private readonly int seuilMs;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EfSlowQueryInterceptor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations de l'intercepteur.</param>
|
||||
/// <param name="options">Les options de performance EF injectées pour récupérer le seuil de lenteur configuré.</param>
|
||||
public EfSlowQueryInterceptor(ILogger<EfSlowQueryInterceptor> logger, IOptions<EfPerformanceOptions> options)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.seuilMs = options.Value.SeuilMs;
|
||||
|
||||
this.logger.LogDebug("[EfSlowQueryInterceptor] Constructeur appelé — seuil : {SeuilMs} ms.", this.seuilMs);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
|
||||
{
|
||||
this.JournaliserSiLent(eventData.Duration);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.JournaliserSiLent(eventData.Duration);
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
|
||||
{
|
||||
this.JournaliserSiLent(eventData.Duration);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<int> NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.JournaliserSiLent(eventData.Duration);
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result)
|
||||
{
|
||||
this.JournaliserSiLent(eventData.Duration);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<object?> ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object? result, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.JournaliserSiLent(eventData.Duration);
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remonte la pile d'appels pour trouver la première méthode dans <c>Webzine.Repository</c>.
|
||||
/// Toutes les requêtes EF Core du projet transitent par ce namespace, ce qui garantit
|
||||
/// un résultat pertinent sans parcourir l'intégralité de la stack.
|
||||
/// </summary>
|
||||
/// <returns>Chaîne <c>Classe.Méthode</c> ou <c>"inconnu"</c> si rien trouvé.</returns>
|
||||
/// <remarks>
|
||||
/// <see cref="StackTrace"/> est instancié uniquement quand le seuil est dépassé,
|
||||
/// ce qui évite tout impact sur le chemin nominal.
|
||||
/// Ref : <see href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stacktrace"/>.
|
||||
/// </remarks>
|
||||
private static string TrouverAppelantRepository()
|
||||
{
|
||||
// skipFrames: 1 pour sauter TrouverAppelantRepository elle-même
|
||||
// fNeedFileInfo: false — on ne veut pas les numéros de ligne (coût supplémentaire inutile)
|
||||
var frames = new StackTrace(skipFrames: 1, fNeedFileInfo: false).GetFrames();
|
||||
|
||||
foreach (var frame in frames)
|
||||
{
|
||||
var methode = frame.GetMethod();
|
||||
if (methode?.DeclaringType?.Namespace?.StartsWith("Webzine.Repository", StringComparison.Ordinal) == true)
|
||||
{
|
||||
return $"{methode.DeclaringType.Name}.{methode.Name}";
|
||||
}
|
||||
}
|
||||
|
||||
return "inconnu";
|
||||
}
|
||||
|
||||
private void JournaliserSiLent(TimeSpan duree)
|
||||
{
|
||||
if (duree.TotalMilliseconds > this.seuilMs)
|
||||
{
|
||||
var appelant = TrouverAppelantRepository();
|
||||
|
||||
this.logger.LogWarning(
|
||||
"[EfSlowQueryInterceptor] Opération EF Core lente détectée — durée réelle : {DureeMs} ms — seuil : {SeuilMs} ms — dépassement : +{Depassement} ms.{NewLine}Appelant : {Appelant}",
|
||||
duree.TotalMilliseconds.ToString("F2"),
|
||||
this.seuilMs,
|
||||
(duree.TotalMilliseconds - this.seuilMs).ToString("F2"),
|
||||
Environment.NewLine,
|
||||
appelant);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
namespace Webzine.WebApplication.Middlewares
|
||||
{
|
||||
using System.Diagnostics;
|
||||
|
||||
public class LogTempsExecutionMiddleware
|
||||
{
|
||||
/// <summary>
|
||||
/// log à chaque requete http.
|
||||
/// </summary>
|
||||
// _next représente le maillon suivant dans la chaîne (le prochain middleware ou le contrôleur)
|
||||
private readonly RequestDelegate next;
|
||||
private readonly ILogger<LogTempsExecutionMiddleware> logger;
|
||||
|
||||
// Le constructeur récupère "_next" et le Logger
|
||||
public LogTempsExecutionMiddleware(RequestDelegate next, ILogger<LogTempsExecutionMiddleware> logger)
|
||||
{
|
||||
this.next = next;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
// méthode appelée à chaque requête HTTP
|
||||
|
||||
/// <summary>
|
||||
/// Middleware chargé de journaliser le cycle de vie d'une requête HTTP (entrée, exécution, sortie et temps de réponse).
|
||||
/// </summary>
|
||||
/// <param name="context">Le contexte HTTP encapsulant toutes les informations de la requête et de la réponse.</param>
|
||||
/// <returns>Une tâche (<see cref="Task"/>) représentant l'opération asynchrone.</returns>
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
// (Avant le contrôleur)
|
||||
var chronometre = Stopwatch.StartNew(); // lance le chrono
|
||||
|
||||
// --- IN ---
|
||||
var methode = context.Request.Method;
|
||||
var endpoint = context.Request.Path;
|
||||
var traceId = context.TraceIdentifier; // Identifiant unique généré par .NET
|
||||
|
||||
this.logger.LogInformation("[IN] TraceId: {traceId} | Méthode: {methode} | Endpoint: {endpoint}", traceId, methode, endpoint);
|
||||
|
||||
await this.next(context);
|
||||
|
||||
// (Après le contrôleur)
|
||||
chronometre.Stop(); // arrête le chrono
|
||||
var tempsEcoule = chronometre.ElapsedMilliseconds;
|
||||
|
||||
var httpCode = context.Response.StatusCode; // exemple: 200, 404, 500
|
||||
|
||||
// --- OUT ---
|
||||
if (httpCode >= 500)
|
||||
{
|
||||
this.logger.LogError("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||
}
|
||||
else if (httpCode >= 400)
|
||||
{
|
||||
this.logger.LogWarning("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.LogInformation("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ using Microsoft.EntityFrameworkCore;
|
||||
using NLog;
|
||||
using NLog.Web;
|
||||
|
||||
using Webzine.Business;
|
||||
using Webzine.Business.Contracts;
|
||||
using Webzine.Business.Seeders;
|
||||
using Webzine.EntitiesContext;
|
||||
using Webzine.Entity;
|
||||
@@ -14,6 +16,8 @@ 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.
|
||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||
@@ -23,6 +27,14 @@ try
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Ajoute les services necessaires pour permettre l'utilisation des
|
||||
// controllers avec des vues.
|
||||
builder.Services.AddControllersWithViews(options =>
|
||||
{
|
||||
options.Filters.Add<ValidationActionFilter>();
|
||||
options.Filters.Add<GlobalExceptionFilter>();
|
||||
})
|
||||
|
||||
// Ajoute les services necessaires pour permettre l'utilisation des controllers avec des vues.
|
||||
builder.Services.AddControllersWithViews()
|
||||
|
||||
@@ -37,6 +49,13 @@ try
|
||||
builder.Services.Configure<SpotifySeederOptions>(builder.Configuration.GetSection("SpotifySeeder"));
|
||||
builder.Services.AddHttpClient<SeedDataSpotify>();
|
||||
|
||||
builder.Services.Configure<EfPerformanceOptions>(options =>
|
||||
{
|
||||
options.SeuilMs = builder.Configuration.GetValue<int>("EfPerformance:SeuilMs");
|
||||
});
|
||||
builder.Services.AddSingleton<EfSlowQueryInterceptor>();
|
||||
|
||||
// En fonction de la configuration, utilise soit les repositories basés sur une base de données, soit les repositories basés sur des listes locales.
|
||||
// En fonction de la configuration, utilise soit les repositories bases sur une base de donnees, soit les repositories bases sur des listes locales.
|
||||
var repositoryType = builder.Configuration.GetValue<RepositoryType>("Repository");
|
||||
var seederType = builder.Configuration.GetValue<SeederType>("Seeder");
|
||||
@@ -46,13 +65,21 @@ try
|
||||
{
|
||||
if (builder.Environment.IsProduction())
|
||||
{
|
||||
builder.Services.AddDbContext<WebzineDbContext>(options =>
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("PostGreSQLConnection")));
|
||||
builder.Services.AddDbContext<WebzineDbContext>((serviceProvider, options) =>
|
||||
{
|
||||
options
|
||||
.UseNpgsql(builder.Configuration.GetConnectionString("PostGreSQLConnection"))
|
||||
.AddInterceptors(serviceProvider.GetRequiredService<EfSlowQueryInterceptor>());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Services.AddDbContext<WebzineDbContext>(options =>
|
||||
options.UseSqlite(builder.Configuration.GetConnectionString("SqliteConnection")));
|
||||
builder.Services.AddDbContext<WebzineDbContext>((serviceProvider, options) =>
|
||||
{
|
||||
options
|
||||
.UseSqlite(builder.Configuration.GetConnectionString("SqliteConnection"))
|
||||
.AddInterceptors(serviceProvider.GetRequiredService<EfSlowQueryInterceptor>());
|
||||
});
|
||||
}
|
||||
|
||||
builder.Services.AddScoped<DbEntityRepository>();
|
||||
@@ -70,17 +97,27 @@ try
|
||||
builder.Services.AddSingleton<InMemoryDataStore>();
|
||||
}
|
||||
|
||||
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 reponses HTTP pour reduire la taille des donnees envoyees au client et ameliorer les performances de l'application.
|
||||
builder.Services.AddResponseCompression();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseMiddleware<Webzine.WebApplication.Middlewares.LogTempsExecutionMiddleware>();
|
||||
|
||||
if (repositoryType == RepositoryType.Db)
|
||||
{
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<WebzineDbContext>();
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
if (shouldSeed)
|
||||
{
|
||||
db.Database.EnsureDeleted();
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
@titre.Artiste.Nom
|
||||
</a>
|
||||
-
|
||||
<a asp-action="Details"
|
||||
<a asp-action="Index"
|
||||
asp-controller="Titre"
|
||||
asp-route-id="@titre.IdTitre">
|
||||
@titre.Libelle
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="d-flex flex-wrap align-items-center gap-3">
|
||||
<a asp-action="Details"
|
||||
<a asp-action="Index"
|
||||
asp-controller="Titre"
|
||||
asp-route-id="@titre.IdTitre"
|
||||
class="btn btn-primary btn-sm">
|
||||
@@ -90,7 +90,7 @@
|
||||
<img class="card-img-top" src="@titre.UrlJaquette" alt="@titre.Album" loading="lazy" />
|
||||
|
||||
<div class="card-body">
|
||||
<a asp-controller="Titre" asp-action="Details" asp-route-id="@titre.IdTitre" class="card-link">
|
||||
<a asp-controller="Titre" asp-action="Index" asp-route-id="@titre.IdTitre" class="card-link">
|
||||
@titre.Libelle
|
||||
</a>
|
||||
<br />
|
||||
|
||||
@@ -56,7 +56,7 @@ else
|
||||
<td class="text-secondary font-monospace">@dureeFormatee</td>
|
||||
<td>
|
||||
<a asp-controller="Titre"
|
||||
asp-action="Details"
|
||||
asp-action="Index"
|
||||
asp-route-id="@titre.IdTitre"
|
||||
class="text-primary">
|
||||
@titre.Libelle
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
@if (!Model.Artistes.Any())
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<p>Aucun artiste n'a été trouvé.</p>
|
||||
<p>Aucun artiste n'a été trouvé.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
@if (!Model.Titres.Any())
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<p>Aucun titre n'a été trouvé.</p>
|
||||
<p>Aucun titre n'a été trouvé.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
{
|
||||
<div class="d-flex align-items-start my-3">
|
||||
<a asp-controller="Titre"
|
||||
asp-action="Details"
|
||||
asp-action="Index"
|
||||
asp-route-id="@titre.IdTitre"
|
||||
class="me-3 text-black">
|
||||
<img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70" height="70" class="object-fit-cover" loading="lazy" />
|
||||
@@ -58,7 +58,7 @@
|
||||
</a>
|
||||
-
|
||||
<a asp-controller="Titre"
|
||||
asp-action="Details"
|
||||
asp-action="Index"
|
||||
asp-route-id="@titre.IdTitre">
|
||||
@titre.Libelle
|
||||
</a>
|
||||
|
||||
@@ -60,13 +60,14 @@
|
||||
<div class="d-flex gap-2">
|
||||
|
||||
<form asp-action="Like" method="post">
|
||||
<input type="hidden" name="IdTitre" value="@Model.Details.IdTitre" />
|
||||
<input type="hidden" name="IdTitre" value="@Model.Details.IdTitre"/>
|
||||
<button type="submit" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fa fa-thumbs-up me-1"></i> Like
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<a asp-area="Administration" asp-controller="Titre" asp-action="Edit" asp-route-id="@Model.Details.IdTitre" class="btn text-primary btn-sm">
|
||||
<a asp-area="Administration" asp-controller="Titre" asp-action="Edit"
|
||||
asp-route-id="@Model.Details.IdTitre" class="btn text-primary btn-sm">
|
||||
<i class="fa fa-pen-to-square me-1"></i> Editer
|
||||
</a>
|
||||
|
||||
@@ -88,7 +89,7 @@
|
||||
class="img-fluid rounded shadow"
|
||||
alt="Jaquette"
|
||||
loading="lazy"
|
||||
fetchpriority="high" />
|
||||
fetchpriority="high"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -135,7 +136,7 @@
|
||||
<input name="Auteur"
|
||||
class="form-control input-full"
|
||||
placeholder="Votre nom"
|
||||
required />
|
||||
required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -145,10 +146,10 @@
|
||||
</label>
|
||||
<div class="col">
|
||||
<textarea name="Contenu"
|
||||
rows="3"
|
||||
class="form-control input-full"
|
||||
placeholder="Votre commentaire..."
|
||||
required></textarea>
|
||||
rows="3"
|
||||
class="form-control input-full"
|
||||
placeholder="Votre commentaire..."
|
||||
required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -170,7 +171,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))
|
||||
{
|
||||
@@ -183,7 +184,7 @@
|
||||
width="50"
|
||||
height="50"
|
||||
class="rounded-circle me-3 shadow-sm"
|
||||
alt="avatar" />
|
||||
alt="avatar"/>
|
||||
|
||||
<div>
|
||||
<strong>@comment.Auteur</strong>,
|
||||
@@ -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>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Webzine.Business.Contracts\Webzine.Business.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Webzine.Business\Webzine.Business.csproj" />
|
||||
<ProjectReference Include="..\Webzine.EntitiesContext\Webzine.EntitiesContext.csproj" />
|
||||
<ProjectReference Include="..\Webzine.Entity\Webzine.Entity.csproj" />
|
||||
|
||||
@@ -23,5 +23,8 @@
|
||||
"TracksPerAlbum": 40,
|
||||
"MaxCommentsPerTrack": 3
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"AllowedHosts": "*",
|
||||
"EfPerformance": {
|
||||
"SeuilMs": 10
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<rules>
|
||||
<!-- Vos logs d'application en Debug+ -->
|
||||
<logger name="Webzine.WebApplication.*" minlevel="Debug" writeTo="allfile,ownfile-web,console" />
|
||||
<logger name="Webzine.WebApplication.*" minlevel="Info" writeTo="allfile,ownfile-web,console" />
|
||||
|
||||
<!-- Logs Microsoft en Warning+ sauf Hosting.Lifetime -->
|
||||
<logger name="Microsoft.*" minlevel="Warn" writeTo="allfile" final="true" />
|
||||
|
||||
Reference in New Issue
Block a user