Issue #1445 - Cleanup on MvcOptions.ApplicationModelConventions.

This commit is contained in:
sornaks 2015-02-06 10:54:17 -08:00
parent f474f7116a
commit d2c3985cd6
20 changed files with 358 additions and 94 deletions

View File

@ -0,0 +1,93 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.ApplicationModels;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Contains the extension methods for <see cref="MvcOptions.Conventions"/>.
/// </summary>
public static class ApplicationModelConventionExtensions
{
/// <summary>
/// Adds a <see cref="IControllerModelConvention"/> to all the controllers in the application.
/// </summary>
/// <param name="conventions">The list of <see cref="IApplicationModelConvention">
/// in <see cref="MvcOptions"/>.</param>
/// <param name="controllerModelConvention">The <see cref="IControllerModelConvention"/> which needs to be
/// added.</param>
public static void Add(
[NotNull] this List<IApplicationModelConvention> conventions,
[NotNull] IControllerModelConvention controllerModelConvention)
{
conventions.Add(new ControllerApplicationModelConvention(controllerModelConvention));
}
/// <summary>
/// Adds a <see cref="IActionModelConvention"/> to all the actions in the application.
/// </summary>
/// <param name="conventions">The list of <see cref="IApplicationModelConvention">
/// in <see cref="MvcOptions"/>.</param>
/// <param name="actionModelConvention">The <see cref="IActionModelConvention"/> which needs to be
/// added.</param>
public static void Add(
this List<IApplicationModelConvention> conventions,
IActionModelConvention actionModelConvention)
{
conventions.Add(new ActionApplicationModelConvention(actionModelConvention));
}
private class ActionApplicationModelConvention : IApplicationModelConvention
{
private IActionModelConvention _actionModelConvention;
/// <summary>
/// Initializes a new instance of <see cref="ActionApplicationModelConvention"/>.
/// </summary>
/// <param name="actionModelConvention">The action convention to be applied on all actions
/// in the application.</param>
public ActionApplicationModelConvention([NotNull] IActionModelConvention actionModelConvention)
{
_actionModelConvention = actionModelConvention;
}
/// <inheritdoc />
public void Apply([NotNull] ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
foreach (var action in controller.Actions)
{
_actionModelConvention.Apply(action);
}
}
}
}
private class ControllerApplicationModelConvention : IApplicationModelConvention
{
private IControllerModelConvention _controllerModelConvention;
/// <summary>
/// Initializes a new instance of <see cref="ControllerApplicationModelConvention"/>.
/// </summary>
/// <param name="controllerConvention">The controller convention to be applied on all controllers
/// in the application.</param>
public ControllerApplicationModelConvention([NotNull] IControllerModelConvention controllerConvention)
{
_controllerModelConvention = controllerConvention;
}
/// <inheritdoc />
public void Apply([NotNull] ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
_controllerModelConvention.Apply(controller);
}
}
}
}
}

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// Allows customization of the of the <see cref="ApplicationModel"/>.
/// </summary>
/// <remarks>
/// Implementaions of this interface can be registered in <see cref="MvcOptions.ApplicationModelConventions"/>
/// Implementaions of this interface can be registered in <see cref="MvcOptions.Conventions"/>
/// to customize metadata about the application.
///
/// <see cref="IApplicationModelConvention"/> run before other types of customizations to the

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Mvc
private readonly IControllerModelBuilder _applicationModelBuilder;
private readonly IAssemblyProvider _assemblyProvider;
private readonly IReadOnlyList<IFilter> _globalFilters;
private readonly IEnumerable<IApplicationModelConvention> _modelConventions;
private readonly IEnumerable<IApplicationModelConvention> _conventions;
private readonly ILogger _logger;
public ControllerActionDescriptorProvider([NotNull] IAssemblyProvider assemblyProvider,
@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc
_assemblyProvider = assemblyProvider;
_applicationModelBuilder = applicationModelBuilder;
_globalFilters = globalFilters.Filters;
_modelConventions = optionsAccessor.Options.ApplicationModelConventions;
_conventions = optionsAccessor.Options.Conventions;
_logger = loggerFactory.Create<ControllerActionDescriptorProvider>();
}
@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Mvc
public IEnumerable<ControllerActionDescriptor> GetDescriptors()
{
var applicationModel = BuildModel();
ApplicationModelConventions.ApplyConventions(applicationModel, _modelConventions);
ApplicationModelConventions.ApplyConventions(applicationModel, _conventions);
if (_logger.IsEnabled(LogLevel.Verbose))
{
foreach (var controller in applicationModel.Controllers)

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc
public MvcOptions()
{
ApplicationModelConventions = new List<IApplicationModelConvention>();
Conventions = new List<IApplicationModelConvention>();
ModelBinders = new List<ModelBinderDescriptor>();
ViewEngines = new List<ViewEngineDescriptor>();
ValueProviderFactories = new List<ValueProviderFactoryDescriptor>();
@ -133,7 +133,7 @@ namespace Microsoft.AspNet.Mvc
/// Gets a list of <see cref="IApplicationModelConvention"/> instances that will be applied to
/// the <see cref="ApplicationModel"/> when discovering actions.
/// </summary>
public List<IApplicationModelConvention> ApplicationModelConventions { get; private set; }
public List<IApplicationModelConvention> Conventions { get; private set; }
/// <summary>
/// Gets or sets the flag which causes content negotiation to ignore Accept header

View File

@ -8,7 +8,7 @@ using Microsoft.AspNet.Mvc.ApplicationModels;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class WebApiActionConventionsApplicationModelConvention : IApplicationModelConvention
public class WebApiActionConventionsApplicationModelConvention : IControllerModelConvention
{
private static readonly string[] SupportedHttpMethodConventions = new string[]
{
@ -21,13 +21,31 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
"OPTIONS",
};
public void Apply(ApplicationModel application)
public void Apply(ControllerModel controller)
{
foreach (var controller in application.Controllers)
if (IsConventionApplicable(controller))
{
if (IsConventionApplicable(controller))
var newActions = new List<ActionModel>();
foreach (var action in controller.Actions)
{
Apply(controller);
SetHttpMethodFromConvention(action);
// Action Name doesn't really come into play with attribute routed actions. However for a
// non-attribute-routed action we need to create a 'named' version and an 'unnamed' version.
if (!IsActionAttributeRouted(action))
{
var namedAction = action;
var unnamedAction = new ActionModel(namedAction);
unnamedAction.RouteConstraints.Add(new UnnamedActionRouteConstraint());
newActions.Add(unnamedAction);
}
}
foreach (var action in newActions)
{
controller.Actions.Add(action);
}
}
}
@ -37,32 +55,6 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
return controller.Attributes.OfType<IUseWebApiActionConventions>().Any();
}
private void Apply(ControllerModel controller)
{
var newActions = new List<ActionModel>();
foreach (var action in controller.Actions)
{
SetHttpMethodFromConvention(action);
// Action Name doesn't really come into play with attribute routed actions. However for a
// non-attribute-routed action we need to create a 'named' version and an 'unnamed' version.
if (!IsActionAttributeRouted(action))
{
var namedAction = action;
var unnamedAction = new ActionModel(namedAction);
unnamedAction.RouteConstraints.Add(new UnnamedActionRouteConstraint());
newActions.Add(unnamedAction);
}
}
foreach (var action in newActions)
{
controller.Actions.Add(action);
}
}
private bool IsActionAttributeRouted(ActionModel action)
{
if (action.Controller.AttributeRoutes.Count > 0)

View File

@ -6,16 +6,13 @@ using Microsoft.AspNet.Mvc.ApplicationModels;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class WebApiOverloadingApplicationModelConvention : IApplicationModelConvention
public class WebApiOverloadingApplicationModelConvention : IActionModelConvention
{
public void Apply(ApplicationModel application)
public void Apply(ActionModel action)
{
foreach (var controller in application.Controllers)
if (IsConventionApplicable(action.Controller))
{
if (IsConventionApplicable(controller))
{
Apply(controller);
}
action.ActionConstraints.Add(new OverloadActionConstraint());
}
}
@ -23,13 +20,5 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
return controller.Attributes.OfType<IUseWebApiOverloading>().Any();
}
private void Apply(ControllerModel controller)
{
foreach (var action in controller.Actions)
{
action.ActionConstraints.Add(new OverloadActionConstraint());
}
}
}
}

View File

@ -9,27 +9,11 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class WebApiParameterConventionsApplicationModelConvention : IApplicationModelConvention
public class WebApiParameterConventionsApplicationModelConvention : IActionModelConvention
{
public void Apply(ApplicationModel application)
public void Apply(ActionModel action)
{
foreach (var controller in application.Controllers)
{
if (IsConventionApplicable(controller))
{
Apply(controller);
}
}
}
private bool IsConventionApplicable(ControllerModel controller)
{
return controller.Attributes.OfType<IUseWebApiParameterConventions>().Any();
}
private void Apply(ControllerModel controller)
{
foreach (var action in controller.Actions)
if (IsConventionApplicable(action.Controller))
{
foreach (var parameter in action.Parameters)
{
@ -57,5 +41,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
}
}
}
private bool IsConventionApplicable(ControllerModel controller)
{
return controller.Attributes.OfType<IUseWebApiParameterConventions>().Any();
}
}
}

View File

@ -6,7 +6,7 @@ using Microsoft.AspNet.Mvc.ApplicationModels;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class WebApiRoutesApplicationModelConvention : IApplicationModelConvention
public class WebApiRoutesApplicationModelConvention : IControllerModelConvention
{
private readonly string _area;
@ -15,14 +15,11 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
_area = area;
}
public void Apply(ApplicationModel application)
public void Apply(ControllerModel controller)
{
foreach (var controller in application.Controllers)
if (IsConventionApplicable(controller))
{
if (IsConventionApplicable(controller))
{
Apply(controller);
}
controller.RouteConstraints.Add(new AreaAttribute(_area));
}
}
@ -30,10 +27,5 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
return controller.Attributes.OfType<IUseWebApiRoutes>().Any();
}
private void Apply(ControllerModel controller)
{
controller.RouteConstraints.Add(new AreaAttribute(_area));
}
}
}

