Published on

Modern API Documentation in .NET with Scalar and OpenAPI

4 min read

Introduction

.NET 9.0 introduces significant improvements to API documentation with first-class support for OpenAPI. This post will guide you through creating beautiful, interactive API documentation that developers will love to use.

Understanding OpenAPI and Scalar

What is Scalar?

Scalar is a modern API documentation tool that replaces Swashbuckle. It provides:

  • 🎨 Modern, responsive UI
  • 🚀 Better performance
  • 📱 Mobile-friendly interface
  • 🔍 Enhanced search capabilities
  • 🎭 Dark/light theme support

you can find more info about Scalar here.

Why Choose OpenAPI with Scalar?

  • Developer Experience: Interactive documentation with try-it-now functionality
  • Code Generation: Automatic client SDK generation
  • Testing: Built-in request builder and mock servers
  • Standards: Industry-standard API specification format

Implementation Guide

1. Project Setup

Create a new .NET 9.0 project:

dotnet new web -n ModernApiDocs
cd ModernApiDocs

Install required packages:

dotnet add package Microsoft.AspNetCore.OpenApi
dotnet add package Scalar.AspNetCore

2. Basic Configuration

Add this to your Program.cs:

using Scalar.AspNetCore;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Configure OpenAPI
builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, _) =>
    {
        document.Info = new()
        {
            Title = "Product Catalog API",
            Version = "v1",
            Description = """
                Modern API for managing product catalogs.
                Supports JSON and XML responses.
                Rate limited to 1000 requests per hour.
                """,
            Contact = new()
            {
                Name = "API Support",
                Email = "api@example.com",
                Url = new Uri("https://api.example.com/support")
            }
        };
        return Task.CompletedTask;
    });
});

var app = builder.Build();

// Enable OpenAPI and Scalar
app.MapOpenApi().CacheOutput();
app.MapScalarApiReference();

// Redirect root to Scalar UI
app.MapGet("/", () => Results.Redirect("/scalar/v1"))
   .ExcludeFromDescription();

app.Run();

3. Sample API Endpoints

Here's a practical example with a product catalog API:

// Product model
public record Product(int Id, string Name, decimal Price);

app.MapGet("/products", (int? pageSize, int? page) =>
{
    var products = GetProducts(pageSize ?? 10, page ?? 1);
    return Results.Ok(products);
})
.Produces<List<Product>>(200)
.Produces(400)
.WithName("GetProducts")
.WithTags("Products")
.WithSummary("Retrieve a list of products")
.WithDescription("""
    Returns a paginated list of products.
    Default page size is 10.
    Use page parameter for pagination.
    """);

app.MapPost("/products", (Product product) =>
{
    // Add product logic here
    return Results.Created($"/products/{product.Id}", product);
})
.Produces<Product>(201)
.Produces(400)
.WithName("CreateProduct")
.WithTags("Products")
.WithSummary("Create a new product")
.WithDescription("Add a new product to the catalog.");

Advanced Features

Custom Response Examples

app.MapGet("/products/{id}", (int id) =>
{
    var product = GetProduct(id);
    return product is null ? Results.NotFound() : Results.Ok(product);
})
.Produces<Product>(200)
.Produces(404)
.WithOpenApi(operation => {
    operation.Responses["200"].Content["application/json"].Example =
        new Product(1, "Sample Product", 29.99m);
    return operation;
});

Authentication Configuration

For JWT auth with OpenAPI, you need the JWT bearer package:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Update your Program.cs:

builder
        .Services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Authority = builder.Configuration["Jwt:Authority"];
            options.Audience = builder.Configuration["Jwt:Audience"];
            options.RequireHttpsMetadata = true;
            options.TokenValidationParameters =
                new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = builder.Configuration["Jwt:Authority"],
                };
        });


builder.Services.AddOpenApi(opt =>
{
    opt.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});


// Placeholder for missing code snippets


// Add authentication middleware
app.UseAuthentication();
app.UseAuthorization();

internal sealed class BearerSecuritySchemeTransformer(
    IAuthenticationSchemeProvider authenticationSchemeProvider
) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(
        OpenApiDocument document,
        OpenApiDocumentTransformerContext context,
        CancellationToken cancellationToken
    )
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
        {
            var requirements = new Dictionary<string, OpenApiSecurityScheme>
            {
                ["Bearer"] = new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer", // "bearer" refers to the header name here
                    In = ParameterLocation.Header,
                    BearerFormat = "Json Web Token",
                },
            };
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes = requirements;
        }
    }
}

Best Practices

  1. Documentation Organization

    • Group related endpoints using tags
    • Provide clear summaries and descriptions
    • Include request/response examples
  2. Security

    • Document authentication requirements
    • Specify rate limits
    • Include error responses
  3. Performance

    • Enable response caching
    • Use appropriate HTTP methods
    • Document pagination

Testing Your API Documentation

  1. Run your application:
dotnet run
  1. Access the documentation:
    • Scalar UI: http://localhost:5175/scalar/v1
    • Raw OpenAPI JSON: http://localhost:5175/openapi/v1.json

Sample GitHub Repository

You can find the complete source code for this guide in the ModernApiDocs repository.

Conclusion

Scalar with OpenAPI provides a modern, developer-friendly way to document your .NET APIs. By following these practices, you'll create documentation that helps developers understand and integrate with your API efficiently.

Resources