Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Require attribute routing with [ApiController]
Browse files Browse the repository at this point in the history
Fixes #6870
  • Loading branch information
pranavkm authored and rynowak committed Oct 4, 2017
1 parent e659b57 commit 950db65
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ public ApiControllerApplicationModelProvider(IOptions<ApiBehaviorOptions> apiBeh
}

/// <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>
public int Order => -1000 + 10;
public int Order => -1000 + 100;

public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
Expand All @@ -45,21 +46,19 @@ public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
foreach (var controllerModel in context.Result.Controllers)
{
if (controllerModel.Attributes.OfType<IApiBehaviorMetadata>().Any())
{
if (_apiBehaviorOptions.EnableModelStateInvalidFilter)
{
Debug.Assert(_apiBehaviorOptions.InvalidModelStateResponseFactory != null);
controllerModel.Filters.Add(_modelStateInvalidFilter);
}

continue;
}
var isApiController = controllerModel.Attributes.OfType<IApiBehaviorMetadata>().Any();
var controllerHasSelectorModel = controllerModel.Selectors.Any(s => s.AttributeRouteModel != null);

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)
{
Debug.Assert(_apiBehaviorOptions.InvalidModelStateResponseFactory != null);
Expand Down
14 changes: 14 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Core/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -415,4 +415,7 @@
<data name="ValidationProblemDescription_Title" xml:space="preserve">
<value>One or more validation errors occured.</value>
</data>
<data name="ApiController_AttributeRouteRequired" xml:space="preserve">
<value>Action methods on controllers annotated with {0} must have an attribute route.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ public void OnProvidersExecuting_AddsModelStateInvalidFilter_IfTypeIsAnnotatedWi
{
InvalidModelStateResponseFactory = _ => null,
});

var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance);

// Act
provider.OnProvidersExecuting(context);

// Assert
var controllerModel = Assert.Single(context.Result.Controllers);
Assert.IsType<ModelStateInvalidFilter>(controllerModel.Filters.Last());
var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
Assert.IsType<ModelStateInvalidFilter>(actionModel.Filters.Last());
}

[Fact]
Expand Down Expand Up @@ -109,6 +109,25 @@ public void OnProvidersExecuting_SkipsAddingFilterToActionIfFeatureIsDisabledUsi
});
}

[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)
{
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
Expand All @@ -117,20 +136,28 @@ private static ApplicationModelProviderContext GetContext(Type type)
}

[ApiController]
[Route("TestApi")]
private class TestApiController : Controller
{
[HttpGet]
public IActionResult TestAction() => null;
}


private class SimpleController : Controller
{
public IActionResult ActionWithoutFilter() => null;

[TestApiBehavior]
[HttpGet("/Simple/ActionWithFilter")]
public IActionResult ActionWithFilter() => null;
}

[ApiController]
private class ActionsWithoutAttributeRouting
{
public IActionResult Index() => null;
}

[AttributeUsage(AttributeTargets.Method)]
private class TestApiBehavior : Attribute, IApiBehaviorMetadata
{
Expand Down

0 comments on commit 950db65

Please sign in to comment.