Add ApiExplorer details to ApplicationModel

This change allows you to set global defaults for ApiExplorer on the
ApplicationModel. Additionally, we're more lenient about configuring
ApiExplorer = on with conventional routing. If you turn on ApiExplorer at
the application level, we'll just skip over all conventionally routed
controllers instead of throwing.
This commit is contained in:
Ryan Nowak 2015-01-23 15:13:59 -08:00
parent 8399dc5f4e
commit ee419e2442
5 changed files with 128 additions and 14 deletions

View File

@ -58,8 +58,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// Gets or sets the <see cref="ApiExplorerModel"/> for this action.
/// </summary>
/// <remarks>
/// Setting the value of any properties on <see cref="ActionModel.ApiExplorer"/> will override any
/// values set on the associated <see cref="ControllerModel.ApiExplorer"/>.
/// <see cref="ActionModel.ApiExplorer"/> allows configuration of settings for ApiExplorer
/// which apply to the action.
///
/// Settings applied by <see cref="ActionModel.ApiExplorer"/> override settings from
/// <see cref="ApplicationModel.ApiExplorer"/> and <see cref="ControllerModel.ApiExplorer"/>.
/// </remarks>
public ApiExplorerModel ApiExplorer { get; set; }

View File

@ -9,10 +9,24 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
{
public ApplicationModel()
{
ApiExplorer = new ApiExplorerModel();
Controllers = new List<ControllerModel>();
Filters = new List<IFilter>();
}
/// <summary>
/// Gets or sets the <see cref="ApiExplorerModel"/> for the application.
/// </summary>
/// <remarks>
/// <see cref="ApplicationModel.ApiExplorer"/> allows configuration of default settings
/// for ApiExplorer that apply to all actions unless overridden by
/// <see cref="ControllerModel.ApiExplorer"/> or <see cref="ActionModel.ApiExplorer"/>.
///
/// If using <see cref="ApplicationModel.ApiExplorer"/> to set <see cref="ApiExplorerModel.IsVisible"/> to
/// <c>true</c>, this setting will only be honored for actions which use attribute routing.
/// </remarks>
public ApiExplorerModel ApiExplorer { get; set; }
public IList<ControllerModel> Controllers { get; private set; }
public IList<IFilter> Filters { get; private set; }

View File

@ -51,6 +51,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// <summary>
/// Gets or sets the <see cref="ApiExplorerModel"/> for this controller.
/// </summary>
/// <remarks>
/// <see cref="ControllerModel.ApiExplorer"/> allows configuration of settings for ApiExplorer
/// which apply to all actions in the controller unless overridden by <see cref="ActionModel.ApiExplorer"/>.
///
/// Settings applied by <see cref="ControllerModel.ApiExplorer"/> override settings from
/// <see cref="ApplicationModel.ApiExplorer"/>.
/// </remarks>
public ApiExplorerModel ApiExplorer { get; set; }
public ApplicationModel Application { get; set; }

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
@ -55,7 +56,7 @@ namespace Microsoft.AspNet.Mvc
actionDescriptor.ControllerName = controller.ControllerName;
actionDescriptor.ControllerTypeInfo = controller.ControllerType;
AddApiExplorerInfo(actionDescriptor, action, controller);
AddApiExplorerInfo(actionDescriptor, application, controller, action);
AddRouteConstraints(removalConstraints, actionDescriptor, controller, action);
if (IsAttributeRoutedAction(actionDescriptor))
@ -284,18 +285,40 @@ namespace Microsoft.AspNet.Mvc
private static void AddApiExplorerInfo(
ControllerActionDescriptor actionDescriptor,
ActionModel action,
ControllerModel controller)
ApplicationModel application,
ControllerModel controller,
ActionModel action)
{
var apiExplorerIsVisible = action.ApiExplorer?.IsVisible ?? controller.ApiExplorer?.IsVisible ?? false;
if (apiExplorerIsVisible)
var isVisible =
action.ApiExplorer?.IsVisible ??
controller.ApiExplorer?.IsVisible ??
application.ApiExplorer?.IsVisible ??
false;
var isVisibleSetOnActionOrController =
action.ApiExplorer?.IsVisible ??
controller.ApiExplorer?.IsVisible ??
false;
// ApiExplorer isn't supported on conventional-routed actions, but we still allow you to configure
// it at the application level when you have a mix of controller types. We'll just skip over enabling
// ApiExplorer for conventional-routed controllers when this happens.
var isVisibleSetOnApplication = application.ApiExplorer?.IsVisible ?? false;
if (isVisibleSetOnActionOrController && !IsAttributeRoutedAction(actionDescriptor))
{
if (!IsAttributeRoutedAction(actionDescriptor))
{
// ApiExplorer is only supported on attribute routed actions.
throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction(
actionDescriptor.DisplayName));
}
// ApiExplorer is only supported on attribute routed actions.
throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction(
actionDescriptor.DisplayName));
}
else if (isVisibleSetOnApplication && !IsAttributeRoutedAction(actionDescriptor))
{
// This is the case where we're going to be lenient, just ignore it.
}
else if (isVisible)
{
Debug.Assert(IsAttributeRoutedAction(actionDescriptor));
var apiExplorerActionData = new ApiDescriptionActionData()
{

View File

@ -1092,6 +1092,38 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal("Store", action.GetProperty<ApiDescriptionActionData>().GroupName);
}
[Fact]
public void ApiExplorer_IsVisibleOnApplication_CanOverrideOnController()
{
// Arrange
var convention = new ApiExplorerIsVisibleConvention(isVisible: true);
var provider = GetProvider(typeof(ApiExplorerExplicitlyNotVisibleController).GetTypeInfo(), convention);
// Act
var actions = provider.GetDescriptors();
// Assert
var action = Assert.Single(actions);
Assert.Null(action.GetProperty<ApiDescriptionActionData>());
}
[Fact]
public void ApiExplorer_IsVisibleOnApplication_CanOverrideOnAction()
{
// Arrange
var convention = new ApiExplorerIsVisibleConvention(isVisible: true);
var provider = GetProvider(
typeof(ApiExplorerExplicitlyNotVisibleOnActionController).GetTypeInfo(),
convention);
// Act
var actions = provider.GetDescriptors();
// Assert
var action = Assert.Single(actions);
Assert.Null(action.GetProperty<ApiDescriptionActionData>());
}
[Theory]
[InlineData("A", typeof(ApiExplorerEnabledConventionalRoutedController))]
[InlineData("A", typeof(ApiExplorerEnabledActionConventionalRoutedController))]
@ -1110,6 +1142,23 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal(expected, ex.Message);
}
[Fact]
public void ApiExplorer_SkipsConventionalRoutedController_WhenConfiguredOnApplication()
{
// Arrange
var convention = new ApiExplorerIsVisibleConvention(isVisible: true);
var provider = GetProvider(
typeof(ConventionallyRoutedController).GetTypeInfo(),
convention);
// Act
var actions = provider.GetDescriptors();
// Assert
var action = Assert.Single(actions);
Assert.Null(action.GetProperty<ApiDescriptionActionData>());
}
// Verifies the sequence of conventions running
[Fact]
public void ApplyConventions_RunsInOrderOfDecreasingScope()
@ -1352,7 +1401,7 @@ namespace Microsoft.AspNet.Mvc.Test
private ControllerActionDescriptorProvider GetProvider(
TypeInfo type,
IOptions<MvcOptions> options)
IApplicationModelConvention convention)
{
var modelBuilder = new StaticControllerModelBuilder(type);
@ -1361,6 +1410,9 @@ namespace Microsoft.AspNet.Mvc.Test
.SetupGet(ap => ap.CandidateAssemblies)
.Returns(new Assembly[] { type.Assembly });
var options = new MockMvcOptionsAccessor();
options.Options.ApplicationModelConventions.Add(convention);
return new ControllerActionDescriptorProvider(
assemblyProvider.Object,
modelBuilder,
@ -1894,5 +1946,20 @@ namespace Microsoft.AspNet.Mvc.Test
{
}
}
private class ApiExplorerIsVisibleConvention : IApplicationModelConvention
{
private bool _isVisible;
public ApiExplorerIsVisibleConvention(bool isVisible)
{
_isVisible = isVisible;
}
public void Apply([NotNull] ApplicationModel application)
{
application.ApiExplorer.IsVisible = _isVisible;
}
}
}
}