diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 0f7bca59694c..70d3ac7fc250 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -134,7 +134,7 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< bootstrapHostBuilder.ConfigureSlimWebHost( webHostBuilder => { - AspNetCore.WebHost.UseKestrel(webHostBuilder); + AspNetCore.WebHost.ConfigureWebDefaultsCore(webHostBuilder); webHostBuilder.Configure(ConfigureEmptyApplication); diff --git a/src/DefaultBuilder/src/WebHost.cs b/src/DefaultBuilder/src/WebHost.cs index 4a49c3ef8e3c..7e21713ae774 100644 --- a/src/DefaultBuilder/src/WebHost.cs +++ b/src/DefaultBuilder/src/WebHost.cs @@ -223,14 +223,17 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder) } }); - UseKestrel(builder); + ConfigureWebDefaultsCore(builder, services => + { + services.AddRouting(); + }); builder .UseIIS() .UseIISIntegration(); } - internal static void UseKestrel(IWebHostBuilder builder) + internal static void ConfigureWebDefaultsCore(IWebHostBuilder builder, Action? configureRouting = null) { builder.UseKestrel((builderContext, options) => { @@ -257,7 +260,17 @@ internal static void UseKestrel(IWebHostBuilder builder) services.AddTransient(); services.AddTransient, ForwardedHeadersOptionsSetup>(); - services.AddRouting(); + // Provide a way for the default host builder to configure routing. This probably means calling AddRouting. + // A lambda is used here because we don't want to reference AddRouting directly because of trimming. + // This avoids the overhead of calling AddRoutingCore multiple times on app startup. + if (configureRouting == null) + { + services.AddRoutingCore(); + } + else + { + configureRouting(services); + } }); } diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs index 104e13788f2c..b583cd4b4482 100644 --- a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs @@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Tests; @@ -2267,6 +2268,141 @@ public async Task SupportsDisablingMiddlewareAutoRegistration() Assert.True(app.Properties.ContainsKey("__AuthorizationMiddlewareSet")); } + [Fact] + public async Task UsingCreateBuilderResultsInRegexConstraintBeingPresent() + { + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseTestServer(); + + var app = builder.Build(); + + var chosenRoute = string.Empty; + + app.Use((context, next) => + { + chosenRoute = context.GetEndpoint()?.DisplayName; + return next(context); + }); + + app.MapGet("/products/{productId:regex(^[a-z]{{4}}\\d{{4}}$)}", (string productId) => productId).WithDisplayName("RegexRoute"); + + await app.StartAsync(); + + var client = app.GetTestClient(); + + _ = await client.GetAsync("https://localhost/products/abcd1234"); + Assert.Equal("RegexRoute", chosenRoute); + } + + [Fact] + public async Task UsingCreateSlimBuilderResultsInAlphaConstraintStillWorking() + { + var builder = WebApplication.CreateSlimBuilder(); + builder.WebHost.UseTestServer(); + + var app = builder.Build(); + + var chosenRoute = string.Empty; + + app.Use((context, next) => + { + chosenRoute = context.GetEndpoint()?.DisplayName; + return next(context); + }); + + app.MapGet("/products/{productId:alpha:minlength(4):maxlength(4)}", (string productId) => productId).WithDisplayName("AlphaRoute"); + + await app.StartAsync(); + + var client = app.GetTestClient(); + + _ = await client.GetAsync("https://localhost/products/abcd"); + Assert.Equal("AlphaRoute", chosenRoute); + } + + [Fact] + public async Task UsingCreateSlimBuilderResultsInErrorWhenTryingToUseRegexConstraint() + { + var builder = WebApplication.CreateSlimBuilder(); + builder.WebHost.UseTestServer(); + + var app = builder.Build(); + + app.MapGet("/products/{productId:regex(^[a-z]{{4}}\\d{{4}}$)}", (string productId) => productId).WithDisplayName("AlphaRoute"); + + await app.StartAsync(); + + var client = app.GetTestClient(); + + var ex = await Record.ExceptionAsync(async () => + { + _ = await client.GetAsync("https://localhost/products/abcd1234"); + }); + + Assert.IsType(ex); + Assert.IsType(ex.InnerException.InnerException); + Assert.Equal( + "A route parameter uses the regex constraint, which isn't registered. If this application was configured using CreateSlimBuilder(...) or AddRoutingCore(...) then this constraint is not registered by default. To use the regex constraint, configure route options at app startup: services.Configure(options => options.SetParameterPolicy(\"regex\"));", + ex.InnerException.InnerException.Message); + } + + [Fact] + public async Task UsingCreateSlimBuilderWorksIfRegexConstraintAddedViaAddRouting() + { + var builder = WebApplication.CreateSlimBuilder(); + builder.Services.AddRouting(); + builder.WebHost.UseTestServer(); + + var app = builder.Build(); + + var chosenRoute = string.Empty; + + app.Use((context, next) => + { + chosenRoute = context.GetEndpoint()?.DisplayName; + return next(context); + }); + + app.MapGet("/products/{productId:regex(^[a-z]{{4}}\\d{{4}}$)}", (string productId) => productId).WithDisplayName("RegexRoute"); + + await app.StartAsync(); + + var client = app.GetTestClient(); + + _ = await client.GetAsync("https://localhost/products/abcd1234"); + Assert.Equal("RegexRoute", chosenRoute); + } + + [Fact] + public async Task UsingCreateSlimBuilderWorksIfRegexConstraintAddedViaAddRoutingCoreWithActionDelegate() + { + var builder = WebApplication.CreateSlimBuilder(); + builder.Services.AddRoutingCore().Configure(options => + { + options.SetParameterPolicy("regex"); + }); + builder.WebHost.UseTestServer(); + + var app = builder.Build(); + + var chosenRoute = string.Empty; + + app.Use((context, next) => + { + chosenRoute = context.GetEndpoint()?.DisplayName; + return next(context); + }); + + app.MapGet("/products/{productId:regex(^[a-z]{{4}}\\d{{4}}$)}", (string productId) => productId).WithDisplayName("RegexRoute"); + + await app.StartAsync(); + + var client = app.GetTestClient(); + + _ = await client.GetAsync("https://localhost/products/abcd1234"); + Assert.Equal("RegexRoute", chosenRoute); + } + private class UberHandler : AuthenticationHandler { public UberHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs index a72cf88051cc..2cfebc44d4bd 100644 --- a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.HostFiltering; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Configuration; @@ -110,6 +111,19 @@ public void CreateDefaultBuilder_RegistersEventSourceLogger() args.Payload.OfType().Any(p => p.Contains("Request starting"))); } + [Fact] + public void WebHost_CreateDefaultBuilder_ConfiguresRegexInlineRouteConstraint_ByDefault() + { + var host = WebHost.CreateDefaultBuilder() + .Configure(_ => { }) + .Build(); + + var routeOptions = host.Services.GetService>(); + + Assert.True(routeOptions.Value.ConstraintMap.ContainsKey("regex")); + Assert.Equal(typeof(RegexInlineRouteConstraint), routeOptions.Value.ConstraintMap["regex"]); + } + private class TestEventListener : EventListener { private volatile bool _disposed; diff --git a/src/Http/Routing/src/Constraints/RegexErrorStubRouteConstraint.cs b/src/Http/Routing/src/Constraints/RegexErrorStubRouteConstraint.cs new file mode 100644 index 000000000000..aeb0324b0596 --- /dev/null +++ b/src/Http/Routing/src/Constraints/RegexErrorStubRouteConstraint.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Routing.Constraints; + +internal sealed class RegexErrorStubRouteConstraint : IRouteConstraint +{ + public RegexErrorStubRouteConstraint(string _) + { + throw new InvalidOperationException(Resources.RegexRouteContraint_NotConfigured); + } + + bool IRouteConstraint.Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) + { + // Should never get called, but is same as throw in constructor in case constructor is changed. + throw new InvalidOperationException(Resources.RegexRouteContraint_NotConfigured); + } +} diff --git a/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs b/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs index 6c912748bca5..01d332757839 100644 --- a/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs +++ b/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs @@ -26,6 +26,20 @@ public static class RoutingServiceCollectionExtensions /// The to add the services to. /// The so that additional calls can be chained. public static IServiceCollection AddRouting(this IServiceCollection services) + { + services.AddRoutingCore(); + services.TryAddEnumerable(ServiceDescriptor.Singleton, RegexInlineRouteConstraintSetup>()); + return services; + } + + /// + /// Adds services required for routing requests. This is similar to + /// except that it + /// excludes certain options that can be opted in separately, if needed. + /// + /// The to add the services to. + /// The so that additional calls can be chained. + public static IServiceCollection AddRoutingCore(this IServiceCollection services) { ArgumentNullException.ThrowIfNull(services); diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj index 068c308b367c..6b8a4ffc52f4 100644 --- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj +++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj @@ -50,5 +50,6 @@ + diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index a552550b5e1c..fff1ced433fd 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Microsoft.AspNetCore.Routing.RouteHandlerServices static Microsoft.AspNetCore.Routing.RouteHandlerServices.Map(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! handler, System.Collections.Generic.IEnumerable! httpMethods, System.Func! populateMetadata, System.Func! createRequestDelegate) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! +static Microsoft.Extensions.DependencyInjection.RoutingServiceCollectionExtensions.AddRoutingCore(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! diff --git a/src/Http/Routing/src/RegexInlineRouteConstraintSetup.cs b/src/Http/Routing/src/RegexInlineRouteConstraintSetup.cs new file mode 100644 index 000000000000..478e4834d8ac --- /dev/null +++ b/src/Http/Routing/src/RegexInlineRouteConstraintSetup.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Routing.Constraints; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection; + +internal sealed class RegexInlineRouteConstraintSetup : IConfigureOptions +{ + public void Configure(RouteOptions options) + { + var existingRegexConstraintType = options.TrimmerSafeConstraintMap["regex"]; + + // Don't override regex constraint if it has already been overridden + // this behavior here is just to add it back in if someone calls AddRouting(...) + // after setting up routing with AddRoutingCore(...). + if (existingRegexConstraintType == typeof(RegexErrorStubRouteConstraint)) + { + options.SetParameterPolicy("regex"); + } + } +} diff --git a/src/Http/Routing/src/Resources.resx b/src/Http/Routing/src/Resources.resx index 9b3d90ce5b04..a1fe756e745d 100644 --- a/src/Http/Routing/src/Resources.resx +++ b/src/Http/Routing/src/Resources.resx @@ -249,4 +249,7 @@ This RequestDelegate cannot be called before the final endpoint is built. + + A route parameter uses the regex constraint, which isn't registered. If this application was configured using CreateSlimBuilder(...) or AddRoutingCore(...) then this constraint is not registered by default. To use the regex constraint, configure route options at app startup: services.Configure<RouteOptions>(options => options.SetParameterPolicy<RegexInlineRouteConstraint>("regex")); + \ No newline at end of file diff --git a/src/Http/Routing/src/RouteOptions.cs b/src/Http/Routing/src/RouteOptions.cs index 4ae9a9688bf8..f43de9171ac1 100644 --- a/src/Http/Routing/src/RouteOptions.cs +++ b/src/Http/Routing/src/RouteOptions.cs @@ -113,9 +113,10 @@ private static IDictionary GetDefaultConstraintMap() AddConstraint(defaults, "max"); AddConstraint(defaults, "range"); - // Regex-based constraints + // The alpha constraint uses a compiled regex which has a minimal size cost. AddConstraint(defaults, "alpha"); - AddConstraint(defaults, "regex"); + + AddConstraint(defaults, "regex"); // Used to generate error message at runtime with helpful message. AddConstraint(defaults, "required"); diff --git a/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs b/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs index 31fd8f7387f5..be7bc7e3524a 100644 --- a/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs +++ b/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs @@ -16,6 +16,8 @@ public class DefaultInlineConstraintResolverTest public DefaultInlineConstraintResolverTest() { var routeOptions = new RouteOptions(); + routeOptions.SetParameterPolicy("regex"); + _constraintResolver = GetInlineConstraintResolver(routeOptions); } diff --git a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs index cfc4acb837b6..47d67f6eb601 100644 --- a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs +++ b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs @@ -3546,7 +3546,8 @@ private static DefaultParameterPolicyFactory CreateParameterPolicyFactory() ConstraintMap = { ["slugify"] = typeof(SlugifyParameterTransformer), - ["upper-case"] = typeof(UpperCaseParameterTransform) + ["upper-case"] = typeof(UpperCaseParameterTransform), + ["regex"] = typeof(RegexInlineRouteConstraint) // Regex not included by default since introduction of CreateSlimBuilder } }), serviceCollection.BuildServiceProvider()); diff --git a/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs b/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs index 5cc7fed190db..57496ffa6dd7 100644 --- a/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs +++ b/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.Options; @@ -15,7 +16,9 @@ internal class RouteMatcherBuilder : MatcherBuilder public RouteMatcherBuilder() { - _constraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), new TestServiceProvider()); + var routeOptions = new RouteOptions(); + routeOptions.SetParameterPolicy("regex"); + _constraintResolver = new DefaultInlineConstraintResolver(Options.Create(routeOptions), new TestServiceProvider()); _endpoints = new List(); } diff --git a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs index 51ffe2e7c0a3..9617f4e815f2 100644 --- a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs +++ b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.Template; using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Routing.Tree; @@ -27,10 +28,13 @@ public override void AddEndpoint(RouteEndpoint endpoint) public override Matcher Build() { + var routeOptions = new RouteOptions(); + routeOptions.SetParameterPolicy("regex"); + var builder = new TreeRouteBuilder( NullLoggerFactory.Instance, new DefaultObjectPool(new UriBuilderContextPooledObjectPolicy()), - new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), new TestServiceProvider())); + new DefaultInlineConstraintResolver(Options.Create(routeOptions), new TestServiceProvider())); var selector = new DefaultEndpointSelector(); diff --git a/src/Http/Routing/test/UnitTests/RouteTest.cs b/src/Http/Routing/test/UnitTests/RouteTest.cs index 3042f805ca1d..767e02a737c9 100644 --- a/src/Http/Routing/test/UnitTests/RouteTest.cs +++ b/src/Http/Routing/test/UnitTests/RouteTest.cs @@ -1861,6 +1861,7 @@ private static IInlineConstraintResolver GetInlineConstraintResolver() private static void ConfigureRouteOptions(RouteOptions options) { options.ConstraintMap["test-policy"] = typeof(TestPolicy); + options.SetParameterPolicy("regex"); } private class TestPolicy : IParameterPolicy diff --git a/src/Http/Routing/test/UnitTests/RoutingServiceCollectionExtensionsTests.cs b/src/Http/Routing/test/UnitTests/RoutingServiceCollectionExtensionsTests.cs new file mode 100644 index 000000000000..18a3fa0e44e9 --- /dev/null +++ b/src/Http/Routing/test/UnitTests/RoutingServiceCollectionExtensionsTests.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Routing; +public class RoutingServiceCollectionExtensionsTests +{ + [Fact] + public void AddRouting_ThrowsOnNull_ServicesParameter() + { + var ex = Record.Exception(() => + { + RoutingServiceCollectionExtensions.AddRouting(null); + }); + + Assert.IsType(ex); + Assert.Equal("services", (ex as ArgumentNullException).ParamName); + } + + [Fact] + public void AddRoutingWithOptions_ThrowsOnNull_ConfigureOptionsParameter() + { + var services = new ServiceCollection(); + + var ex = Record.Exception(() => + { + RoutingServiceCollectionExtensions.AddRouting(services, null); + }); + + Assert.IsType(ex); + Assert.Equal("configureOptions", (ex as ArgumentNullException).ParamName); + } + + [Fact] + public void AddRoutingWithOptions_ThrowsOnNull_ServicesParameter() + { + var ex = Record.Exception(() => + { + RoutingServiceCollectionExtensions.AddRouting(null, options => { }); + }); + + Assert.IsType(ex); + Assert.Equal("services", (ex as ArgumentNullException).ParamName); + } + + public class DummyRegexRouteConstraint : IRouteConstraint + { + public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) + { + return true; + } + } + + [Fact] + public void AddRouting_DoesNot_Replace_Existing_RouteConstraint() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + services.Configure(options => + { + options.SetParameterPolicy("regex"); + }); + + // Act + services.AddRouting(); + var provider = services.BuildServiceProvider(); + + // Assert + var options = provider.GetService>(); + var regexRouteConstraintType = options.Value.ConstraintMap["regex"]; + Assert.Equal(typeof(DummyRegexRouteConstraint), regexRouteConstraintType); + } +} diff --git a/src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs b/src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs index 8a98737930c2..864bb35a7e4a 100644 --- a/src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs +++ b/src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -128,7 +129,9 @@ private static IRouteBuilder CreateRouteBuilder() services.AddSingleton(_inlineConstraintResolver); services.AddSingleton(); services.AddSingleton(); - services.Configure(options => { }); + services.Configure(options => { + options.SetParameterPolicy("regex"); + }); var applicationBuilder = Mock.Of(); applicationBuilder.ApplicationServices = services.BuildServiceProvider(); @@ -143,6 +146,8 @@ private static IInlineConstraintResolver GetInlineConstraintResolver() var services = new ServiceCollection().AddOptions(); var serviceProvider = services.BuildServiceProvider(); var accessor = serviceProvider.GetRequiredService>(); + accessor.Value.SetParameterPolicy("regex"); + return new DefaultInlineConstraintResolver(accessor, serviceProvider); } } diff --git a/src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs b/src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs index 3658dbd5a63b..951ebc1fd4c5 100644 --- a/src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs +++ b/src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs @@ -3,6 +3,7 @@ using System.Globalization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.Template; using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.Logging; @@ -2056,6 +2057,8 @@ private static string CreateRouteGroup(int order, string template) private static DefaultInlineConstraintResolver CreateConstraintResolver() { var options = new RouteOptions(); + options.SetParameterPolicy("regex"); + var optionsMock = new Mock>(); optionsMock.SetupGet(o => o.Value).Returns(options); diff --git a/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs b/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs index 6a450fa381c0..15a46705ed8d 100644 --- a/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs +++ b/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs @@ -467,6 +467,7 @@ private Dictionary MultiRegistrationServiceTypes { typeof(MvcCoreRouteOptionsSetup), typeof(MvcCoreRouteOptionsSetup), + typeof(RegexInlineRouteConstraintSetup), } }, {