View File

@ -22,10 +22,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
public void Configure(MvcOptions options, string name = "")
{
// Add webapi behaviors to controllers with the appropriate attributes
options.ApplicationModelConventions.Add(new WebApiActionConventionsApplicationModelConvention());
options.ApplicationModelConventions.Add(new WebApiParameterConventionsApplicationModelConvention());
options.ApplicationModelConventions.Add(new WebApiOverloadingApplicationModelConvention());
options.ApplicationModelConventions.Add(new WebApiRoutesApplicationModelConvention(area: DefaultAreaName));
options.Conventions.Add(new WebApiActionConventionsApplicationModelConvention());
options.Conventions.Add(new WebApiParameterConventionsApplicationModelConvention());
options.Conventions.Add(new WebApiOverloadingApplicationModelConvention());
options.Conventions.Add(new WebApiRoutesApplicationModelConvention(area: DefaultAreaName));
// Add an action filter for handling the HttpResponseException.
options.Filters.Add(new HttpResponseExceptionActionFilter());

View File

@ -0,0 +1,59 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reflection;
using Xunit;
namespace Microsoft.AspNet.Mvc.ApplicationModels
{
public class ActionApplicationModelConventionTest
{
[Fact]
public void DefaultActionModelConvention_AppliesToAllActionsInApp()
{
// Arrange
var options = new MvcOptions();
var app = new ApplicationModel();
app.Controllers.Add(new ControllerModel(typeof(HelloController).GetTypeInfo(), new List<object>()));
app.Controllers.Add(new ControllerModel(typeof(WorldController).GetTypeInfo(), new List<object>()));
options.Conventions.Add(new SimpleActionConvention());
// Act
options.Conventions[0].Apply(app);
// Assert
foreach (var controller in app.Controllers)
{
foreach (var action in controller.Actions)
{
Assert.True(action.Properties.ContainsKey("TestProperty"));
}
}
}
private class HelloController : Controller
{
public string GetHello()
{
return "Hello";
}
}
private class WorldController : Controller
{
public string GetWorld()
{
return "World!";
}
}
private class SimpleActionConvention : IActionModelConvention
{
public void Apply([NotNull] ActionModel action)
{
action.Properties.Add("TestProperty", "TestValue");
}
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNet.Mvc;
using Xunit;
namespace Microsoft.AspNet.Mvc.ApplicationModels
{
public class ControllerApplicationModelConventionTest
{
[Fact]
public void DefaultControllerModelConvention_AppliesToAllControllers()
{
// Arrange
var options = new MvcOptions();
var app = new ApplicationModel();
app.Controllers.Add(new ControllerModel(typeof(HelloController).GetTypeInfo(), new List<object>()));
app.Controllers.Add(new ControllerModel(typeof(WorldController).GetTypeInfo(), new List<object>()));
options.Conventions.Add(new SimpleControllerConvention());
// Act
options.Conventions[0].Apply(app);
// Assert
foreach (var controller in app.Controllers)
{
Assert.True(controller.Properties.ContainsKey("TestProperty"));
}
}
private class HelloController : Controller { }
private class WorldController : Controller { }
private class SimpleControllerConvention : IControllerModelConvention
{
public void Apply([NotNull] ControllerModel controller)
{
controller.Properties.Add("TestProperty", "TestValue");
}
}
}
}

View File

@ -1187,7 +1187,7 @@ namespace Microsoft.AspNet.Mvc.Test
.Callback(() => { Assert.Equal(3, sequence++); });
var options = new MockMvcOptionsAccessor();
options.Options.ApplicationModelConventions.Add(applicationConvention.Object);
options.Options.Conventions.Add(applicationConvention.Object);
var applicationModel = new ApplicationModel();
@ -1209,7 +1209,7 @@ namespace Microsoft.AspNet.Mvc.Test
actionModel.Parameters.Add(parameterModel);
// Act
ApplicationModelConventions.ApplyConventions(applicationModel, options.Options.ApplicationModelConventions);
ApplicationModelConventions.ApplyConventions(applicationModel, options.Options.Conventions);
// Assert
Assert.Equal(4, sequence);
@ -1411,7 +1411,7 @@ namespace Microsoft.AspNet.Mvc.Test
.Returns(new Assembly[] { type.Assembly });
var options = new MockMvcOptionsAccessor();
options.Options.ApplicationModelConventions.Add(convention);
options.Options.Conventions.Add(convention);
return new ControllerActionDescriptorProvider(
assemblyProvider.Object,

View File

@ -3,6 +3,7 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost;
@ -116,5 +117,44 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Specific Action Description", body);
}
[Fact]
public async Task ApplicationModelExtensions_AddsConventionToAllControllers()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/Lisence/GetLisence");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Copyright (c) Microsoft Open Technologies, Inc. All rights reserved." +
" Licensed under the Apache License, Version 2.0. See License.txt " +
"in the project root for license information.", body);
}
[Fact]
public async Task ApplicationModelExtensions_AddsConventionToAllActions()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Home/GetHelloWorld");
request.Headers.Add("helloWorld", "HelloWorld");
// Act
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("From Header - HelloWorld", body);
}
}
}

