From a73d073eeab8266930523246b79e4b561111aec1 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 6 Sep 2018 11:17:01 -0700 Subject: [PATCH] Allow ApiControlelrAttribute to be applied to assemblies Fixes #7343 --- .../ApiControllerFacts.cs | 3 ++- .../ApiControllerAttribute.cs | 14 ++++++++----- .../ApiBehaviorApplicationModelProvider.cs | 15 ++++++++++++- .../ApiControllerFactsTest.cs | 21 +++++++++++++++++-- ...rnsTrue_IfAttributeIsDeclaredOnAssembly.cs | 11 ++++++++++ 5 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs index 84534a5d9f..d010d58894 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs @@ -25,7 +25,8 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return false; } - if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true)) + if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true) && + !method.ContainingAssembly.HasAttribute(symbolCache.IApiBehaviorMetadata)) { return false; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs index 0f1f1627bf..0be60296de 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs @@ -2,16 +2,20 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Mvc.Internal; namespace Microsoft.AspNetCore.Mvc { /// - /// Indicates that a type and all derived types are used to serve HTTP API responses. The presence of - /// this attribute can be used to target conventions, filters and other behaviors based on the purpose - /// of the controller. + /// Indicates that a type and all derived types are used to serve HTTP API responses. + /// + /// Controllers decorated with this attribute are configured with features and behavior targeted at improving the + /// developer experience for building APIs. + /// + /// + /// When decorated on an assembly, all controllers in the assembly will be treated as controllers with API behavior. + /// /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class ApiControllerAttribute : ControllerAttribute, IApiBehaviorMetadata { } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs index 54f24f8bb2..62831919de 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; @@ -75,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels { foreach (var controller in context.Result.Controllers) { - if (!controller.Attributes.OfType().Any()) + if (!IsApiController(controller)) { continue; } @@ -123,5 +124,17 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels return false; } } + + private static bool IsApiController(ControllerModel controller) + { + if (controller.Attributes.OfType().Any()) + { + return true; + } + + var controllerAssembly = controller.ControllerType.Assembly; + var assemblyAttributes = controllerAssembly.GetCustomAttributes(); + return assemblyAttributes.OfType().Any(); + } } } diff --git a/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs b/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs index f096f51455..e0875d0f93 100644 --- a/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiControllerFactsTest; using Microsoft.CodeAnalysis; using Xunit; @@ -110,9 +111,25 @@ namespace TestNamespace Assert.True(result); } - private Task GetCompilation() + [Fact] + public async Task IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly() { - var testSource = MvcTestSource.Read(GetType().Name, "TestFile"); + // Arrange + var compilation = await GetCompilation(nameof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly)); + var symbolCache = new ApiControllerSymbolCache(compilation); + var type = compilation.GetTypeByMetadataName(typeof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController).FullName); + var method = (IMethodSymbol)type.GetMembers(nameof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController.Action)).First(); + + // Act + var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method); + + // Assert + Assert.True(result); + } + + private Task GetCompilation(string testFile = "TestFile") + { + var testSource = MvcTestSource.Read(GetType().Name, testFile); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); return project.GetCompilationAsync(); diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs new file mode 100644 index 0000000000..a8b5d0ee8b --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiController] + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiControllerFactsTest +{ + public class IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController : ControllerBase + { + public IActionResult Action() => null; + } +}