Aller au contenu

« Swashbuckle » : différence entre les versions

De Banane Atomic
 
(11 versions intermédiaires par le même utilisateur non affichées)
Ligne 3 : Ligne 3 :
* [https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-2.1 ASP.NET Core Web API help pages with Swagger / Open API]
* [https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-2.1 ASP.NET Core Web API help pages with Swagger / Open API]
* [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag?view=aspnetcore-2.1&tabs=visual-studio-code%2Cvisual-studio-xml Get started with NSwag and ASP.NET Core]
* [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag?view=aspnetcore-2.1&tabs=visual-studio-code%2Cvisual-studio-xml Get started with NSwag and ASP.NET Core]
* [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-5.0&tabs=visual-studio-code Swashbuckle]


= [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag?tabs=netcore-cli NSwag] =
= Installation =
== Installation ==
{{warn | It is no longer actively maintained and has been remove from .NET 9 templates. [https://github.com/dotnet/aspnetcore/issues/54599 ]}}
<kode lang='powershell'>
<kode lang='ps'>
# pour vscode
dotnet add package Swashbuckle.AspNetCore
dotnet add package NSwag.AspNetCore
# Add to the project file *.csproj:
#  <ItemGroup>
#    <PackageReference Include="NSwag.AspNetCore" Version="11.17.15" />
</kode>
== [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag?tabs=netcore-cli#add-and-configure-swagger-middleware Configuration] ==
=== Configuration Web API ===
<filebox fn='Program.cs'>
builder.Services.AddSwaggerDocument(configuration =>
{
    configuration.Title = "MyApp";
    configuration.Version = typeof(Program).Assembly.GetSimplifiedVersion();
});
 
if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi3();
}
</filebox>
 
=== Configuration MVC ===
<filebox fn='Startup.cs' collapsed>
using NJsonSchema;
using NSwag.AspNetCore;
using System.Reflection;
 
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMvc();
 
    // à ajouter avant app.UseSpa
    app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, settings =>
    {
        settings.GeneratorSettings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase;
        settings.PostProcess = document =>
        {
            //document.Info.Version = "v1";
            document.Info.Title = "Test API";
            document.Info.Description = "A simple ASP.NET Core web API";
            //document.Info.TermsOfService = "None";
            document.Info.Contact = new NSwag.SwaggerContact
            {
                Name = "Nicolas",
                //Email = string.Empty,
                //Url = "https://twitter.com/spboyer"
            };
            /*document.Info.License = new NSwag.SwaggerLicense
            {
                Name = "Use under LICX",
                Url = "https://example.com/license"
            };*/
        };
    });
</filebox>
 
== Problème avec IActionResult ==
{{boxx|NSwag}} utilise la réflexion pour obtenir le type de retour. Avec {{boxx|IActionResult}} il ne peut pas.
<kode lang='csharp'>
[HttpGet]
// utiliser SwaggerResponse
[SwaggerResponse(HttpStatusCode.OK, typeof(IReadOnlyList<ItemDto>))]
[SwaggerResponse(HttpStatusCode.BadRequest, typeof(void))]
// ou ProducesResponseType
[ProducesResponseType(typeof(IReadOnlyList<ItemDto>), StatusCodes.Status200OK)]
public IActionResult Get()
</kode>
</kode>
= [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-5.0&tabs=visual-studio-code Swashbuckle] =
== Swashbuckle installation ==
Already installed in .NET 7+
<filebox fn='MyWebApi.csproj' lang='xml'>
<ItemGroup>
  <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</filebox>


<filebox fn='Program.cs'>
<filebox fn='Program.cs'>
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen();
Ligne 98 : Ligne 22 :
</filebox>
</filebox>


== Installation Old ==
= Installation Old =
<kode lang='bash'>
<kode lang='bash'>
dotnet add package Swashbuckle.AspNetCore
dotnet add package Swashbuckle.AspNetCore
Ligne 127 : Ligne 51 :
</filebox>
</filebox>


== Usage ==
= Usage =
<filebox fn='Controllers/ItemController.cs'>
<filebox fn='Controllers/ItemController.cs'>
[ApiController]
[ApiController]
Ligne 140 : Ligne 64 :
</filebox>
</filebox>


== [https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-7.0&tabs=visual-studio-code#xml-comments XML documentation] ==
= [https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-7.0&tabs=visual-studio-code#xml-comments XML documentation] =
{{warn | {{boxx|&}} has to be escaped as {{boxx|<nowiki>&amp;</nowiki>}}}}
{{warn | {{boxx|&}} has to be escaped as {{boxx|&amp;amp;}}}}
<filebox fn='MyProject.csproj' lang='xml'>
<filebox fn='MyProject.csproj' lang='xml'>
<PropertyGroup>
<PropertyGroup>
Ligne 162 : Ligne 86 :


<filebox fn='Controllers/ItemController.cs'>
<filebox fn='Controllers/ItemController.cs'>
/// <summary>Fetch all the items.</summary>
[Produces("application/json")]
/// <param name="cancellationToken"></param>
public class MyController() : ControllerBase
/// <returns>All the items</returns>
{
/// <remarks>
    /// <summary>Get all the items.</summary>
/// Sample request:
    /// <remarks>
///
    /// Sample request:
///    GET /item
    ///
/// </remarks>
    ///    GET /item
/// <response code="200">Returns all the items.</response>
    /// </remarks>
[HttpGet]
    /// <response code="200">Returns all the items.</response>
[Produces("text/json")]
    [HttpGet]
[ProducesResponseType(typeof(IEnumerable<Item>), StatusCodes.Status200OK)]
    public IEnumerable<Item> GetAll() { /* ... */ }
public IActionResult GetAll() { /* ... */ }
 
    /// <summary>Update an item.</summary>
    /// <param name="timeSeries">The new time series to add.</param>
    /// <remarks>
    /// Sample request:
    ///
    ///    PUT /item/9
    /// </remarks>
    [HttpPut("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)] // by default returning void or Task is associated to status code 200
    public Task UpdateAsync(int id, CreateUpdateItemQuery query) { /* ... */ }
</filebox>
</filebox>


== [https://medium.com/@celery_liu/asp-net-core-web-api-with-swagger-api-versioning-for-dotnet-8-c8ce2fd7808c Version] ==
= [https://medium.com/@celery_liu/asp-net-core-web-api-with-swagger-api-versioning-for-dotnet-8-c8ce2fd7808c Version] =
* [[Asp.net_core_8_web_api#Version|ASP.NET Core Version]]
* [[Asp.net_core_8_web_api#Version|ASP.NET Core Version]]
<filebox fn='Program.cs'>
<filebox fn='Program.cs'>
Ligne 223 : Ligne 157 :
</filebox>
</filebox>


== Document Filter ==
= Document Filter =
<filebox fn='Program.cs'>
<filebox fn='Program.cs'>
builder.Services.AddSwaggerGen(c =>
builder.Services.AddSwaggerGen(c =>
Ligne 249 : Ligne 183 :
</filebox>
</filebox>


=== [https://vrcode.medium.com/implement-health-check-in-dot-net-core-api-and-integrate-in-swagger-5b812601cb35 Add HealthChecks endpoint] ===
== [https://vrcode.medium.com/implement-health-check-in-dot-net-core-api-and-integrate-in-swagger-5b812601cb35 Add HealthChecks endpoint] ==
<filebox fn='Program.cs' collapsed>
<filebox fn='Program.cs' collapsed>
builder.Services.AddHealthChecks();
builder.Services.AddHealthChecks();
Ligne 296 : Ligne 230 :
</filebox>
</filebox>


== [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-5.0&tabs=visual-studio-code#customize-the-ui Dark theme] ==
= [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-5.0&tabs=visual-studio-code#customize-the-ui Dark theme] =
<filebox fn='Startup.cs'>
<filebox fn='Startup.cs'>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
Ligne 393 : Ligne 327 :
</filebox>
</filebox>


== [https://stackoverflow.com/questions/61881770/invalidoperationexception-cant-use-schemaid-the-same-schemaid-is-already-us The same schemaId is already used] ==
= [https://stackoverflow.com/questions/61881770/invalidoperationexception-cant-use-schemaid-the-same-schemaid-is-already-us The same schemaId is already used] =
Occurs when you have 2 classes with the same name in different namespaces.<br>
Occurs when you have 2 classes with the same name in different namespaces.<br>
Fix it by using the full class name (with namspace) for the schema id.
Fix it by using the full class name (with namspace) for the schema id.

Dernière version du 18 janvier 2025 à 10:52

Liens

Installation

It is no longer actively maintained and has been remove from .NET 9 templates. [1]
dotnet add package Swashbuckle.AspNetCore
Program.cs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger()
       .UseSwaggerUI();
}

Installation Old

dotnet add package Swashbuckle.AspNetCore
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "EFWebApi", Version = "v1" });
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c => 
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "EFWebApi v1");
            c.RoutePrefix = string.Empty;  // serve the Swagger UI at the app's root (http://localhost:<port>/)
        });
    }

Usage

Controllers/ItemController.cs
[ApiController]
[Route("[controller]")]
[Produces("application/json")]  // set the Media type
public class ItemController : ControllerBase
{
    [HttpGet]
    [ProducesResponseType(typeof(IEnumerable<Item>), StatusCodes.Status200OK)]  // set the status code and the return type
    public IActionResult Get() { /* ... */ }
}

XML documentation

& has to be escaped as &amp;
MyProject.csproj
<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
Program.cs
builder.Services.AddSwaggerGen(
    options =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "My Web Api", Version = "v1" });

        // Set the comments path for the Swagger JSON and UI.
        var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
        options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
    });
}
Controllers/ItemController.cs
[Produces("application/json")]
public class MyController() : ControllerBase
{
    /// <summary>Get all the items.</summary>
    /// <remarks>
    /// Sample request:
    ///
    ///     GET /item
    /// </remarks>
    /// <response code="200">Returns all the items.</response>
    [HttpGet]
    public IEnumerable<Item> GetAll() { /* ... */ }

    /// <summary>Update an item.</summary>
    /// <param name="timeSeries">The new time series to add.</param>
    /// <remarks>
    /// Sample request:
    ///
    ///     PUT /item/9
    /// </remarks>
    [HttpPut("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)] // by default returning void or Task is associated to status code 200
    public Task UpdateAsync(int id, CreateUpdateItemQuery query) { /* ... */ }

Version

Program.cs
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();

// nuget package Asp.Versioning.Mvc.ApiExplorer
builder.Services
    .AddApiVersioning(/* ... */)
    .AddApiExplorer(
        options =>
        {
            options.GroupNameFormat = "'v'VVV";
            options.SubstituteApiVersionInUrl = true;
        });

app.UseSwagger();
app.UseSwaggerUI(
    options =>
    {
        var descriptions = app.DescribeApiVersions();
        foreach (var description in descriptions)
            options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
    });
ConfigureSwaggerOptions.cs
public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions<SwaggerGenOptions>
{
    public void Configure(SwaggerGenOptions options)
    {
        foreach (var description in provider.ApiVersionDescriptions)
        {
            options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
        }

        static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
            => new()
            {
                Title = "Power Analytics Model Management API",
                Version = description.ApiVersion.ToString(),
                Description = "API Description."
            };
    }
}

Document Filter

Program.cs
builder.Services.AddSwaggerGen(c =>
{
    c.DocumentFilter<MyDocumentFilter>();
});
MyDocumentFilter.cs
public class MyDocumentFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        // add new property
        swaggerDoc.Extensions.Add("propertyName", new OpenApiObject
        {
            ["propertyName"] = new OpenApiString("value"),
            ["propertyName"] = new OpenApiArray
            {
                { new OpenApiString("value") }
            }
        }
    }
}

Add HealthChecks endpoint

Program.cs
builder.Services.AddHealthChecks();

builder.Services.AddSwaggerGen(c =>
{
    c.DocumentFilter<HealthChecksDocumentFilter>();
});

var app = builder.Build();
app.MapHealthChecks("/health");
HealthChecksDocumentFilter.cs
public class HealthChecksDocumentFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var pathItem = new OpenApiPathItem();
        var operation = new OpenApiOperation();
        operation.Tags.Add(new OpenApiTag { Name = "ApiHealth" });

        var properties = new Dictionary<string, OpenApiSchema>
        {
            { "status", new OpenApiSchema() { Type = "string" } },
            { "errors", new OpenApiSchema() { Type = "array" } }
        };

        var response = new OpenApiResponse();

        response.Content.Add("application/json", new OpenApiMediaType
        {
            Schema = new OpenApiSchema
            {
                Type = "object",
                AdditionalPropertiesAllowed = true,
                Properties = properties
            }
        });

        operation.Responses.Add("200", response);
        pathItem.AddOperation(OperationType.Get, operation);
        swaggerDoc.Paths.Add("/health", pathItem);
    }
}

Dark theme

Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseStaticFiles();
        app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "EFWebApi v1");
            c.RoutePrefix = string.Empty;
            c.InjectStylesheet("/swagger-ui/dark-theme.css");
        });
    }
wwwroot/swagger-ui/dark-theme.css
.swagger-ui .topbar .download-url-wrapper .select-label select {
    border: 2px solid #89bf04;;
}

body {
    background-color: #303030;
}
.swagger-ui,
.swagger-ui .info .title,
.swagger-ui .opblock-tag,
.swagger-ui section.models h4,
.swagger-ui .opblock .opblock-summary-operation-id,
.swagger-ui .opblock .opblock-summary-path,
.swagger-ui .opblock .opblock-summary-path__deprecated,
.swagger-ui table thead tr td,
.swagger-ui table thead tr th,
.swagger-ui .parameter__name,
.swagger-ui .parameter__type,
.swagger-ui .response-col_status,
.swagger-ui .model-title,
.swagger-ui .model,
.swagger-ui .tab li {
    color: #f0f0f0;
}

.swagger-ui input[type="email"],
.swagger-ui input[type="file"],
.swagger-ui input[type="password"],
.swagger-ui input[type="search"],
.swagger-ui input[type="text"],
.swagger-ui textarea {
    background: #303030;
    color: #f0f0f0;
    border: 1px solid gray;
}

.swagger-ui .opblock .opblock-section-header {
    background-color: #1b1b1b;
}
.swagger-ui .opblock .opblock-section-header h4,
.swagger-ui .btn {
    color: #f0f0f0;
}

.swagger-ui input[disabled], .swagger-ui select[disabled], .swagger-ui textarea[disabled] {
    background-color: #303030;
    color: lightgray;
}

.swagger-ui select {
    background: #303030 url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="gray"><path d="M13.418 7.859a.695.695 0 01.978 0 .68.68 0 010 .969l-3.908 3.83a.697.697 0 01-.979 0l-3.908-3.83a.68.68 0 010-.969.695.695 0 01.978 0L10 11l3.418-3.141z"/></svg>') right 10px center no-repeat;
    color: #f0f0f0;
}

.swagger-ui .response-control-media-type--accept-controller select {
    border-color: #89bf04;
}
.swagger-ui .response-control-media-type__accept-message {
    color: #89bf04;
}

.arrow path {
    fill: gray;
}

.swagger-ui section.models .model-container {
    border: 1px solid #61affe;
}

.swagger-ui .model-toggle::after {
    background: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="gray"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>') 50% no-repeat;
}

.swagger-ui .download-contents {
    width: unset;
}

The same schemaId is already used

Occurs when you have 2 classes with the same name in different namespaces.
Fix it by using the full class name (with namspace) for the schema id.

Program.cs
builder.Services.AddSwaggerGen(options =>
{
    options.CustomSchemaIds(type => type.ToString());
});

Url

Url Resource
http://localhost:<port>/swagger swagger UI
http://localhost:<port>/swagger/v1/swagger.json swagger json

Ouvrir le navigateur sur swagger

Dans un projet Web API avec Visual Studio Code, ouvrir le navigateur sur la page swagger.

.vscode\launch.json
{
    "configurations": [
        {
            "serverReadyAction": {
                "action": "openExternally",
                "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)",
                "uriFormat": "%s/swagger"
            },
            // autre solution
            "launchBrowser": {
                "enabled": true,
                "args": "${auto-detect-url}",
                "windows": {
                    "command": "cmd.exe",
                    "args": "/C start ${auto-detect-url}/swagger/index.html?url=/swagger/v1/swagger.json#!/Items"
                }
            }

Paramètres optionnels

Swagger ne gère pas les paramètres optionnels s'ils font partie du chemin, il les considère comme des paramètres required.