View File

@ -23,8 +23,8 @@ namespace ApiExplorerWebSite
{
options.Filters.AddService(typeof(ApiExplorerDataFilter));
options.ApplicationModelConventions.Add(new ApiExplorerVisibilityEnabledConvention());
options.ApplicationModelConventions.Add(new ApiExplorerVisibilityDisabledConvention(
options.Conventions.Add(new ApiExplorerVisibilityEnabledConvention());
options.Conventions.Add(new ApiExplorerVisibilityDisabledConvention(
typeof(ApiExplorerVisbilityDisabledByConventionController)));
options.OutputFormatters.Clear();

View File

@ -12,5 +12,12 @@ namespace ApplicationModelWebSite
var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.Properties["description"].ToString();
}
[HttpGet("Home/GetHelloWorld")]
public object GetHelloWorld([FromHeader] string helloWorld)
{
var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.Properties["source"].ToString() + " - " + helloWorld;
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace ApplicationModelWebSite.Controllers
{
public class LisenceController : Controller
{
[HttpGet("Lisence/GetLisence")]
public string GetLisence()
{
var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.Properties["lisence"].ToString();
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.ApplicationModels;
namespace ApplicationModelWebSite
{
public class ControllerLisenceConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
controller.Properties["lisence"] = "Copyright (c) Microsoft Open Technologies, Inc. All rights reserved." +
" Licensed under the Apache License, Version 2.0. See License.txt " +
"in the project root for license information.";
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ApplicationModels;
namespace ApplicationModelWebSite
{
public class FromHeaderConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
foreach (var param in action.Parameters)
{
if (param.Attributes.Any(p => p.GetType() == typeof(FromHeaderAttribute)))
{
param.Action.Properties["source"] = "From Header";
}
}
}
}
}

View File

@ -19,7 +19,9 @@ namespace ApplicationModelWebSite
services.Configure<MvcOptions>(options =>
{
options.ApplicationModelConventions.Add(new ApplicationDescription("Common Application Description"));
options.Conventions.Add(new ApplicationDescription("Common Application Description"));
options.Conventions.Add(new ControllerLisenceConvention());
options.Conventions.Add(new FromHeaderConvention());
});
});

View File

@ -23,7 +23,7 @@ namespace BasicWebSite
services.ConfigureMvcOptions(options =>
{
options.ApplicationModelConventions.Add(new ApplicationDescription("This is a basic website."));
options.Conventions.Add(new ApplicationDescription("This is a basic website."));
});
});