From bbdcc7f2adb1a1b6303918db3142dc95a0ee0f2a Mon Sep 17 00:00:00 2001 From: Lum Date: Sat, 11 Jul 2026 19:41:09 +0100 Subject: [PATCH] Blog: render user avatar through a null-safe helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /BlogSpot/Details/1 retournait 500 (avec un 500-sur-500 sur la page d'erreur elle-même) parce que DisplayTemplates /ApplicationUser.cshtml faisait `var avuri = "/Avatars/" + Model.UserName + ".s.png"` : avec enable, Razor émet un null-check implicite sur Model.UserName et lève NullReferenceException quand l'auteur n'a pas de UserName posé (donnée héritée, user partiellement initialisé). - UserDisplayHelpers.AvatarSrc : helper statique pur dans Yavsc.Abstract.Identity qui retourne YavscConstants.DefaultAvatar pour user null / UserName vide ou whitespace, et un path /avatars/.s.png sinon. - ApplicationUser.cshtml : utilise le helper. - Tests : 4 cas (null, vide, whitespace, valide) dans Yavsc.Org.Tests/NonRegression. Bonus : le path d'avatar passe de "/Avatars/" (S majuscule, ne résolvait pas dans le middleware de fichiers statiques) à YavscConstants.AvatarsPath ("/avatars" minuscule), pour fermer l'autre trou que centraliser le calcul permettait de fixer proprement. --- .../Identity/UserDisplayHelpers.cs | 36 +++++++++++ .../NonRegression/UserDisplayHelpersTests.cs | 63 +++++++++++++++++++ .../DisplayTemplates/ApplicationUser.cshtml | 6 +- 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/Yavsc.Abstract/Identity/UserDisplayHelpers.cs create mode 100644 src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs diff --git a/src/Yavsc.Abstract/Identity/UserDisplayHelpers.cs b/src/Yavsc.Abstract/Identity/UserDisplayHelpers.cs new file mode 100644 index 00000000..c2700e93 --- /dev/null +++ b/src/Yavsc.Abstract/Identity/UserDisplayHelpers.cs @@ -0,0 +1,36 @@ +namespace Yavsc.Abstract.Identity +{ + /// + /// Helpers Razor-friendly pour rendre un user dans une vue + /// sans exposer le template à des accesseurs nullables qui + /// lèveraient . + /// + public static class UserDisplayHelpers + { + /// + /// Chemin d'avatar à utiliser pour + /// dans un display template. Défense contre null + /// (user pas chargé, FK orpheline) et contre un + /// UserName vide (donnée héritée, user partiellement + /// initialisé). Sans cette garde, Razor émet une + /// NullReferenceException dès qu'un accesseur .UserName + /// apparaît dans le template, ce qui remonte en 500 et + /// masque la page d'erreur elle-même. + /// + /// + /// Le path retourné est aligné sur + /// (minuscule). + /// Les anciens display templates utilisaient "/Avatars/" + /// avec un S majuscule, en désaccord avec le path statique + /// servi par le middleware de fichiers — les images ne + /// résolvaient pas. Centraliser le calcul ici ferme les + /// deux trous. + /// + public static string AvatarSrc(IApplicationUser? user) + { + if (string.IsNullOrWhiteSpace(user?.UserName)) + return YavscConstants.DefaultAvatar; + return $"{YavscConstants.AvatarsPath}/{user!.UserName}.s.png"; + } + } +} diff --git a/src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs b/src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs new file mode 100644 index 00000000..a0249fa4 --- /dev/null +++ b/src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs @@ -0,0 +1,63 @@ +using Xunit; +using Yavsc.Abstract; +using Yavsc.Abstract.Identity; + +namespace Yavsc.Org.Tests.NonRegression; + +/// +/// Régression sur le 500 GET /BlogSpot/Details/{id} : un +/// billet dont l'auteur a un UserName null ou absent faisait +/// lever NullReferenceException dans le display template +/// ApplicationUser.cshtml à l'évaluation de +/// Model.UserName. ASP.NET transforme en 500, et la page +/// d'erreur elle-même crash (ErrorViewModel manquant), donc on +/// ne voit rien — juste un 500 muet. +/// +/// Le fix passe par qui +/// retourne pour toute +/// donnée partielle. Ces tests couvrent les trois formes de +/// "donnée absente" : user null, UserName vide, UserName whitespace. +/// +public class UserDisplayHelpersTests +{ + [Fact] + public void AvatarSrc_null_user_returns_default_avatar() + { + Assert.Equal(YavscConstants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(null)); + } + + [Fact] + public void AvatarSrc_user_with_empty_UserName_returns_default_avatar() + { + var user = new FakeUser { UserName = "" }; + Assert.Equal(YavscConstants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(user)); + } + + [Fact] + public void AvatarSrc_user_with_whitespace_UserName_returns_default_avatar() + { + var user = new FakeUser { UserName = " " }; + Assert.Equal(YavscConstants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(user)); + } + + [Fact] + public void AvatarSrc_valid_user_returns_canonical_avatars_path() + { + var user = new FakeUser { UserName = "alice" }; + // Le path doit matcher YavscConstants.AvatarsPath (minuscule), + // pas un /Avatars/ avec S majuscule qui ne résout pas + // dans le middleware de fichiers statiques. + var expected = $"{YavscConstants.AvatarsPath}/alice.s.png"; + Assert.Equal(expected, UserDisplayHelpers.AvatarSrc(user)); + } + + private sealed class FakeUser : IApplicationUser + { + public string Id { get; set; } = ""; + public string? UserName { get; set; } + public string? Avatar { get; set; } + public IAccountBalance? AccountBalance => null; + public string? DedicatedGoogleCalendar => null; + public ILocation? PostalAddress => null; + } +} diff --git a/src/Yavsc.Org/Views/Shared/DisplayTemplates/ApplicationUser.cshtml b/src/Yavsc.Org/Views/Shared/DisplayTemplates/ApplicationUser.cshtml index b7bd532f..2e4ef1bb 100644 --- a/src/Yavsc.Org/Views/Shared/DisplayTemplates/ApplicationUser.cshtml +++ b/src/Yavsc.Org/Views/Shared/DisplayTemplates/ApplicationUser.cshtml @@ -1,7 +1,11 @@ @using Yavsc.Abstract.Identity @model ApplicationUser @{ - var avuri = "/Avatars/" + Model.UserName + ".s.png"; + // Le helper défend contre Model null et contre UserName vide + // ou whitespace. Sans cette garde, Razor lève + // NullReferenceException ici, ce qui propage un 500 et + // masque aussi la page d'erreur. + var avuri = UserDisplayHelpers.AvatarSrc(Model); }