Require attribute routing with [ApiController]

Fixes #6870
This commit is contained in:
Pranav K 2017-09-22 17:35:49 -07:00 committed by Ryan Nowak
parent e659b578f6
commit 950db6587c
4 changed files with 60 additions and 17 deletions

View File

@ -33,9 +33,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
/// <remarks> /// <remarks>
/// Order is set to execute after the <see cref="DefaultApplicationModelProvider"/>. /// Order is set to execute after the <see cref="DefaultApplicationModelProvider"/> and allow any other user
/// <see cref="IApplicationModelProvider"/> that configure routing to execute.
/// </remarks> /// </remarks>
public int Order => -1000 + 10; public int Order => -1000 + 100;
public void OnProvidersExecuted(ApplicationModelProviderContext context) public void OnProvidersExecuted(ApplicationModelProviderContext context)
{ {
@ -45,21 +46,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
foreach (var controllerModel in context.Result.Controllers) foreach (var controllerModel in context.Result.Controllers)
{ {
if (controllerModel.Attributes.OfType<IApiBehaviorMetadata>().Any()) var isApiController = controllerModel.Attributes.OfType<IApiBehaviorMetadata>().Any();
{ var controllerHasSelectorModel = controllerModel.Selectors.Any(s => s.AttributeRouteModel != null);
if (_apiBehaviorOptions.EnableModelStateInvalidFilter)
{
Debug.Assert(_apiBehaviorOptions.InvalidModelStateResponseFactory != null);
controllerModel.Filters.Add(_modelStateInvalidFilter);
}
continue;
}
foreach (var actionModel in controllerModel.Actions) foreach (var actionModel in controllerModel.Actions)
{ {
if (actionModel.Attributes.OfType<IApiBehaviorMetadata>().Any()) if (isApiController || actionModel.Attributes.OfType<IApiBehaviorMetadata>().Any())
{ {
if (!controllerHasSelectorModel && !actionModel.Selectors.Any(s => s.AttributeRouteModel != null))
{
// Require attribute routing with controllers annotated with ApiControllerAttribute
throw new InvalidOperationException(Resources.FormatApiController_AttributeRouteRequired(nameof(ApiControllerAttribute)));
}
if (_apiBehaviorOptions.EnableModelStateInvalidFilter) if (_apiBehaviorOptions.EnableModelStateInvalidFilter)
{ {
Debug.Assert(_apiBehaviorOptions.InvalidModelStateResponseFactory != null); Debug.Assert(_apiBehaviorOptions.InvalidModelStateResponseFactory != null);

View File

@ -1340,6 +1340,20 @@ namespace Microsoft.AspNetCore.Mvc.Core
internal static string FormatValidationProblemDescription_Title() internal static string FormatValidationProblemDescription_Title()
=> GetString("ValidationProblemDescription_Title"); => GetString("ValidationProblemDescription_Title");
/// <summary>
/// Action methods on controllers annotated with {0} must have an attribute route.
/// </summary>
internal static string ApiController_AttributeRouteRequired
{
get => GetString("ApiController_AttributeRouteRequired");
}
/// <summary>
/// Action methods on controllers annotated with {0} must have an attribute route.
/// </summary>
internal static string FormatApiController_AttributeRouteRequired(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ApiController_AttributeRouteRequired"), p0);
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -415,4 +415,7 @@
<data name="ValidationProblemDescription_Title" xml:space="preserve"> <data name="ValidationProblemDescription_Title" xml:space="preserve">
<value>One or more validation errors occured.</value> <value>One or more validation errors occured.</value>
</data> </data>
<data name="ApiController_AttributeRouteRequired" xml:space="preserve">
<value>Action methods on controllers annotated with {0} must have an attribute route.</value>
</data>
</root> </root>

View File

@ -22,15 +22,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
InvalidModelStateResponseFactory = _ => null, InvalidModelStateResponseFactory = _ => null,
}); });
var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance); var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance);
// Act // Act
provider.OnProvidersExecuting(context); provider.OnProvidersExecuting(context);
// Assert // Assert
var controllerModel = Assert.Single(context.Result.Controllers); var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
Assert.IsType<ModelStateInvalidFilter>(controllerModel.Filters.Last()); Assert.IsType<ModelStateInvalidFilter>(actionModel.Filters.Last());
} }
[Fact] [Fact]
@ -109,6 +109,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}); });
} }
[Fact]
public void OnProvidersExecuting_ThrowsIfControllerWithAttribute_HasActionsWithoutAttributeRouting()
{
// Arrange
var context = GetContext(typeof(ActionsWithoutAttributeRouting));
var options = new TestOptionsManager<ApiBehaviorOptions>(new ApiBehaviorOptions
{
InvalidModelStateResponseFactory = _ => null,
});
var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
Assert.Equal(
"Action methods on controllers annotated with ApiControllerAttribute must have an attribute route.",
ex.Message);
}
private static ApplicationModelProviderContext GetContext(Type type) private static ApplicationModelProviderContext GetContext(Type type)
{ {
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
@ -117,20 +136,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
[ApiController] [ApiController]
[Route("TestApi")]
private class TestApiController : Controller private class TestApiController : Controller
{ {
[HttpGet]
public IActionResult TestAction() => null; public IActionResult TestAction() => null;
} }
private class SimpleController : Controller private class SimpleController : Controller
{ {
public IActionResult ActionWithoutFilter() => null; public IActionResult ActionWithoutFilter() => null;
[TestApiBehavior] [TestApiBehavior]
[HttpGet("/Simple/ActionWithFilter")]
public IActionResult ActionWithFilter() => null; public IActionResult ActionWithFilter() => null;
} }
[ApiController]
private class ActionsWithoutAttributeRouting
{
public IActionResult Index() => null;
}
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
private class TestApiBehavior : Attribute, IApiBehaviorMetadata private class TestApiBehavior : Attribute, IApiBehaviorMetadata
{ {