Accueil › Références › Mémos

Mémos & pièges

Tout ce qu'on cherche en urgence, sur une seule page : les commandes, les tableaux de décision, le décodeur de messages d'erreur et la liste des pièges classiques. Conçue pour être imprimée ou gardée ouverte en second écran (Ctrl+F est ton ami).

Commandes du quotidien

Projets et paquets

dotnet new list                    # modèles disponibles
dotnet new webapi -o Api           # API minimale
dotnet new blazor -o Web           # Blazor Web App
dotnet new classlib -o Domaine
dotnet new xunit -o Tests
dotnet new gitignore
dotnet new sln -n Boutique

dotnet sln add **/*.csproj
dotnet add reference ../Domaine/Domaine.csproj
dotnet package add Serilog.AspNetCore
dotnet package list --outdated
dotnet package list --vulnerable --include-transitive

Compiler, lancer, tester

dotnet build -warnaserror
dotnet run
dotnet run --launch-profile https
dotnet watch                       # rechargement à chaud
dotnet run -e Cle=valeur           # .NET 11
dotnet test
dotnet test --filter "FullyQualifiedName~Tarif"
dotnet format                      # met en forme
dotnet publish -c Release -o ./out
dotnet publish -c Release /t:PublishContainer
dotnet --info                      # diagnostic d'environnement

EF Core

dotnet tool install --global dotnet-ef
dotnet ef migrations add NomExplicite
dotnet ef migrations list
dotnet ef migrations remove
dotnet ef database update
dotnet ef database update NomMigration   # revenir à un état
dotnet ef migrations script --idempotent -o m.sql
dotnet ef migrations bundle
dotnet ef dbcontext scaffold "conn" Microsoft.EntityFrameworkCore.SqlServer
dotnet ef dbcontext info

Diagnostic

dotnet tool install --global dotnet-counters
dotnet tool install --global dotnet-trace
dotnet tool install --global dotnet-gcdump

dotnet-counters ps
dotnet-counters monitor -p 1234 \
  --counters System.Runtime,Microsoft.AspNetCore.Hosting
dotnet-trace collect -p 1234 --profile cpu-sampling
dotnet-gcdump collect -p 1234 -o snap.gcdump
dotnet user-secrets set "Cle" "valeur"

Tableaux de décision

Quelle collection ?

BesoinType
Liste ordonnée qui grandit — le défautList<T>
Retrouver par cléDictionary<K,V>
Tester l'appartenance / dédoublonnerHashSet<T>
Taille fixeT[]
File / pileQueue<T> / Stack<T>
Exposer sans laisser modifierIReadOnlyList<T>
Accepter en paramètreIEnumerable<T>
Multi-threadConcurrentDictionary<K,V>, Channel<T>
Vraiment immuableImmutableArray<T>

Durée de vie d'un service

DuréeUne instance par…Exemples
AddSingletonapplicationCache, configuration, table de correspondance (sans état modifiable, thread-safe)
AddScopedrequête HTTP (circuit en Blazor Server !)DbContext, services métier, contexte utilisateur
AddTransientdemandePetit service sans état

⚠️ Jamais un scoped injecté dans un singleton : IServiceScopeFactory et une portée créée à la demande.

Modificateurs d'accès : qui voit quoi

ModificateurMême classeFille (même projet)Autre classe (même projet)Fille (autre projet)Ailleurs
private
private protected
protected
internal
protected internal
public

Défauts : un type au niveau namespace est internal ; un membre est private ; un membre d'interface est public.

Mode de rendu Blazor

SituationMode
Page de contenu, formulaire simple, référencementStatic SSR (défaut)
Application métier interne, accès direct à la baseInteractiveServer
Hors ligne, latence critique, serveur à déchargerInteractiveWebAssembly
Premier affichage instantané puis autonomieInteractiveAuto

Codes HTTP à utiliser

CodeQuand le renvoyer
200 OKLecture ou action réussie avec contenu
201 CreatedCréation — avec l'en-tête Location
204 No ContentModification ou suppression réussie, rien à renvoyer
304 Not ModifiedLe client a déjà la bonne version (ETag)
400 Bad RequestCorps ou paramètres invalides
401 UnauthorizedNon authentifié (« qui es-tu ? »)
403 ForbiddenAuthentifié mais sans droit
404 Not FoundRessource inexistante — ou masquée volontairement
409 ConflictConflit métier (doublon, stock, état incompatible)
412 Precondition FailedETag / If-Match obsolète
422 UnprocessableSyntaxe correcte, sémantique refusée
429 Too Many RequestsQuota dépassé — avec Retry-After
500 / 503Bug serveur / indisponibilité temporaire

LINQ en un écran

// FILTRER              // TRANSFORMER          // ORDONNER
.Where(p => p.Actif)     .Select(p => p.Nom)     .OrderBy(p => p.Nom)
.Take(10).Skip(20)       .SelectMany(p => p.Tags).OrderByDescending(p => p.Date)
.Distinct()              .Cast<T>()              .ThenBy(p => p.Id)
.DistinctBy(p => p.Ref)  .OfType<T>()            .Reverse()
.Chunk(100)

// AGRÉGER                          // UN SEUL ÉLÉMENT
.Count()  .Count(p => p.Actif)      .First()   .FirstOrDefault()
.Any()    .All(p => p.Valide)       .Single()  .SingleOrDefault()
.Sum(p => p.Prix)  .Average(...)    .Last()    .ElementAt(3)
.Min() .Max() .MinBy(...) .MaxBy(...)

// REGROUPER / JOINDRE              // MATÉRIALISER (déclenche l'exécution)
.GroupBy(p => p.Categorie)          .ToList()  .ToArray()
.ToDictionary(p => p.Id)            .ToDictionary(...)  .ToHashSet()
.ToLookup(p => p.Categorie)         .ToListAsync(ct)     // EF Core
.Join(...) .GroupJoin(...)
.LeftJoin(...) .RightJoin(...)       // .NET 10
.FullJoin(...)                       // .NET 11
À éviterÀ la place
.Count() > 0.Any()
.Count() sur une List.Count (propriété)
.Where(...).Count().Count(...)
.OrderBy() puis .Where()Filtrer d'abord
.ToList() au milieu d'une chaîneUne seule fois, à la fin
.First() sans certitude.FirstOrDefault() + test

async : à faire / à ne pas faire

Jamais
  • .Result, .Wait(), .GetAwaiter().GetResult()
  • async void (hors gestionnaire d'événement)
  • Task.Run autour d'un appel réseau ou base
  • Plusieurs opérations parallèles sur le même DbContext
  • Oublier de transmettre le CancellationToken
  • Lancer une tâche sans jamais l'attendre ni gérer ses erreurs
Toujours
  • await de bout en bout, jusqu'au point d'entrée
  • Task<T> FaireAsync(..., CancellationToken ct = default)
  • Task.WhenAll pour les appels indépendants
  • ConfigureAwait(false) dans une bibliothèque réutilisable
  • Parallel.ForEachAsync ou SemaphoreSlim pour plafonner la concurrence
  • Suffixe Async dans le nom

EF Core : réflexes

ObjectifRéflexe
Lire pour afficher.AsNoTracking() + .Select(...) vers un DTO
Charger des données liées.Include() — et .AsSplitQuery() dès deux collections
Voir ce qui part en base.ToQueryString(), ou LogTo(Console.WriteLine)
Modifier beaucoup de lignesExecuteUpdateAsync / ExecuteDeleteAsync
Éviter les modifications perduesPropriété [Timestamp] + gérer DbUpdateConcurrencyException
Pagination fiableOrderBy(x => x.Cle).ThenBy(x => x.Id) puis Skip/Take
MontantsHasPrecision(18, 2) — jamais double pour de l'argent
Migrations en productionScript idempotent ou bundle depuis la CI
TesterTestcontainers, pas UseInMemoryDatabase

Décodeur de messages d'erreur

Compilation (C#)

CodeTraduction pratique
CS0103Nom inconnu ici → faute de frappe, ou variable déclarée dans un autre bloc
CS0246Type introuvable → using manquant, référence absente, ou classe internal dans un autre projet
CS1503 / CS0029Mauvais type d'argument ou conversion impossible
CS0120Membre d'instance appelé sans objet (contexte statique)
CS0161Un chemin de code ne retourne pas de valeur
CS1061Ce membre n'existe pas sur ce type (ou n'est pas accessible)
CS8602 / CS8618Déréférencement possible d'un null / propriété non nullable non initialisée
CS0051Une méthode publique expose un type moins accessible
CS0108Un membre masque celui du parent → tu voulais override

Exécution

MessageCause la plus fréquente
NullReferenceExceptionUne variable vaut null : active les types nullables et remonte à l'origine
InvalidOperationException: Collection was modifiedModification d'une collection pendant un foreachRemoveAll ou parcourir une copie
Unable to resolve service for type 'X'Service non enregistré, ou enregistré après Build()
Cannot consume scoped service from singletonDépendance captive → IServiceScopeFactory
Cannot access a disposed object 'AppDb'Usage d'un service scoped après la fin de la requête
A second operation was started on this contextParallélisme sur un même DbContextawait chaque appel
The LINQ expression could not be translatedMéthode C# non traduisible en SQL → filtrer en SQL, finir en mémoire
Headers are read-only, response has already startedModification d'en-têtes après le début de l'écriture de la réponse
No 'Access-Control-Allow-Origin' headerCORS non configuré, ou UseCors placé trop tard
SocketException: Address already in usenew HttpClient() par appel → IHttpClientFactory
Invalid object name 'Produits'Migrations non appliquées sur cette base
Application « figée » sous charge, sans erreurFamine du pool de threads : cherche .Result / .Wait()

Les vingt pièges classiques

#PiègeCorrectionChapitre
1.Result sur du code asyncawait de bout en bout8
2Problème N+1 en EF CoreInclude ou projection15
3Renvoyer une entité EF au clientUn DTO par cas d'usage14
4UseAuthorization avant UseAuthenticationInverser l'ordre13
5Exposer List<T> en propriété publiqueIReadOnlyList<T> + méthode d'ajout4
6catch { } silencieuxAttraper ce qu'on sait traiter, journaliser, relancer9
7throw ex;throw;9
8Classe sans modificateur, invisible ailleursAjouter public6
9Scoped dans un singletonIServiceScopeFactory11
10new HttpClient() par appelIHttpClientFactory9
11Secret dans appsettings.json versionnéuser-secrets, variables, coffre-fort12
12Log interpolé ($"...{id}")Modèle + arguments nommés12
13Endpoint sans paginationSkip/Take plafonnés + tri stable14
14Autorisation sans vérifier la propriété (IDOR)Filtrer sur le propriétaire17
15@onclick sans @rendermodeAjouter un mode interactif16
16Interop JS dans OnInitializedAsyncOnAfterRenderAsync(premierRendu)16
17Abonnement sans désabonnementDispose / DisposeAsync16
18Clé de cache sans identifiant utilisateurInclure l'utilisateur ou le locataire19
19Sonde liveness qui teste la baseSéparer « vivant » et « prêt »20
20ASPNETCORE_ENVIRONMENT=Development en productionLe fixer explicitement au déploiement20

Rappels de syntaxe

Null et sécurité

string? peutEtreNull;
x?.Membre                 // null si x est null
x?.Methode()              // idem
x ?? "defaut"             // valeur de repli
x ??= Calculer()          // affecter si null
x!.Membre                 // « je garantis » (danger)
x is null / is not null
ArgumentNullException.ThrowIfNull(x);
if (!int.TryParse(s, out var n)) n = 0;

Pattern matching

o is Client c                  // type + variable
o is { Age: >= 18 }            // propriété
o is Client { Ville: "Lyon" } c
o is > 10 and < 20             // relationnel
o is "a" or "b"                // logique
liste is [var seul]            // liste
liste is [_, _, ..]            // au moins deux
var r = x switch { ... , _ => d };
var (a, b) = point;            // déconstruction

Formats de chaînes

$"{prix:C}"        // 12,50 €
$"{taux:P1}"       // 12,3 %
$"{n:N2}"          // 1 234,56
$"{n:0.##}"        // 12,5
$"{d:dd/MM/yyyy}"  // 30/07/2026
$"{d:O}"           // ISO 8601
$"{nom,-20}"       // aligné à gauche
$"{n,10:N2}"       // aligné à droite
$"{{accolade}}"    // échapper
"""ligne 1
ligne 2"""         // chaîne brute

Dates : quel type ?

DateOnly       // une date sans heure (naissance)
TimeOnly       // une heure sans date (ouverture)
DateTime       // date + heure — préciser le Kind
DateTimeOffset // date + heure + décalage ⭐
TimeSpan       // une durée

// Règle : stocker en UTC, afficher en local
DateTimeOffset.UtcNow          // ⭐ le défaut
DateTime.UtcNow                // acceptable
DateTime.Now                   // ⚠️ dépend du serveur
TimeProvider                   // injectable, testable

Liste de contrôle avant la mise en production

Configuration

  • ASPNETCORE_ENVIRONMENT=Production
  • Aucun secret dans le dépôt ni dans l'image
  • ValidateOnStart() sur les options
  • Clés Data Protection partagées et persistées
  • Niveaux de logs raisonnables (pas de Debug)

Sécurité

  • HTTPS + HSTS, en-têtes de sécurité
  • FallbackPolicy : fermé par défaut
  • Propriété des ressources vérifiée (pas d'IDOR)
  • Limitation de débit sur la connexion
  • dotnet list package --vulnerable propre

Fiabilité

  • Sondes « vivant » et « prêt » distinctes
  • Migrations appliquées par la CI, compatibles deux versions
  • Délais d'expiration sur tous les appels sortants
  • Arrêt propre : CancellationToken propagé
  • Limites mémoire du conteneur fixées

Observabilité

  • Logs structurés, exportés, avec identifiant de trace
  • OpenTelemetry : traces + métriques
  • Alertes sur taux d'erreur et p99
  • Version déployée journalisée au démarrage
  • Tableau de bord consulté au moins une fois après déploiement
Les cinq règles qui résument tout
  1. Rends les états invalides impossibles : constructeurs, required, types dédiés, encapsulation.
  2. Dépends d'abstractions là où tu veux pouvoir remplacer ou tester — et nulle part ailleurs.
  3. Regarde ce que fait vraiment ton code : le SQL généré, le nombre de requêtes, les logs, les traces.
  4. Mesure avant d'optimiser, et vérifie le gain après.
  5. Écris pour celui qui relira dans six mois — c'est-à-dire toi, sans le contexte.