Aller au contenu

« Swashbuckle » : différence entre les versions

De Banane Atomic
 
(56 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]


= Ajout de NSwag à un projet .Net Core =
= Installation =
<kode lang='powershell'>
{{warn | It is no longer actively maintained and has been remove from .NET 9 templates. [https://github.com/dotnet/aspnetcore/issues/54599 ]}}
# pour vscode
<kode lang='ps'>
dotnet add MyProject.csproj package NSwag.AspNetCore
dotnet add package Swashbuckle.AspNetCore
# Ajoute au fichier MyProject.csproj:
</kode>
<ItemGroup>  
 
#    <PackageReference Include="NSwag.AspNetCore" Version="11.17.15" />  
<filebox fn='Program.cs'>
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
 
if (app.Environment.IsDevelopment())
{
    app.UseSwagger()
      .UseSwaggerUI();
}
</filebox>
 
= Installation Old =
<kode lang='bash'>
dotnet add package Swashbuckle.AspNetCore
</kode>
</kode>


{{info | Url : {{boxx|<nowiki>http://localhost:<port>/swagger</nowiki>}}}}
== [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='Startup.cs'>
<filebox fn='Startup.cs'>
public void ConfigureServices(IServiceCollection services)
public void ConfigureServices(IServiceCollection services)
{
{
     services.AddSwaggerDocument();
     services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "EFWebApi", Version = "v1" });
    });
}


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
{
     app.UseOpenApi();
     if (env.IsDevelopment())
    app.UseSwaggerUi3();
    {
        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>/)
        });
    }
</filebox>
</filebox>


=== Configuration MVC ===
= Usage =
<filebox fn='Startup.cs'>
<filebox fn='Controllers/ItemController.cs'>
using NJsonSchema;
[ApiController]
using NSwag.AspNetCore;
[Route("[controller]")]
using System.Reflection;
[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() { /* ... */ }
}
</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] =
{{warn | {{boxx|&}} has to be escaped as {{boxx|&amp;amp;}}}}
<filebox fn='MyProject.csproj' lang='xml'>
<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</filebox>
 
<filebox fn='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));
    });
}
</filebox>


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
<filebox fn='Controllers/ItemController.cs'>
[Produces("application/json")]
public class MyController() : ControllerBase
{
{
     app.UseMvc();
     /// <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) { /* ... */ }
</filebox>
 
= [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]]
<filebox fn='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;
        });


    // à ajouter avant app.UseSpa
app.UseSwagger();
    app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, settings =>
app.UseSwaggerUI(
    options =>
     {
     {
         settings.GeneratorSettings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase;
         var descriptions = app.DescribeApiVersions();
         settings.PostProcess = document =>
         foreach (var description in descriptions)
            options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
    });
</filebox>
 
<filebox fn='ConfigureSwaggerOptions.cs'>
public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions<SwaggerGenOptions>
{
    public void Configure(SwaggerGenOptions options)
    {
        foreach (var description in provider.ApiVersionDescriptions)
         {
         {
             //document.Info.Version = "v1";
             options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
            document.Info.Title = "Test API";
        }
            document.Info.Description = "A simple ASP.NET Core web API";
 
            //document.Info.TermsOfService = "None";
        static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
             document.Info.Contact = new NSwag.SwaggerContact
             => new()
             {
             {
                 Name = "Nicolas",
                 Title = "Power Analytics Model Management API",
                 //Email = string.Empty,
                 Version = description.ApiVersion.ToString(),
                 //Url = "https://twitter.com/spboyer"
                 Description = "API Description."
             };
             };
            /*document.Info.License = new NSwag.SwaggerLicense
    }
}
</filebox>
 
= Document Filter =
<filebox fn='Program.cs'>
builder.Services.AddSwaggerGen(c =>
{
    c.DocumentFilter<MyDocumentFilter>();
});
</filebox>
 
<filebox fn='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
             {
             {
                 Name = "Use under LICX",
                 { new OpenApiString("value") }
                Url = "https://example.com/license"
            }
             };*/
        }
    }
}
</filebox>
 
== [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>
builder.Services.AddHealthChecks();
 
builder.Services.AddSwaggerGen(c =>
{
    c.DocumentFilter<HealthChecksDocumentFilter>();
});
 
var app = builder.Build();
app.MapHealthChecks("/health");
</filebox>
 
<filebox fn='HealthChecksDocumentFilter.cs' collapsed>
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);
    }
}
</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] =
<filebox fn='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");
        });
    }
</filebox>
 
<filebox fn='wwwroot/swagger-ui/dark-theme.css' collapsed>
.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;
}
</filebox>
</filebox>


= Problème avec IActionResult =
= [https://stackoverflow.com/questions/61881770/invalidoperationexception-cant-use-schemaid-the-same-schemaid-is-already-us The same schemaId is already used] =
{{boxx|NSwag}} utilise la réflexion pour obtenir le type de retour. Avec {{boxx|IActionResult}} il ne peut pas.
Occurs when you have 2 classes with the same name in different namespaces.<br>
<kode lang='csharp'>
Fix it by using the full class name (with namspace) for the schema id.
[HttpGet]
<filebox fn='Program.cs'>
// utiliser SwaggerResponse
builder.Services.AddSwaggerGen(options =>
[SwaggerResponse(HttpStatusCode.OK, typeof(IEnumerable<Item>))]
{
[SwaggerResponse(HttpStatusCode.BadRequest, typeof(void))]
    options.CustomSchemaIds(type => type.ToString());
// ou ProducesResponseType
});
[ProducesResponseType(200, Type = typeof(IEnumerable<Item>))]
</filebox>
public IActionResult Get()
</kode>


= Paramètres optionnels =
= Url =
Swagger ne gère pas les paramètres optionnels s'ils font partie du chemin, il les considère comme des paramètres required.
{| class="wikitable wtp wtmono1"
! Url
! Resource
|-
| <nowiki>http://localhost:<port>/swagger</nowiki> || swagger UI
|-
| <nowiki>http://localhost:<port>/swagger/v1/swagger.json</nowiki> || swagger json
|}


= Ouvrir le navigateur sur swagger =
= [https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#starting-a-web-browser Ouvrir le navigateur sur swagger] =
Dans un projet Web API avec Visual Studio Code, ouvrir le navigateur sur la page swagger.
Dans un projet Web API avec Visual Studio Code, ouvrir le navigateur sur la page swagger.
<filebox fn='.vscode\launch.json'>
<filebox fn='.vscode\launch.json'>
"configurations": [
{
{
     "launchBrowser": {
     "configurations": [
        "enabled": true,
        {
        "args": "${auto-detect-url}",
            "serverReadyAction": {
        "windows": {
                "action": "openExternally",
            "command": "cmd.exe",
                "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)",
            "args": "/C start ${auto-detect-url}/swagger/index.html?url=/swagger/v1/swagger.json#!/Items"
                "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"
                }
            }
</filebox>
</filebox>
= 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.

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.