Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .docfx/api/namespaces/Codebelt.Extensions.Asp.Versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
uid: Codebelt.Extensions.Asp.Versioning
summary: *content
---
Use this namespace when building versioned ASP.NET Core RESTful APIs that accept the API version from the HTTP `Accept` header — a common pattern for APIs consumed by real browsers and heterogeneous clients — without writing bespoke middleware to filter browser MIME types or translate Asp.Versioning error codes into structured exception types. It also provides semantic API version metadata for APIs whose public contract needs `major.minor.patch`, pre-release, and build metadata while still participating in the `Asp.Versioning.ApiVersion` model.
Use this namespace when building versioned ASP.NET Core RESTful APIs that accept the API version from the HTTP `Accept` header — a common pattern for APIs consumed by real browsers and heterogeneous clients — without writing bespoke middleware to filter browser MIME types or translate Asp.Versioning error codes into structured exception types. It also provides semantic API version metadata for APIs whose public contract needs `major.minor.patch`, pre-release, and build metadata while still participating in the `Asp.Versioning.ApiVersion` model.

RESTful API versioning built on `Asp.Versioning` normally requires coordinating three separate registration calls — `AddApiVersioning`, `AddMvc`, and `AddApiExplorer` — alongside a version reader that copes with the broad range of `Accept` header values real browsers and HTTP clients send. This namespace provides a single-call registration path and a filtered media-type version reader that handles that coordination automatically.

Start with `AddRestfulApiVersioning` on `IServiceCollection`: it wires all three calls together, installs `RestfulApiVersionReader` to parse the API version from the `Accept` header while ignoring irrelevant browser MIME types, and registers problem-details integration compatible with RFC 7807. To translate Asp.Versioning status-code responses into typed `HttpStatusCodeException` values that the rest of your pipeline can handle, call `UseRestfulApiVersioning` on `IApplicationBuilder` in the middleware pipeline. Configure behaviour — default API version, parameter name, accepted media types, version selector strategy, and problem-details style — through `RestfulApiVersioningOptions`.

Use `SemanticApiVersion`, `SemanticApiVersionParser`, and the semantic version attributes when you need `Asp.Versioning` endpoints to advertise or map versions such as `1.2.3-alpha+build.5`. `SemanticApiVersion.CompareTo` follows Semantic Versioning precedence and ignores build metadata for ordering, while equality keeps build metadata as part of exact version identity. The existing RESTful API Explorer group-name format is unchanged; `SemanticApiVersionFormatter` collapses semantic endpoint versions such as `1.0.1`, `1.2.0`, and `1.2.3-alpha+build.5` to the `v1` group.
Start with `AddRestfulApiVersioning` on `IServiceCollection`: it wires all three calls together, installs `RestfulApiVersionReader` to parse the API version from the `Accept` header while ignoring irrelevant browser MIME types, and registers problem-details integration compatible with RFC 7807. To translate Asp.Versioning status-code responses into typed `HttpStatusCodeException` values that the rest of your pipeline can handle, call `UseRestfulApiVersioning` on `IApplicationBuilder` in the middleware pipeline. Configure behaviour — default API version, parameter name, accepted media types, version selector strategy, and problem-details style — through `RestfulApiVersioningOptions`.
Use `SemanticApiVersion`, `SemanticApiVersionParser`, and the semantic version attributes when you need `Asp.Versioning` endpoints to advertise or map versions such as `1.2.3-alpha+build.5`. `SemanticApiVersion.CompareTo` follows Semantic Versioning precedence and ignores build metadata for ordering, while equality keeps build metadata as part of exact version identity. The existing RESTful API Explorer group-name format is unchanged; `SemanticApiVersionFormatter` collapses semantic endpoint versions such as `1.0.1`, `1.2.0`, and `1.2.3-alpha+build.5` to the `v1` group.

[!INCLUDE [availability-modern](../../includes/availability-modern.md)]

Expand Down
6 changes: 3 additions & 3 deletions .docfx/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
"*.md"
],
"exclude": [
"bin/**",
"obj/**",
"api/namespaces/**",
"api/types/**"
"api/types/**",
"bin/**",
"obj/**"
]
}
],
Expand Down
4 changes: 2 additions & 2 deletions .docfx/toc.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
- name: Asp.Versioning API
href: api/Codebelt.Extensions.Asp.Versioning.html
href: api/Codebelt.Extensions.Asp.Versioning.yml
- name: NuGet
href: packages
href: packages/index.md
1 change: 1 addition & 0 deletions Codebelt.Extensions.Asp.Versioning.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
</Folder>
<Folder Name="/test/">
<Project Path="test/Codebelt.Extensions.Asp.Versioning.Tests/Codebelt.Extensions.Asp.Versioning.Tests.csproj" />
<Project Path="test/Codebelt.Extensions.Asp.Versioning.FunctionalTests/Codebelt.Extensions.Asp.Versioning.FunctionalTests.csproj" />
</Folder>
</Solution>
134 changes: 134 additions & 0 deletions src/Codebelt.Extensions.Asp.Versioning/ApiVersionAliasParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using Asp.Versioning;
using Cuemon;
using System;
using System.Collections.Generic;

namespace Codebelt.Extensions.Asp.Versioning;

/// <summary>
/// Resolves friendly API version aliases before delegating to another <see cref="IApiVersionParser"/>.
/// </summary>
/// <remarks>
/// Use this parser when callers should be able to request a version by a shortened or compatibility-oriented token, while the application still works with the canonical <see cref="ApiVersion"/> instance.
/// Alias matching is performed before the fallback parser is invoked.
/// </remarks>
/// <seealso cref="IApiVersionParser" />
public class ApiVersionAliasParser : IApiVersionParser
{
private readonly IReadOnlyDictionary<string, ApiVersion> _aliases;
private readonly IApiVersionParser _fallback;

/// <summary>
/// Creates an API version parser that recognizes shortened aliases for a single semantic API version.
/// </summary>
/// <param name="version">The semantic API version to expose through major, major-minor, and major-minor-patch aliases.</param>
/// <returns>
/// An <see cref="IApiVersionParser"/> that maps aliases such as <c>1</c>, <c>1.2</c>, and <c>1.2.3</c> to the specified <paramref name="version"/>.
/// </returns>
/// <remarks>
/// The returned parser falls back to <see cref="ApiVersionParser.Default"/> when a requested version is not one of the generated aliases.
/// </remarks>
public static IApiVersionParser CreateSemanticVersionAlias(SemanticApiVersion version)
{
return CreateSemanticVersionAlias([version]);
}

/// <summary>
/// Creates an API version parser that recognizes shortened aliases for the specified semantic API versions.
/// </summary>
/// <param name="versions">The semantic API versions to expose through major, major-minor, and major-minor-patch aliases.</param>
/// <returns>
/// An <see cref="IApiVersionParser"/> that maps each generated alias to its corresponding <see cref="SemanticApiVersion"/>.
/// </returns>
/// <remarks>
/// For each supplied version, aliases are generated from <see cref="ApiVersion.MajorVersion"/>, <see cref="ApiVersion.MinorVersion"/>, and <see cref="SemanticApiVersion.PatchVersion"/>.
/// If duplicate aliases are produced, the first version that contributed the alias is retained. The returned parser falls back to <see cref="ApiVersionParser.Default"/> when a requested version is not one of the generated aliases.
/// </remarks>
public static IApiVersionParser CreateSemanticVersionAlias(IEnumerable<SemanticApiVersion> versions)
{
var aliases = new Dictionary<string, ApiVersion>();
foreach (var version in versions)
{
aliases.TryAdd($"{version.MajorVersion}", version);
aliases.TryAdd($"{version.MajorVersion}.{version.MinorVersion}", version);
aliases.TryAdd($"{version.MajorVersion}.{version.MinorVersion}.{version.PatchVersion}", version);
}
return new ApiVersionAliasParser(aliases);
}

/// <summary>
/// Initializes a new instance of the <see cref="ApiVersionAliasParser"/> class that falls back to <see cref="ApiVersionParser.Default"/>.
/// </summary>
/// <param name="aliases">The alias map to use before invoking the default parser.</param>
/// <remarks>
/// The dictionary key is the external version token accepted from a request, and the dictionary value is the canonical <see cref="ApiVersion"/> returned for that token.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// <paramref name="aliases"/> is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="aliases"/> contains no entries.
/// </exception>
public ApiVersionAliasParser(IReadOnlyDictionary<string, ApiVersion> aliases) : this(aliases, ApiVersionParser.Default)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ApiVersionAliasParser"/> class with the specified alias map and fallback parser.
/// </summary>
/// <param name="aliases">The alias map to use before invoking <paramref name="fallback"/>.</param>
/// <param name="fallback">The parser to use when <paramref name="aliases"/> does not contain the requested version token.</param>
/// <remarks>
/// The dictionary key is the external version token accepted from a request, and the dictionary value is the canonical <see cref="ApiVersion"/> returned for that token.
/// Alias lookup uses the comparer configured by the supplied <paramref name="aliases"/> dictionary.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// <paramref name="aliases"/> or <paramref name="fallback"/> is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="aliases"/> contains no entries.
/// </exception>
public ApiVersionAliasParser(IReadOnlyDictionary<string, ApiVersion> aliases, IApiVersionParser fallback)
{
Validator.ThrowIfSequenceNullOrEmpty(aliases);
Validator.ThrowIfNull(fallback);
_aliases = aliases;
_fallback = fallback;
}

/// <summary>
/// Parses the specified text into an API version by resolving aliases before using the fallback parser.
/// </summary>
/// <param name="text">The API version text to parse.</param>
/// <returns>The API version that matched either an alias or the fallback parser.</returns>
/// <exception cref="FormatException">
/// <paramref name="text"/> is neither a known alias nor a version accepted by the fallback parser.
/// </exception>
public ApiVersion Parse(ReadOnlySpan<char> text)
{
return TryParse(text, out var apiVersion)
? apiVersion
: throw new FormatException("The specified API version is not valid.");
}

/// <summary>
/// Tries to parse the specified text into an API version by resolving aliases before using the fallback parser.
/// </summary>
/// <param name="text">The API version text to parse.</param>
/// <param name="apiVersion">
/// When this method returns, contains the API version that matched either an alias or the fallback parser; otherwise, contains <c>null</c>.
/// </param>
/// <returns><c>true</c> if <paramref name="text"/> matched an alias or was accepted by the fallback parser; otherwise, <c>false</c>.</returns>
/// <remarks>
/// Override this method to customize alias resolution while preserving the same parse contract.
/// </remarks>
public virtual bool TryParse(ReadOnlySpan<char> text, out ApiVersion apiVersion)
{
if (_aliases.TryGetValue(text.ToString(), out apiVersion))
{
return true;
}

return _fallback.TryParse(text, out apiVersion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public RestfulApiVersioningOptions UseApiVersionSelector<T>() where T : class, I
public IApiVersionConventionBuilder Conventions { get; set; }

/// <summary>
/// Gets or sets the default API version applied to services that d o not have explicit versions.
/// Gets or sets the default API version applied to services that do not have explicit versions.
/// </summary>
/// <value>The default API version applied to services that do not have explicit versions.</value>
public ApiVersion DefaultApiVersion { get; set; }
Expand Down
Loading
Loading