From e3d75a5237f7e9a6fbf22929a3837ddd7682b54b Mon Sep 17 00:00:00 2001 From: "codebelt-aicia[bot]" Date: Sat, 23 May 2026 14:30:52 +0000 Subject: [PATCH 1/5] V10.0.7/service update --- .../PackageReleaseNotes.txt | 6 +++ CHANGELOG.md | 4 ++ Directory.Packages.props | 48 +++++++++---------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/.nuget/Codebelt.Extensions.Asp.Versioning/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Asp.Versioning/PackageReleaseNotes.txt index 9352e9a..6165b8c 100644 --- a/.nuget/Codebelt.Extensions.Asp.Versioning/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Asp.Versioning/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 10.0.7 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 10.0.6 Availability: .NET 10 and .NET 9 diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c351c..e52d990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ For more details, please refer to `PackageReleaseNotes.txt` on a per assembly ba > [!NOTE] > Changelog entries prior to version 8.4.0 was migrated from previous versions of Cuemon.Extensions.Asp.Versioning. +## [10.0.7] - 2026-05-23 + +This is a service update that focuses on package dependencies. + ## [10.0.6] - 2026-04-18 This is a service update that focuses on package dependencies. diff --git a/Directory.Packages.props b/Directory.Packages.props index 52de1f8..9e519c9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,25 +1,25 @@ - - - true - - - - - - - - - - - - - - - - - - - - - + + + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f20a5c1e8fb716e318d6e5c49ca60cdd16f5f148 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Sat, 23 May 2026 21:06:27 +0200 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=92=AC=20finalize=2010.0.7=20release?= =?UTF-8?q?=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comprehensive changelog entry for version 10.0.7 with dual-framework support details. Emphasizes explicit Asp.Versioning package versions for .NET 9 (8.1.1) and .NET 10 (10.0.0), enhanced test coverage, and dependency updates. Updated comparison links. --- CHANGELOG.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e52d990..229ae8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,12 @@ For more details, please refer to `PackageReleaseNotes.txt` on a per assembly ba ## [10.0.7] - 2026-05-23 -This is a service update that focuses on package dependencies. +This is a service update that focuses on package dependencies and explicit dual-framework support through conditionally-targeted Asp.Versioning package versions, ensuring compatibility with .NET 9 (Asp.Versioning 8.1.1) and .NET 10 (Asp.Versioning 10.0.0). + +### Changed + +- `Directory.Packages.props` to conditionally target Asp.Versioning package versions: version 8.1.1 for .NET 9 target framework and version 10.0.0 for .NET 10 target framework, providing version-appropriate behavior for each framework, +- Dependencies upgraded to the latest compatible versions for all supported target frameworks (.NET 10 and .NET 9). ## [10.0.6] - 2026-04-18 @@ -124,7 +129,9 @@ This major release is first and foremost focused on ironing out any wrinkles tha - RestfulApiVersionReader class in the Codebelt.Extensions.Asp.Versioning namespace that represents a RESTful API version reader that reads the value from a filtered list of HTTP Accept headers in the request - RestfulProblemDetailsFactory class in the Codebelt.Extensions.Asp.Versioning namespace that represents a RESTful implementation of the IProblemDetailsFactory which throws variants of HttpStatusCodeException that needs to be translated accordingly -[Unreleased]: https://github.com/codebeltnet/asp-versioning/compare/v10.0.5...HEAD +[Unreleased]: https://github.com/codebeltnet/asp-versioning/compare/v10.0.7...HEAD +[10.0.7]: https://github.com/codebeltnet/asp-versioning/compare/v10.0.6...v10.0.7 +[10.0.6]: https://github.com/codebeltnet/asp-versioning/compare/v10.0.5...v10.0.6 [10.0.5]: https://github.com/codebeltnet/asp-versioning/compare/v10.0.4...v10.0.5 [10.0.4]: https://github.com/codebeltnet/asp-versioning/compare/v10.0.3...v10.0.4 [10.0.3]: https://github.com/codebeltnet/asp-versioning/compare/v10.0.2...v10.0.3 From 35d6ebb419274a6dadeff134063d9c1980f97ae4 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Sat, 23 May 2026 21:06:33 +0200 Subject: [PATCH 3/5] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependencies?= =?UTF-8?q?=20and=20tooling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor Directory.Packages.props to use framework-conditional targeting for Asp.Versioning: 8.1.1 for .NET 9 and 10.0.0 for .NET 10. Upgrade test SDK and coverage tools to latest compatible versions. Bump nginx base image from 1.30.0 to 1.31.0 in Dockerfile.docfx. --- .docfx/Dockerfile.docfx | 2 +- Directory.Packages.props | 56 +++++++++++++++++++++++----------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/.docfx/Dockerfile.docfx b/.docfx/Dockerfile.docfx index 04c49b3..9d42262 100644 --- a/.docfx/Dockerfile.docfx +++ b/.docfx/Dockerfile.docfx @@ -1,4 +1,4 @@ -ARG NGINX_VERSION=1.30.0-alpine +ARG NGINX_VERSION=1.31.0-alpine FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base RUN rm -rf /usr/share/nginx/html/* diff --git a/Directory.Packages.props b/Directory.Packages.props index 9e519c9..4e49a37 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,25 +1,33 @@ - - - true - - - - - - - - - - - - - - - - - - - - - + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f7edd84ea905afb06ef87476b85e422f9960b935 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Sat, 23 May 2026 21:06:40 +0200 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=A7=AA=20enhance=20unit=20tests=20and?= =?UTF-8?q?=20test=20fixtures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand FakeController with new endpoints (problem418, teapot) for testing unmapped HTTP status codes and IProblemDetailsService integration. Add UseApiVersionSelector{T} test case to RestfulApiVersioningOptionsTest validating fluent builder pattern for API version selector configuration. --- .../Assets/FakeController.cs | 35 ++++++++++++++++++- .../RestfulApiVersioningOptionsTest.cs | 10 ++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/test/Codebelt.Extensions.Asp.Versioning.Tests/Assets/FakeController.cs b/test/Codebelt.Extensions.Asp.Versioning.Tests/Assets/FakeController.cs index 5cf8010..766113c 100644 --- a/test/Codebelt.Extensions.Asp.Versioning.Tests/Assets/FakeController.cs +++ b/test/Codebelt.Extensions.Asp.Versioning.Tests/Assets/FakeController.cs @@ -1,6 +1,9 @@ -using Cuemon.AspNetCore.Http; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Http; using Cuemon.AspNetCore.Mvc.Filters.Diagnostics; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Options; namespace Codebelt.Extensions.Asp.Versioning.Assets @@ -34,5 +37,35 @@ public IActionResult GetException() { throw new GoneException(); } + + [HttpGet] + [Route("not-acceptable")] + public IActionResult GetNotAcceptable() + { + return StatusCode(StatusCodes.Status406NotAcceptable); + } + + [HttpGet] + [Route("teapot")] + public IActionResult GetTeapot() + { + return StatusCode(StatusCodes.Status418ImATeapot); + } + + [HttpGet] + [Route("problem418")] + public async Task GetProblem418([FromServices] IProblemDetailsService problemDetailsService) + { + await problemDetailsService.WriteAsync(new ProblemDetailsContext + { + HttpContext = HttpContext, + ProblemDetails = new Microsoft.AspNetCore.Mvc.ProblemDetails + { + Status = StatusCodes.Status418ImATeapot, + Detail = "I'm a teapot - an unmapped status code" + } + }); + return new EmptyResult(); + } } } diff --git a/test/Codebelt.Extensions.Asp.Versioning.Tests/RestfulApiVersioningOptionsTest.cs b/test/Codebelt.Extensions.Asp.Versioning.Tests/RestfulApiVersioningOptionsTest.cs index 4c2a284..83b1be0 100644 --- a/test/Codebelt.Extensions.Asp.Versioning.Tests/RestfulApiVersioningOptionsTest.cs +++ b/test/Codebelt.Extensions.Asp.Versioning.Tests/RestfulApiVersioningOptionsTest.cs @@ -72,6 +72,16 @@ public void RestfulApiVersioningOptions_ValidAcceptHeadersIsNull_ShouldThrowInva Assert.IsType(sut3.InnerException); } + [Fact] + public void RestfulApiVersioningOptions_UseApiVersionSelector_ShouldUpdateApiVersionSelectorType() + { + var sut = new RestfulApiVersioningOptions(); + var result = sut.UseApiVersionSelector(); + + Assert.Same(sut, result); + Assert.Equal(typeof(LowestImplementedApiVersionSelector), sut.ApiVersionSelectorType); + } + [Fact] public void RestfulApiVersioningOptions_ShouldHaveDefaultValues() { From 9f71b652f43bcc6aacdb449ae1bef2cbf0dccd27 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Sat, 23 May 2026 21:06:47 +0200 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=8E=AF=20add=20integration=20tests=20?= =?UTF-8?q?for=20middleware=20and=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create ApplicationBuilderExtensionsTest for comprehensive UseRestfulApiVersioning middleware testing with custom factory and default status code mapping scenarios. Create ServiceCollectionExtensionsTest for AddRestfulApiVersioning configuration including API explorer options and IProblemDetailsService integration validation. --- .../ApplicationBuilderExtensionsTest.cs | 113 ++++++++++++++++++ .../ServiceCollectionExtensionsTest.cs | 75 ++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 test/Codebelt.Extensions.Asp.Versioning.Tests/ApplicationBuilderExtensionsTest.cs create mode 100644 test/Codebelt.Extensions.Asp.Versioning.Tests/ServiceCollectionExtensionsTest.cs diff --git a/test/Codebelt.Extensions.Asp.Versioning.Tests/ApplicationBuilderExtensionsTest.cs b/test/Codebelt.Extensions.Asp.Versioning.Tests/ApplicationBuilderExtensionsTest.cs new file mode 100644 index 0000000..c8572ec --- /dev/null +++ b/test/Codebelt.Extensions.Asp.Versioning.Tests/ApplicationBuilderExtensionsTest.cs @@ -0,0 +1,113 @@ +using System; +using System.Threading.Tasks; +using Codebelt.Extensions.Asp.Versioning.Assets; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Cuemon.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Codebelt.Extensions.Asp.Versioning +{ + public class ApplicationBuilderExtensionsTest : Test + { + public ApplicationBuilderExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + private static void ConfigureServices(IServiceCollection services) + { + // Use minimal controller registration. + // [ApiController]'s ClientErrorResultFilter automatically converts StatusCode(4xx) results + // to problem details (sets application/problem+json content-type), which prevents + // UseStatusCodePages from firing (it requires content-type to be null). + // The minimal-API lambda endpoints added in ConfigureApp bypass [ApiController] filters. + services.AddControllers().AddApplicationPart(typeof(FakeController).Assembly); + } + + private static void ConfigureApp(IApplicationBuilder app, Func factory = null) + { + if (factory != null) + { + app.UseRestfulApiVersioning(factory); + } + else + { + app.UseRestfulApiVersioning(); + } + + app.UseRouting(); + app.UseEndpoints(endpoints => + { + // Minimal-API lambda endpoints: just set the status code with no content-type. + // These bypass [ApiController]'s ClientErrorResultFilter so UseStatusCodePages can fire. + endpoints.MapGet("/test/not-acceptable", ctx => + { + ctx.Response.StatusCode = StatusCodes.Status406NotAcceptable; + return Task.CompletedTask; + }); + endpoints.MapGet("/test/teapot", ctx => + { + ctx.Response.StatusCode = StatusCodes.Status418ImATeapot; + return Task.CompletedTask; + }); + }); + } + + [Fact] + public async Task UseRestfulApiVersioning_WithCustomFactory_ShouldInvokeProvidedFactory() + { + var customFactoryInvoked = false; + + using (var app = WebHostTestFactory.Create(ConfigureServices, appBuilder => + { + ConfigureApp(appBuilder, context => + { + customFactoryInvoked = true; + return new NotAcceptableException("Custom factory: version not acceptable"); + }); + })) + { + var client = app.Host.GetTestClient(); + + // /test/not-acceptable is a minimal-API endpoint that sets 406 with no content-type. + // UseStatusCodePages fires; the custom factory (non-null ??= branch) is invoked. + var ex = await Assert.ThrowsAsync(() => client.GetAsync("/test/not-acceptable")); + + Assert.True(customFactoryInvoked); + Assert.StartsWith("Custom factory:", ex.Message); + } + } + + [Fact] + public async Task UseRestfulApiVersioning_DefaultFactory_WithMappedStatusCode_ShouldThrowCorrespondingException() + { + using (var app = WebHostTestFactory.Create(ConfigureServices, app => ConfigureApp(app))) + { + var client = app.Host.GetTestClient(); + + // /test/not-acceptable returns 406 with no content-type. + // UseStatusCodePages fires; default factory invoked with Response.StatusCode=406. + // TryParse(406) succeeds -> NotAcceptableException is returned and thrown. + await Assert.ThrowsAsync(() => client.GetAsync("/test/not-acceptable")); + } + } + + [Fact] + public async Task UseRestfulApiVersioning_DefaultFactory_WithUnmappedStatusCode_ShouldThrowInternalServerErrorException() + { + using (var app = WebHostTestFactory.Create(ConfigureServices, app => ConfigureApp(app))) + { + var client = app.Host.GetTestClient(); + + // /test/teapot returns 418 with no content-type. + // UseStatusCodePages fires; default factory invoked with Response.StatusCode=418. + // TryParse(418) fails (418 is not in Cuemon's mapped codes) -> InternalServerErrorException. + await Assert.ThrowsAsync(() => client.GetAsync("/test/teapot")); + } + } + } +} diff --git a/test/Codebelt.Extensions.Asp.Versioning.Tests/ServiceCollectionExtensionsTest.cs b/test/Codebelt.Extensions.Asp.Versioning.Tests/ServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000..2f223df --- /dev/null +++ b/test/Codebelt.Extensions.Asp.Versioning.Tests/ServiceCollectionExtensionsTest.cs @@ -0,0 +1,75 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Asp.Versioning; +using Asp.Versioning.ApiExplorer; +using Codebelt.Extensions.Asp.Versioning.Assets; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Cuemon.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Codebelt.Extensions.Asp.Versioning +{ + public class ServiceCollectionExtensionsTest : Test + { + public ServiceCollectionExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void AddRestfulApiVersioning_ShouldConfigureApiExplorerOptions_WithExpectedGroupNameFormat() + { + using (var app = WebHostTestFactory.Create(services => + { + services.AddControllers().AddApplicationPart(typeof(FakeController).Assembly); + services.AddRestfulApiVersioning(o => + { + o.ParameterName = "version"; + o.DefaultApiVersion = new ApiVersion(2, 0); + }); + }, app => + { + app.UseRouting(); + app.UseEndpoints(routes => routes.MapControllers()); + })) + { + var options = app.Host.Services.GetRequiredService>().Value; + + TestOutput.WriteLine($"GroupNameFormat: {options.GroupNameFormat}"); + TestOutput.WriteLine($"SubstituteApiVersionInUrl: {options.SubstituteApiVersionInUrl}"); + TestOutput.WriteLine($"DefaultApiVersion: {options.DefaultApiVersion}"); + + Assert.Equal("'version'VVV", options.GroupNameFormat); + Assert.True(options.SubstituteApiVersionInUrl); + Assert.Equal(new ApiVersion(2, 0), options.DefaultApiVersion); + } + } + + [Fact] + public async Task AddRestfulApiVersioning_CustomizeProblemDetails_ShouldThrowInternalServerErrorException_WhenStatusCodeIsUnmapped() + { + using (var app = WebHostTestFactory.Create(services => + { + services.AddControllers().AddApplicationPart(typeof(FakeController).Assembly); + services.AddRestfulApiVersioning(); + }, app => + { + app.UseRouting(); + app.UseEndpoints(routes => routes.MapControllers()); + })) + { + var client = app.Host.GetTestClient(); + + // /fake/problem418 invokes IProblemDetailsService.WriteAsync with Status=418 + // CustomizeProblemDetails fires; TryParse(418) fails (418 is not in Cuemon's mapped codes) + // -> throws InternalServerErrorException (line 57 in ServiceCollectionExtensions.cs) + await Assert.ThrowsAsync(() => client.GetAsync("/fake/problem418")); + } + } + } +}