Add IParameterTransformer support (#8329)
This commit is contained in:
parent
105f8b47a1
commit
dfae9c208a
|
|
@ -110,7 +110,8 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
|||
{
|
||||
var dataSource = new MvcEndpointDataSource(
|
||||
actionDescriptorCollectionProvider,
|
||||
new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())));
|
||||
new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())),
|
||||
new MockParameterPolicyFactory());
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@
|
|||
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-preview3-35202</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-preview3-35202</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-preview3-35202</MicrosoftAspNetCoreResponseCachingPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-preview3-35202</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-preview3-35202</MicrosoftAspNetCoreRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-a-preview3-parameter-transformers-16968</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-a-preview3-parameter-transformers-16968</MicrosoftAspNetCoreRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35202</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35202</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview3-35202</MicrosoftAspNetCoreSessionPackageVersion>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
|
|
@ -220,6 +221,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
}
|
||||
|
||||
public static string ReplaceTokens(string template, IDictionary<string, string> values)
|
||||
{
|
||||
return ReplaceTokens(template, values, routeTokenTransformer: null);
|
||||
}
|
||||
|
||||
public static string ReplaceTokens(string template, IDictionary<string, string> values, IParameterTransformer routeTokenTransformer)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var state = TemplateParserState.Plaintext;
|
||||
|
|
@ -371,6 +377,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (routeTokenTransformer != null)
|
||||
{
|
||||
value = routeTokenTransformer.Transform(value);
|
||||
}
|
||||
|
||||
builder.Append(value);
|
||||
|
||||
if (c == '[')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IActionModelConvention"/> that sets attribute routing token replacement
|
||||
/// to use the specified <see cref="IParameterTransformer"/> on <see cref="ActionModel"/> selectors.
|
||||
/// </summary>
|
||||
public class RouteTokenTransformerConvention : IActionModelConvention
|
||||
{
|
||||
private readonly IParameterTransformer _parameterTransformer;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="RouteTokenTransformerConvention"/> with the specified <see cref="IParameterTransformer"/>.
|
||||
/// </summary>
|
||||
/// <param name="parameterTransformer">The <see cref="IParameterTransformer"/> to use with attribute routing token replacement.</param>
|
||||
public RouteTokenTransformerConvention(IParameterTransformer parameterTransformer)
|
||||
{
|
||||
if (parameterTransformer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parameterTransformer));
|
||||
}
|
||||
|
||||
_parameterTransformer = parameterTransformer;
|
||||
}
|
||||
|
||||
public void Apply(ActionModel action)
|
||||
{
|
||||
if (ShouldApply(action))
|
||||
{
|
||||
action.Properties[typeof(IParameterTransformer)] = _parameterTransformer;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool ShouldApply(ActionModel action) => true;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
|||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Resources = Microsoft.AspNetCore.Mvc.Core.Resources;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
|
|
@ -389,15 +390,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
try
|
||||
{
|
||||
actionDescriptor.Properties.TryGetValue(typeof(IParameterTransformer), out var transformer);
|
||||
var routeTokenTransformer = transformer as IParameterTransformer;
|
||||
|
||||
actionDescriptor.AttributeRouteInfo.Template = AttributeRouteModel.ReplaceTokens(
|
||||
actionDescriptor.AttributeRouteInfo.Template,
|
||||
actionDescriptor.RouteValues);
|
||||
actionDescriptor.RouteValues,
|
||||
routeTokenTransformer);
|
||||
|
||||
if (actionDescriptor.AttributeRouteInfo.Name != null)
|
||||
{
|
||||
actionDescriptor.AttributeRouteInfo.Name = AttributeRouteModel.ReplaceTokens(
|
||||
actionDescriptor.AttributeRouteInfo.Name,
|
||||
actionDescriptor.RouteValues);
|
||||
actionDescriptor.RouteValues,
|
||||
routeTokenTransformer);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
private readonly IActionDescriptorCollectionProvider _actions;
|
||||
private readonly MvcEndpointInvokerFactory _invokerFactory;
|
||||
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
||||
|
||||
// The following are protected by this lock for WRITES only. This pattern is similar
|
||||
// to DefaultActionDescriptorChangeProvider - see comments there for details on
|
||||
|
|
@ -33,7 +34,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
public MvcEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actions,
|
||||
MvcEndpointInvokerFactory invokerFactory)
|
||||
MvcEndpointInvokerFactory invokerFactory,
|
||||
ParameterPolicyFactory parameterPolicyFactory)
|
||||
{
|
||||
if (actions == null)
|
||||
{
|
||||
|
|
@ -45,8 +47,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
throw new ArgumentNullException(nameof(invokerFactory));
|
||||
}
|
||||
|
||||
if (parameterPolicyFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parameterPolicyFactory));
|
||||
}
|
||||
|
||||
_actions = actions;
|
||||
_invokerFactory = invokerFactory;
|
||||
_parameterPolicyFactory = parameterPolicyFactory;
|
||||
|
||||
ConventionalEndpointInfos = new List<MvcEndpointInfo>();
|
||||
|
||||
|
|
@ -253,9 +261,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
segmentParts = segment.Parts.ToList();
|
||||
}
|
||||
if (allParameterPolicies == null)
|
||||
{
|
||||
allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory);
|
||||
}
|
||||
|
||||
var parameterRouteValue = action.RouteValues[parameterPart.Name];
|
||||
|
||||
// Replace parameter with literal value
|
||||
if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies))
|
||||
{
|
||||
// Check if the parameter has a transformer policy
|
||||
// Use the first transformer policy
|
||||
for (var k = 0; k < parameterPolicies.Count; k++)
|
||||
{
|
||||
if (parameterPolicies[k] is IParameterTransformer parameterTransformer)
|
||||
{
|
||||
parameterRouteValue = parameterTransformer.Transform(parameterRouteValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModel
|
||||
{
|
||||
public class RouteTokenTransformerConventionTest
|
||||
{
|
||||
[Fact]
|
||||
public void Apply_NullAttributeRouteModel_NoOp()
|
||||
{
|
||||
// Arrange
|
||||
var convention = new RouteTokenTransformerConvention(new TestParameterTransformer());
|
||||
|
||||
var model = new ActionModel(GetMethodInfo(), Array.Empty<object>());
|
||||
model.Selectors.Add(new SelectorModel()
|
||||
{
|
||||
AttributeRouteModel = null
|
||||
});
|
||||
|
||||
// Act
|
||||
convention.Apply(model);
|
||||
|
||||
// Assert
|
||||
Assert.Null(model.Selectors[0].AttributeRouteModel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_HasAttributeRouteModel_SetRouteTokenTransformer()
|
||||
{
|
||||
// Arrange
|
||||
var transformer = new TestParameterTransformer();
|
||||
var convention = new RouteTokenTransformerConvention(transformer);
|
||||
|
||||
var model = new ActionModel(GetMethodInfo(), Array.Empty<object>());
|
||||
model.Selectors.Add(new SelectorModel()
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel()
|
||||
});
|
||||
|
||||
// Act
|
||||
convention.Apply(model);
|
||||
|
||||
// Assert
|
||||
Assert.True(model.Properties.TryGetValue(typeof(IParameterTransformer), out var routeTokenTransformer));
|
||||
Assert.Equal(transformer, routeTokenTransformer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_ShouldApplyFalse_NoOp()
|
||||
{
|
||||
// Arrange
|
||||
var transformer = new TestParameterTransformer();
|
||||
var convention = new CustomRouteTokenTransformerConvention(transformer);
|
||||
|
||||
var model = new ActionModel(GetMethodInfo(), Array.Empty<object>());
|
||||
model.Selectors.Add(new SelectorModel()
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel()
|
||||
});
|
||||
|
||||
// Act
|
||||
convention.Apply(model);
|
||||
|
||||
// Assert
|
||||
Assert.False(model.Properties.TryGetValue(typeof(IParameterTransformer), out _));
|
||||
}
|
||||
|
||||
private MethodInfo GetMethodInfo()
|
||||
{
|
||||
return typeof(RouteTokenTransformerConventionTest).GetMethod(nameof(GetMethodInfo), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
private class TestParameterTransformer : IParameterTransformer
|
||||
{
|
||||
public string Transform(string value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomRouteTokenTransformerConvention : RouteTokenTransformerConvention
|
||||
{
|
||||
public CustomRouteTokenTransformerConvention(IParameterTransformer parameterTransformer) : base(parameterTransformer)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ShouldApply(ActionModel action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -161,6 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{"{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }},
|
||||
{"{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" }},
|
||||
{"{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }},
|
||||
{"{controller:upper-case}/{action=TestAction}.{ext?}", new[] { "TESTCONTROLLER", "TESTCONTROLLER/TestAction.{ext?}" }},
|
||||
};
|
||||
|
||||
return data;
|
||||
|
|
@ -217,6 +218,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
[InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })]
|
||||
[InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })]
|
||||
[InlineData("{area:exists}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })]
|
||||
[InlineData("{area:exists:upper-case}/{controller}/{action}/{id?}", new[] { "TESTAREA/TestController/TestAction/{id?}" })]
|
||||
public void Endpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -230,6 +232,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||
services.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
||||
services.Configure<RouteOptions>(options =>
|
||||
{
|
||||
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
});
|
||||
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute, serviceProvider: services.BuildServiceProvider()));
|
||||
|
||||
|
|
@ -377,6 +383,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
[Theory]
|
||||
[InlineData("{controller}/{action}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController1/TestAction3", "TestController2/TestAction1" })]
|
||||
[InlineData("{controller}/{action:regex((TestAction1|TestAction2))}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController2/TestAction1" })]
|
||||
[InlineData("{controller}/{action:regex((TestAction1|TestAction2)):upper-case}", new[] { "TestController1/TESTACTION1", "TestController1/TESTACTION2", "TestController2/TESTACTION1" })]
|
||||
public void Endpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -750,14 +757,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(actionDescriptorCollectionProvider);
|
||||
services.AddRouting(options =>
|
||||
{
|
||||
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
});
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var dataSource = new MvcEndpointDataSource(
|
||||
actionDescriptorCollectionProvider,
|
||||
mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())));
|
||||
mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())),
|
||||
serviceProvider.GetRequiredService<ParameterPolicyFactory>());
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private class UpperCaseParameterTransform : IParameterTransformer
|
||||
{
|
||||
public string Transform(string value)
|
||||
{
|
||||
return value?.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private MvcEndpointInfo CreateEndpointInfo(
|
||||
string name,
|
||||
string template,
|
||||
|
|
@ -768,13 +789,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
if (serviceProvider == null)
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddRouting();
|
||||
var services = new ServiceCollection();
|
||||
services.AddRouting();
|
||||
services.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform());
|
||||
|
||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||
serviceCollection.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||
services.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
||||
services.Configure<RouteOptions>(options =>
|
||||
{
|
||||
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
});
|
||||
|
||||
serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
serviceProvider = services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
var parameterPolicyFactory = serviceProvider.GetRequiredService<ParameterPolicyFactory>();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,170 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParameterTransformer_TokenReplacement_Found()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/_ParameterTransformer_/_Test_");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ParameterTransformer", result.Controller);
|
||||
Assert.Equal("Test", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParameterTransformer_TokenReplacement_NotFound()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ParameterTransformer/Test");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_Parameters_Found()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/EndpointRouting/Index");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_Parameters_DefaultValue_Found()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/EndpointRouting");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_Found()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/_EndpointRouting_/ParameterTransformer");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_NotFound()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/EndpointRouting/ParameterTransformer");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_LinkToSelf()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
|
||||
Assert.Equal("/_EndpointRouting_/ParameterTransformer", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_LinkWithAmbientController()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "Get", id = 5 });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
|
||||
Assert.Equal("/_EndpointRouting_/5", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_LinkToAttributeRoutedController()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "ShowPosts", controller = "Blog" });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
|
||||
Assert.Equal("/Blog/ShowPosts", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_LinkToConventionalController()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "Index", controller = "Home" });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
|
||||
Assert.Equal("/", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async override Task HasEndpointMatch()
|
||||
{
|
||||
|
|
@ -123,5 +287,119 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
// Assert
|
||||
Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_NotFound()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/ConventionalTransformer/Index");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_DefaultValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_WithParam()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Param", result.Action);
|
||||
|
||||
Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_", Assert.Single(result.ExpectedUrls));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_LinkToConventionalController()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new { action = "Index", controller = "Home" });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
Assert.Equal("/", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_LinkToConventionalControllerWithParam()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new { action = "Param", controller = "ConventionalTransformer", param = "value" });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_LinkToSelf()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new {});
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_", result.Link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
public class ControllerRouteTokenTransformerConvention : IApplicationModelConvention
|
||||
{
|
||||
private readonly Type _controllerType;
|
||||
private readonly IParameterTransformer _parameterTransformer;
|
||||
|
||||
public ControllerRouteTokenTransformerConvention(Type controllerType, IParameterTransformer parameterTransformer)
|
||||
{
|
||||
if (parameterTransformer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parameterTransformer));
|
||||
}
|
||||
|
||||
_controllerType = controllerType;
|
||||
_parameterTransformer = parameterTransformer;
|
||||
}
|
||||
|
||||
public void Apply(ApplicationModel application)
|
||||
{
|
||||
foreach (var controller in application.Controllers.Where(c => c.ControllerType == _controllerType))
|
||||
{
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
action.Properties[typeof(IParameterTransformer)] = _parameterTransformer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
public class ConventionalTransformerController : Controller
|
||||
{
|
||||
private readonly TestResponseGenerator _generator;
|
||||
|
||||
public ConventionalTransformerController(TestResponseGenerator generator)
|
||||
{
|
||||
_generator = generator;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
return _generator.Generate();
|
||||
}
|
||||
|
||||
public IActionResult Param(string param)
|
||||
{
|
||||
return _generator.Generate($"/ConventionalTransformerRoute/_ConventionalTransformer_/Param/{param}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
[Route("/{controller:test-transformer}")]
|
||||
public class EndpointRoutingController : Controller
|
||||
{
|
||||
private readonly TestResponseGenerator _generator;
|
||||
|
||||
public EndpointRoutingController(TestResponseGenerator generator)
|
||||
{
|
||||
_generator = generator;
|
||||
}
|
||||
|
||||
[Route("/{controller}/{action=Index}")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return _generator.Generate("/EndpointRouting/Index", "/EndpointRouting");
|
||||
}
|
||||
|
||||
[Route("/{controller:test-transformer}/{action}")]
|
||||
public IActionResult ParameterTransformer()
|
||||
{
|
||||
return _generator.Generate("/_EndpointRouting_/ParameterTransformer");
|
||||
}
|
||||
|
||||
[Route("{id}")]
|
||||
public IActionResult Get(int id)
|
||||
{
|
||||
return _generator.Generate("/_EndpointRouting_/" + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace RoutingWebSite.Controllers
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
[Route("Order/[action]/{orderId?}", Name = "Order_[action]")]
|
||||
public class OrderController : Controller
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
[Route("[controller]/[action]", Name = "[controller]_[action]")]
|
||||
public class ParameterTransformerController : Controller
|
||||
{
|
||||
private readonly TestResponseGenerator _generator;
|
||||
|
||||
public ParameterTransformerController(TestResponseGenerator generator)
|
||||
{
|
||||
_generator = generator;
|
||||
}
|
||||
|
||||
public IActionResult Test()
|
||||
{
|
||||
return _generator.Generate("/_ParameterTransformer_/_Test_");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace RoutingWebSite.Controllers
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
public class RouteDataController : Controller
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
public class RemoveControllerActionDescriptorProvider : IActionDescriptorProvider
|
||||
{
|
||||
private readonly Type _controllerType;
|
||||
|
||||
public RemoveControllerActionDescriptorProvider(Type controllerType)
|
||||
{
|
||||
_controllerType = controllerType;
|
||||
}
|
||||
|
||||
public int Order => int.MaxValue;
|
||||
|
||||
public void OnProvidersExecuted(ActionDescriptorProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnProvidersExecuting(ActionDescriptorProviderContext context)
|
||||
{
|
||||
foreach (var item in context.Results.ToList())
|
||||
{
|
||||
if (item is ControllerActionDescriptor controllerActionDescriptor)
|
||||
{
|
||||
if (controllerActionDescriptor.ControllerTypeInfo == _controllerType)
|
||||
{
|
||||
context.Results.Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Builder;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RoutingWebSite
|
||||
|
|
@ -14,8 +15,20 @@ namespace RoutingWebSite
|
|||
// Set up application services
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc()
|
||||
services
|
||||
.AddMvc(options =>
|
||||
{
|
||||
// Add route token transformer to one controller
|
||||
options.Conventions.Add(new ControllerRouteTokenTransformerConvention(
|
||||
typeof(ParameterTransformerController),
|
||||
new TestParameterTransformer()));
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
services
|
||||
.AddRouting(options =>
|
||||
{
|
||||
options.ConstraintMap["test-transformer"] = typeof(TestParameterTransformer);
|
||||
});
|
||||
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
|
|
@ -32,6 +45,12 @@ namespace RoutingWebSite
|
|||
constraints: new { controller = "DataTokens" },
|
||||
dataTokens: new { hasDataTokens = true });
|
||||
|
||||
routes.MapRoute(
|
||||
"ConventionalTransformerRoute",
|
||||
"ConventionalTransformerRoute/{controller:test-transformer}/{action=Index}/{param:test-transformer?}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "ConventionalTransformer" });
|
||||
|
||||
routes.MapAreaRoute(
|
||||
"flightRoute",
|
||||
"adminRoute",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
|
|
@ -14,11 +19,17 @@ namespace RoutingWebSite
|
|||
// Set up application services
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc()
|
||||
services
|
||||
.AddMvc()
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
|
||||
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
|
||||
// EndpointRoutingController is not compatible with old routing
|
||||
// Remove its action to avoid errors
|
||||
var actionDescriptorProvider = new RemoveControllerActionDescriptorProvider(typeof(EndpointRoutingController));
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IActionDescriptorProvider>(actionDescriptorProvider));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
public class TestParameterTransformer : IParameterTransformer
|
||||
{
|
||||
public string Transform(string value)
|
||||
{
|
||||
return "_" + value + "_";
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue