diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index ba9926ac57..8f4612672b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -618,6 +618,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal model.DisplayName = action.DisplayName; + // REVIEW: When should conventions be run + // Metadata should have lower precedence that data source metadata if (conventions != null) { foreach (var convention in conventions) @@ -638,8 +640,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal bool suppressLinkGeneration, bool suppressPathMatching) { - metadata.Add(action); - + // Add action metadata first so it has a low precedence if (action.EndpointMetadata != null) { foreach (var d in action.EndpointMetadata) @@ -647,6 +648,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal metadata.Add(d); } } + + metadata.Add(action); if (dataTokens != null) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 2292feeeaa..d354543cc6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -1305,6 +1305,36 @@ namespace Microsoft.AspNetCore.Mvc.Internal }); } + [Fact] + public void Endpoints_AttributeRoutes_ActionMetadataDoesNotOverrideDataSourceMetadata() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + CreateActionDescriptor(new { controller = "TestController", action = "TestAction" }, + "{controller}/{action}/{id?}", + new List { new RouteValuesAddressMetadata("fakeroutename", new RouteValueDictionary(new { fake = "Fake!" })) }) + ); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction/{id?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(0, matcherEndpoint.Order); + + var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata(); + Assert.Equal("{controller}/{action}/{id?}", routeValuesAddress.RouteName); + Assert.Equal("TestController", routeValuesAddress.RequiredValues["controller"]); + Assert.Equal("TestAction", routeValuesAddress.RequiredValues["action"]); + }); + } + private MvcEndpointDataSource CreateMvcEndpointDataSource( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null) @@ -1387,6 +1417,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal actionDescriptors.Add(CreateActionDescriptor(requiredValue, attributeRouteTemplate)); } + return GetActionDescriptorCollection(actionDescriptors.ToArray()); + } + + private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params ActionDescriptor[] actionDescriptors) + { var actionDescriptorCollectionProviderMock = new Mock(); actionDescriptorCollectionProviderMock .Setup(m => m.ActionDescriptors) @@ -1394,12 +1429,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal return actionDescriptorCollectionProviderMock.Object; } - private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null) - { - return CreateActionDescriptor(new { controller = controller, action = action, area = area }, attributeRouteTemplate: null); - } - - private ActionDescriptor CreateActionDescriptor(object requiredValues, string attributeRouteTemplate = null) + private ActionDescriptor CreateActionDescriptor( + object requiredValues, + string attributeRouteTemplate = null, + IList metadata = null) { var actionDescriptor = new ActionDescriptor(); var routeValues = new RouteValueDictionary(requiredValues); @@ -1415,6 +1448,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Template = attributeRouteTemplate }; } + actionDescriptor.EndpointMetadata = metadata; return actionDescriptor; }