Core API Swagger and OpenAPI Documentation
An API without documentation is hard for anyone to use. Swagger (based on the OpenAPI specification) generates interactive documentation for the BookStore API automatically from the code. Developers can browse all endpoints, see required parameters, and test requests — all from a browser, without Postman.
What Is OpenAPI and Swagger?
| Term | What It Is |
|---|---|
| OpenAPI | A standard specification format (JSON/YAML) that describes an API's endpoints, parameters, and responses |
| Swagger | Tools that implement OpenAPI — including Swagger UI (browser interface) and Swagger Editor |
| Swashbuckle | The .NET library that generates OpenAPI specs from ASP.NET Core code |
Swagger Is Already Installed
The Web API project template adds Swagger automatically. These packages are already in the project:
Swashbuckle.AspNetCore ← already installed by the template
And in Program.cs:
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
Run the project and navigate to https://localhost:7001/swagger to see the Swagger UI.
Customizing the Swagger Info Section
// Program.cs
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "BookStore API",
Version = "v1",
Description = "A complete RESTful API for managing books in an online bookstore.",
Contact = new OpenApiContact
{
Name = "eStudy247 Support",
Email = "support@estudy247.com",
Url = new Uri("https://estudy247.com")
},
License = new OpenApiLicense
{
Name = "MIT License",
Url = new Uri("https://opensource.org/licenses/MIT")
}
});
});
Adding JWT Authorization to Swagger
By default, the Swagger UI does not know about JWT tokens. Adding this configuration adds an "Authorize" button so that protected endpoints can be tested:
// Program.cs
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" });
// Define the JWT security scheme
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "Enter your JWT token. Example: eyJhbGci..."
});
// Apply the security requirement globally
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
Now the Swagger UI has an "Authorize" button at the top. Enter the JWT token there and all subsequent requests from Swagger include it automatically.
Documenting Endpoints with XML Comments
Enable XML documentation in the project file so that C# XML comments appear in Swagger:
<!-- BookStoreAPI.csproj -->
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
// Program.cs – include the XML file in Swagger
builder.Services.AddSwaggerGen(options =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});
Add XML comments to controller actions:
/// <summary>
/// Retrieves all books. Supports optional filtering by category.
/// </summary>
/// <param name="category">Filter books by category (e.g., Technology, Fiction)</param>
/// <returns>A list of books matching the filter criteria.</returns>
/// <response code="200">Books retrieved successfully</response>
[HttpGet]
[AllowAnonymous]
[ProducesResponseType(typeof(List<BookResponseDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAll([FromQuery] string? category)
{
var books = await _bookService.GetAllAsync();
if (!string.IsNullOrEmpty(category))
books = books.Where(b => b.Category == category).ToList();
return Ok(_mapper.Map<List<BookResponseDto>>(books));
}
/// <summary>
/// Retrieves a single book by its ID.
/// </summary>
/// <param name="id">The unique identifier of the book.</param>
/// <returns>The book with the specified ID.</returns>
/// <response code="200">Book found and returned</response>
/// <response code="404">Book not found</response>
[HttpGet("{id:int}")]
[AllowAnonymous]
[ProducesResponseType(typeof(BookResponseDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
var book = await _bookService.GetByIdAsync(id);
if (book == null) return NotFound();
return Ok(_mapper.Map<BookResponseDto>(book));
}
/// <summary>
/// Creates a new book. Requires Admin role.
/// </summary>
/// <param name="dto">Book details to create.</param>
/// <returns>The newly created book.</returns>
/// <response code="201">Book created successfully</response>
/// <response code="400">Invalid request data</response>
/// <response code="401">Not authenticated</response>
/// <response code="403">Not authorized (Admin role required)</response>
[HttpPost]
[Authorize(Roles = "Admin")]
[ProducesResponseType(typeof(BookResponseDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Create([FromBody] BookCreateDto dto)
{
var book = _mapper.Map<Book>(dto);
var created = await _bookService.CreateAsync(book);
var result = _mapper.Map<BookResponseDto>(created);
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
}
Grouping Endpoints with Tags
[ApiController]
[Route("api/v2/[controller]")]
[Tags("Books V2")] // ← Groups endpoints under "Books V2" in Swagger
public class BooksController : ControllerBase { ... }
Versioned Swagger Documents
When API versioning is in use, each version gets its own Swagger document:
// Program.cs
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" });
options.SwaggerDoc("v2", new OpenApiInfo { Title = "BookStore API", Version = "v2" });
});
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "BookStore API v1");
options.SwaggerEndpoint("/swagger/v2/swagger.json", "BookStore API v2");
});
What the Swagger UI Shows
BookStore API v2
├── Books V2
│ ├── GET /api/v2/books → GetAll (with category filter, paging)
│ ├── POST /api/v2/books → Create (🔒 Admin)
│ ├── GET /api/v2/books/{id} → GetById
│ ├── PUT /api/v2/books/{id} → Update (🔒 Admin)
│ └── DELETE /api/v2/books/{id} → Delete (🔒 Admin)
├── Auth
│ └── POST /api/auth/login → Login
Key Points
- Swagger generates interactive API documentation automatically from the code — no manual maintenance needed.
- Adding JWT security definition to Swagger lets the browser-based UI test protected endpoints.
- XML comments on action methods appear as descriptions in the Swagger UI.
[ProducesResponseType]documents the possible HTTP responses for each action.- For versioned APIs, each version gets its own Swagger document and its own dropdown in the UI.
