diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs index 20ab53e773..276f3179f3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs @@ -36,6 +36,9 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions /// public IList ActionConstraints { get; set; } + /// + /// Gets or sets the endpoint metadata for this action. + /// public IList EndpointMetadata { get; set; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs index 27a548c37a..4c7e4aa8f2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs @@ -138,10 +138,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal ActionModel action) { var defaultControllerConstraints = Enumerable.Empty(); + var defaultControllerEndpointMetadata = Enumerable.Empty(); if (controller.Selectors.Count > 0) { defaultControllerConstraints = controller.Selectors[0].ActionConstraints .Where(constraint => !(constraint is IRouteTemplateProvider)); + defaultControllerEndpointMetadata = controller.Selectors[0].EndpointMetadata; } var actionDescriptors = new List(); @@ -164,8 +166,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal AddActionConstraints(actionDescriptor, actionSelector, controllerConstraints); - // REVIEW: Need to get metadata from controller - actionDescriptor.EndpointMetadata = actionSelector.EndpointMetadata.ToList(); + // Metadata for the action is more significant so order it before the controller metadata + var actionDescriptorMetadata = actionSelector.EndpointMetadata.ToList(); + actionDescriptorMetadata.AddRange(defaultControllerEndpointMetadata); + + actionDescriptor.EndpointMetadata = actionDescriptorMetadata; } return actionDescriptors; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index 9dbd548266..e35f55a9d9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -252,6 +253,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.Value); } + [Fact] + public void GetDescriptors_EndpointMetadata_ContainsAttributesFromActionAndController() + { + // Arrange & Act + var descriptors = GetDescriptors( + typeof(AuthorizeController).GetTypeInfo()); + + // Assert + Assert.Equal(2, descriptors.Count()); + + var anonymousAction = Assert.Single(descriptors, a => a.RouteValues["action"] == "AllowAnonymousAction"); + + Assert.NotNull(anonymousAction.EndpointMetadata); + + Assert.Collection(anonymousAction.EndpointMetadata, + metadata => Assert.IsType(metadata), + metadata => Assert.IsType(metadata)); + + var authorizeAction = Assert.Single(descriptors, a => a.RouteValues["action"] == "AuthorizeAction"); + + Assert.NotNull(authorizeAction.EndpointMetadata); + + Assert.Collection(authorizeAction.EndpointMetadata, + metadata => Assert.Equal("ActionPolicy", Assert.IsType(metadata).Policy), + metadata => Assert.Equal("ControllerPolicy", Assert.IsType(metadata).Policy)); + } + [Fact] public void GetDescriptors_ActionWithHttpMethods_AddedToEndpointMetadata() { @@ -272,7 +300,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.False(httpMethodMetadata.AcceptCorsPreflight); Assert.Equal("GET", Assert.Single(httpMethodMetadata.HttpMethods)); - }); + }, + metadata => Assert.IsType(metadata)); } [Fact] @@ -1865,6 +1894,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal public void AttributeRoutedAction() { } } + [Authorize("ControllerPolicy")] + private class AuthorizeController + { + [AllowAnonymous] + public void AllowAnonymousAction() { } + + [Authorize("ActionPolicy")] + public void AuthorizeAction() { } + } + private class EmptyController { } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs index 4b1e2c4429..2573072e01 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs @@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure } [Fact] - public void GetDescriptors_CopiesEndPointMetadataFromModel() + public void GetDescriptors_CopiesEndpointMetadataFromModel() { // Arrange var expected = new object();