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..3ec775c29 --- /dev/null +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionActionConstraint.cs @@ -0,0 +1,91 @@ +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; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + + 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 reader = context.HttpContext.RequestServices + ?.GetService>() + ?.Value + ?.ApiVersionReader; + + if (reader != null) + { + 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/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..e645deccf --- /dev/null +++ b/plugins/MyTested.AspNetCore.Mvc.Versioning/Internal/ApiVersionAwareActionSelector.cs @@ -0,0 +1,99 @@ +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; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + + 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 reader = context.HttpContext.RequestServices + ?.GetService>() + ?.Value + ?.ApiVersionReader; + + if (reader != null) + { + 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/MyTested.AspNetCore.Mvc.Versioning.csproj b/plugins/MyTested.AspNetCore.Mvc.Versioning/MyTested.AspNetCore.Mvc.Versioning.csproj index d8746bd08..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,7 +29,8 @@ - + + diff --git a/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs b/plugins/MyTested.AspNetCore.Mvc.Versioning/Plugins/VersioningTestPlugin.cs index fae8f7ef4..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 System.Linq; + using Asp.Versioning; + using Internal; using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Mvc.Versioning; + 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); + }); + } + }; } } 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..c1160424a 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; @@ -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() @@ -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-Custom-Version", "1.0")) + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithVersionNeutralQueryController() + { + MyPipeline + .Configuration() + .ShouldMap("api/neutral-query?v=99.0") + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithVersionNeutralHeaderController() + { + MyPipeline + .Configuration() + .ShouldMap(request => request + .WithLocation("api/neutral-header") + .WithHeader("X-Custom-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?v=1.0") + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithQueryCompetingV2() + { + MyPipeline + .Configuration() + .ShouldMap("api/query-competing?v=2.0") + .To(c => c.Index()) + .Which() + .ShouldReturn() + .Ok(); + } + + [Fact] + public void PipelineAssertionShouldWorkCorrectlyWithHeaderCompetingV1() + { + MyPipeline + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("X-Custom-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-Custom-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..9efe2f498 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; @@ -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,17 +37,164 @@ public void RouteAssertionShouldWorkCorrectlyWithQueryVersioningWhichDoesNotExis { MyRouting .Configuration() - .ShouldMap("api/versioning?api-version=1.0") + .ShouldMap("api/versioning?v=1.0") .ToNonExistingRoute(); } [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-Custom-Version", "1.0")) + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderVersioningWhichDoesNotExist() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-versioning") + .WithHeader("X-Custom-Version", "2.0")) + .ToNonExistingRoute(); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithVersionNeutralQueryController() + { + MyRouting + .Configuration() + .ShouldMap("api/neutral-query?v=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-Custom-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?v=1.0") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithQueryCompetingV2() + { + MyRouting + .Configuration() + .ShouldMap("api/query-competing?v=2.0") + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithQueryCompetingNonExistentVersion() + { + MyRouting + .Configuration() + .ShouldMap("api/query-competing?v=3.0") + .ToNonExistingRoute(); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingV1() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("X-Custom-Version", "1.0")) + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingV2() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("X-Custom-Version", "2.0")) + .To(c => c.Index()); + } + + [Fact] + public void RouteAssertionShouldWorkCorrectlyWithHeaderCompetingNonExistentVersion() + { + MyRouting + .Configuration() + .ShouldMap(request => request + .WithLocation("api/header-competing") + .WithHeader("X-Custom-Version", "3.0")) + .ToNonExistingRoute(); + } } } 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/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/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/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(); + } +} 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..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(); + services.AddApiVersioning(options => + { + options.ApiVersionReader = ApiVersionReader.Combine( + new QueryStringApiVersionReader("v"), + new HeaderApiVersionReader("X-Custom-Version"), + new UrlSegmentApiVersionReader()); + }).AddMvc(); } } }