diff --git a/src/Http/HttpAbstractions.slnf b/src/Http/HttpAbstractions.slnf index 076dcdc406d6..d0c1db772f9a 100644 --- a/src/Http/HttpAbstractions.slnf +++ b/src/Http/HttpAbstractions.slnf @@ -2,6 +2,8 @@ "solution": { "path": "..\\..\\AspNetCore.sln", "projects": [ + "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", + "src\\Extensions\\Features\\test\\Microsoft.Extensions.Features.Tests.csproj", "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", @@ -9,8 +11,6 @@ "src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj", "src\\Http\\Authentication.Core\\test\\Microsoft.AspNetCore.Authentication.Core.Test.csproj", - "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", - "src\\Extensions\\Features\\test\\Microsoft.Extensions.Features.Tests.csproj", "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", "src\\Http\\Headers\\test\\Microsoft.Net.Http.Headers.Tests.csproj", "src\\Http\\Http.Abstractions\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks.csproj", @@ -46,6 +46,7 @@ "src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj", "src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj", "src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj", + "src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj", "src\\Security\\Authorization\\Policy\\src\\Microsoft.AspNetCore.Authorization.Policy.csproj", "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", "src\\Servers\\IIS\\IISIntegration\\src\\Microsoft.AspNetCore.Server.IISIntegration.csproj", diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index dfe298c88907..3bad4e2aa733 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -29,6 +29,8 @@ Microsoft.AspNetCore.Routing.EndpointGroupNameAttribute.EndpointGroupName.get -> Microsoft.AspNetCore.Routing.EndpointNameAttribute Microsoft.AspNetCore.Routing.EndpointNameAttribute.EndpointNameAttribute(string! endpointName) -> void Microsoft.AspNetCore.Routing.EndpointNameAttribute.EndpointName.get -> string! +Microsoft.AspNetCore.Routing.RouteOptions.SetParameterPolicy(string! token, System.Type! type) -> void +Microsoft.AspNetCore.Routing.RouteOptions.SetParameterPolicy(string! token) -> void static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, Microsoft.AspNetCore.Routing.Patterns.RoutePattern! pattern, System.Delegate! handler) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! handler) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapDelete(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! handler) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! diff --git a/src/Http/Routing/src/RouteOptions.cs b/src/Http/Routing/src/RouteOptions.cs index 5f2ade71a57a..1a2e81f66b7f 100644 --- a/src/Http/Routing/src/RouteOptions.cs +++ b/src/Http/Routing/src/RouteOptions.cs @@ -72,6 +72,7 @@ internal ICollection EndpointDataSources /// public IDictionary ConstraintMap { + [RequiresUnreferencedCode($"The linker cannot determine what constraints are being added via the ConstraintMap property. Prefer {nameof(RouteOptions)}.{nameof(SetParameterPolicy)} instead for setting constraints. This warning can be suppressed if this property is being used to read of delete constraints.")] get { return _constraintTypeMap; @@ -124,7 +125,32 @@ private static IDictionary GetDefaultConstraintMap() return defaults; } - // This API could be exposed on RouteOptions + /// + /// Adds or overwrites the parameter policy with the associated route pattern token. + /// + /// The parameter policy type. + /// The route token used to apply the parameter policy. + public void SetParameterPolicy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]T>(string token) where T : IParameterPolicy + { + ConstraintMap[token] = typeof(T); + } + + /// + /// Adds or overwrites the parameter policy with the associated route pattern token. + /// + /// The route token used to apply the parameter policy. + /// The parameter policy type. + /// Throws an exception if the type is not an . + public void SetParameterPolicy(string token, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type) + { + if (!type.IsAssignableTo(typeof(IParameterPolicy))) + { + throw new InvalidOperationException($"{type} must implement {typeof(IParameterPolicy)}"); + } + + ConstraintMap[token] = type; + } + private static void AddConstraint<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TConstraint>(Dictionary constraintMap, string text) where TConstraint : IRouteConstraint { constraintMap[text] = typeof(TConstraint); diff --git a/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs b/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs index 23673ef31c0e..5d4731aad0d2 100644 --- a/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs +++ b/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs @@ -269,6 +269,46 @@ public void ResolveConstraint_SupportsCustomConstraints() Assert.IsType(constraint); } + [Fact] + public void ResolveConstraint_SupportsCustomConstraintsUsingNonGenericOverload() + { + // Arrange + var routeOptions = new RouteOptions(); + routeOptions.SetParameterPolicy("custom", typeof(CustomRouteConstraint)); + var resolver = GetInlineConstraintResolver(routeOptions); + + // Act + var constraint = resolver.ResolveConstraint("custom(argument)"); + + // Assert + Assert.IsType(constraint); + } + + [Fact] + public void SetParameterPolicyThrowsIfTypeIsNotIParameterPolicy() + { + // Arrange + var routeOptions = new RouteOptions(); + var ex = Assert.Throws(() => routeOptions.SetParameterPolicy("custom", typeof(string))); + + Assert.Equal("System.String must implement Microsoft.AspNetCore.Routing.IParameterPolicy", ex.Message); + } + + [Fact] + public void ResolveConstraint_SupportsCustomConstraintsUsingGenericOverloads() + { + // Arrange + var routeOptions = new RouteOptions(); + routeOptions.SetParameterPolicy("custom"); + var resolver = GetInlineConstraintResolver(routeOptions); + + // Act + var constraint = resolver.ResolveConstraint("custom(argument)"); + + // Assert + Assert.IsType(constraint); + } + [Fact] public void ResolveConstraint_CustomConstraintThatDoesNotImplementIRouteConstraint_Throws() {