« Swagger » : différence entre les versions

De Banane Atomic
Aller à la navigationAller à la recherche
 
 
(42 versions intermédiaires par le même utilisateur non affichées)
Ligne 4 : Ligne 4 :
* [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]


= Ajout de NSwag à un projet .Net Core =
= [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag?tabs=netcore-cli NSwag] =
== Installation ==
<kode lang='powershell'>
<kode lang='powershell'>
# pour vscode
# pour vscode
dotnet add MyProject.csproj package NSwag.AspNetCore
dotnet add package NSwag.AspNetCore
# Ajoute au fichier MyProject.csproj:
# Add to the project file *.csproj:
#  <ItemGroup>  
#  <ItemGroup>  
#    <PackageReference Include="NSwag.AspNetCore" Version="11.17.15" />  
#    <PackageReference Include="NSwag.AspNetCore" Version="11.17.15" />  
</kode>
</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();
});


{{info | Url : {{boxx|<nowiki>http://localhost:<port>/swagger</nowiki>}}}}
if (app.Environment.IsDevelopment())
{
== Configuration ==
    app.UseOpenApi();
<filebox fn='Startup.cs'>
    app.UseSwaggerUi3();
}
</filebox>
 
=== Configuration MVC ===
<filebox fn='Startup.cs' collapsed>
using NJsonSchema;
using NJsonSchema;
using NSwag.AspNetCore;
using NSwag.AspNetCore;
Ligne 50 : Ligne 65 :
</filebox>
</filebox>


= Problème avec IActionResult =
== Problème avec IActionResult ==
{{boxx|NSwag}} utilise la réflexion pour obtenir le type de retour. Avec {{boxx|IActionResult}} il ne peut pas.
{{boxx|NSwag}} utilise la réflexion pour obtenir le type de retour. Avec {{boxx|IActionResult}} il ne peut pas.
<kode lang='csharp'>
<kode lang='csharp'>
[HttpGet]
[HttpGet]
// utiliser SwaggerResponse
// utiliser SwaggerResponse
[SwaggerResponse(HttpStatusCode.OK, typeof(IEnumerable<Item>))]
[SwaggerResponse(HttpStatusCode.OK, typeof(IReadOnlyList<ItemDto>))]
[SwaggerResponse(HttpStatusCode.BadRequest, typeof(void))]
[SwaggerResponse(HttpStatusCode.BadRequest, typeof(void))]
// ou ProducesResponseType
// ou ProducesResponseType
[ProducesResponseType(200, Type = typeof(IEnumerable<Item>))]
[ProducesResponseType(typeof(IReadOnlyList<ItemDto>), StatusCodes.Status200OK)]
public IActionResult Get()
public IActionResult Get()
</kode>
</kode>


= Paramètres optionnels =
= [https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-5.0&tabs=visual-studio-code Swashbuckle] =
Swagger ne gère pas les paramètres optionnels s'ils font partie du chemin, il les considère comme des paramètres required.
== 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'>
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
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>
 
<filebox fn='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>/)
        });
    }
</filebox>
 
== Usage ==
<filebox fn='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() { /* ... */ }
}
</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] ==
<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>
 
<filebox fn='Controllers/ItemController.cs'>
/// <summary>Fetch all the items.</summary>
/// <param name="cancellationToken"></param>
/// <returns>All the items</returns>
/// <remarks>
/// Sample request:
///
///    GET /item
/// </remarks>
/// <response code="200">Returns all the items.</response>
[HttpGet]
[Produces("text/json")]
[ProducesResponseType(typeof(IEnumerable<Item>), StatusCodes.Status200OK)]
public IActionResult GetAll() { /* ... */ }
</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;
        });
 
app.UseSwagger();
app.UseSwaggerUI(
    options =>
    {
        var descriptions = app.DescribeApiVersions();
        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)
        {
            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."
            };
    }
}
</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
            {
                { new OpenApiString("value") }
            }
        }
    }
}
</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>
 
= Url =
{| 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 septembre 2024 à 14:49

Liens

NSwag

Installation

Powershell.svg
# pour vscode
dotnet add package NSwag.AspNetCore
# Add to the project file *.csproj:
#  <ItemGroup> 
#    <PackageReference Include="NSwag.AspNetCore" Version="11.17.15" />

Configuration

Configuration Web API

Program.cs
builder.Services.AddSwaggerDocument(configuration =>
{
    configuration.Title = "MyApp";
    configuration.Version = typeof(Program).Assembly.GetSimplifiedVersion();
});

if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi3();
}

Configuration MVC

Startup.cs
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"
            };*/
        };
    });

Problème avec IActionResult

NSwag utilise la réflexion pour obtenir le type de retour. Avec IActionResult il ne peut pas.

Csharp.svg
[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()

Swashbuckle

Swashbuckle installation

Already installed in .NET 7+

MyWebApi.csproj
<ItemGroup>
  <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
Program.cs
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

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

Installation Old

Bash.svg
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

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
/// <summary>Fetch all the items.</summary>
/// <param name="cancellationToken"></param>
/// <returns>All the items</returns>
/// <remarks>
/// Sample request:
///
///     GET /item
/// </remarks>
/// <response code="200">Returns all the items.</response>
[HttpGet]
[Produces("text/json")]
[ProducesResponseType(typeof(IEnumerable<Item>), StatusCodes.Status200OK)]
public IActionResult GetAll() { /* ... */ }

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;
}

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.