Fix for #1192 - Support customizing reflected model through attributes
This adds support for attributes which interact with reflected model. These conventions are applied after all of our built-in constructs so that you can see and modify the results.
This commit is contained in:
parent
6c49d0b40e
commit
d8995a7767
13
Mvc.sln
13
Mvc.sln
|
|
@ -82,6 +82,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UrlHelperWebSite", "test\We
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiExplorerWebSite", "test\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.kproj", "{61061528-071E-424E-965A-07BCC2F02672}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ReflectedModelWebSite", "test\WebSites\ReflectedModelWebSite\ReflectedModelWebSite.kproj", "{C2EF54F8-8886-4260-A322-44F76245F95D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -422,6 +424,16 @@ Global
|
|||
{61061528-071E-424E-965A-07BCC2F02672}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{61061528-071E-424E-965A-07BCC2F02672}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{61061528-071E-424E-965A-07BCC2F02672}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -461,5 +473,6 @@ Global
|
|||
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{A192E504-2881-41DC-90D1-B7F1DD1134E8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{61061528-071E-424E-965A-07BCC2F02672} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{C2EF54F8-8886-4260-A322-44F76245F95D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
// 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;
|
||||
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||
|
||||
namespace MvcSample.Web
|
||||
{
|
||||
// Adds an auto-generated route-name to each action in the controller
|
||||
public class AutoGenerateRouteNamesAttribute : Attribute, IReflectedControllerModelConvention
|
||||
{
|
||||
public void Apply(ReflectedControllerModel model)
|
||||
{
|
||||
foreach (var action in model.Actions)
|
||||
{
|
||||
if (action.AttributeRouteModel == null)
|
||||
{
|
||||
action.AttributeRouteModel = new ReflectedAttributeRouteModel();
|
||||
}
|
||||
|
||||
if (action.AttributeRouteModel.Name == null)
|
||||
{
|
||||
action.AttributeRouteModel.Name = string.Format(
|
||||
"{0}_{1}",
|
||||
model.ControllerName,
|
||||
action.ActionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using Microsoft.AspNet.Mvc;
|
|||
|
||||
namespace MvcSample.Web
|
||||
{
|
||||
[AutoGenerateRouteNames]
|
||||
[Route("api/REST")]
|
||||
public class SimpleRest : Controller
|
||||
{
|
||||
|
|
@ -23,5 +24,13 @@ namespace MvcSample.Web
|
|||
{
|
||||
return Url.Action(action, controller);
|
||||
}
|
||||
|
||||
[HttpGet("Link/{name}")]
|
||||
public string GenerateLinkByName(string name = null)
|
||||
{
|
||||
// This action leverages [AutoGenerateRouteNames]. Try a URL like api/Rest/Link/SimpleRest_ThisIsAGetMethod
|
||||
// which matches the auto-generated name for the ThisIsAGetMethod action.
|
||||
return Url.RouteUrl(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,22 +8,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
public class ControllerDescriptor
|
||||
{
|
||||
public ControllerDescriptor(TypeInfo controllerTypeInfo)
|
||||
{
|
||||
if (controllerTypeInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException("controllerTypeInfo");
|
||||
}
|
||||
public string Name { get; set; }
|
||||
|
||||
ControllerTypeInfo = controllerTypeInfo;
|
||||
|
||||
Name = controllerTypeInfo.Name.EndsWith("Controller", StringComparison.Ordinal)
|
||||
? controllerTypeInfo.Name.Substring(0, controllerTypeInfo.Name.Length - "Controller".Length)
|
||||
: controllerTypeInfo.Name;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public TypeInfo ControllerTypeInfo { get; private set; }
|
||||
public TypeInfo ControllerTypeInfo { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,19 +31,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
private readonly IActionDiscoveryConventions _conventions;
|
||||
private readonly IReadOnlyList<IFilter> _globalFilters;
|
||||
private readonly IEnumerable<IReflectedApplicationModelConvention> _modelConventions;
|
||||
private readonly IInlineConstraintResolver _constraintResolver;
|
||||
|
||||
public ReflectedActionDescriptorProvider(IControllerAssemblyProvider controllerAssemblyProvider,
|
||||
IActionDiscoveryConventions conventions,
|
||||
IGlobalFilterProvider globalFilters,
|
||||
IOptionsAccessor<MvcOptions> optionsAccessor,
|
||||
IInlineConstraintResolver constraintResolver)
|
||||
IOptionsAccessor<MvcOptions> optionsAccessor)
|
||||
{
|
||||
_controllerAssemblyProvider = controllerAssemblyProvider;
|
||||
_conventions = conventions;
|
||||
_globalFilters = globalFilters.Filters;
|
||||
_modelConventions = optionsAccessor.Options.ApplicationModelConventions;
|
||||
_constraintResolver = constraintResolver;
|
||||
}
|
||||
|
||||
public int Order
|
||||
|
|
@ -60,12 +57,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
public IEnumerable<ReflectedActionDescriptor> GetDescriptors()
|
||||
{
|
||||
var model = BuildModel();
|
||||
|
||||
foreach (var convention in _modelConventions)
|
||||
{
|
||||
convention.OnModelCreated(model);
|
||||
}
|
||||
|
||||
ApplyConventions(model);
|
||||
return Build(model);
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +72,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
foreach (var controllerType in controllerTypes)
|
||||
{
|
||||
var controllerModel = new ReflectedControllerModel(controllerType);
|
||||
var controllerModel = new ReflectedControllerModel(controllerType)
|
||||
{
|
||||
Application = applicationModel,
|
||||
};
|
||||
|
||||
applicationModel.Controllers.Add(controllerModel);
|
||||
|
||||
foreach (var methodInfo in controllerType.AsType().GetMethods())
|
||||
|
|
@ -93,10 +89,13 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
foreach (var actionInfo in actionInfos)
|
||||
{
|
||||
var actionModel = new ReflectedActionModel(methodInfo);
|
||||
var actionModel = new ReflectedActionModel(methodInfo)
|
||||
{
|
||||
ActionName = actionInfo.ActionName,
|
||||
Controller = controllerModel,
|
||||
IsActionNameMatchRequired = actionInfo.RequireActionNameMatch,
|
||||
};
|
||||
|
||||
actionModel.ActionName = actionInfo.ActionName;
|
||||
actionModel.IsActionNameMatchRequired = actionInfo.RequireActionNameMatch;
|
||||
actionModel.HttpMethods.AddRange(actionInfo.HttpMethods ?? Enumerable.Empty<string>());
|
||||
|
||||
if (actionInfo.AttributeRoute != null)
|
||||
|
|
@ -107,7 +106,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
foreach (var parameter in methodInfo.GetParameters())
|
||||
{
|
||||
actionModel.Parameters.Add(new ReflectedParameterModel(parameter));
|
||||
actionModel.Parameters.Add(new ReflectedParameterModel(parameter)
|
||||
{
|
||||
Action = actionModel,
|
||||
});
|
||||
}
|
||||
|
||||
controllerModel.Actions.Add(actionModel);
|
||||
|
|
@ -118,6 +120,62 @@ namespace Microsoft.AspNet.Mvc
|
|||
return applicationModel;
|
||||
}
|
||||
|
||||
public void ApplyConventions(ReflectedApplicationModel model)
|
||||
{
|
||||
// Conventions are applied from the outside-in to allow for scenarios where an action overrides
|
||||
// a controller, etc.
|
||||
foreach (var convention in _modelConventions)
|
||||
{
|
||||
convention.Apply(model);
|
||||
}
|
||||
|
||||
// First apply the conventions from attributes in decreasing order of scope.
|
||||
foreach (var controller in model.Controllers)
|
||||
{
|
||||
// ToArray is needed here to prevent issues with modifying the attributes collection
|
||||
// while iterating it.
|
||||
var controllerConventions =
|
||||
controller.Attributes
|
||||
.OfType<IReflectedControllerModelConvention>()
|
||||
.ToArray();
|
||||
|
||||
foreach (var controllerConvention in controllerConventions)
|
||||
{
|
||||
controllerConvention.Apply(controller);
|
||||
}
|
||||
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
// ToArray is needed here to prevent issues with modifying the attributes collection
|
||||
// while iterating it.
|
||||
var actionConventions =
|
||||
action.Attributes
|
||||
.OfType<IReflectedActionModelConvention>()
|
||||
.ToArray();
|
||||
|
||||
foreach (var actionConvention in actionConventions)
|
||||
{
|
||||
actionConvention.Apply(action);
|
||||
}
|
||||
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
// ToArray is needed here to prevent issues with modifying the attributes collection
|
||||
// while iterating it.
|
||||
var parameterConventions =
|
||||
parameter.Attributes
|
||||
.OfType<IReflectedParameterModelConvention>()
|
||||
.ToArray();
|
||||
|
||||
foreach (var parameterConvention in parameterConventions)
|
||||
{
|
||||
parameterConvention.Apply(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
|
||||
{
|
||||
var actions = new List<ReflectedActionDescriptor>();
|
||||
|
|
@ -132,7 +190,12 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
foreach (var controller in model.Controllers)
|
||||
{
|
||||
var controllerDescriptor = new ControllerDescriptor(controller.ControllerType);
|
||||
var controllerDescriptor = new ControllerDescriptor()
|
||||
{
|
||||
ControllerTypeInfo = controller.ControllerType,
|
||||
Name = controller.ControllerName,
|
||||
};
|
||||
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
// Controllers with multiple [Route] attributes (or user defined implementation of
|
||||
|
|
@ -351,7 +414,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
private static void AddApiExplorerInfo(
|
||||
ReflectedActionDescriptor actionDescriptor,
|
||||
ReflectedActionModel action,
|
||||
ReflectedActionModel action,
|
||||
ReflectedControllerModel controller)
|
||||
{
|
||||
var apiExplorerIsVisible = action.ApiExplorerIsVisible ?? controller.ApiExplorerIsVisible ?? false;
|
||||
|
|
@ -360,7 +423,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var apiExplorerActionData = new ApiDescriptionActionData()
|
||||
{
|
||||
GroupName = action.ApiExplorerGroupName ?? controller.ApiExplorerGroupName,
|
||||
};
|
||||
};
|
||||
|
||||
actionDescriptor.SetProperty(apiExplorerActionData);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows customization of the of the <see cref="ReflectedActionModel"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To use this interface, create an <see cref="System.Attribute"/> class which implements the interface and
|
||||
/// place it on an action method.
|
||||
///
|
||||
/// <see cref="IReflectedActionModelConvention"/> customizations run after
|
||||
/// <see cref="IReflectedActionModelConvention"/> customications and before
|
||||
/// <see cref="IReflectedParameterModelConvention"/> customizations.
|
||||
/// </remarks>
|
||||
public interface IReflectedActionModelConvention
|
||||
{
|
||||
/// <summary>
|
||||
/// Called to apply the convention to the <see cref="ReflectedActionModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="ReflectedActionModel"/>.</param>
|
||||
void Apply([NotNull] ReflectedActionModel model);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,22 @@
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows customization of the of the <see cref="ReflectedApplicationModel"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementaions of this interface can be registered in <see cref="MvcOptions.ApplicationModelConventions"/>
|
||||
/// to customize metadata about the application.
|
||||
///
|
||||
/// <see cref="IReflectedApplicationModelConvention"/> run before other types of customizations to the
|
||||
/// reflected model.
|
||||
/// </remarks>
|
||||
public interface IReflectedApplicationModelConvention
|
||||
{
|
||||
void OnModelCreated([NotNull] ReflectedApplicationModel model);
|
||||
/// <summary>
|
||||
/// Called to apply the convention to the <see cref="ReflectedApplicationModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="ReflectedApplicationModel"/>.</param>
|
||||
void Apply([NotNull] ReflectedApplicationModel model);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows customization of the of the <see cref="ReflectedControllerModel"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To use this interface, create an <see cref="System.Attribute"/> class which implements the interface and
|
||||
/// place it on a controller class.
|
||||
///
|
||||
/// <see cref="IReflectedControllerModelConvention"/> customizations run after
|
||||
/// <see cref="IReflectedApplicationModelConvention"/> customizations and before
|
||||
/// <see cref="IReflectedActionModelConvention"/> customizations.
|
||||
/// </remarks>
|
||||
public interface IReflectedControllerModelConvention
|
||||
{
|
||||
/// <summary>
|
||||
/// Called to apply the convention to the <see cref="ReflectedControllerModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="ReflectedControllerModel"/>.</param>
|
||||
void Apply([NotNull] ReflectedControllerModel model);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows customization of the of the <see cref="ReflectedControllerModel"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To use this interface, create an <see cref="System.Attribute"/> class which implements the interface and
|
||||
/// place it on an action method parameter.
|
||||
///
|
||||
/// <see cref="IReflectedParameterModelConvention"/> customizations run after
|
||||
/// <see cref="IReflectedActionModelConvention"/> customizations.
|
||||
/// </remarks>
|
||||
public interface IReflectedParameterModelConvention
|
||||
{
|
||||
/// <summary>
|
||||
/// Called to apply the convention to the <see cref="ReflectedParameterModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="ReflectedParameterModel"/>.</param>
|
||||
void Apply([NotNull] ReflectedParameterModel model);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,8 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
|
||||
public List<object> Attributes { get; private set; }
|
||||
|
||||
public ReflectedControllerModel Controller { get; set; }
|
||||
|
||||
public List<IFilter> Filters { get; private set; }
|
||||
|
||||
public List<string> HttpMethods { get; private set; }
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
|
||||
public List<ReflectedActionModel> Actions { get; private set; }
|
||||
|
||||
public ReflectedApplicationModel Application { get; set; }
|
||||
|
||||
public List<object> Attributes { get; private set; }
|
||||
|
||||
public string ControllerName { get; set; }
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
IsOptional = ParameterInfo.HasDefaultValue;
|
||||
}
|
||||
|
||||
public ReflectedActionModel Action { get; set; }
|
||||
|
||||
public List<object> Attributes { get; private set; }
|
||||
|
||||
public bool IsOptional { get; set; }
|
||||
|
|
|
|||
|
|
@ -227,8 +227,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
controllerAssemblyProvider,
|
||||
actionDiscoveryConventions,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor(),
|
||||
Mock.Of<IInlineConstraintResolver>());
|
||||
new MockMvcOptionsAccessor());
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(string httpMethod)
|
||||
|
|
|
|||
|
|
@ -197,8 +197,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
controllerAssemblyProvider.Object,
|
||||
actionDiscoveryConventions,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor(),
|
||||
Mock.Of<IInlineConstraintResolver>());
|
||||
new MockMvcOptionsAccessor());
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(string httpMethod)
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Test
|
||||
{
|
||||
|
|
@ -1130,6 +1130,56 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
Assert.Equal("Store", action.GetProperty<ApiDescriptionActionData>().GroupName);
|
||||
}
|
||||
|
||||
// Verifies the sequence of conventions running
|
||||
[Fact]
|
||||
public void ApplyConventions_RunsInOrderOfDecreasingScope()
|
||||
{
|
||||
// Arrange
|
||||
var sequence = 0;
|
||||
|
||||
var applicationConvention = new Mock<IReflectedApplicationModelConvention>();
|
||||
applicationConvention
|
||||
.Setup(c => c.Apply(It.IsAny<ReflectedApplicationModel>()))
|
||||
.Callback(() => { Assert.Equal(0, sequence++); });
|
||||
|
||||
var controllerConvention = new Mock<IReflectedControllerModelConvention>();
|
||||
controllerConvention
|
||||
.Setup(c => c.Apply(It.IsAny<ReflectedControllerModel>()))
|
||||
.Callback(() => { Assert.Equal(1, sequence++); });
|
||||
|
||||
var actionConvention = new Mock<IReflectedActionModelConvention>();
|
||||
actionConvention
|
||||
.Setup(c => c.Apply(It.IsAny<ReflectedActionModel>()))
|
||||
.Callback(() => { Assert.Equal(2, sequence++); });
|
||||
|
||||
var parameterConvention = new Mock<IReflectedParameterModelConvention>();
|
||||
parameterConvention
|
||||
.Setup(c => c.Apply(It.IsAny<ReflectedParameterModel>()))
|
||||
.Callback(() => { Assert.Equal(3, sequence++); });
|
||||
|
||||
var options = new MockMvcOptionsAccessor();
|
||||
options.Options.ApplicationModelConventions.Add(applicationConvention.Object);
|
||||
|
||||
var provider = GetProvider(typeof(ConventionsController).GetTypeInfo(), options);
|
||||
|
||||
var model = provider.BuildModel();
|
||||
|
||||
var controller = model.Controllers.Single();
|
||||
controller.Attributes.Add(controllerConvention.Object);
|
||||
|
||||
var action = controller.Actions.Single();
|
||||
action.Attributes.Add(actionConvention.Object);
|
||||
|
||||
var parameter = action.Parameters.Single();
|
||||
parameter.Attributes.Add(parameterConvention.Object);
|
||||
|
||||
// Act
|
||||
provider.ApplyConventions(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, sequence);
|
||||
}
|
||||
|
||||
private ReflectedActionDescriptorProvider GetProvider(
|
||||
TypeInfo controllerTypeInfo,
|
||||
IEnumerable<IFilter> filters = null)
|
||||
|
|
@ -1145,8 +1195,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
assemblyProvider.Object,
|
||||
conventions,
|
||||
new TestGlobalFilterProvider(filters),
|
||||
new MockMvcOptionsAccessor(),
|
||||
Mock.Of<IInlineConstraintResolver>());
|
||||
new MockMvcOptionsAccessor());
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
|
@ -1165,12 +1214,29 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
assemblyProvider.Object,
|
||||
conventions,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor(),
|
||||
Mock.Of<IInlineConstraintResolver>());
|
||||
new MockMvcOptionsAccessor());
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
private ReflectedActionDescriptorProvider GetProvider(
|
||||
TypeInfo type,
|
||||
IOptionsAccessor<MvcOptions> options)
|
||||
{
|
||||
var conventions = new StaticActionDiscoveryConventions(type);
|
||||
|
||||
var assemblyProvider = new Mock<IControllerAssemblyProvider>();
|
||||
assemblyProvider
|
||||
.SetupGet(ap => ap.CandidateAssemblies)
|
||||
.Returns(new Assembly[] { type.Assembly });
|
||||
|
||||
return new ReflectedActionDescriptorProvider(
|
||||
assemblyProvider.Object,
|
||||
conventions,
|
||||
new TestGlobalFilterProvider(),
|
||||
options);
|
||||
}
|
||||
|
||||
private IEnumerable<ActionDescriptor> GetDescriptors(params TypeInfo[] controllerTypeInfos)
|
||||
{
|
||||
var conventions = new StaticActionDiscoveryConventions(controllerTypeInfos);
|
||||
|
|
@ -1184,8 +1250,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
assemblyProvider.Object,
|
||||
conventions,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor(),
|
||||
null);
|
||||
new MockMvcOptionsAccessor());
|
||||
|
||||
return provider.GetDescriptors();
|
||||
}
|
||||
|
|
@ -1588,5 +1653,10 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
|
||||
public void Create() { }
|
||||
}
|
||||
|
||||
private class ConventionsController
|
||||
{
|
||||
public void Create(int productId) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
// 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;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Xunit;
|
||||
using System.Net;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class ReflectedModelTest
|
||||
{
|
||||
private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(ReflectedModelWebSite));
|
||||
private readonly Action<IApplicationBuilder> _app = new ReflectedModelWebSite.Startup().Configure;
|
||||
|
||||
[Fact]
|
||||
public async Task ControllerModel_CustomizedWithAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/CoolController/GetControllerName");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("CoolController", body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActionModel_CustomizedWithAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/ReflectedActionModel/ActionName");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("ActionName", body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParameterModel_CustomizedWithAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/ReflectedParameterModel/GetParameterIsOptional");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("True", body);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
|
||||
"ModelBindingWebSite": "",
|
||||
"MvcSample.Web": "",
|
||||
"ReflectedModelWebSite": "",
|
||||
"RoutingWebSite": "",
|
||||
"RazorWebSite": "",
|
||||
"ValueProvidersSite": "",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace ApiExplorer
|
|||
_type = type.GetTypeInfo();
|
||||
}
|
||||
|
||||
public void OnModelCreated(ReflectedApplicationModel model)
|
||||
public void Apply(ReflectedApplicationModel model)
|
||||
{
|
||||
foreach (var controller in model.Controllers)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace ApiExplorer
|
|||
// convention
|
||||
public class ApiExplorerVisibilityEnabledConvention : IReflectedApplicationModelConvention
|
||||
{
|
||||
public void OnModelCreated(ReflectedApplicationModel model)
|
||||
public void Apply(ReflectedApplicationModel model)
|
||||
{
|
||||
foreach (var controller in model.Controllers)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ namespace AutofacWebSite
|
|||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
var configuration = app.GetTestConfiguration();
|
||||
|
||||
app.UseServices(services => {
|
||||
services.AddMvc();
|
||||
services.AddMvc(configuration);
|
||||
services.AddTransient<HelloWorldBuilder>();
|
||||
|
||||
var builder = new ContainerBuilder();
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc": "",
|
||||
"Microsoft.AspNet.Mvc.TestConfiguration": "",
|
||||
"Microsoft.Framework.DependencyInjection.Autofac": "1.0.0-*",
|
||||
"Autofac": "3.3.0"
|
||||
},
|
||||
"frameworks" : {
|
||||
"aspnet50" : { }
|
||||
"frameworks": {
|
||||
"aspnet50": { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
// 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;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||
|
||||
namespace ReflectedModelWebSite
|
||||
{
|
||||
// This controller uses an reflected model attribute to change an action name, and thus
|
||||
// the URL.
|
||||
public class ReflectedActionModelController : Controller
|
||||
{
|
||||
[ActionName2("ActionName")]
|
||||
public string GetActionName()
|
||||
{
|
||||
var actionDescriptor = (ReflectedActionDescriptor)ActionContext.ActionDescriptor;
|
||||
|
||||
return actionDescriptor.Name;
|
||||
}
|
||||
|
||||
private class ActionName2Attribute : Attribute, IReflectedActionModelConvention
|
||||
{
|
||||
private readonly string _actionName;
|
||||
|
||||
public ActionName2Attribute(string actionName)
|
||||
{
|
||||
_actionName = actionName;
|
||||
}
|
||||
|
||||
public void Apply(ReflectedActionModel model)
|
||||
{
|
||||
model.ActionName = _actionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||
|
||||
namespace ReflectedModelWebSite
|
||||
{
|
||||
// This controller uses an reflected model attribute to change the controller name, and thus
|
||||
// the URL.
|
||||
[ControllerName("CoolController")]
|
||||
public class ReflectedControllerModelController : Controller
|
||||
{
|
||||
public string GetControllerName()
|
||||
{
|
||||
var actionDescriptor = (ReflectedActionDescriptor)ActionContext.ActionDescriptor;
|
||||
|
||||
return actionDescriptor.ControllerName;
|
||||
}
|
||||
|
||||
private class ControllerNameAttribute : Attribute, IReflectedControllerModelConvention
|
||||
{
|
||||
private readonly string _controllerName;
|
||||
|
||||
public ControllerNameAttribute(string controllerName)
|
||||
{
|
||||
_controllerName = controllerName;
|
||||
}
|
||||
|
||||
public void Apply(ReflectedControllerModel model)
|
||||
{
|
||||
model.ControllerName = _controllerName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// 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;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||
|
||||
namespace ReflectedModelWebSite
|
||||
{
|
||||
// This controller uses an reflected model attribute to change a parameter to optional.
|
||||
public class ReflectedParameterModelController : Controller
|
||||
{
|
||||
public string GetParameterIsOptional([Optional] int? id)
|
||||
{
|
||||
var actionDescriptor = (ReflectedActionDescriptor)ActionContext.ActionDescriptor;
|
||||
|
||||
return actionDescriptor.Parameters[0].IsOptional.ToString();
|
||||
}
|
||||
|
||||
private class OptionalAttribute : Attribute, IReflectedParameterModelConvention
|
||||
{
|
||||
public void Apply(ReflectedParameterModel model)
|
||||
{
|
||||
model.IsOptional = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>c2ef54f8-8886-4260-a322-44f76245f95d</ProjectGuid>
|
||||
<OutputType>Web</OutputType>
|
||||
<RootNamespace>ReflectedModelWebSite</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(OutputType) == 'Console'">
|
||||
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(OutputType) == 'Web'">
|
||||
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>9411</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -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 Microsoft.AspNet.Builder;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
namespace ReflectedModelWebSite
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
var configuration = app.GetTestConfiguration();
|
||||
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddMvc(configuration);
|
||||
});
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc": "",
|
||||
"Microsoft.AspNet.Mvc.TestConfiguration": ""
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": { }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue