Documenting C# Web APIs with OpenAPI and Swagger
This article provides a comprehensive guide to understanding OpenAPI and Swagger within the context of ASP.NET Core. It explains the distinction between the OpenAPI Specification and Swagger tooling, outlines how to integrate these tools into a C# Web API project, and demonstrates how to effectively document endpoints using attributes, XML comments, and built-in framework features.
Understanding OpenAPI vs. Swagger
OpenAPI is an industry-standard, language-agnostic specification for describing RESTful APIs. It defines the structure, endpoints, request parameters, response bodies, status codes, and authentication mechanisms of an API in either JSON or YAML format.
Swagger refers to the open-source tooling created by SmartBear that implements the OpenAPI Specification. In the .NET ecosystem, tools like Swashbuckle or NSwag read application code, automatically generate the OpenAPI contract, and serve Swagger UI—an interactive web interface that allows developers to view and test API endpoints directly within a browser.
Integrating Swagger into an ASP.NET Core Web API
To enable Swagger documentation and the interactive UI in a C# Web
API project, install the Swashbuckle.AspNetCore package via
NuGet:
dotnet add package Swashbuckle.AspNetCore
Next, configure the service container and HTTP request pipeline
inside Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add API controllers
builder.Services.AddControllers();
// Register the Swagger generator
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Enable middleware for generating the OpenAPI JSON schema and Swagger UI
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();Documenting Endpoints
Detailed endpoint documentation helps consumers understand request formats, authentication requirements, and potential HTTP responses.
1. Enabling XML Comments
XML comments provide natural inline documentation for your endpoints
and data models. First, enable XML file generation in your project’s
.csproj file:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>Next, configure Swagger to include these generated XML files in
Program.cs:
using System.Reflection;
builder.Services.AddSwaggerGen(options =>
{
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});2. Using XML Tags and Attributes in Controllers
You can combine standard XML tags (<summary>,
<param>, <response>) with ASP.NET
Core attributes like [ProducesResponseType] and
[Consumes] to supply clear documentation and response
definitions.
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
/// <summary>
/// Retrieves a specific product by its unique identifier.
/// </summary>
/// <param name="id">The unique integer identifier of the product.</param>
/// <returns>The product details if found.</returns>
/// <response code="200">Returns the requested product item.</response>
/// <response code="404">If the product does not exist.</response>
[HttpGet("{id}")]
[ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
var product = FindProduct(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}3. Documenting Data Transfer Objects (DTOs)
Adding XML tags and validation attributes to DTO models enriches the generated Swagger schemas with descriptions, requirements, and default values:
using System.ComponentModel.DataAnnotations;
public class ProductDto
{
/// <summary>
/// The unique identifier of the product.
/// </summary>
/// <example>101</example>
public int Id { get; set; }
/// <summary>
/// The name of the product.
/// </summary>
/// <example>Wireless Keyboard</example>
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
/// <summary>
/// The price in USD.
/// </summary>
/// <example>49.99</example>
[Range(0.01, 10000.00)]
public decimal Price { get; set; }
}4. Documenting Minimal APIs
If using ASP.NET Core Minimal APIs instead of controllers, use endpoint extension methods to achieve the same level of detail:
app.MapGet("/api/products/{id}", (int id) =>
{
var product = FindProduct(id);
return product is not null ? Results.Ok(product) : Results.NotFound();
})
.WithName("GetProductById")
.WithSummary("Retrieves a product by ID")
.WithDescription("Provides the complete product metadata based on the specified ID.")
.Produces<ProductDto>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);