Fix MVC integration with UseEndpoint (#8047)

This commit is contained in:
James Newton-King 2018-07-11 11:47:33 +12:00 committed by GitHub
parent 1e7be641ae
commit 183ecd85d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 147 deletions

View File

@ -122,7 +122,6 @@ namespace Microsoft.AspNetCore.Builder
configureRoutes(routeBuilder);
mvcEndpointDataSource.ConventionalEndpointInfos.AddRange(routeBuilder.EndpointInfos);
mvcEndpointDataSource.InitializeEndpoints();
return app.UseEndpoint();
}

View File

@ -24,8 +24,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private readonly IServiceProvider _serviceProvider;
private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders;
private readonly List<Endpoint> _endpoints;
private readonly object _lock = new object();
private IChangeToken _changeToken;
private bool _initialized;
public MvcEndpointDataSource(
IActionDescriptorCollectionProvider actions,
@ -62,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ConventionalEndpointInfos = new List<MvcEndpointInfo>();
}
public void InitializeEndpoints()
private void InitializeEndpoints()
{
foreach (var action in _actions.ActionDescriptors.Items)
{
@ -387,8 +389,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
public override IReadOnlyList<Endpoint> Endpoints
{
get
{
if (!_initialized)
{
lock (_lock)
{
if (!_initialized)
{
InitializeEndpoints();
_initialized = true;
}
}
}
return _endpoints;
}
}
// REVIEW: Infos added after endpoints are initialized will not be used
public List<MvcEndpointInfo> ConventionalEndpointInfos { get; }
private class RouteNameMetadata : IRouteNameMetadata

View File

@ -59,10 +59,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var endpoint = Assert.Single(dataSource.Endpoints);
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
var endpointValue = matcherEndpoint.RequiredValues["Name"];
@ -115,10 +115,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var endpoint = Assert.Single(dataSource.Endpoints);
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
var invokerDelegate = matcherEndpoint.Invoker((next) => Task.CompletedTask);
@ -192,7 +192,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var inspectors = finalEndpointTemplates
@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.ToArray();
// Assert
Assert.Collection(dataSource.Endpoints, inspectors);
Assert.Collection(endpoints, inspectors);
}
[Theory]
@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var inspectors = finalEndpointTemplates
@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.ToArray();
// Assert
Assert.Collection(dataSource.Endpoints, inspectors);
Assert.Collection(endpoints, inspectors);
}
[Fact]
@ -243,10 +243,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new RouteValueDictionary(new { action = "TestAction" })));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(dataSource.Endpoints,
Assert.Collection(endpoints,
(e) => Assert.Equal("TestController", Assert.IsType<MatcherEndpoint>(e).Template),
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<MatcherEndpoint>(e).Template));
}
@ -266,10 +266,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
constraints: new RouteValueDictionary(new { action = "(TestAction1|TestAction2)" })));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(dataSource.Endpoints,
Assert.Collection(endpoints,
(e) => Assert.Equal("TestController/TestAction1", Assert.IsType<MatcherEndpoint>(e).Template),
(e) => Assert.Equal("TestController/TestAction2", Assert.IsType<MatcherEndpoint>(e).Template));
}
@ -291,14 +291,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
endpointInfoRoute));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
var inspectors = finalEndpointTemplates
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<MatcherEndpoint>(e).Template)))
.ToArray();
// Assert
Assert.Collection(dataSource.Endpoints, inspectors);
Assert.Collection(endpoints, inspectors);
}
[Fact]
@ -312,10 +312,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
CreateEndpointInfo(string.Empty, "named/{controller}/{action}/{id?}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var endpoint = Assert.Single(dataSource.Endpoints);
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
Assert.Null(routeNameMetadata);
@ -333,11 +333,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
CreateEndpointInfo("namedRoute", "named/{controller}/{action}/{id?}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
dataSource.Endpoints,
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
@ -372,11 +372,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
"named/{controller}/{action}/{id?}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
dataSource.Endpoints,
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
@ -413,10 +413,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Empty(dataSource.Endpoints);
Assert.Empty(endpoints);
}
// Since area, controller, action and page are special, check to see if the followin test succeeds for a
@ -431,10 +431,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Empty(dataSource.Endpoints);
Assert.Empty(endpoints);
}
[Fact]
@ -447,10 +447,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Empty(dataSource.Endpoints);
Assert.Empty(endpoints);
}
[Fact]
@ -463,10 +463,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area=admin}/{controller}/{action}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Empty(dataSource.Endpoints);
Assert.Empty(endpoints);
}
[Fact]
@ -479,10 +479,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
Assert.Empty(dataSource.Endpoints);
Assert.Empty(endpoints);
}
[Fact]
@ -495,10 +495,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var endpoint = Assert.Single(dataSource.Endpoints);
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
Assert.Equal("Foo/Bar", matcherEndpoint.Template);
AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults);
@ -517,10 +517,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var endpoint = Assert.Single(dataSource.Endpoints);
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
Assert.Equal("Foo/Bar", matcherEndpoint.Template);
AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults);
@ -540,10 +540,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}/{subscription=general}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var endpoint = Assert.Single(dataSource.Endpoints);
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.Template);
AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults);
@ -562,10 +562,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}"));
// Act
dataSource.InitializeEndpoints();
var endpoints = dataSource.Endpoints;
// Assert
var endpoint = Assert.Single(dataSource.Endpoints);
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
Assert.Equal("Foo/Bar", matcherEndpoint.Template);
AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults);

View File

@ -1,103 +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.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class RouteDataTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
{
public RouteDataTest(MvcTestFixture<BasicWebSite.Startup> fixture)
{
Client = fixture.CreateDefaultClient();
}
public HttpClient Client { get; }
[Fact]
public async Task RouteData_Routers_ConventionalRoute()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/Routing/Conventional");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ResultData>(body);
Assert.Equal(
new string[]
{
typeof(RouteCollection).FullName,
typeof(Route).FullName,
typeof(MvcRouteHandler).FullName,
},
result.Routers);
}
[Fact]
public async Task RouteData_Routers_AttributeRoute()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/Routing/Attribute");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ResultData>(body);
Assert.Equal(new string[]
{
typeof(RouteCollection).FullName,
typeof(AttributeRoute).FullName,
typeof(MvcAttributeRouteHandler).FullName,
},
result.Routers);
}
// Verifies that components in the MVC pipeline can modify datatokens
// without impacting any static data.
//
// This does two request, to verify that the data in the route is not modified
[Fact]
public async Task RouteData_DataTokens_FilterCanSetDataTokens()
{
// Arrange
var response = await Client.GetAsync("http://localhost/Routing/DataTokens");
// Guard
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ResultData>(body);
Assert.Single(result.DataTokens);
Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "DataTokens");
// Act
response = await Client.GetAsync("http://localhost/Routing/Conventional");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
body = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<ResultData>(body);
Assert.Single(result.DataTokens);
Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "Conventional");
}
private class ResultData
{
public Dictionary<string, object> DataTokens { get; set; }
public string[] Routers { get; set; }
}
}
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;
using Newtonsoft.Json;
using Xunit;
@ -22,6 +23,85 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public HttpClient Client { get; }
[Fact]
public async Task RouteData_Routers_ConventionalRoute()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/RouteData/Conventional");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ResultData>(body);
Assert.Equal(
new string[]
{
typeof(RouteCollection).FullName,
typeof(Route).FullName,
typeof(MvcRouteHandler).FullName,
},
result.Routers);
}
[Fact]
public async Task RouteData_Routers_AttributeRoute()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/RouteData/Attribute");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ResultData>(body);
Assert.Equal(new string[]
{
typeof(RouteCollection).FullName,
typeof(AttributeRoute).FullName,
typeof(MvcAttributeRouteHandler).FullName,
},
result.Routers);
}
// Verifies that components in the MVC pipeline can modify datatokens
// without impacting any static data.
//
// This does two request, to verify that the data in the route is not modified
[Fact]
public async Task RouteData_DataTokens_FilterCanSetDataTokens()
{
// Arrange
var response = await Client.GetAsync("http://localhost/RouteData/DataTokens");
// Guard
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ResultData>(body);
Assert.Single(result.DataTokens);
Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "DataTokens");
// Act
response = await Client.GetAsync("http://localhost/RouteData/Conventional");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
body = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<ResultData>(body);
Assert.Single(result.DataTokens);
Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "Conventional");
}
private class ResultData
{
public Dictionary<string, object> DataTokens { get; set; }
public string[] Routers { get; set; }
}
[Fact]
public virtual async Task ConventionalRoutedController_ActionIsReachable()
{

View File

@ -6,16 +6,16 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
namespace BasicWebSite
namespace RoutingWebSite.Controllers
{
public class RoutingController : Controller
public class RouteDataController : Controller
{
public object Conventional()
{
return GetData();
}
[Route("Routing/Attribute")]
[Route("RouteData/Attribute")]
public object Attribute()
{
return GetData();