diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index bf8df39064..df110bee25 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -577,16 +577,16 @@ private static EndpointMetadataCollection BuildEndpointMetadata( bool suppressLinkGeneration, bool suppressPathMatching) { - var metadata = new List - { - action - }; + var metadata = new List(); + // Add action metadata first so it has a low precedence if (action.EndpointMetadata != null) { metadata.AddRange(action.EndpointMetadata); } + metadata.Add(action); + if (dataTokens != null) { metadata.Add(new DataTokensMetadata(dataTokens)); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 618b281eba..a3e0ebe5c3 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 @@ public void Endpoints_AttributeRoutes_DefaultDifferentCaseFromRouteValue_UseDefa }); } + [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) @@ -1381,6 +1411,11 @@ private IActionDescriptorCollectionProvider GetActionDescriptorCollection(string actionDescriptors.Add(CreateActionDescriptor(requiredValue, attributeRouteTemplate)); } + return GetActionDescriptorCollection(actionDescriptors.ToArray()); + } + + private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params ActionDescriptor[] actionDescriptors) + { var actionDescriptorCollectionProviderMock = new Mock(); actionDescriptorCollectionProviderMock .Setup(m => m.ActionDescriptors) @@ -1388,12 +1423,10 @@ private IActionDescriptorCollectionProvider GetActionDescriptorCollection(string 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); @@ -1409,6 +1442,7 @@ private ActionDescriptor CreateActionDescriptor(object requiredValues, string at Template = attributeRouteTemplate }; } + actionDescriptor.EndpointMetadata = metadata; return actionDescriptor; }