From 085a65681927140276818f6b12ea78864732bd96 Mon Sep 17 00:00:00 2001 From: Jay Harris Date: Sun, 22 Feb 2026 19:36:08 +0800 Subject: [PATCH 1/4] refactor(versioning): migrate from Microsoft.AspNetCore.Mvc.Versioning to Asp.Versioning.Mvc Replace deprecated Microsoft.AspNetCore.Mvc.Versioning v5.1.0 with Asp.Versioning.Mvc v8.1.1 and update all namespace references from Microsoft.AspNetCore.Mvc.Versioning to Asp.Versioning across plugin source and test projects. --- .../MyTested.AspNetCore.Mvc.Versioning.csproj | 2 +- .../Plugins/VersioningTestPlugin.cs | 2 +- .../PluginsTests/VersioningTestPluginTests.cs | 2 +- .../Setups/Controllers/QueryVersioningController.cs | 1 + .../Setups/Controllers/VersioningController.cs | 1 + test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj b/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj index d8746bd08..4ef04ef7d 100644 --- a/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj @@ -29,7 +29,7 @@ - + diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs index fae8f7ef4..3e03c379e 100644 --- a/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs @@ -2,7 +2,7 @@ { using System; using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Mvc.Versioning; + using Asp.Versioning; public class VersioningTestPlugin : IHttpFeatureRegistrationPlugin { diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/PluginsTests/VersioningTestPluginTests.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/PluginsTests/VersioningTestPluginTests.cs index 9a79db8c6..c24197786 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/PluginsTests/VersioningTestPluginTests.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/PluginsTests/VersioningTestPluginTests.cs @@ -1,7 +1,7 @@ namespace MyTested.AspNetCore.Mvc.Test.PluginsTests { using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Mvc.Versioning; + using Asp.Versioning; using Plugins; using Xunit; diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryVersioningController.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryVersioningController.cs index 18fe8dcdc..e2006f7dd 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryVersioningController.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryVersioningController.cs @@ -1,5 +1,6 @@ namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers { + using Asp.Versioning; using Microsoft.AspNetCore.Mvc; [ApiController] diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/VersioningController.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/VersioningController.cs index b642d857d..2ea99ca59 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/VersioningController.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/VersioningController.cs @@ -1,5 +1,6 @@ namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers { + using Asp.Versioning; using Microsoft.AspNetCore.Mvc; [ApiController] diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs index e90d645e6..23b8735fc 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs @@ -8,7 +8,7 @@ public class TestStartup : DefaultStartup public override void ConfigureServices(IServiceCollection services) { services.AddControllers(); - services.AddApiVersioning(); + services.AddApiVersioning().AddMvc(); } } } From 7febcdd0dead651b565b0fb447c61002ede1aebd Mon Sep 17 00:00:00 2001 From: Jay Harris Date: Sun, 22 Feb 2026 19:51:46 +0800 Subject: [PATCH 2/4] feat(versioning): add version-aware action selection for legacy routing Asp.Versioning.Mvc no longer provides a custom IActionSelector for legacy routing (it relies on endpoint routing middleware instead). Since MyTested disables endpoint routing, this adds: - ApiVersionActionConstraint: filters actions during route matching to prevent ambiguous match errors on versioned controllers - ApiVersionActionConstraintProvider: registers the constraint for actions with API version metadata - ApiVersionAwareActionSelector: decorator around IActionSelector that filters candidates by requested API version for query string versioning scenarios - Updated VersioningTestPlugin to implement IServiceRegistrationPlugin and IRoutingServiceRegistrationPlugin for proper service registration --- .../Internal/ApiVersionActionConstraint.cs | 79 +++++++++++++++++ .../ApiVersionActionConstraintProvider.cs | 31 +++++++ .../Internal/ApiVersionAwareActionSelector.cs | 87 +++++++++++++++++++ .../MyTested.AspNetCore.Mvc.Versioning.csproj | 1 + .../Plugins/VersioningTestPlugin.cs | 41 ++++++++- 5 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs create mode 100644 plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraintProvider.cs create mode 100644 plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs new file mode 100644 index 000000000..9ea97efb4 --- /dev/null +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs @@ -0,0 +1,79 @@ +namespace MyTested.AspNetCore.Mvc.Internal +{ + using System.Linq; + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc.Abstractions; + using Microsoft.AspNetCore.Mvc.ActionConstraints; + using Microsoft.AspNetCore.Routing; + + internal class ApiVersionActionConstraint : IActionConstraint + { + public int Order => 0; + + public bool Accept(ActionConstraintContext context) + { + var requestedVersion = GetRequestedApiVersion(context.RouteContext); + + if (requestedVersion == null) + { + return true; + } + + var metadata = context.CurrentCandidate.Action.GetApiVersionMetadata(); + + if (metadata == ApiVersionMetadata.Empty || metadata.IsApiVersionNeutral) + { + return true; + } + + if (!metadata.IsMappedTo(requestedVersion)) + { + return false; + } + + if (context.Candidates.Count <= 1) + { + return true; + } + + var mapping = metadata.MappingTo(requestedVersion); + if (mapping == ApiVersionMapping.Explicit) + { + return true; + } + + var hasExplicitCandidate = context.Candidates.Any(c => + { + if (c.Action == context.CurrentCandidate.Action) + { + return false; + } + + var otherMetadata = c.Action.GetApiVersionMetadata(); + return otherMetadata.MappingTo(requestedVersion) == ApiVersionMapping.Explicit; + }); + + return !hasExplicitCandidate; + } + + private static ApiVersion GetRequestedApiVersion(RouteContext context) + { + var parser = ApiVersionParser.Default; + + if (context.RouteData.Values.TryGetValue("version", out var routeVersion) + && routeVersion is string versionString + && parser.TryParse(versionString, out var parsedVersion)) + { + return parsedVersion; + } + + var queryVersion = context.HttpContext.Request.Query["api-version"].FirstOrDefault(); + if (queryVersion != null && parser.TryParse(queryVersion, out var parsedQueryVersion)) + { + return parsedQueryVersion; + } + + return null; + } + } +} diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraintProvider.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraintProvider.cs new file mode 100644 index 000000000..767c73b78 --- /dev/null +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraintProvider.cs @@ -0,0 +1,31 @@ +namespace MyTested.AspNetCore.Mvc.Internal +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc.Abstractions; + using Microsoft.AspNetCore.Mvc.ActionConstraints; + + internal class ApiVersionActionConstraintProvider : IActionConstraintProvider + { + private static readonly ApiVersionActionConstraint SharedConstraint = new(); + + public int Order => -1000; + + public void OnProvidersExecuting(ActionConstraintProviderContext context) + { + var metadata = context.Action.GetApiVersionMetadata(); + + if (metadata != ApiVersionMetadata.Empty && !metadata.IsApiVersionNeutral) + { + context.Results.Add(new ActionConstraintItem(SharedConstraint) + { + Constraint = SharedConstraint, + IsReusable = true + }); + } + } + + public void OnProvidersExecuted(ActionConstraintProviderContext context) + { + } + } +} diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs new file mode 100644 index 000000000..a5930f194 --- /dev/null +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs @@ -0,0 +1,87 @@ +namespace MyTested.AspNetCore.Mvc.Internal +{ + using System.Collections.Generic; + using System.Linq; + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc.Abstractions; + using Microsoft.AspNetCore.Mvc.Infrastructure; + using Microsoft.AspNetCore.Routing; + + internal class ApiVersionAwareActionSelector : IActionSelector + { + private readonly IActionSelector inner; + + public ApiVersionAwareActionSelector(IActionSelector inner) + { + this.inner = inner; + } + + public IReadOnlyList SelectCandidates(RouteContext context) + => this.inner.SelectCandidates(context); + + public ActionDescriptor SelectBestCandidate( + RouteContext context, + IReadOnlyList candidates) + { + var requestedVersion = GetRequestedApiVersion(context); + + if (requestedVersion == null) + { + return this.inner.SelectBestCandidate(context, candidates); + } + + var versionedCandidates = new List(); + + foreach (var candidate in candidates) + { + var metadata = candidate.GetApiVersionMetadata(); + + if (metadata == ApiVersionMetadata.Empty + || metadata.IsApiVersionNeutral + || metadata.IsMappedTo(requestedVersion)) + { + versionedCandidates.Add(candidate); + } + } + + if (versionedCandidates.Count == 0) + { + return null; + } + + if (versionedCandidates.Count > 1) + { + var explicitlyMapped = versionedCandidates + .Where(c => c.GetApiVersionMetadata().MappingTo(requestedVersion) == ApiVersionMapping.Explicit) + .ToList(); + + if (explicitlyMapped.Count > 0) + { + versionedCandidates = explicitlyMapped; + } + } + + return this.inner.SelectBestCandidate(context, versionedCandidates); + } + + private static ApiVersion GetRequestedApiVersion(RouteContext context) + { + var parser = ApiVersionParser.Default; + + if (context.RouteData.Values.TryGetValue("version", out var routeVersion) + && routeVersion is string versionString + && parser.TryParse(versionString, out var parsedVersion)) + { + return parsedVersion; + } + + var queryVersion = context.HttpContext.Request.Query["api-version"].FirstOrDefault(); + if (queryVersion != null && parser.TryParse(queryVersion, out var parsedQueryVersion)) + { + return parsedQueryVersion; + } + + return null; + } + } +} diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj b/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj index 4ef04ef7d..8d242f981 100644 --- a/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj @@ -29,6 +29,7 @@ + diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs index 3e03c379e..2354746d4 100644 --- a/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs @@ -1,14 +1,49 @@ -namespace MyTested.AspNetCore.Mvc.Plugins +namespace MyTested.AspNetCore.Mvc.Plugins { using System; - using Microsoft.AspNetCore.Http; + using System.Linq; using Asp.Versioning; + using Internal; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc.ActionConstraints; + using Microsoft.AspNetCore.Mvc.Infrastructure; + using Microsoft.Extensions.DependencyInjection; - public class VersioningTestPlugin : IHttpFeatureRegistrationPlugin + public class VersioningTestPlugin + : IHttpFeatureRegistrationPlugin, + IServiceRegistrationPlugin, + IRoutingServiceRegistrationPlugin { public Action HttpFeatureRegistrationDelegate => httpContext => httpContext .Features .Set(new ApiVersioningFeature(httpContext)); + + public Func ServiceSelectorPredicate + => serviceDescriptor + => serviceDescriptor.ServiceType == typeof(IActionConstraintProvider); + + public Action ServiceRegistrationDelegate + => serviceCollection + => serviceCollection.AddSingleton(); + + public Action RoutingServiceRegistrationDelegate + => serviceCollection => + { + var selectorDescriptor = serviceCollection + .FirstOrDefault(d => d.ServiceType == typeof(IActionSelector)); + + if (selectorDescriptor != null) + { + serviceCollection.Remove(selectorDescriptor); + serviceCollection.AddSingleton(sp => + { + var innerSelector = (IActionSelector)ActivatorUtilities + .CreateInstance(sp, selectorDescriptor.ImplementationType); + + return new ApiVersionAwareActionSelector(innerSelector); + }); + } + }; } } From 4913cb8d749d870a996ab88d13f503ecff514ab3 Mon Sep 17 00:00:00 2001 From: Jay Harris Date: Sun, 22 Feb 2026 22:40:02 +0800 Subject: [PATCH 3/4] feat(versioning): add header versioning, version-neutral, and competing controller tests Add support for reading API version from x-api-version request header in both ApiVersionAwareActionSelector and ApiVersionActionConstraint. Extend test coverage with 24 new tests covering: - Header-based version routing and pipeline assertions - Version-neutral controllers for query-string and header routing - Multiple controllers competing for the same route at different versions across URL-segment, query-string, and header strategies --- .../Internal/ApiVersionActionConstraint.cs | 6 + .../Internal/ApiVersionAwareActionSelector.cs | 6 + .../WhichControllerInstanceBuilderTests.cs | 120 +++++++++++++- .../RoutingTests/RouteTestBuilderTests.cs | 151 +++++++++++++++++- .../HeaderCompetingV1Controller.cs | 14 ++ .../HeaderCompetingV2Controller.cs | 14 ++ .../Controllers/HeaderVersioningController.cs | 14 ++ .../Controllers/NeutralHeaderController.cs | 14 ++ .../Controllers/NeutralQueryController.cs | 14 ++ .../Controllers/QueryCompetingV1Controller.cs | 14 ++ .../Controllers/QueryCompetingV2Controller.cs | 14 ++ .../Controllers/UrlCompetingV1Controller.cs | 14 ++ .../Controllers/UrlCompetingV2Controller.cs | 14 ++ 13 files changed, 405 insertions(+), 4 deletions(-) create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderCompetingV1Controller.cs create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderCompetingV2Controller.cs create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderVersioningController.cs create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/NeutralHeaderController.cs create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/NeutralQueryController.cs create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryCompetingV1Controller.cs create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryCompetingV2Controller.cs create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/UrlCompetingV1Controller.cs create mode 100644 test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/UrlCompetingV2Controller.cs diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs index 9ea97efb4..3a0054858 100644 --- a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs @@ -73,6 +73,12 @@ private static ApiVersion GetRequestedApiVersion(RouteContext context) return parsedQueryVersion; } + var headerVersion = context.HttpContext.Request.Headers["x-api-version"].FirstOrDefault(); + if (headerVersion != null && parser.TryParse(headerVersion, out var parsedHeaderVersion)) + { + return parsedHeaderVersion; + } + return null; } } diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs index a5930f194..a2fe174a1 100644 --- a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs @@ -81,6 +81,12 @@ private static ApiVersion GetRequestedApiVersion(RouteContext context) return parsedQueryVersion; } + var headerVersion = context.HttpContext.Request.Headers["x-api-version"].FirstOrDefault(); + if (headerVersion != null && parser.TryParse(headerVersion, out var parsedHeaderVersion)) + { + return parsedHeaderVersion; + } + return null; } } diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/PipelineTests/WhichControllerInstanceBuilderTests.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/PipelineTests/WhichControllerInstanceBuilderTests.cs index 1b471183b..f87bef850 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/PipelineTests/WhichControllerInstanceBuilderTests.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/PipelineTests/WhichControllerInstanceBuilderTests.cs @@ -1,4 +1,4 @@ -namespace MyTested.AspNetCore.Mvc.Test.BuildersTests.PipelineTests +namespace MyTested.AspNetCore.Mvc.Test.BuildersTests.PipelineTests { using Setups.Controllers; using Xunit; @@ -30,7 +30,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithQueryVersioning() } [Fact] - public void RouteAssertionShouldWorkCorrectlyWithActionVersioningWhichDoesNotExist() + public void PipelineAssertionShouldWorkCorrectlyWithActionVersioning() { MyPipeline .Configuration() @@ -40,5 +40,121 @@ public void RouteAssertionShouldWorkCorrectlyWithActionVersioningWhichDoesNotExi .ShouldReturn() .Ok(); } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithHeaderVersioning() + { + MyPipeline + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-versioning") + .WithHeader("x-api-version", "1.0")) + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithVersionNeutralQueryController() + { + MyPipeline + .Configuration() + .ShouldMap("api/neutral-query?api-version=99.0") + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithVersionNeutralHeaderController() + { + MyPipeline + .Configuration() + .ShouldMap(request => request + .WithLocation("api/neutral-header") + .WithHeader("x-api-version", "99.0")) + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithUrlSegmentCompetingV1() + { + MyPipeline + .Configuration() + .ShouldMap("api/v1.0/competing") + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithUrlSegmentCompetingV2() + { + MyPipeline + .Configuration() + .ShouldMap("api/v2.0/competing") + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithQueryCompetingV1() + { + MyPipeline + .Configuration() + .ShouldMap("api/query-competing?api-version=1.0") + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithQueryCompetingV2() + { + MyPipeline + .Configuration() + .ShouldMap("api/query-competing?api-version=2.0") + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithHeaderCompetingV1() + { + MyPipeline + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("x-api-version", "1.0")) + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithHeaderCompetingV2() + { + MyPipeline + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("x-api-version", "2.0")) + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } } } diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/RoutingTests/RouteTestBuilderTests.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/RoutingTests/RouteTestBuilderTests.cs index fca5e8ab0..c46a2d48a 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/RoutingTests/RouteTestBuilderTests.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/RoutingTests/RouteTestBuilderTests.cs @@ -1,4 +1,4 @@ -namespace MyTested.AspNetCore.Mvc.Test.BuildersTests.RoutingTests +namespace MyTested.AspNetCore.Mvc.Test.BuildersTests.RoutingTests { using Setups.Controllers; using Xunit; @@ -42,12 +42,159 @@ public void RouteAssertionShouldWorkCorrectlyWithQueryVersioningWhichDoesNotExis } [Fact] - public void RouteAssertionShouldWorkCorrectlyWithActionVersioningWhichDoesNotExist() + public void RouteAssertionShouldWorkCorrectlyWithActionVersioning() { MyRouting .Configuration() .ShouldMap("api/v3.0/versioning") .To(c => c.SpecificVersion()); } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderVersioning() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-versioning") + .WithHeader("x-api-version", "1.0")) + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderVersioningWhichDoesNotExist() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-versioning") + .WithHeader("x-api-version", "2.0")) + .ToNonExistingRoute(); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithVersionNeutralQueryController() + { + MyRouting + .Configuration() + .ShouldMap("api/neutral-query?api-version=99.0") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithVersionNeutralQueryControllerWithoutVersion() + { + MyRouting + .Configuration() + .ShouldMap("api/neutral-query") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithVersionNeutralHeaderController() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/neutral-header") + .WithHeader("x-api-version", "99.0")) + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithVersionNeutralHeaderControllerWithoutVersion() + { + MyRouting + .Configuration() + .ShouldMap("api/neutral-header") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithUrlSegmentCompetingV1() + { + MyRouting + .Configuration() + .ShouldMap("api/v1.0/competing") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithUrlSegmentCompetingV2() + { + MyRouting + .Configuration() + .ShouldMap("api/v2.0/competing") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithUrlSegmentCompetingNonExistentVersion() + { + MyRouting + .Configuration() + .ShouldMap("api/v3.0/competing") + .ToNonExistingRoute(); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithQueryCompetingV1() + { + MyRouting + .Configuration() + .ShouldMap("api/query-competing?api-version=1.0") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithQueryCompetingV2() + { + MyRouting + .Configuration() + .ShouldMap("api/query-competing?api-version=2.0") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithQueryCompetingNonExistentVersion() + { + MyRouting + .Configuration() + .ShouldMap("api/query-competing?api-version=3.0") + .ToNonExistingRoute(); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingV1() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("x-api-version", "1.0")) + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingV2() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("x-api-version", "2.0")) + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingNonExistentVersion() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("x-api-version", "3.0")) + .ToNonExistingRoute(); + } } } diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderCompetingV1Controller.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderCompetingV1Controller.cs new file mode 100644 index 000000000..e923e0075 --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderCompetingV1Controller.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersion("1.0")] + [Route("api/header-competing")] + public class HeaderCompetingV1Controller : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderCompetingV2Controller.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderCompetingV2Controller.cs new file mode 100644 index 000000000..bc8c7aefb --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderCompetingV2Controller.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersion("2.0")] + [Route("api/header-competing")] + public class HeaderCompetingV2Controller : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderVersioningController.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderVersioningController.cs new file mode 100644 index 000000000..8cfb02f70 --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/HeaderVersioningController.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersion("1.0")] + [Route("api/header-versioning")] + public class HeaderVersioningController : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/NeutralHeaderController.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/NeutralHeaderController.cs new file mode 100644 index 000000000..13ec2c688 --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/NeutralHeaderController.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersionNeutral] + [Route("api/neutral-header")] + public class NeutralHeaderController : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/NeutralQueryController.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/NeutralQueryController.cs new file mode 100644 index 000000000..b751020a2 --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/NeutralQueryController.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersionNeutral] + [Route("api/neutral-query")] + public class NeutralQueryController : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryCompetingV1Controller.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryCompetingV1Controller.cs new file mode 100644 index 000000000..701f15341 --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryCompetingV1Controller.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersion("1.0")] + [Route("api/query-competing")] + public class QueryCompetingV1Controller : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryCompetingV2Controller.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryCompetingV2Controller.cs new file mode 100644 index 000000000..2c01df98e --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/QueryCompetingV2Controller.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersion("2.0")] + [Route("api/query-competing")] + public class QueryCompetingV2Controller : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/UrlCompetingV1Controller.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/UrlCompetingV1Controller.cs new file mode 100644 index 000000000..d1d68539d --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/UrlCompetingV1Controller.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{version:apiVersion}/competing")] + public class UrlCompetingV1Controller : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/UrlCompetingV2Controller.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/UrlCompetingV2Controller.cs new file mode 100644 index 000000000..0bff0294e --- /dev/null +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/Setups/Controllers/UrlCompetingV2Controller.cs @@ -0,0 +1,14 @@ +namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers +{ + using Asp.Versioning; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [ApiVersion("2.0")] + [Route("api/v{version:apiVersion}/competing")] + public class UrlCompetingV2Controller : ControllerBase + { + [HttpGet] + public IActionResult Index() => this.Ok(); + } +} From e75616266902afa1dcbe4c97daf99d98302f9090 Mon Sep 17 00:00:00 2001 From: Jay Harris Date: Sun, 22 Feb 2026 23:13:07 +0800 Subject: [PATCH 4/4] refactor(versioning): use configured IApiVersionReader instead of hardcoded keys Replace hardcoded query string ("api-version") and header ("x-api-version") version extraction with the user-configured IApiVersionReader resolved from IOptions. This ensures the plugin respects custom reader keys configured via AddApiVersioning(). Tests updated to use non-default keys ("v" for query string, "X-Custom-Version" for header) to prove the configuration is honored. --- .../Internal/ApiVersionActionConstraint.cs | 22 ++++++++++------- .../Internal/ApiVersionAwareActionSelector.cs | 22 ++++++++++------- .../WhichControllerInstanceBuilderTests.cs | 16 ++++++------- .../RoutingTests/RouteTestBuilderTests.cs | 24 +++++++++---------- .../TestStartup.cs | 11 +++++++-- 5 files changed, 57 insertions(+), 38 deletions(-) diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs index 3a0054858..3ec775c29 100644 --- a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs @@ -5,6 +5,8 @@ namespace MyTested.AspNetCore.Mvc.Internal using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; internal class ApiVersionActionConstraint : IActionConstraint { @@ -67,16 +69,20 @@ private static ApiVersion GetRequestedApiVersion(RouteContext context) return parsedVersion; } - var queryVersion = context.HttpContext.Request.Query["api-version"].FirstOrDefault(); - if (queryVersion != null && parser.TryParse(queryVersion, out var parsedQueryVersion)) - { - return parsedQueryVersion; - } + var reader = context.HttpContext.RequestServices + ?.GetService>() + ?.Value + ?.ApiVersionReader; - var headerVersion = context.HttpContext.Request.Headers["x-api-version"].FirstOrDefault(); - if (headerVersion != null && parser.TryParse(headerVersion, out var parsedHeaderVersion)) + if (reader != null) { - return parsedHeaderVersion; + var rawVersions = reader.Read(context.HttpContext.Request); + + if (rawVersions.Count > 0 + && parser.TryParse(rawVersions[0], out var parsedReaderVersion)) + { + return parsedReaderVersion; + } } return null; diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs index a2fe174a1..e645deccf 100644 --- a/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs @@ -6,6 +6,8 @@ namespace MyTested.AspNetCore.Mvc.Internal using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; internal class ApiVersionAwareActionSelector : IActionSelector { @@ -75,16 +77,20 @@ private static ApiVersion GetRequestedApiVersion(RouteContext context) return parsedVersion; } - var queryVersion = context.HttpContext.Request.Query["api-version"].FirstOrDefault(); - if (queryVersion != null && parser.TryParse(queryVersion, out var parsedQueryVersion)) - { - return parsedQueryVersion; - } + var reader = context.HttpContext.RequestServices + ?.GetService>() + ?.Value + ?.ApiVersionReader; - var headerVersion = context.HttpContext.Request.Headers["x-api-version"].FirstOrDefault(); - if (headerVersion != null && parser.TryParse(headerVersion, out var parsedHeaderVersion)) + if (reader != null) { - return parsedHeaderVersion; + var rawVersions = reader.Read(context.HttpContext.Request); + + if (rawVersions.Count > 0 + && parser.TryParse(rawVersions[0], out var parsedReaderVersion)) + { + return parsedReaderVersion; + } } return null; diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/PipelineTests/WhichControllerInstanceBuilderTests.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/PipelineTests/WhichControllerInstanceBuilderTests.cs index f87bef850..c1160424a 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/PipelineTests/WhichControllerInstanceBuilderTests.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/PipelineTests/WhichControllerInstanceBuilderTests.cs @@ -22,7 +22,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithQueryVersioning() { MyPipeline .Configuration() - .ShouldMap("api/versioning?api-version=2.0") + .ShouldMap("api/versioning?v=2.0") .To(c => c.Index()) .Which() .ShouldReturn() @@ -48,7 +48,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithHeaderVersioning() .Configuration() .ShouldMap(request => request .WithLocation("api/header-versioning") - .WithHeader("x-api-version", "1.0")) + .WithHeader("X-Custom-Version", "1.0")) .To(c => c.Index()) .Which() .ShouldReturn() @@ -60,7 +60,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithVersionNeutralQueryControlle { MyPipeline .Configuration() - .ShouldMap("api/neutral-query?api-version=99.0") + .ShouldMap("api/neutral-query?v=99.0") .To(c => c.Index()) .Which() .ShouldReturn() @@ -74,7 +74,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithVersionNeutralHeaderControll .Configuration() .ShouldMap(request => request .WithLocation("api/neutral-header") - .WithHeader("x-api-version", "99.0")) + .WithHeader("X-Custom-Version", "99.0")) .To(c => c.Index()) .Which() .ShouldReturn() @@ -110,7 +110,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithQueryCompetingV1() { MyPipeline .Configuration() - .ShouldMap("api/query-competing?api-version=1.0") + .ShouldMap("api/query-competing?v=1.0") .To(c => c.Index()) .Which() .ShouldReturn() @@ -122,7 +122,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithQueryCompetingV2() { MyPipeline .Configuration() - .ShouldMap("api/query-competing?api-version=2.0") + .ShouldMap("api/query-competing?v=2.0") .To(c => c.Index()) .Which() .ShouldReturn() @@ -136,7 +136,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithHeaderCompetingV1() .Configuration() .ShouldMap(request => request .WithLocation("api/header-competing") - .WithHeader("x-api-version", "1.0")) + .WithHeader("X-Custom-Version", "1.0")) .To(c => c.Index()) .Which() .ShouldReturn() @@ -150,7 +150,7 @@ public void PipelineAssertionShouldWorkCorrectlyWithHeaderCompetingV2() .Configuration() .ShouldMap(request => request .WithLocation("api/header-competing") - .WithHeader("x-api-version", "2.0")) + .WithHeader("X-Custom-Version", "2.0")) .To(c => c.Index()) .Which() .ShouldReturn() diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/RoutingTests/RouteTestBuilderTests.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/RoutingTests/RouteTestBuilderTests.cs index c46a2d48a..9efe2f498 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/RoutingTests/RouteTestBuilderTests.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/BuildersTests/RoutingTests/RouteTestBuilderTests.cs @@ -28,7 +28,7 @@ public void RouteAssertionShouldWorkCorrectlyWithQueryVersioning() { MyRouting .Configuration() - .ShouldMap("api/versioning?api-version=2.0") + .ShouldMap("api/versioning?v=2.0") .To(c => c.Index()); } @@ -37,7 +37,7 @@ public void RouteAssertionShouldWorkCorrectlyWithQueryVersioningWhichDoesNotExis { MyRouting .Configuration() - .ShouldMap("api/versioning?api-version=1.0") + .ShouldMap("api/versioning?v=1.0") .ToNonExistingRoute(); } @@ -57,7 +57,7 @@ public void RouteAssertionShouldWorkCorrectlyWithHeaderVersioning() .Configuration() .ShouldMap(request => request .WithLocation("api/header-versioning") - .WithHeader("x-api-version", "1.0")) + .WithHeader("X-Custom-Version", "1.0")) .To(c => c.Index()); } @@ -68,7 +68,7 @@ public void RouteAssertionShouldWorkCorrectlyWithHeaderVersioningWhichDoesNotExi .Configuration() .ShouldMap(request => request .WithLocation("api/header-versioning") - .WithHeader("x-api-version", "2.0")) + .WithHeader("X-Custom-Version", "2.0")) .ToNonExistingRoute(); } @@ -77,7 +77,7 @@ public void RouteAssertionShouldWorkCorrectlyWithVersionNeutralQueryController() { MyRouting .Configuration() - .ShouldMap("api/neutral-query?api-version=99.0") + .ShouldMap("api/neutral-query?v=99.0") .To(c => c.Index()); } @@ -97,7 +97,7 @@ public void RouteAssertionShouldWorkCorrectlyWithVersionNeutralHeaderController( .Configuration() .ShouldMap(request => request .WithLocation("api/neutral-header") - .WithHeader("x-api-version", "99.0")) + .WithHeader("X-Custom-Version", "99.0")) .To(c => c.Index()); } @@ -142,7 +142,7 @@ public void RouteAssertionShouldWorkCorrectlyWithQueryCompetingV1() { MyRouting .Configuration() - .ShouldMap("api/query-competing?api-version=1.0") + .ShouldMap("api/query-competing?v=1.0") .To(c => c.Index()); } @@ -151,7 +151,7 @@ public void RouteAssertionShouldWorkCorrectlyWithQueryCompetingV2() { MyRouting .Configuration() - .ShouldMap("api/query-competing?api-version=2.0") + .ShouldMap("api/query-competing?v=2.0") .To(c => c.Index()); } @@ -160,7 +160,7 @@ public void RouteAssertionShouldWorkCorrectlyWithQueryCompetingNonExistentVersio { MyRouting .Configuration() - .ShouldMap("api/query-competing?api-version=3.0") + .ShouldMap("api/query-competing?v=3.0") .ToNonExistingRoute(); } @@ -171,7 +171,7 @@ public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingV1() .Configuration() .ShouldMap(request => request .WithLocation("api/header-competing") - .WithHeader("x-api-version", "1.0")) + .WithHeader("X-Custom-Version", "1.0")) .To(c => c.Index()); } @@ -182,7 +182,7 @@ public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingV2() .Configuration() .ShouldMap(request => request .WithLocation("api/header-competing") - .WithHeader("x-api-version", "2.0")) + .WithHeader("X-Custom-Version", "2.0")) .To(c => c.Index()); } @@ -193,7 +193,7 @@ public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingNonExistentVersi .Configuration() .ShouldMap(request => request .WithLocation("api/header-competing") - .WithHeader("x-api-version", "3.0")) + .WithHeader("X-Custom-Version", "3.0")) .ToNonExistingRoute(); } } diff --git a/test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs b/test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs index 23b8735fc..c2e84c924 100644 --- a/test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs +++ b/test/MyTested.AspNetCore.Mvc.Versioning.Test/TestStartup.cs @@ -1,5 +1,6 @@ -namespace MyTested.AspNetCore.Mvc.Test +namespace MyTested.AspNetCore.Mvc.Test { + using Asp.Versioning; using Microsoft.Extensions.DependencyInjection; using Setups; @@ -8,7 +9,13 @@ public class TestStartup : DefaultStartup public override void ConfigureServices(IServiceCollection services) { services.AddControllers(); - services.AddApiVersioning().AddMvc(); + services.AddApiVersioning(options => + { + options.ApiVersionReader = ApiVersionReader.Combine( + new QueryStringApiVersionReader("v"), + new HeaderApiVersionReader("X-Custom-Version"), + new UrlSegmentApiVersionReader()); + }).AddMvc(); } } }