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-transitiveCompiler, 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'environnementEF 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 infoDiagnostic
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 ?
| Besoin | Type |
|---|---|
| Liste ordonnée qui grandit — le défaut | List<T> |
| Retrouver par clé | Dictionary<K,V> |
| Tester l'appartenance / dédoublonner | HashSet<T> |
| Taille fixe | T[] |
| File / pile | Queue<T> / Stack<T> |
| Exposer sans laisser modifier | IReadOnlyList<T> |
| Accepter en paramètre | IEnumerable<T> |
| Multi-thread | ConcurrentDictionary<K,V>, Channel<T> |
| Vraiment immuable | ImmutableArray<T> |
Durée de vie d'un service
| Durée | Une instance par… | Exemples |
|---|---|---|
AddSingleton | application | Cache, configuration, table de correspondance (sans état modifiable, thread-safe) |
AddScoped | requête HTTP (circuit en Blazor Server !) | DbContext, services métier, contexte utilisateur |
AddTransient | demande | Petit 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
| Modificateur | Même classe | Fille (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
| Situation | Mode |
|---|---|
| Page de contenu, formulaire simple, référencement | Static SSR (défaut) |
| Application métier interne, accès direct à la base | InteractiveServer |
| Hors ligne, latence critique, serveur à décharger | InteractiveWebAssembly |
| Premier affichage instantané puis autonomie | InteractiveAuto |
Codes HTTP à utiliser
| Code | Quand le renvoyer |
|---|---|
200 OK | Lecture ou action réussie avec contenu |
201 Created | Création — avec l'en-tête Location |
204 No Content | Modification ou suppression réussie, rien à renvoyer |
304 Not Modified | Le client a déjà la bonne version (ETag) |
400 Bad Request | Corps ou paramètres invalides |
401 Unauthorized | Non authentifié (« qui es-tu ? ») |
403 Forbidden | Authentifié mais sans droit |
404 Not Found | Ressource inexistante — ou masquée volontairement |
409 Conflict | Conflit métier (doublon, stock, état incompatible) |
412 Precondition Failed | ETag / If-Match obsolète |
422 Unprocessable | Syntaxe correcte, sémantique refusée |
429 Too Many Requests | Quota dépassé — avec Retry-After |
500 / 503 | Bug 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îne | Une 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.Runautour 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
awaitde bout en bout, jusqu'au point d'entréeTask<T> FaireAsync(..., CancellationToken ct = default)Task.WhenAllpour les appels indépendantsConfigureAwait(false)dans une bibliothèque réutilisableParallel.ForEachAsyncouSemaphoreSlimpour plafonner la concurrence- Suffixe
Asyncdans le nom
EF Core : réflexes
| Objectif | Ré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 lignes | ExecuteUpdateAsync / ExecuteDeleteAsync |
| Éviter les modifications perdues | Propriété [Timestamp] + gérer DbUpdateConcurrencyException |
| Pagination fiable | OrderBy(x => x.Cle).ThenBy(x => x.Id) puis Skip/Take |
| Montants | HasPrecision(18, 2) — jamais double pour de l'argent |
| Migrations en production | Script idempotent ou bundle depuis la CI |
| Tester | Testcontainers, pas UseInMemoryDatabase |
Décodeur de messages d'erreur
Compilation (C#)
| Code | Traduction pratique |
|---|---|
CS0103 | Nom inconnu ici → faute de frappe, ou variable déclarée dans un autre bloc |
CS0246 | Type introuvable → using manquant, référence absente, ou classe internal dans un autre projet |
CS1503 / CS0029 | Mauvais type d'argument ou conversion impossible |
CS0120 | Membre d'instance appelé sans objet (contexte statique) |
CS0161 | Un chemin de code ne retourne pas de valeur |
CS1061 | Ce membre n'existe pas sur ce type (ou n'est pas accessible) |
CS8602 / CS8618 | Déréférencement possible d'un null / propriété non nullable non initialisée |
CS0051 | Une méthode publique expose un type moins accessible |
CS0108 | Un membre masque celui du parent → tu voulais override |
Exécution
| Message | Cause la plus fréquente |
|---|---|
NullReferenceException | Une variable vaut null : active les types nullables et remonte à l'origine |
InvalidOperationException: Collection was modified | Modification d'une collection pendant un foreach → RemoveAll ou parcourir une copie |
Unable to resolve service for type 'X' | Service non enregistré, ou enregistré après Build() |
Cannot consume scoped service from singleton | Dé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 context | Parallélisme sur un même DbContext → await chaque appel |
The LINQ expression could not be translated | Méthode C# non traduisible en SQL → filtrer en SQL, finir en mémoire |
Headers are read-only, response has already started | Modification d'en-têtes après le début de l'écriture de la réponse |
No 'Access-Control-Allow-Origin' header | CORS non configuré, ou UseCors placé trop tard |
SocketException: Address already in use | new HttpClient() par appel → IHttpClientFactory |
Invalid object name 'Produits' | Migrations non appliquées sur cette base |
| Application « figée » sous charge, sans erreur | Famine du pool de threads : cherche .Result / .Wait() |
Les vingt pièges classiques
| # | Piège | Correction | Chapitre |
|---|---|---|---|
| 1 | .Result sur du code async | await de bout en bout | 8 |
| 2 | Problème N+1 en EF Core | Include ou projection | 15 |
| 3 | Renvoyer une entité EF au client | Un DTO par cas d'usage | 14 |
| 4 | UseAuthorization avant UseAuthentication | Inverser l'ordre | 13 |
| 5 | Exposer List<T> en propriété publique | IReadOnlyList<T> + méthode d'ajout | 4 |
| 6 | catch { } silencieux | Attraper ce qu'on sait traiter, journaliser, relancer | 9 |
| 7 | throw ex; | throw; | 9 |
| 8 | Classe sans modificateur, invisible ailleurs | Ajouter public | 6 |
| 9 | Scoped dans un singleton | IServiceScopeFactory | 11 |
| 10 | new HttpClient() par appel | IHttpClientFactory | 9 |
| 11 | Secret dans appsettings.json versionné | user-secrets, variables, coffre-fort | 12 |
| 12 | Log interpolé ($"...{id}") | Modèle + arguments nommés | 12 |
| 13 | Endpoint sans pagination | Skip/Take plafonnés + tri stable | 14 |
| 14 | Autorisation sans vérifier la propriété (IDOR) | Filtrer sur le propriétaire | 17 |
| 15 | @onclick sans @rendermode | Ajouter un mode interactif | 16 |
| 16 | Interop JS dans OnInitializedAsync | OnAfterRenderAsync(premierRendu) | 16 |
| 17 | Abonnement sans désabonnement | Dispose / DisposeAsync | 16 |
| 18 | Clé de cache sans identifiant utilisateur | Inclure l'utilisateur ou le locataire | 19 |
| 19 | Sonde liveness qui teste la base | Séparer « vivant » et « prêt » | 20 |
| 20 | ASPNETCORE_ENVIRONMENT=Development en production | Le fixer explicitement au déploiement | 20 |
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éconstructionFormats 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 bruteDates : 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, testableListe 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 --vulnerablepropre
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 :
CancellationTokenpropagé - 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
- Rends les états invalides impossibles : constructeurs,
required, types dédiés, encapsulation. - Dépends d'abstractions là où tu veux pouvoir remplacer ou tester — et nulle part ailleurs.
- Regarde ce que fait vraiment ton code : le SQL généré, le nombre de requêtes, les logs, les traces.
- Mesure avant d'optimiser, et vérifie le gain après.
- Écris pour celui qui relira dans six mois — c'est-à-dire toi, sans le contexte.