Accueil › Nouveautés › Chapitre 21
Tout .NET 10 LTS
Sorti en novembre 2025, supporté jusqu'en novembre 2028. C'est la version sur laquelle bâtir aujourd'hui, et celle qu'utilisent tous les exemples de cette documentation. Ce chapitre est un inventaire orienté « qu'est-ce que ça change pour moi », pas une liste de notes de version.
- C# 14 : membres d'extension (propriétés et statiques), mot-clé
field, affectation conditionnelle au null. - Minimal API : validation intégrée par annotations, Server-Sent Events, OpenAPI 3.1 + YAML + commentaires XML.
- Blazor :
[PersistentState](fin de la double initialisation), passkeys, interop JS enrichie, actifs statiques optimisés. - EF Core 10 : types complexes et colonnes JSON matures, filtres de requête nommés, recherche vectorielle SQL Server,
ExecuteUpdatesimplifié. - LINQ :
LeftJoin/RightJoinde première classe, opérateurs asynchrones intégrés. - Runtime : JIT plus agressif (dévirtualisation, allocations sur la pile), démarrage et empreinte en baisse.
- SDK : exécution d'un simple fichier
.cs, verbes CLI réorganisés. - Sécurité : passkeys dans Identity, cryptographie post-quantique, comportement 401/403 corrigé pour les API.
Migrer depuis .NET 8 : la liste de contrôle
# 1. Le TFM
# <TargetFramework>net8.0</TargetFramework> → net10.0
# 2. Les paquets Microsoft.* alignés sur 10.x
dotnet list package --outdated
# 3. Reconstruire en traitant les avertissements comme des erreurs
dotnet build -warnaserror
# 4. Passer les tests, puis lire les changements de comportement annoncés
dotnet test
| À vérifier après migration | Pourquoi |
|---|---|
| Requêtes 401/403 sur les endpoints d'API | Une requête non authentifiée vers un endpoint d'API renvoie désormais 401/403 au lieu d'une redirection vers la page de connexion. Plus correct — mais si ton front interprétait la redirection, il casse |
| Documents OpenAPI générés | Version 3.1 par défaut : les types nullables s'écrivent type: [null, "string"]. Certains générateurs de clients anciens ne comprennent pas |
| Colonnes JSON EF Core sur SQL Server 2025 | Les colonnes nvarchar contenant du JSON peuvent basculer vers le type json à la première migration. Vérifie la migration générée |
| Requêtes avec collections paramétrées | Nouvelle stratégie de traduction par défaut (un paramètre par valeur) : plans d'exécution différents, généralement meilleurs |
| Journalisation EF | Les constantes intégrées au SQL sont désormais masquées par défaut dans les logs (bonne nouvelle, mais tes logs changent) |
Dans la pratique, une migration .NET 8 → 10 sur une application web bien testée se règle en une demi-journée. Le gros du travail est de lire les changements de comportement documentés, pas de réécrire du code.
C# 14
Membres d'extension
public static class ExtensionsProduit
{
extension(Produit produit)
{
// PROPRIÉTÉ d'extension : impossible avant C# 14
public bool EstEnPromotion => produit.PrixBarre is not null;
public decimal Economie => (produit.PrixBarre ?? produit.Prix) - produit.Prix;
// Méthode, comme avant
public string Etiquette() => produit.EstEnPromotion
? $"{produit.Nom} (-{produit.Economie:C})"
: produit.Nom;
}
extension(Produit) // membres STATIQUES d'extension
{
public static Produit Vide => new() { Nom = "(aucun)", Prix = 0 };
public static Produit Depuis(string csv) => /* ... */ null!;
}
}
// Utilisation : indistinguable d'un vrai membre du type
if (produit.EstEnPromotion) Console.WriteLine(produit.Etiquette());
var p = Produit.Depuis(ligne);
Utile en particulier sur les types que tu ne possèdes pas (types du framework, code généré, bibliothèque tierce), là où l'héritage n'est pas une option.
Le mot-clé field
// Avant : déclarer, nommer et gérer le champ de soutien à la main
private string _reference = "";
public string Reference
{
get => _reference;
set => _reference = value.ToUpperInvariant();
}
// C# 14
public string Reference
{
get;
set => field = value.ToUpperInvariant(); // « field » = le champ généré
}
// Autre usage fréquent : initialisation paresseuse
public List<Ligne> Lignes => field ??= Charger();
// Affectation conditionnelle au null : n'affecte que si la cible existe
commande?.Adresse = nouvelleAdresse;
utilisateur?.Preferences?.Theme = "sombre";
// nameof sur un générique non lié
var nom = nameof(Dictionary<,>); // "Dictionary"
// Modificateurs sur les paramètres de lambda (sans répéter les types)
var tryParse = (string s, out int r) => int.TryParse(s, out r);
// Conversions implicites vers Span : moins de surcharges à écrire
void Traiter(ReadOnlySpan<byte> donnees) { }
Traiter(monTableau); // conversion directe
// Constructeurs et événements partiels (utile aux générateurs de source)
public partial class Vue { public partial Vue(IService s); }
// Opérateurs d'affectation composée définis par l'utilisateur : += sans allocation
public void operator +=(Vecteur autre) { X += autre.X; Y += autre.Y; }
Runtime et performance
| Amélioration | Effet concret |
|---|---|
| Dévirtualisation étendue (tableaux, méthodes d'interface, structs) | Les appels via interface ou foreach sur tableau deviennent des appels directs : gains « gratuits » sur du code existant |
| Allocation sur la pile de petits tableaux et de valeurs encapsulées | Moins de pression sur le ramasse-miettes, sans changer une ligne de code |
| Promotion de champs de structs, meilleure élimination de bornes | Boucles numériques plus rapides |
| Prise en charge de jeux d'instructions récents (AVX 10.2) | Accélération du traitement vectoriel sur processeurs modernes |
| DATAS (adaptation dynamique du tas) mature | Empreinte mémoire nettement réduite en conteneur, à débit comparable |
| Native AOT étendu | Plus de scénarios compilables en natif, binaires plus petits |
Ces gains ne demandent aucune modification de code : recompiler pour net10.0 suffit. Sur
des applications web réelles, on observe couramment quelques pourcents de débit en plus et une empreinte
mémoire en baisse — précisément pourquoi une montée de version majeure est presque toujours rentable.
Bibliothèques
// LINQ : jointures externes de première classe (enfin)
var lignes = etudiants.LeftJoin(
departements,
e => e.DepartementId,
d => d.Id,
(e, d) => new { e.Nom, Departement = d?.Nom ?? "(aucun)" });
// RightJoin existe aussi — et EF Core 10 les traduit en SQL
// LINQ asynchrone intégré (plus de paquet System.Linq.Async)
var total = await flux.Where(x => x.Actif).SumAsync(x => x.Montant, ct);
var liste = await flux.Take(100).ToListAsync(ct);
// Comparaison de chaînes avec ordre numérique : "fichier2" < "fichier10"
var comparateur = StringComparer.Create(CultureInfo.CurrentCulture,
CompareOptions.NumericOrdering);
noms.Sort(comparateur);
// System.Text.Json : options plus fines
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
AllowOutOfOrderMetadataProperties = true, // JSON polymorphe plus tolérant
RespectNullableAnnotations = true // les annotations nullables sont honorées
};
// Cryptographie post-quantique (selon la plateforme) : ML-KEM, ML-DSA, SLH-DSA
// Utile pour les systèmes à longue durée de conservation des données chiffrées.
ASP.NET Core 10
Validation intégrée aux Minimal API
builder.Services.AddValidation();
public record CreerClient(
[Required, StringLength(80, MinimumLength = 2)] string Nom,
[Required, EmailAddress] string Email,
[Range(18, 130)] int Age);
app.MapPost("/clients", (CreerClient dto) => TypedResults.Created("/clients/1", dto));
// Invalide → 400 + ProblemDetails par champ, sans une ligne de code de validation.
// Traverse les objets imbriqués et les collections ; annoter [ValidatableType] au besoin.
// Désactivation ponctuelle : .DisableValidation()
Les API de validation vivent désormais dans Microsoft.Extensions.Validation, donc
utilisables hors HTTP (services de fond, traitements par lots).
Server-Sent Events
app.MapGet("/pouls", (CancellationToken ct) =>
{
async IAsyncEnumerable<Mesure> Flux([EnumeratorCancellation] CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
yield return new Mesure(Random.Shared.Next(60, 100), DateTime.UtcNow);
await Task.Delay(2000, ct);
}
}
return TypedResults.ServerSentEvents(Flux(ct), eventType: "pouls");
});
OpenAPI
- OpenAPI 3.1 par défaut (JSON Schema 2020-12),
Microsoft.OpenApi2.0. - Sortie YAML :
app.MapOpenApi("/openapi/{documentName}.yaml"). - Commentaires XML repris automatiquement dans le document, via un générateur de source (compatible
AOT) : active
<GenerateDocumentationFile>. Descriptionsur[ProducesResponseType], transformateurs par endpoint, etIOpenApiDocumentProviderinjectable pour produire le document hors requête HTTP (utile en CI).
Blazor
// Fin de la double initialisation au prérendu : un attribut suffit
[PersistentState]
public List<Film>? Films { get; set; }
protected override async Task OnInitializedAsync()
=> Films ??= await Service.ChargerAsync();
| Nouveauté Blazor 10 | À quoi ça sert |
|---|---|
[PersistentState] | Transporter l'état du prérendu vers la phase interactive, sans enregistrement manuel |
NotFoundPage sur le routeur + NavigationManager.NotFound() | Une vraie page 404 pilotée depuis le code |
InputHidden, validation par générateur de source | Formulaires plus complets, validation des objets imbriqués et collections |
| Passkeys (WebAuthn) dans le modèle Blazor Web App | Connexion sans mot de passe, résistante au hameçonnage |
Interop JS : InvokeConstructorAsync, GetValueAsync, SetValueAsync | Manipuler des objets JavaScript sans écrire de fonction d'aide |
| Script Blazor servi comme actif statique, préchargement des ressources | Compression et empreintes automatiques : premier rendu plus rapide |
QuickGrid : RowClass, fermeture programmée des options de colonne | Mise en forme conditionnelle des lignes |
Persistance et reconnexion de circuit améliorées, ReconnectModal | Une coupure réseau ne détruit plus la session utilisateur |
| Hot Reload pour WebAssembly | Boucle de développement enfin comparable au mode serveur |
Sécurité et divers
- Passkeys dans ASP.NET Core Identity (voir chapitre 17).
- 401/403 au lieu d'une redirection pour les endpoints d'API non authentifiés — comportement attendu
par tous les clients HTTP. Réversible via
OnRedirectToLogin. - Nouvelles métriques d'authentification, d'autorisation et d'Identity (durée, refus, connexions).
- Domaine
.localhosten développement :https://monapp.dev.localhost:7099, avec certificat de développement adapté. IMemoryPoolFactoryet éviction automatique des blocs mémoire inutilisés, avec métriques.- Désérialisation JSON via
PipeReader: moins de copies, gain automatique.
EF Core 10
// Table splitting : les colonnes vivent dans la table du propriétaire
mb.Entity<Client>(b =>
{
b.ComplexProperty(c => c.AdresseLivraison); // Adresse_Rue, Adresse_Ville…
b.ComplexProperty(c => c.AdresseFacturation); // optionnel accepté depuis EF 10
});
// Ou une seule colonne JSON, collections comprises
mb.Entity<Client>().ComplexProperty(c => c.Preferences, p => p.ToJson());
// Sémantique de VALEUR : ce qui échouait avec les owned types fonctionne
client.AdresseFacturation = client.AdresseLivraison; // simple copie de propriétés
await db.SaveChangesAsync();
// ExecuteUpdate fonctionne aussi dans le JSON
await db.Blogs.ExecuteUpdateAsync(s =>
s.SetProperty(b => b.Details.Vues, b => b.Details.Vues + 1), ct);
mb.Entity<Blog>()
.HasQueryFilter("SuppressionLogique", b => !b.EstSupprime)
.HasQueryFilter("Locataire", b => b.LocataireId == _locataire.Id);
var corbeille = await db.Blogs.IgnoreQueryFilters(["SuppressionLogique"]).ToListAsync(ct);
await db.Blogs.ExecuteUpdateAsync(s =>
{
s.SetProperty(b => b.Vues, 8);
if (nomChange) s.SetProperty(b => b.Nom, "nouveau"); // conditionnel : impossible avant
}, ct);
| Autre nouveauté EF 10 | Intérêt |
|---|---|
Recherche vectorielle SQL Server (SqlVector<float>, EF.Functions.VectorDistance) | Recherche sémantique et RAG directement en base, sans base vectorielle dédiée |
Type json natif (SQL Server 2025 / Azure SQL) | Stockage et requêtes JSON bien plus efficaces qu'en nvarchar |
| Recherche plein texte et recherche hybride sur Cosmos DB | Pertinence textuelle + similarité vectorielle combinées |
Traduction de LeftJoin / RightJoin | Fin du triptyque SelectMany/GroupJoin/DefaultIfEmpty |
| Collections paramétrées : un paramètre SQL par valeur (avec remplissage) | Meilleurs plans d'exécution et cache de plans préservé |
| Constantes intégrées masquées dans les logs | Moins de données personnelles qui fuient dans les journaux |
Analyseur contre la concaténation dans FromSqlRaw | Détection d'une injection SQL potentielle à la compilation |
| Ordre cohérent dans les requêtes découpées | Corrige une source de corruption de données silencieuse |
| Noms de contraintes par défaut personnalisables | Conventions de nommage d'entreprise respectées |
SDK et outillage
# Exécuter un simple fichier .cs, sans projet : idéal pour un script ou une démonstration
cat > bonjour.cs <<'EOF'
#:package Humanizer@2.14.1
Console.WriteLine(TimeSpan.FromMinutes(135).Humanize());
EOF
dotnet run bonjour.cs
# Et le convertir en vrai projet quand il grandit
dotnet project convert bonjour.cs
# Verbes CLI réorganisés (les anciennes formes fonctionnent toujours)
dotnet package add Serilog
dotnet package list --vulnerable
dotnet package search json
# Exécuter un outil sans l'installer durablement
dnx dotnet-ef -- migrations list
Côté conteneurs, dotnet publish /t:PublishContainer gagne des options (dont les images
chiseled, sans shell), et les modèles de tests basculent sur xUnit v3 avec
Microsoft.Testing.Platform.
Vérifie que c'est passé
🎯 Quiz — 4 questions
1. Après migration en .NET 10, ton front JavaScript reçoit du HTML au lieu d'un JSON d'erreur sur une requête non authentifiée. Que s'est-il passé ?
OnRedirectToLogin.2. Quel est l'apport principal des membres d'extension de C# 14 ?
extension.3. Pourquoi préférer un type complexe EF 10 à un owned type pour une adresse ?
client.Facturation = client.Livraison et l'absence de support d'ExecuteUpdate.4. Que faut-il faire pour bénéficier des gains de performance du runtime .NET 10 ?
- .NET 10 = le socle : LTS jusqu'en novembre 2028, à préférer pour tout nouveau projet.
- Migration depuis .NET 8 : changer le TFM, aligner les paquets, lire les changements de comportement.
- C# 14 : membres d'extension et
fieldsont les deux à connaître. - Minimal API : validation intégrée, SSE, OpenAPI 3.1 avec commentaires XML.
- EF Core 10 : types complexes, filtres nommés,
ExecuteUpdatesimplifié, vecteurs. - Blazor :
[PersistentState]règle enfin le problème du prérendu.