Support page parameter in attribute route (#8530)
This commit is contained in:
parent
70ddf15cbc
commit
7854d65c11
|
|
@ -268,24 +268,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory);
|
allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace parameter with literal value
|
||||||
var parameterRouteValue = action.RouteValues[parameterPart.Name];
|
var parameterRouteValue = action.RouteValues[parameterPart.Name];
|
||||||
|
|
||||||
// Replace parameter with literal value
|
// Route value could be null if it is a "known" route value.
|
||||||
if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies))
|
// Do not use the null value to de-normalize the route pattern,
|
||||||
|
// instead leave the parameter unchanged.
|
||||||
|
// e.g.
|
||||||
|
// RouteValues will contain a null "page" value if there are Razor pages
|
||||||
|
// Skip replacing the {page} parameter
|
||||||
|
if (parameterRouteValue != null)
|
||||||
{
|
{
|
||||||
// Check if the parameter has a transformer policy
|
if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies))
|
||||||
// Use the first transformer policy
|
|
||||||
for (var k = 0; k < parameterPolicies.Count; k++)
|
|
||||||
{
|
{
|
||||||
if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer)
|
// Check if the parameter has a transformer policy
|
||||||
|
// Use the first transformer policy
|
||||||
|
for (var k = 0; k < parameterPolicies.Count; k++)
|
||||||
{
|
{
|
||||||
parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue);
|
if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer)
|
||||||
break;
|
{
|
||||||
|
parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue);
|
segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,42 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Collection(endpoints, inspectors);
|
Assert.Collection(endpoints, inspectors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Endpoints_SingleAction_ConventionalRoute_ContainsParameterWithNullRequiredRouteValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||||
|
new { controller = "TestController", action = "TestAction", page = (string)null });
|
||||||
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||||
|
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||||
|
string.Empty,
|
||||||
|
"{controller}/{action}/{page}",
|
||||||
|
new RouteValueDictionary(new { action = "TestAction" })));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpoints = dataSource.Endpoints;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Endpoints_SingleAction_AttributeRoute_ContainsParameterWithNullRequiredRouteValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||||
|
"{controller}/{action}/{page}",
|
||||||
|
new { controller = "TestController", action = "TestAction", page = (string)null });
|
||||||
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpoints = dataSource.Endpoints;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(endpoints,
|
||||||
|
(e) => Assert.Equal("TestController/TestAction/{page}", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Endpoints_SingleAction_WithActionDefault()
|
public void Endpoints_SingleAction_WithActionDefault()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
{
|
||||||
|
public class LinkBuilder
|
||||||
|
{
|
||||||
|
public LinkBuilder(string url)
|
||||||
|
{
|
||||||
|
Url = url;
|
||||||
|
|
||||||
|
Values = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "link", string.Empty }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, object> Values { get; set; }
|
||||||
|
|
||||||
|
public LinkBuilder To(object values)
|
||||||
|
{
|
||||||
|
var dictionary = new RouteValueDictionary(values);
|
||||||
|
foreach (var kvp in dictionary)
|
||||||
|
{
|
||||||
|
Values.Add("link_" + kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator string(LinkBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
{
|
||||||
|
// See TestResponseGenerator for the code that generates this data.
|
||||||
|
public class RoutingResult
|
||||||
|
{
|
||||||
|
public string[] ExpectedUrls { get; set; }
|
||||||
|
|
||||||
|
public string ActualUrl { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, object> RouteValues { get; set; }
|
||||||
|
|
||||||
|
public string RouteName { get; set; }
|
||||||
|
|
||||||
|
public string Action { get; set; }
|
||||||
|
|
||||||
|
public string Controller { get; set; }
|
||||||
|
|
||||||
|
public string Link { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -382,7 +382,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
var expected = "ConventionalRoute - Hello from mypage";
|
var expected = "ConventionalRoute - Hello from mypage";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.GetStringAsync("/PageRoute/ConventionalRoute/mypage");
|
var response = await Client.GetStringAsync("/PageRoute/ConventionalRouteView/mypage");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, response.Trim());
|
Assert.Equal(expected, response.Trim());
|
||||||
|
|
@ -395,7 +395,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
var expected = "AttributeRoute - Hello from test-page";
|
var expected = "AttributeRoute - Hello from test-page";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.GetStringAsync("/PageRoute/Attribute/test-page");
|
var response = await Client.GetStringAsync("/PageRoute/AttributeView/test-page");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, response.Trim());
|
Assert.Equal(expected, response.Trim());
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test\XmlAssert.cs" />
|
<Compile Include="..\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test\XmlAssert.cs" />
|
||||||
<Compile Include="..\Microsoft.AspNetCore.Mvc.Core.TestCommon\ActivityReplacer.cs" />
|
|
||||||
<EmbeddedResource Include="compiler\resources\**\*" />
|
<EmbeddedResource Include="compiler\resources\**\*" />
|
||||||
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
@ -29,6 +28,8 @@
|
||||||
<ProjectReference Include="..\..\benchmarkapps\BasicViews\BasicViews.csproj" />
|
<ProjectReference Include="..\..\benchmarkapps\BasicViews\BasicViews.csproj" />
|
||||||
<ProjectReference Include="..\..\samples\MvcSandbox\MvcSandbox.csproj" />
|
<ProjectReference Include="..\..\samples\MvcSandbox\MvcSandbox.csproj" />
|
||||||
|
|
||||||
|
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Core.TestCommon\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj" />
|
||||||
|
|
||||||
<ProjectReference Include="..\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.csproj" />
|
<ProjectReference Include="..\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.csproj" />
|
||||||
<ProjectReference Include="..\WebSites\ApplicationModelWebSite\ApplicationModelWebSite.csproj" />
|
<ProjectReference Include="..\WebSites\ApplicationModelWebSite\ApplicationModelWebSite.csproj" />
|
||||||
<ProjectReference Include="..\WebSites\BasicWebSite\BasicWebSite.csproj" />
|
<ProjectReference Include="..\WebSites\BasicWebSite\BasicWebSite.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -10,13 +11,34 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
public class EndpointRoutingTest : RoutingTestsBase<RoutingWebSite.Startup>
|
public class RoutingEndpointRoutingTest : RoutingTestsBase<RoutingWebSite.Startup>
|
||||||
{
|
{
|
||||||
public EndpointRoutingTest(MvcTestFixture<RoutingWebSite.Startup> fixture)
|
public RoutingEndpointRoutingTest(MvcTestFixture<RoutingWebSite.Startup> fixture)
|
||||||
: base(fixture)
|
: base(fixture)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AttributeRoutedAction_ContainsPage_RouteMatched()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await Client.GetAsync("http://localhost/PageRoute/Attribute/pagevalue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||||
|
|
||||||
|
Assert.Contains("/PageRoute/Attribute/pagevalue", result.ExpectedUrls);
|
||||||
|
Assert.Equal("PageRoute", result.Controller);
|
||||||
|
Assert.Equal("AttributeRoute", result.Action);
|
||||||
|
|
||||||
|
Assert.Contains(
|
||||||
|
new KeyValuePair<string, object>("page", "pagevalue"),
|
||||||
|
result.RouteValues);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ParameterTransformer_TokenReplacement_Found()
|
public async Task ParameterTransformer_TokenReplacement_Found()
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class RoutingEndpointRoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase<BasicWebSite.StartupWithEndpointRouting>
|
||||||
|
{
|
||||||
|
public RoutingEndpointRoutingWithoutRazorPagesTests(MvcTestFixture<BasicWebSite.StartupWithEndpointRouting> fixture)
|
||||||
|
: base(fixture)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConventionalRoutedAction_RouteContainsPage_RouteNotMatched()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await Client.GetAsync("http://localhost/PageRoute/ConventionalRoute/pagevalue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||||
|
|
||||||
|
Assert.Equal("PageRoute", result.Controller);
|
||||||
|
Assert.Equal("ConventionalRoute", result.Action);
|
||||||
|
|
||||||
|
// pagevalue is not used in "page" route value because it is a required value
|
||||||
|
Assert.False(result.RouteValues.ContainsKey("page"));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public abstract Task HasEndpointMatch();
|
public abstract Task HasEndpointMatch();
|
||||||
|
|
||||||
|
|
@ -1282,61 +1301,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
return new LinkBuilder(url);
|
return new LinkBuilder(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// See TestResponseGenerator in RoutingWebSite for the code that generates this data.
|
|
||||||
protected class RoutingResult
|
|
||||||
{
|
|
||||||
public string[] ExpectedUrls { get; set; }
|
|
||||||
|
|
||||||
public string ActualUrl { get; set; }
|
|
||||||
|
|
||||||
public Dictionary<string, object> RouteValues { get; set; }
|
|
||||||
|
|
||||||
public string RouteName { get; set; }
|
|
||||||
|
|
||||||
public string Action { get; set; }
|
|
||||||
|
|
||||||
public string Controller { get; set; }
|
|
||||||
|
|
||||||
public string Link { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class LinkBuilder
|
|
||||||
{
|
|
||||||
public LinkBuilder(string url)
|
|
||||||
{
|
|
||||||
Url = url;
|
|
||||||
|
|
||||||
Values = new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "link", string.Empty }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Url { get; set; }
|
|
||||||
|
|
||||||
public Dictionary<string, object> Values { get; set; }
|
|
||||||
|
|
||||||
public LinkBuilder To(object values)
|
|
||||||
{
|
|
||||||
var dictionary = new RouteValueDictionary(values);
|
|
||||||
foreach (var kvp in dictionary)
|
|
||||||
{
|
|
||||||
Values.Add("link_" + kvp.Key, kvp.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator string(LinkBuilder builder)
|
|
||||||
{
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class RoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase<BasicWebSite.Startup>
|
||||||
|
{
|
||||||
|
public RoutingWithoutRazorPagesTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||||
|
: base(fixture)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
// 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.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public abstract class RoutingWithoutRazorPagesTestsBase<TStartup> : IClassFixture<MvcTestFixture<TStartup>> where TStartup : class
|
||||||
|
{
|
||||||
|
protected RoutingWithoutRazorPagesTestsBase(MvcTestFixture<TStartup> fixture)
|
||||||
|
{
|
||||||
|
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||||
|
Client = factory.CreateDefaultClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||||
|
builder.UseStartup<TStartup>();
|
||||||
|
|
||||||
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AttributeRoutedAction_ContainsPage_RouteMatched()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await Client.GetAsync("http://localhost/PageRoute/Attribute/pagevalue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||||
|
|
||||||
|
Assert.Contains("/PageRoute/Attribute/pagevalue", result.ExpectedUrls);
|
||||||
|
Assert.Equal("PageRoute", result.Controller);
|
||||||
|
Assert.Equal("AttributeRoute", result.Action);
|
||||||
|
|
||||||
|
Assert.Contains(
|
||||||
|
new KeyValuePair<string, object>("page", "pagevalue"),
|
||||||
|
result.RouteValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConventionalRoutedAction_RouteContainsPage_RouteNotMatched()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await Client.GetAsync("http://localhost/PageRoute/ConventionalRoute/pagevalue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||||
|
|
||||||
|
Assert.Equal("PageRoute", result.Controller);
|
||||||
|
Assert.Equal("ConventionalRoute", result.Action);
|
||||||
|
|
||||||
|
Assert.Equal("pagevalue", result.RouteValues["page"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\Common\TestResponseGenerator.cs" Link="TestResponseGenerator.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc.Formatters.Xml\Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj" />
|
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc.Formatters.Xml\Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,32 @@ namespace BasicWebSite.Controllers
|
||||||
// without affecting view lookups.
|
// without affecting view lookups.
|
||||||
public class PageRouteController : Controller
|
public class PageRouteController : Controller
|
||||||
{
|
{
|
||||||
|
private readonly TestResponseGenerator _generator;
|
||||||
|
|
||||||
|
public PageRouteController(TestResponseGenerator generator)
|
||||||
|
{
|
||||||
|
_generator = generator;
|
||||||
|
}
|
||||||
|
|
||||||
public IActionResult ConventionalRoute(string page)
|
public IActionResult ConventionalRoute(string page)
|
||||||
|
{
|
||||||
|
return _generator.Generate("/PageRoute/ConventionalRoute/" + page);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/PageRoute/Attribute/{page}")]
|
||||||
|
public IActionResult AttributeRoute(string page)
|
||||||
|
{
|
||||||
|
return _generator.Generate("/PageRoute/Attribute/" + page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult ConventionalRouteView(string page)
|
||||||
{
|
{
|
||||||
ViewData["page"] = page;
|
ViewData["page"] = page;
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("/PageRoute/Attribute/{page}")]
|
[HttpGet("/PageRoute/AttributeView/{page}")]
|
||||||
public IActionResult AttributeRoute(string page)
|
public IActionResult AttributeRouteView(string page)
|
||||||
{
|
{
|
||||||
ViewData["page"] = page;
|
ViewData["page"] = page;
|
||||||
return View();
|
return View();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace BasicWebSite
|
namespace BasicWebSite
|
||||||
|
|
@ -42,6 +43,8 @@ namespace BasicWebSite
|
||||||
services.AddSingleton<ContactsRepository>();
|
services.AddSingleton<ContactsRepository>();
|
||||||
services.AddScoped<RequestIdService>();
|
services.AddScoped<RequestIdService>();
|
||||||
services.AddTransient<ServiceActionFilter>();
|
services.AddTransient<ServiceActionFilter>();
|
||||||
|
services.AddScoped<TestResponseGenerator>();
|
||||||
|
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace BasicWebSite
|
namespace BasicWebSite
|
||||||
|
|
@ -22,6 +23,8 @@ namespace BasicWebSite
|
||||||
|
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
services.AddScoped<RequestIdService>();
|
services.AddScoped<RequestIdService>();
|
||||||
|
services.AddScoped<TestResponseGenerator>();
|
||||||
|
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app)
|
||||||
|
|
@ -35,6 +38,8 @@ namespace BasicWebSite
|
||||||
"ActionAsMethod",
|
"ActionAsMethod",
|
||||||
"{controller}/{action}",
|
"{controller}/{action}",
|
||||||
defaults: new { controller = "Home", action = "Index" });
|
defaults: new { controller = "Home", action = "Index" });
|
||||||
|
|
||||||
|
routes.MapRoute("PageRoute", "{controller}/{action}/{page}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace VersioningWebSite
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
// Generates a response based on the expected URL and action context
|
// Generates a response based on the expected URL and action context
|
||||||
public class TestResponseGenerator
|
public class TestResponseGenerator
|
||||||
|
|
@ -63,4 +63,4 @@ namespace VersioningWebSite
|
||||||
return urlHelper;
|
return urlHelper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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 PageRouteController
|
||||||
|
{
|
||||||
|
private readonly TestResponseGenerator _generator;
|
||||||
|
|
||||||
|
public PageRouteController(TestResponseGenerator generator)
|
||||||
|
{
|
||||||
|
_generator = generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult ConventionalRoute(string page)
|
||||||
|
{
|
||||||
|
return _generator.Generate("/PageRoute/ConventionalRoute/" + page);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/PageRoute/Attribute/{page}")]
|
||||||
|
public IActionResult AttributeRoute(string page)
|
||||||
|
{
|
||||||
|
return _generator.Generate("/PageRoute/Attribute/" + page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,11 +10,11 @@ namespace RoutingWebSite
|
||||||
{
|
{
|
||||||
public class RemoveControllerActionDescriptorProvider : IActionDescriptorProvider
|
public class RemoveControllerActionDescriptorProvider : IActionDescriptorProvider
|
||||||
{
|
{
|
||||||
private readonly Type _controllerType;
|
private readonly ControllerToRemove[] _controllerTypes;
|
||||||
|
|
||||||
public RemoveControllerActionDescriptorProvider(Type controllerType)
|
public RemoveControllerActionDescriptorProvider(params ControllerToRemove[] controllerTypes)
|
||||||
{
|
{
|
||||||
_controllerType = controllerType;
|
_controllerTypes = controllerTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Order => int.MaxValue;
|
public int Order => int.MaxValue;
|
||||||
|
|
@ -29,12 +29,22 @@ namespace RoutingWebSite
|
||||||
{
|
{
|
||||||
if (item is ControllerActionDescriptor controllerActionDescriptor)
|
if (item is ControllerActionDescriptor controllerActionDescriptor)
|
||||||
{
|
{
|
||||||
if (controllerActionDescriptor.ControllerTypeInfo == _controllerType)
|
var controllerToRemove = _controllerTypes.SingleOrDefault(c => c.ControllerType == controllerActionDescriptor.ControllerTypeInfo);
|
||||||
|
if (controllerToRemove != null)
|
||||||
{
|
{
|
||||||
context.Results.Remove(item);
|
if (controllerToRemove.Actions == null || controllerToRemove.Actions.Contains(controllerActionDescriptor.ActionName))
|
||||||
|
{
|
||||||
|
context.Results.Remove(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ControllerToRemove
|
||||||
|
{
|
||||||
|
public Type ControllerType { get; set; }
|
||||||
|
public string[] Actions { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\Common\TestResponseGenerator.cs" Link="TestResponseGenerator.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,12 @@ namespace RoutingWebSite
|
||||||
defaults: new { controller = "Home", action = "Index" },
|
defaults: new { controller = "Home", action = "Index" },
|
||||||
constraints: new { area = "Travel" });
|
constraints: new { area = "Travel" });
|
||||||
|
|
||||||
|
routes.MapRoute(
|
||||||
|
"PageRoute",
|
||||||
|
"{controller}/{action}/{page}",
|
||||||
|
defaults: null,
|
||||||
|
constraints: new { controller = "PageRoute" });
|
||||||
|
|
||||||
routes.MapRoute(
|
routes.MapRoute(
|
||||||
"ActionAsMethod",
|
"ActionAsMethod",
|
||||||
"{controller}/{action}",
|
"{controller}/{action}",
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,17 @@ namespace RoutingWebSite
|
||||||
|
|
||||||
// EndpointRoutingController is not compatible with old routing
|
// EndpointRoutingController is not compatible with old routing
|
||||||
// Remove its action to avoid errors
|
// Remove its action to avoid errors
|
||||||
var actionDescriptorProvider = new RemoveControllerActionDescriptorProvider(typeof(EndpointRoutingController));
|
var actionDescriptorProvider = new RemoveControllerActionDescriptorProvider(
|
||||||
|
new ControllerToRemove
|
||||||
|
{
|
||||||
|
ControllerType = typeof(EndpointRoutingController),
|
||||||
|
Actions = null, // remove all
|
||||||
|
},
|
||||||
|
new ControllerToRemove
|
||||||
|
{
|
||||||
|
ControllerType = typeof(PageRouteController),
|
||||||
|
Actions = new [] { nameof(PageRouteController.AttributeRoute) }
|
||||||
|
});
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IActionDescriptorProvider>(actionDescriptorProvider));
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<IActionDescriptorProvider>(actionDescriptorProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// 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 Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
|
||||||
|
|
||||||
namespace RoutingWebSite
|
|
||||||
{
|
|
||||||
// Generates a response based on the expected URL and action context
|
|
||||||
public class TestResponseGenerator
|
|
||||||
{
|
|
||||||
private readonly ActionContext _actionContext;
|
|
||||||
private readonly IUrlHelperFactory _urlHelperFactory;
|
|
||||||
|
|
||||||
public TestResponseGenerator(IActionContextAccessor contextAccessor, IUrlHelperFactory urlHelperFactory)
|
|
||||||
{
|
|
||||||
_urlHelperFactory = urlHelperFactory;
|
|
||||||
|
|
||||||
_actionContext = contextAccessor.ActionContext;
|
|
||||||
if (_actionContext == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ActionContext should not be null here.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonResult Generate(params string[] expectedUrls)
|
|
||||||
{
|
|
||||||
var link = (string)null;
|
|
||||||
var query = _actionContext.HttpContext.Request.Query;
|
|
||||||
if (query.ContainsKey("link"))
|
|
||||||
{
|
|
||||||
var values = query
|
|
||||||
.Where(kvp => kvp.Key != "link" && kvp.Key != "link_action" && kvp.Key != "link_controller")
|
|
||||||
.ToDictionary(kvp => kvp.Key.Substring("link_".Length), kvp => (object)kvp.Value[0]);
|
|
||||||
|
|
||||||
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContext);
|
|
||||||
link = urlHelper.Action(query["link_action"], query["link_controller"], values);
|
|
||||||
}
|
|
||||||
|
|
||||||
var attributeRoutingInfo = _actionContext.ActionDescriptor.AttributeRouteInfo;
|
|
||||||
|
|
||||||
return new JsonResult(new
|
|
||||||
{
|
|
||||||
expectedUrls = expectedUrls,
|
|
||||||
actualUrl = _actionContext.HttpContext.Request.Path.Value,
|
|
||||||
routeName = attributeRoutingInfo == null ? null : attributeRoutingInfo.Name,
|
|
||||||
routeValues = new Dictionary<string, object>(_actionContext.RouteData.Values),
|
|
||||||
|
|
||||||
action = ((ControllerActionDescriptor) _actionContext.ActionDescriptor).ActionName,
|
|
||||||
controller = ((ControllerActionDescriptor)_actionContext.ActionDescriptor).ControllerName,
|
|
||||||
|
|
||||||
link,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\Common\TestResponseGenerator.cs" Link="TestResponseGenerator.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue