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. /// Gets or sets the <see cref="ApiExplorerModel"/> for this action.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Setting the value of any properties on <see cref="ActionModel.ApiExplorer"/> will override any /// <see cref="ActionModel.ApiExplorer"/> allows configuration of settings for ApiExplorer
/// values set on the associated <see cref="ControllerModel.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> /// </remarks>
public ApiExplorerModel ApiExplorer { get; set; } public ApiExplorerModel ApiExplorer { get; set; }

View File

@ -9,10 +9,24 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
{ {
public ApplicationModel() public ApplicationModel()
{ {
ApiExplorer = new ApiExplorerModel();
Controllers = new List<ControllerModel>(); Controllers = new List<ControllerModel>();
Filters = new List<IFilter>(); 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<ControllerModel> Controllers { get; private set; }
public IList<IFilter> Filters { get; private set; } public IList<IFilter> Filters { get; private set; }

View File

@ -51,6 +51,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// <summary> /// <summary>
/// Gets or sets the <see cref="ApiExplorerModel"/> for this controller. /// Gets or sets the <see cref="ApiExplorerModel"/> for this controller.
/// </summary> /// </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 ApiExplorerModel ApiExplorer { get; set; }
public ApplicationModel Application { get; set; } public ApplicationModel Application { get; set; }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -55,7 +56,7 @@ namespace Microsoft.AspNet.Mvc
actionDescriptor.ControllerName = controller.ControllerName; actionDescriptor.ControllerName = controller.ControllerName;
actionDescriptor.ControllerTypeInfo = controller.ControllerType; actionDescriptor.ControllerTypeInfo = controller.ControllerType;
AddApiExplorerInfo(actionDescriptor, action, controller); AddApiExplorerInfo(actionDescriptor, application, controller, action);
AddRouteConstraints(removalConstraints, actionDescriptor, controller, action); AddRouteConstraints(removalConstraints, actionDescriptor, controller, action);
if (IsAttributeRoutedAction(actionDescriptor)) if (IsAttributeRoutedAction(actionDescriptor))
@ -284,18 +285,40 @@ namespace Microsoft.AspNet.Mvc
private static void AddApiExplorerInfo( private static void AddApiExplorerInfo(
ControllerActionDescriptor actionDescriptor, ControllerActionDescriptor actionDescriptor,
ActionModel action, ApplicationModel application,
ControllerModel controller) 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(
// ApiExplorer is only supported on attribute routed actions. actionDescriptor.DisplayName));
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() var apiExplorerActionData = new ApiDescriptionActionData()
{ {

View File

@ -1092,6 +1092,38 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal("Store", action.GetProperty<ApiDescriptionActionData>().GroupName); 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] [Theory]
[InlineData("A", typeof(ApiExplorerEnabledConventionalRoutedController))] [InlineData("A", typeof(ApiExplorerEnabledConventionalRoutedController))]
[InlineData("A", typeof(ApiExplorerEnabledActionConventionalRoutedController))] [InlineData("A", typeof(ApiExplorerEnabledActionConventionalRoutedController))]
@ -1110,6 +1142,23 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal(expected, ex.Message); 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 // Verifies the sequence of conventions running
[Fact] [Fact]
public void ApplyConventions_RunsInOrderOfDecreasingScope() public void ApplyConventions_RunsInOrderOfDecreasingScope()
@ -1352,7 +1401,7 @@ namespace Microsoft.AspNet.Mvc.Test
private ControllerActionDescriptorProvider GetProvider( private ControllerActionDescriptorProvider GetProvider(
TypeInfo type, TypeInfo type,
IOptions<MvcOptions> options) IApplicationModelConvention convention)
{ {
var modelBuilder = new StaticControllerModelBuilder(type); var modelBuilder = new StaticControllerModelBuilder(type);
@ -1361,6 +1410,9 @@ namespace Microsoft.AspNet.Mvc.Test
.SetupGet(ap => ap.CandidateAssemblies) .SetupGet(ap => ap.CandidateAssemblies)
.Returns(new Assembly[] { type.Assembly }); .Returns(new Assembly[] { type.Assembly });
var options = new MockMvcOptionsAccessor();
options.Options.ApplicationModelConventions.Add(convention);
return new ControllerActionDescriptorProvider( return new ControllerActionDescriptorProvider(
assemblyProvider.Object, assemblyProvider.Object,
modelBuilder, 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;
}
}
} }
} }