Skip to content

Create shared api defaults library #1323

Closed

Description

@StuartFerguson

Summary

Create a shared .NET library named TransactionProcessing.ApiDefaults to provide a consistent configuration for ASP.NET Core Minimal APIs.

The initial version of the library should standardise:

  • API versioning using Asp.Versioning
  • URL-based API versions (/api/v1/..., /api/v2/...)
  • OpenAPI document generation
  • A separate OpenAPI document for each supported API version
  • Scalar as the API documentation UI

The intention is to allow individual APIs to enable the standard configuration with minimal setup while keeping endpoint definitions and application-specific behaviour within the individual API projects.

Target Framework

Target .NET 10.

Required Packages

Use the current compatible versions of:

  • Asp.Versioning.Http
  • Asp.Versioning.Mvc.ApiExplorer
  • Microsoft.AspNetCore.OpenApi
  • Scalar.AspNetCore

Avoid introducing Swashbuckle unless it is required for functionality that cannot be provided by the standard ASP.NET Core OpenAPI implementation.

API Versioning

Configure API versioning centrally using Asp.Versioning.

The standard configuration should:

  • Use URL segment versioning.
  • Support URLs in the form:
/api/v1/transactions
/api/v2/transactions
  • Use 1.0 as the initial/default API version.
  • Require the version to be explicitly specified in the URL.
  • Report supported and deprecated API versions in HTTP responses.
  • Configure the API Explorer for versioned endpoints.
  • Use version group names such as v1, v2, etc.
  • Substitute the API version into generated URLs.

Example endpoint configuration:

var api = app.NewVersionedApi("Transactions");

var transactions = api
    .MapGroup("/api/v{version:apiVersion}/transactions")
    .HasApiVersion(1.0)
    .HasApiVersion(2.0);

Endpoints should be able to support one or multiple versions:

transactions
    .MapGet("/{id:guid}", GetTransaction)
    .MapToApiVersion(1.0)
    .MapToApiVersion(2.0);

Version-specific implementations must also be supported:

transactions
    .MapPost("/", CreateTransactionV1)
    .MapToApiVersion(1.0);

transactions
    .MapPost("/", CreateTransactionV2)
    .MapToApiVersion(2.0);

OpenAPI

Use the standard ASP.NET Core OpenAPI implementation provided by Microsoft.AspNetCore.OpenApi.

Generate a separate OpenAPI document for each discovered API version.

For example:

/openapi/v1.json
/openapi/v2.json

The OpenAPI documents should be generated from the API versions registered through Asp.Versioning.

Adding a new API version to an application's endpoints should not require manually registering another OpenAPI document.

For example, adding V3 should automatically result in:

/openapi/v3.json

Each document should contain only the endpoints applicable to that API version.

An endpoint mapped to both V1 and V2 should therefore appear in both documents, while a V2-only endpoint should only appear in the V2 document.

Scalar

Use Scalar.AspNetCore to provide an interactive API documentation UI.

Expose the documentation at a consistent route, preferably:

/scalar

Scalar should expose all discovered API versions and allow the user to switch between their OpenAPI definitions.

For example:

Transaction API

API Definition:
    V1
    V2

These should resolve to:

/openapi/v1.json
/openapi/v2.json

The Scalar configuration should automatically include newly discovered API versions.

Configuration

Provide an options object allowing individual APIs to supply basic documentation metadata.

For example:

builder.Services.AddApiDefaults(options =>
{
    options.Title = "Transaction API";
    options.Description = "Transaction processing services";
});

Initial options should include:

public sealed class ApiDefaultsOptions
{
    public string Title { get; set; } = "API";

    public string? Description { get; set; }
}

Do not add application-specific configuration to this options object.

Public API

The desired setup within an ASP.NET Core application should be approximately:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddApiDefaults(options =>
{
    options.Title = "Transaction API";
    options.Description = "Transaction processing services";
});

var app = builder.Build();

app.UseApiDefaults();

app.MapTransactionEndpoints();

app.Run();

The consuming application should not need to configure Asp.Versioning, OpenAPI or Scalar directly.

Suggested Project Structure

TransactionProcessing.ApiDefaults
│
├── ApiDefaultsOptions.cs
├── ApiDefaultsExtensions.cs
│
├── Versioning
│   ├── ApiVersions.cs
│   └── ApiVersioningExtensions.cs
│
└── OpenApi
    ├── OpenApiExtensions.cs
    ├── OpenApiDocumentConfiguration.cs
    └── ScalarExtensions.cs

The exact internal structure can change if a cleaner implementation is identified.

API Version Constants

Provide standard version constants to avoid repeatedly constructing ApiVersion instances.

For example:

public static class ApiVersions
{
    public static readonly ApiVersion V1 = new(1, 0);
    public static readonly ApiVersion V2 = new(2, 0);
}

Consumers should be able to use these when defining endpoints:

.HasApiVersion(ApiVersions.V1)

.MapToApiVersion(ApiVersions.V1)

The design should not require this class to be modified merely for OpenAPI to discover a new version.

Separation of Responsibilities

The shared library should contain infrastructure and conventions only.

It should not contain:

  • Application endpoints
  • Request/response DTOs
  • Business logic
  • Application-specific authentication rules
  • Transaction-specific behaviour

Endpoint ownership remains with the consuming application.

Testing

Add automated tests covering the shared configuration.

At minimum verify:

  1. V1 endpoints are accessible through /api/v1/....
  2. V2 endpoints are accessible through /api/v2/....
  3. An unsupported API version is rejected appropriately.
  4. An endpoint mapped to V1 and V2 works through both versions.
  5. A V1-only endpoint is unavailable through V2.
  6. A V2-only endpoint is unavailable through V1.
  7. /openapi/v1.json is generated.
  8. /openapi/v2.json is generated.
  9. The V1 OpenAPI document contains only V1-compatible endpoints.
  10. The V2 OpenAPI document contains only V2-compatible endpoints.
  11. Scalar is available and references the generated versioned OpenAPI documents.

Prefer integration tests using a small test Minimal API rather than testing implementation details of the extension methods.

Acceptance Criteria

The work is complete when:

  • TransactionProcessing.ApiDefaults targets .NET 10.
  • A consuming Minimal API can enable the functionality using AddApiDefaults(...) and UseApiDefaults().
  • URL segment API versioning is configured consistently.
  • API versions are discoverable without duplicate OpenAPI configuration.
  • A separate OpenAPI document is generated for each API version.
  • OpenAPI documents contain only endpoints applicable to their version.
  • Scalar displays the available API definitions.
  • Adding another API version does not require changes to the consuming application's OpenAPI/Scalar configuration.
  • Common API configuration is contained within the shared library.
  • Automated integration tests demonstrate the expected behaviour.
  • No application-specific business logic is introduced into the shared library.

Out of Scope

For the initial implementation, do not include:

  • Authentication/authorisation configuration
  • JWT/OpenAPI security configuration
  • Health checks
  • Problem Details
  • Exception handling
  • Correlation IDs
  • Logging configuration
  • Telemetry/observability
  • Rate limiting

These can be introduced separately once there is a demonstrated need for them across multiple APIs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions