Swagger and OpenAPI — Auto-Generated API Documentation

Swagger UI provides a browser-based interface for exploring and testing your Web API — every endpoint is documented with request parameters, request body schema, response types, and status codes. It is generated automatically from your code using the OpenAPI specification. For developers building the Angular frontend in Part 5, Swagger is the living API contract: they see exactly which endpoints exist, what they expect, and what they return without reading source code. In production, Swagger should be disabled or secured — it exposes your full API surface.

Configuring Swagger

// dotnet add package Swashbuckle.AspNetCore

// ── Program.cs — Swagger configuration ───────────────────────────────────
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(opts =>
{
    opts.SwaggerDoc("v1", new OpenApiInfo
    {
        Title       = "BlogApp API",
        Version     = "v1",
        Description = "REST API for BlogApp — posts, comments, users.",
        Contact     = new OpenApiContact { Name = "BlogApp Team", Email = "api@blogapp.com" },
    });

    // Include XML documentation comments in Swagger
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    if (File.Exists(xmlPath))
        opts.IncludeXmlComments(xmlPath);

    // Add JWT Bearer security definition
    opts.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Type        = SecuritySchemeType.Http,
        Scheme      = "bearer",
        BearerFormat = "JWT",
        Description = "Enter JWT token (without 'Bearer' prefix)",
    });

    opts.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id   = "Bearer",
                }
            },
            Array.Empty<string>()
        }
    });
});

// ── Middleware — only in Development ──────────────────────────────────────
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(opts =>
    {
        opts.SwaggerEndpoint("/swagger/v1/swagger.json", "BlogApp API v1");
        opts.RoutePrefix = string.Empty;   // serve at app root /
        opts.PersistAuthorization = true;  // remember JWT token across refreshes
    });
}
Note: Enable XML documentation comments in the .csproj file: <GenerateDocumentationFile>true</GenerateDocumentationFile> and suppress the missing-comment warning with <NoWarn>$(NoWarn);1591</NoWarn> (1591 = “missing XML comment for publicly visible type or member”). XML comments on controller actions appear in Swagger as endpoint descriptions. Use /// <summary>Get a post by ID</summary> on the action and /// <param name="id">The post ID</param> for parameters. This is the most impactful documentation you can write for API consumers.
Tip: For generating TypeScript client code from your OpenAPI spec (used by Angular in Part 6), consider NSwag alongside or instead of Swashbuckle. Run nswag openapi2tsclient /input:swagger.json /output:src/app/api/client.ts to generate a strongly-typed Angular HTTP client. The generated client handles serialisation, error handling, and URL building automatically. This eliminates an entire category of Angular-API integration bugs: mismatched property names, wrong endpoint URLs, and unhandled error shapes.
Warning: Disable Swagger in production or secure it with authentication. Swagger exposes your complete API schema including endpoint names, parameter types, and response structures. This is useful for developers but provides attackers with a map of your API surface. In production, either restrict Swagger to internal network access (behind a VPN), require admin authentication before serving it, or disable it entirely with the environment check (if (app.Environment.IsDevelopment())) already shown above.

XML Documentation Comments

/// <summary>
/// Retrieves a paginated list of published posts.
/// </summary>
/// <param name="page">Page number (1-based). Default: 1.</param>
/// <param name="size">Number of posts per page (1-100). Default: 10.</param>
/// <returns>A paginated list of post summaries.</returns>
/// <response code="200">Returns the paginated post list.</response>
/// <response code="400">Invalid pagination parameters.</response>
[HttpGet]
[ProducesResponseType(typeof(PagedResult<PostSummaryDto>), 200)]
[ProducesResponseType(typeof(ValidationProblemDetails), 400)]
public async Task<ActionResult<PagedResult<PostSummaryDto>>> GetAll(
    [FromQuery] int page = 1,
    [FromQuery][Range(1, 100)] int size = 10,
    CancellationToken ct = default)
{
    var result = await _service.GetPageAsync(page, size, ct);
    return Ok(result);
}

Common Mistakes

Mistake 1 — Enabling Swagger in production (exposes API surface to attackers)

❌ Wrong — UseSwagger() called unconditionally; production API fully documented for attackers.

✅ Correct — wrap in if (app.Environment.IsDevelopment()) or secure with authentication in staging.

Mistake 2 — Not adding [ProducesResponseType] attributes (inaccurate Swagger documentation)

❌ Wrong — Swagger only documents 200 responses; clients unaware of 404, 400, 401 possibilities.

✅ Correct — add [ProducesResponseType] for every possible status code the action can return.

🧠 Test Yourself

Swagger is configured with opts.PersistAuthorization = true. What does this do for API developers testing the API?