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;
+ }
+}