Prise en charge OpenAPI intégrée de .NET 9 pour documenter votre API Web

.NET 9 apporte une nouvelle bibliothèque pour documenter vos API Web .NET.
Si vous avez construit des API avec .NET, vous avez probablement entendu parler de Swagger. Swagger a été regroupé dans le cadre des modèles de projet pour les applications Web .NET depuis plusieurs années.
Il vous permet de documenter votre API en générant automatiquement une spécification pour cela. Cette spécification décrit les points de terminaison disponibles et la forme des demandes et des réponses pour ces points de terminaison.
Vous pouvez utiliser des outils comme Swagger UI pour obtenir une vue dynamique de cette spécification (que vous pouvez utiliser pour explorer et tester l’API).
Mais faites tourner un nouveau projet Web .NET 9 et vous trouverez que toutes ces références à Swagger ont disparu, remplacées par des appels mystérieux à «OpenAPI».
Alors, qu’est-ce que OpenAPI, et comment êtes-vous censé générer ces spécifications de fanfaronnade maintenant?
De Swagger à Microsoft.Aspnetcore.OpenAPI
«Swagger» est devenu synonyme de «OpenAPI» (depuis que Swagger a été donné à l’initiative OpenAPI en 2015), mais les deux termes méritent d’être séparés.
Openapi fait référence à la spécification (ce fichier JSON que nous avons vu plus tôt).
Fanfarner fait référence à la famille des produits open-source et commerciaux de SmartBear qui peuvent interagir avec une spécification OpenAPI.
Dans .NET Projects (.NET 8 et plus tôt), Swagger est utilisé par défaut, à quelques fins.
- Pour générer la spécification OpenAPI
- Pour servir cette interface utilisateur pratique pour explorer et interagir avec la spécification
À partir de .NET 9, Microsoft a regroupé son propre outil pour générer la spécification OpenAPI. Tournez un nouveau projet .net 9, et vous trouverez déjà les suivants.
Mais si vous ajoutez cela à un projet existant, vous devrez effectuer quelques étapes.
Installez d’abord le nouveau package Microsoft OpenAPI:
dotnet add package Microsoft.AspNetCore.OpenApi
Deuxièmement, configurez votre application en ajoutant deux lignes dans votre Programme.cs déposer:
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
Exécutez cette application maintenant, accédez à /openapi/v1.json
Et vous vous retrouverez à regarder une spécification JSON OpenAPI.
Vous pouvez toujours utiliser une ui de fanfaronnade
Mais que se passe-t-il si vous voulez utiliser cette interface utilisateur pratique que Swagger fournit pour interagir avec votre spécification?
C’est toujours possible.
La spécification générée est conforme à la norme OpenAPI, nous pouvons donc utiliser des outils familiers (interface utilisateur, redoc, etc.) pour explorer cette spécification.
Pour l’interface utilisateur de Swagger, il s’agit d’installer le package d’interface utilisateur Swagger.
dotnet add package Swashbuckle.AspNetCore.SwaggerUi
Puis la configurer via Programme.cs:
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "v1");
});
}
Maintenant si vous naviguez vers /swagger
Dans votre application, vous verrez l’interface utilisateur standard de Swagger prodiguée de votre spécification OpenAPI (qui a été générée par le package MS OpenAPI).
Mais que se passe-t-il si vous avez besoin de personnaliser la sortie de cette spécification, par exemple en peaufinant comment les points de terminaison sont nommés et / ou comment les modèles de données sont représentés?
Il est avantageux de savoir comment certaines options de fanfaronnade classiques se traduisent par la nouvelle bibliothèque MS.
Personnaliser la sortie générée
Les paramètres par défaut vous mèneront jusqu’à présent, mais parfois vous devrez peut-être modifier la sortie de votre spécification OpenAPI.
Prenez cet exemple de command
pour passer des commandes.
public record CreateOrder
{
public record Command
{
public string UserId { get; set; }
public string ProductId { get; set; }
public class DeliveryDetails
{
public string Address { get; set; }
public string ContactNumber { get; set; }
}
}
public record Response
{
public string OrderId { get; set; }
}
}
Ici, nous avons choisi de nid les classes pour représenter la structure d’une commande.
Le Command
La classe représente les données dont nous avons besoin pour créer une commande, donc elle est imbriquée dans le CreateOrder
classe.
Nous pouvons exposer cela via un point de terminaison .NET pour créer une commande.
private static IResult CreateOrder(HttpContext context, CreateOrder.Command createOrderCommand)
{
Console.WriteLine("Creating order");
Console.WriteLine(createOrderCommand);
return Results.Ok();
}
Cela fonctionne, mais il y a un problème subtil dans la spécification OpenAPI générée.
Dans la spécification le Command
Le modèle sera représenté comme ceci:
{
"components": {
"schemas": {
"Command": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"productId": {
"type": "string"
}
}
}
}
}
}
Notez comment le schéma généré utilise le nom «Commande» pour cet objet.
Disons maintenant que nous allons de l’avant et créons une deuxième commande, en utilisant une approche similaire.
public record CancelOrder
{
public record Command
{
public string OrderId { get; set; }
public string Reason { get; set; }
}
}
Cette deuxième commande dans la spécification résultante reçoit un nom (Command2
) pour le différencier du premier:
{
"components": {
"schemas": {
"Command": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"productId": {
"type": "string"
}
}
},
"Command2": {
"type": "object",
"properties": {
"orderId": {
"type": "string"
},
"reason": {
"type": "string"
}
}
}
}
}
}
Ce n’est peut-être pas ce que vous voulez. Surtout si vous utilisez un outil qui peut générer du code client à partir de cette spécification OpenAPI.
Vous pouvez vous retrouver avec des collisions de noms (deux modèles partageant le même nom) et / ou des noms opaques (comme Command, Command2) qui ne signifient pas grand-chose lorsque vous essayez d’interagir avec votre API à l’aide du client généré.
Disons que vous préférez avoir des modèles comme CreateOrderCommand
et CancelOrderCommand
au lieu de Command
et Command2
.
La façon dont la manière fanvale
Vous pouvez configurer cela en fanfaronnade en utilisant le CustomSchemaIds
Méthode, qui peut modifier comment les ID de schéma sont générés.
services.AddSwaggerGen(c =>
{
c.CustomSchemaIds(type =>
{
if (type.IsNested)
{
return $"{type.DeclaringType.Name}{type.Name}";
}
return type.Name;
});
});
Lorsqu’une classe est imbriquée, le nom sera désormais une combinaison de la classe «contenant» et des noms de la classe imbriqués.
Utilisation de microsoft.aspnetcore.openapi
Mais il n’y a pas CustomSchemaIds
Option dans la nouvelle bibliothèque MS OpenAPI.
Au lieu de cela, la bibliothèque expose un mécanisme de niveau inférieur pour transformer votre schéma lorsqu’il est généré.
Un schéma dans OpenAPI représente la structure des données sur lesquelles dépend de notre API. Dans ce cas, nous nous retrouverons avec un schéma pour chacun de nos types de demande et de réponse.
Pour changer la façon dont ce schéma est généré, nous pouvons définir un transformateur de schéma en utilisant cette syntaxe:
Task Transformer(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken arg3)
{
return Task.CompletedTask;
}
Et enregistrez-le lorsque nous le configurons lorsque nous ajoutons le OpenAPI
service:
builder.Services.AddOpenApi(options =>
{
options.AddSchemaTransformer(Transformer);
});
Ceci sera invoqué pour chaque schéma que la bibliothèque MS tentera de générer.
Mais nous voulons seulement modifier comment cela fonctionne pour les classes imbriquées, alors incluons un chèque pour cela.
Task Transformer(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken arg3)
{
var type = context.JsonTypeInfo.Type;
if (!type.IsNested)
return Task.CompletedTask;
return Task.CompletedTask;
}
Ici, nous obtenons les informations de type utilisant context.JsonTypeInfo
alors vérifiez si la classe est imbriquée. Sinon, nous reviendrons tôt.
Sinon, si le type est imbriqué, nous pouvons appliquer notre logique personnalisée.
Maintenant, c’est là que, au moment de la rédaction, nous devons écrire un peu de code de «niveau inférieur» que l’alternative Swagger.
const string schemaId = "x-schema-id";
schema.Annotations[schemaId] = $"{type.DeclaringType?.Name}{type.Name}";
OpenAPI utilise des annotations pour contrôler la façon dont le schéma est finalement représenté dans la spécification OpenAPI générée.
Dans ce cas, nous voulons changer l’ID, ce qui signifie peaufiner l’annotation avec ID x-schema-id
.
schema.Annotations
est un dictionnaire alors nous allons de l’avant et fixons une valeur pour le x-schema-id
Clé du nouveau nom de type que nous voulons utiliser (une concaténation du parent et des noms de classe imbriqués).
Voici le transformateur de schéma complet:
Task Transformer(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken arg3)
{
var type = context.JsonTypeInfo.Type;
if (!type.IsNested)
return Task.CompletedTask;
const string schemaId = "x-schema-id";
schema.Annotations[schemaId] = $"{type.DeclaringType?.Name}{type.Name}";
return Task.CompletedTask;
}
Dans certains cas, vous n’avez pas besoin de recourir à des annotations comme celle-ci.
Par exemple, vous pouvez ajouter des titres et des descriptions au schéma généré en utilisant directement les propriétés pertinentes, comme ceci:
schema.Title = $"{type.DeclaringType?.Name}{type.Name}";
schema.Description = $"Request/Response for {type.DeclaringType?.Name}{type.Name}";
Qui comprendra cela dans la spécification générée:
"components": {
"schemas": {
"CancelOrderCommand": {
"title": "CancelOrderCommand",
"type": "object",
"properties": {
"orderId": {
"type": "string"
},
"reason": {
"type": "string"
}
},
"description": "Request/Response for CancelOrderCommand"
}
}
}
Erreurs inattendues?
Il est tôt pour cette bibliothèque, et vous pouvez rencontrer des incompatibilités ou un comportement incompatibles avec la façon dont Swagger génère des documents OpenAPI.
Si vous trouvez que quelque chose ne fonctionne pas comme vous vous y attendez, consultez les problèmes GitHub pour ASP.NET.
Par exemple, voici un rapport de bogue pour un problème où les ID de schéma en double sont générés (pour le même objet): https://github.com/dotnet/aspnetcore/issues/58968. Dans ce cas, le problème semble réglé dans la version 9.0.2 du service.
Customations supplémentaires
Nous avons abordé des transformateurs de schéma, mais il existe d’autres transformateurs que vous pouvez utiliser pour modifier vos documents OpenAPI.
Les transformateurs de documents vous permettent de faire des modifications globales de l’ensemble du document OpenAPI.
Les transformateurs d’opération s’appliquent à chaque opération individuelle (par exemple, notre point de terminaison de création).
Vous pouvez approfondir ces moyens et d’autres moyens de personnaliser le document OpenAPI via les documents officiels ici: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customalize-openapi?view=Aspnetcore-9.0.
Résumé
Si vous créez une nouvelle application Web .net 9, vous trouverez qu’il utilise Microsoft.AspNetCore.OpenApi
Au lieu de Swagger pour générer des spécifications OpenAPI pour vos API.
Pour les projets existants, et dans le cas où vous utilisez Swagger sans configuration personnalisée, vous pouvez généralement passer à Microsoft.AspNetCore.OpenApi
en remplacement direct. Si vous avez une configuration personnalisée, vous devrez le modifier pour travailler avec la nouvelle bibliothèque.
Pour de nombreuses opérations, un transformateur de schéma personnalisé est la réponse, vous permettant de modifier les définitions de schéma générées au point que la spécification OpenAPI est générée.
Source link