Add support for running conventions on controller properties, Razor Page parameter and properties

Fixes #6935
This commit is contained in:
Pranav K 2017-12-18 11:43:09 -08:00
parent a08fae1a28
commit dbff416be6
14 changed files with 884 additions and 193 deletions

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// Allows customization of the properties and parameters on controllers and Razor Pages.
/// </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.
/// </remarks>
public interface IParameterModelBaseConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="ParameterModelBase"/>.
/// </summary>
/// <param name="parameter">The <see cref="ParameterModelBase"/>.</param>
void Apply(ParameterModelBase parameter);
}
}

View File

@ -4,7 +4,7 @@
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// Allows customization of the <see cref="ControllerModel"/>.
/// Allows customization of the <see cref="ParameterModel"/>.
/// </summary>
/// <remarks>
/// To use this interface, create an <see cref="System.Attribute"/> class which implements the interface and
@ -15,10 +15,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </remarks>
public interface IParameterModelConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="ParameterModel"/>.
/// </summary>
/// <param name="parameter">The <see cref="ParameterModel"/>.</param>
void Apply(ParameterModel parameter);
}
}
}

View File

@ -5,33 +5,22 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
[DebuggerDisplay("ParameterModel: Name={ParameterName}")]
public class ParameterModel : ICommonModel, IBindingModel
public class ParameterModel : ParameterModelBase, ICommonModel
{
public ParameterModel(
ParameterInfo parameterInfo,
IReadOnlyList<object> attributes)
: base(parameterInfo?.ParameterType, attributes)
{
if (parameterInfo == null)
{
throw new ArgumentNullException(nameof(parameterInfo));
}
if (attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
ParameterInfo = parameterInfo;
Properties = new Dictionary<object, object>();
Attributes = new List<object>(attributes);
ParameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
}
public ParameterModel(ParameterModel other)
: base(other)
{
if (other == null)
{
@ -39,27 +28,23 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
}
Action = other.Action;
Attributes = new List<object>(other.Attributes);
BindingInfo = other.BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
ParameterInfo = other.ParameterInfo;
ParameterName = other.ParameterName;
Properties = new Dictionary<object, object>(other.Properties);
}
public ActionModel Action { get; set; }
public IReadOnlyList<object> Attributes { get; }
public new IDictionary<object, object> Properties => base.Properties;
public IDictionary<object, object> Properties { get; }
public new IReadOnlyList<object> Attributes => base.Attributes;
MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member;
string ICommonModel.Name => ParameterName;
public ParameterInfo ParameterInfo { get; }
public string ParameterName { get; set; }
public BindingInfo BindingInfo { get; set; }
public string ParameterName
{
get => Name;
set => Name = value;
}
}
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation. 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.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// A model type for reading and manipulation properties and parameters.
/// <para>
/// Derived instances of this type represent properties and parameters for controllers, and Razor Pages.
/// </para>
/// </summary>
public abstract class ParameterModelBase : IBindingModel
{
protected ParameterModelBase(
Type parameterType,
IReadOnlyList<object> attributes)
{
ParameterType = parameterType ?? throw new ArgumentNullException(nameof(parameterType));
Attributes = new List<object>(attributes ?? throw new ArgumentNullException(nameof(attributes)));
Properties = new Dictionary<object, object>();
}
protected ParameterModelBase(ParameterModelBase other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
ParameterType = other.ParameterType;
Attributes = new List<object>(other.Attributes);
BindingInfo = other.BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
Name = other.Name;
Properties = new Dictionary<object, object>(other.Properties);
}
public IReadOnlyList<object> Attributes { get; }
public IDictionary<object, object> Properties { get; }
public Type ParameterType { get; }
public string Name { get; protected set; }
public BindingInfo BindingInfo { get; set; }
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// A type which is used to represent a property in a <see cref="ControllerModel"/>.
/// </summary>
[DebuggerDisplay("PropertyModel: Name={PropertyName}")]
public class PropertyModel : ICommonModel, IBindingModel
public class PropertyModel : ParameterModelBase, ICommonModel, IBindingModel
{
/// <summary>
/// Creates a new instance of <see cref="PropertyModel"/>.
@ -23,20 +23,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public PropertyModel(
PropertyInfo propertyInfo,
IReadOnlyList<object> attributes)
: base(propertyInfo?.PropertyType, attributes)
{
if (propertyInfo == null)
{
throw new ArgumentNullException(nameof(propertyInfo));
}
if (attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
PropertyInfo = propertyInfo;
Properties = new Dictionary<object, object>();
Attributes = new List<object>(attributes);
PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
}
/// <summary>
@ -44,6 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary>
/// <param name="other">The <see cref="PropertyModel"/> which needs to be copied.</param>
public PropertyModel(PropertyModel other)
: base(other)
{
if (other == null)
{
@ -51,11 +41,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
}
Controller = other.Controller;
Attributes = new List<object>(other.Attributes);
BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
PropertyInfo = other.PropertyInfo;
PropertyName = other.PropertyName;
Properties = new Dictionary<object, object>(other.Properties);
}
/// <summary>
@ -63,30 +50,18 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary>
public ControllerModel Controller { get; set; }
/// <summary>
/// Gets any attributes which are annotated on the property.
/// </summary>
public IReadOnlyList<object> Attributes { get; }
public IDictionary<object, object> Properties { get; }
MemberInfo ICommonModel.MemberInfo => PropertyInfo;
string ICommonModel.Name => PropertyName;
public new IDictionary<object, object> Properties => base.Properties;
/// <summary>
/// Gets or sets the <see cref="BindingInfo"/> associated with this model.
/// </summary>
public BindingInfo BindingInfo { get; set; }
public new IReadOnlyList<object> Attributes => base.Attributes;
/// <summary>
/// Gets the underlying <see cref="PropertyInfo"/>.
/// </summary>
public PropertyInfo PropertyInfo { get; }
/// <summary>
/// Gets or sets the name of the property represented by this model.
/// </summary>
public string PropertyName { get; set; }
public string PropertyName
{
get => Name;
set => Name = value;
}
}
}
}

View File

@ -2,8 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Microsoft.Extensions.DependencyInjection
@ -18,7 +18,8 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
/// <param name="list">The list of <see cref="IApplicationModelConvention"/>s.</param>
/// <typeparam name="TApplicationModelConvention">The type to remove.</typeparam>
public static void RemoveType<TApplicationModelConvention>(this IList<IApplicationModelConvention> list) where TApplicationModelConvention : IApplicationModelConvention
public static void RemoveType<TApplicationModelConvention>(this IList<IApplicationModelConvention> list)
where TApplicationModelConvention : IApplicationModelConvention
{
if (list == null)
{
@ -127,17 +128,36 @@ namespace Microsoft.Extensions.DependencyInjection
conventions.Add(new ParameterApplicationModelConvention(parameterModelConvention));
}
/// <summary>
/// Adds a <see cref="IParameterModelBaseConvention"/> to all properties and parameters in the application.
/// </summary>
/// <param name="conventions">The list of <see cref="IApplicationModelConvention"/>
/// in <see cref="AspNetCore.Mvc.MvcOptions"/>.</param>
/// <param name="parameterModelConvention">The <see cref="IParameterModelBaseConvention"/> which needs to be
/// added.</param>
public static void Add(
this IList<IApplicationModelConvention> conventions,
IParameterModelBaseConvention parameterModelConvention)
{
if (conventions == null)
{
throw new ArgumentNullException(nameof(conventions));
}
if (parameterModelConvention == null)
{
throw new ArgumentNullException(nameof(parameterModelConvention));
}
conventions.Add(new ParameterBaseApplicationModelConvention(parameterModelConvention));
}
private class ParameterApplicationModelConvention : IApplicationModelConvention
{
private readonly IParameterModelConvention _parameterModelConvention;
public ParameterApplicationModelConvention(IParameterModelConvention parameterModelConvention)
{
if (parameterModelConvention == null)
{
throw new ArgumentNullException(nameof(parameterModelConvention));
}
_parameterModelConvention = parameterModelConvention;
}
@ -167,6 +187,36 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
private class ParameterBaseApplicationModelConvention :
IApplicationModelConvention, IParameterModelBaseConvention
{
private readonly IParameterModelBaseConvention _parameterBaseModelConvention;
public ParameterBaseApplicationModelConvention(IParameterModelBaseConvention parameterModelBaseConvention)
{
_parameterBaseModelConvention = parameterModelBaseConvention;
}
/// <inheritdoc />
public void Apply(ApplicationModel application)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
}
void IParameterModelBaseConvention.Apply(ParameterModelBase parameterModel)
{
if (parameterModel == null)
{
throw new ArgumentNullException(nameof(parameterModel));
}
_parameterBaseModelConvention.Apply(parameterModel);
}
}
private class ActionApplicationModelConvention : IApplicationModelConvention
{
private readonly IActionModelConvention _actionModelConvention;

View File

@ -39,8 +39,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
convention.Apply(applicationModel);
}
var controllers = applicationModel.Controllers.ToArray();
// First apply the conventions from attributes in decreasing order of scope.
foreach (var controller in applicationModel.Controllers)
foreach (var controller in controllers)
{
// ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it.
@ -54,7 +55,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controllerConvention.Apply(controller);
}
foreach (var action in controller.Actions)
var actions = controller.Actions.ToArray();
foreach (var action in actions)
{
// ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it.
@ -68,7 +70,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
actionConvention.Apply(action);
}
foreach (var parameter in action.Parameters)
var parameters = action.Parameters.ToArray();
foreach (var parameter in parameters)
{
// ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it.
@ -81,9 +84,35 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
parameterConvention.Apply(parameter);
}
var parameterBaseConventions = GetConventions<IParameterModelBaseConvention>(conventions, parameter.Attributes);
foreach (var parameterConvention in parameterBaseConventions)
{
parameterConvention.Apply(parameter);
}
}
}
var properties = controller.ControllerProperties.ToArray();
foreach (var property in properties)
{
var parameterBaseConventions = GetConventions<IParameterModelBaseConvention>(conventions, property.Attributes);
foreach (var parameterConvention in parameterBaseConventions)
{
parameterConvention.Apply(property);
}
}
}
}
private static IEnumerable<TConvention> GetConventions<TConvention>(
IEnumerable<IApplicationModelConvention> conventions,
IReadOnlyList<object> attributes)
{
return Enumerable.Concat(
conventions.OfType<TConvention>(),
attributes.OfType<TConvention>());
}
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// Allows customization of the <see cref="PageHandlerModel"/>.
/// </summary>
public interface IPageHandlerModelConvention : IPageConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="PageHandlerModel"/>.
/// </summary>
/// <param name="model">The <see cref="PageHandlerModel"/>.</param>
void Apply(PageHandlerModel model);
}
}

View File

@ -5,29 +5,32 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
[DebuggerDisplay("PageParameterModel: Name={ParameterName}")]
public class PageParameterModel : ICommonModel, IBindingModel
public class PageParameterModel : ParameterModelBase, ICommonModel, IBindingModel
{
public PageParameterModel(
ParameterInfo parameterInfo,
IReadOnlyList<object> attributes)
: base(parameterInfo?.ParameterType, attributes)
{
ParameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
if (parameterInfo == null)
{
throw new ArgumentNullException(nameof(parameterInfo));
}
if (attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
Properties = new Dictionary<object, object>();
Attributes = new List<object>(attributes);
ParameterInfo = parameterInfo;
}
public PageParameterModel(PageParameterModel other)
: base(other)
{
if (other == null)
{
@ -35,27 +38,19 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
}
Handler = other.Handler;
Attributes = new List<object>(other.Attributes);
BindingInfo = other.BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
ParameterInfo = other.ParameterInfo;
ParameterName = other.ParameterName;
Properties = new Dictionary<object, object>(other.Properties);
}
public PageHandlerModel Handler { get; set; }
public IReadOnlyList<object> Attributes { get; }
public IDictionary<object, object> Properties { get; }
MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member;
string ICommonModel.Name => ParameterName;
public ParameterInfo ParameterInfo { get; }
public string ParameterName { get; set; }
public BindingInfo BindingInfo { get; set; }
public string ParameterName
{
get => Name;
set => Name = value;
}
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// Represents a property in a <see cref="PageApplicationModel"/>.
/// </summary>
[DebuggerDisplay("PagePropertyModel: Name={PropertyName}")]
public class PagePropertyModel : ICommonModel, IBindingModel
public class PagePropertyModel : ParameterModelBase, ICommonModel
{
/// <summary>
/// Creates a new instance of <see cref="PagePropertyModel"/>.
@ -23,10 +23,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public PagePropertyModel(
PropertyInfo propertyInfo,
IReadOnlyList<object> attributes)
: base(propertyInfo?.PropertyType, attributes)
{
PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
Properties = new Dictionary<object, object>();
Attributes = new List<object>(attributes) ?? throw new ArgumentNullException(nameof(attributes));
}
/// <summary>
@ -34,6 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary>
/// <param name="other">The <see cref="PagePropertyModel"/> which needs to be copied.</param>
public PagePropertyModel(PagePropertyModel other)
: base(other)
{
if (other == null)
{
@ -41,11 +41,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
}
Page = other.Page;
Attributes = new List<object>(other.Attributes);
BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
PropertyInfo = other.PropertyInfo;
PropertyName = other.PropertyName;
Properties = new Dictionary<object, object>(other.Properties);
}
/// <summary>
@ -53,31 +50,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary>
public PageApplicationModel Page { get; set; }
/// <summary>
/// Gets any attributes which are annotated on the property.
/// </summary>
public IReadOnlyList<object> Attributes { get; }
/// <inheritdoc />
public IDictionary<object, object> Properties { get; }
/// <summary>
/// Gets or sets the <see cref="BindingInfo"/> associated with this model.
/// </summary>
public BindingInfo BindingInfo { get; set; }
/// <summary>
/// Gets the underlying <see cref="PropertyInfo"/>.
/// </summary>
public PropertyInfo PropertyInfo { get; }
/// <summary>
/// Gets or sets the name of the property represented by this model.
/// </summary>
public string PropertyName { get; set; }
MemberInfo ICommonModel.MemberInfo => PropertyInfo;
string ICommonModel.Name => PropertyName;
public PropertyInfo PropertyInfo { get; }
public string PropertyName
{
get => Name;
set => Name = value;
}
}
}

View File

@ -143,6 +143,30 @@ namespace Microsoft.Extensions.DependencyInjection
return conventions;
}
/// <summary>
/// Adds the specified <paramref name="convention"/> to <paramref name="conventions"/>.
/// The added convention will apply to all handler properties and parameters on handler methods.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="convention">The <see cref="IParameterModelBaseConvention"/> to apply.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
public static PageConventionCollection Add(this PageConventionCollection conventions, IParameterModelBaseConvention convention)
{
if (conventions == null)
{
throw new ArgumentNullException(nameof(conventions));
}
if (convention == null)
{
throw new ArgumentNullException(nameof(convention));
}
var adapter = new ParameterModelBaseConventionAdapter(convention);
conventions.Add(adapter);
return conventions;
}
/// <summary>
/// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified area folder.
/// </summary>
@ -461,5 +485,20 @@ namespace Microsoft.Extensions.DependencyInjection
});
};
}
private class ParameterModelBaseConventionAdapter : IPageConvention, IParameterModelBaseConvention
{
private readonly IParameterModelBaseConvention _convention;
public ParameterModelBaseConventionAdapter(IParameterModelBaseConvention convention)
{
_convention = convention;
}
public void Apply(ParameterModelBase parameter)
{
_convention.Apply(parameter);
}
}
}
}

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
private readonly IViewCompilerProvider _viewCompilerProvider;
private readonly IPageApplicationModelConvention[] _conventions;
private readonly PageConventionCollection _conventions;
private readonly FilterCollection _globalFilters;
public DefaultPageLoader(
@ -30,9 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
.OrderBy(p => p.Order)
.ToArray();
_viewCompilerProvider = viewCompilerProvider;
_conventions = pageOptions.Value.Conventions
.OfType<IPageApplicationModelConvention>()
.ToArray();
_conventions = pageOptions.Value.Conventions;
_globalFilters = mvcOptions.Value.Filters;
}
@ -60,12 +58,58 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_applicationModelProviders[i].OnProvidersExecuted(context);
}
for (var i = 0; i < _conventions.Length; i++)
{
_conventions[i].Apply(context.PageApplicationModel);
}
ApplyConventions(_conventions, context.PageApplicationModel);
return CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel, _globalFilters);
}
internal static void ApplyConventions(
PageConventionCollection conventions,
PageApplicationModel pageApplicationModel)
{
var applicationModelConventions = GetConventions<IPageApplicationModelConvention>(pageApplicationModel.HandlerTypeAttributes);
foreach (var convention in applicationModelConventions)
{
convention.Apply(pageApplicationModel);
}
var handlers = pageApplicationModel.HandlerMethods.ToArray();
foreach (var handlerModel in handlers)
{
var handlerModelConventions = GetConventions<IPageHandlerModelConvention>(handlerModel.Attributes);
foreach (var convention in handlerModelConventions)
{
convention.Apply(handlerModel);
}
var parameterModels = handlerModel.Parameters.ToArray();
foreach (var parameterModel in parameterModels)
{
var parameterModelConventions = GetConventions<IParameterModelBaseConvention>(parameterModel.Attributes);
foreach (var convention in parameterModelConventions)
{
convention.Apply(parameterModel);
}
}
}
var properties = pageApplicationModel.HandlerProperties.ToArray();
foreach (var propertyModel in properties)
{
var propertyModelConventions = GetConventions<IParameterModelBaseConvention>(propertyModel.Attributes);
foreach (var convention in propertyModelConventions)
{
convention.Apply(propertyModel);
}
}
IEnumerable<TConvention> GetConventions<TConvention>(
IReadOnlyList<object> attributes)
{
return Enumerable.Concat(
conventions.OfType<TConvention>(),
attributes.OfType<TConvention>());
}
}
}
}

View File

@ -18,8 +18,16 @@ namespace Microsoft.Extensions.DependencyInjection
{
// Arrange
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>()));
var controllerType = typeof(HelloController);
var controllerModel = new ControllerModel(controllerType.GetTypeInfo(), Array.Empty<object>());
app.Controllers.Add(controllerModel);
var actionModel = new ActionModel(controllerType.GetMethod(nameof(HelloController.GetInfo)), Array.Empty<object>());
controllerModel.Actions.Add(actionModel);
var parameterModel = new ParameterModel(
controllerType.GetMethod(nameof(HelloController.GetInfo)).GetParameters()[0],
Array.Empty<object>());
actionModel.Parameters.Add(parameterModel);
var options = new MvcOptions();
options.Conventions.Add(new SimpleParameterConvention());
@ -28,18 +36,9 @@ namespace Microsoft.Extensions.DependencyInjection
options.Conventions[0].Apply(app);
// Assert
foreach (var controller in app.Controllers)
{
foreach (var action in controller.Actions)
{
foreach (var parameter in action.Parameters)
{
var kvp = Assert.Single(parameter.Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
}
}
}
var kvp = Assert.Single(parameterModel.Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
}
[Fact]
@ -47,8 +46,28 @@ namespace Microsoft.Extensions.DependencyInjection
{
// Arrange
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>()));
var controllerType1 = typeof(HelloController).GetTypeInfo();
var actionMethod1 = controllerType1.GetMethod(nameof(HelloController.GetHello));
var controllerModel1 = new ControllerModel(controllerType1, Array.Empty<object>())
{
Actions =
{
new ActionModel(actionMethod1, Array.Empty<object>()),
}
};
var controllerType2 = typeof(WorldController).GetTypeInfo();
var actionMethod2 = controllerType2.GetMethod(nameof(WorldController.GetWorld));
var controllerModel2 = new ControllerModel(controllerType2, Array.Empty<object>())
{
Actions =
{
new ActionModel(actionMethod2, Array.Empty<object>()),
},
};
app.Controllers.Add(controllerModel1);
app.Controllers.Add(controllerModel2);
var options = new MvcOptions();
options.Conventions.Add(new SimpleActionConvention());
@ -57,15 +76,76 @@ namespace Microsoft.Extensions.DependencyInjection
options.Conventions[0].Apply(app);
// Assert
foreach (var controller in app.Controllers)
var kvp = Assert.Single(controllerModel1.Actions[0].Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
kvp = Assert.Single(controllerModel2.Actions[0].Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
}
[Fact]
public void AddedParameterConvention_AppliesToAllPropertiesAndParameters()
{
// Arrange
var app = new ApplicationModel();
var controllerType1 = typeof(HelloController).GetTypeInfo();
var parameterModel1 = new ParameterModel(
controllerType1.GetMethod(nameof(HelloController.GetInfo)).GetParameters()[0],
Array.Empty<object>());
var actionMethod1 = controllerType1.GetMethod(nameof(HelloController.GetInfo));
var property1 = controllerType1.GetProperty(nameof(HelloController.Property1));
var controllerModel1 = new ControllerModel(controllerType1, Array.Empty<object>())
{
foreach (var action in controller.Actions)
ControllerProperties =
{
var kvp = Assert.Single(action.Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
new PropertyModel(property1, Array.Empty<object>()),
},
Actions =
{
new ActionModel(actionMethod1, Array.Empty<object>())
{
Parameters =
{
parameterModel1,
}
}
}
}
};
var controllerType2 = typeof(WorldController).GetTypeInfo();
var property2 = controllerType2.GetProperty(nameof(WorldController.Property2));
var controllerModel2 = new ControllerModel(controllerType2, Array.Empty<object>())
{
ControllerProperties =
{
new PropertyModel(property2, Array.Empty<object>()),
},
};
app.Controllers.Add(controllerModel1);
app.Controllers.Add(controllerModel2);
var options = new MvcOptions();
var convention = new SimplePropertyConvention();
options.Conventions.Add(convention);
// Act
ApplicationModelConventions.ApplyConventions(app, options.Conventions);
// Assert
var kvp = Assert.Single(controllerModel1.ControllerProperties[0].Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
kvp = Assert.Single(controllerModel2.ControllerProperties[0].Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
kvp = Assert.Single(controllerModel1.Actions[0].Parameters[0].Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
}
[Fact]
@ -74,8 +154,8 @@ namespace Microsoft.Extensions.DependencyInjection
// 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>()));
app.Controllers.Add(new ControllerModel(typeof(HelloController).GetTypeInfo(), Array.Empty<object>()));
app.Controllers.Add(new ControllerModel(typeof(WorldController).GetTypeInfo(), Array.Empty<object>()));
options.Conventions.Add(new SimpleControllerConvention());
// Act
@ -115,7 +195,7 @@ namespace Microsoft.Extensions.DependencyInjection
// Arrange
var applicationModel = new ApplicationModel();
applicationModel.Controllers.Add(
new ControllerModel(typeof(HelloController).GetTypeInfo(), new List<object>())
new ControllerModel(typeof(HelloController).GetTypeInfo(), Array.Empty<object>())
{
Application = applicationModel
});
@ -128,18 +208,36 @@ namespace Microsoft.Extensions.DependencyInjection
ApplicationModelConventions.ApplyConventions(applicationModel, conventions);
}
[Fact]
public void ApplicationModelConventions_CopiesControllerModelCollectionOnApply_WhenRegisteredAsAnAttribute()
{
// Arrange
var controllerModelConvention = new ControllerModelCollectionModifyingConvention();
var applicationModel = new ApplicationModel();
applicationModel.Controllers.Add(
new ControllerModel(typeof(HelloController).GetTypeInfo(), new[] { controllerModelConvention })
{
Application = applicationModel
});
var conventions = new List<IApplicationModelConvention>();
// Act & Assert
ApplicationModelConventions.ApplyConventions(applicationModel, conventions);
}
[Fact]
public void ApplicationModelConventions_CopiesActionModelCollectionOnApply()
{
// Arrange
var controllerType = typeof(HelloController).GetTypeInfo();
var applicationModel = new ApplicationModel();
var controllerModel = new ControllerModel(controllerType, new List<object>())
var controllerModel = new ControllerModel(controllerType, Array.Empty<object>())
{
Application = applicationModel
};
controllerModel.Actions.Add(
new ActionModel(controllerType.GetMethod(nameof(HelloController.GetHello)), new List<object>())
new ActionModel(controllerType.GetMethod(nameof(HelloController.GetHello)), Array.Empty<object>())
{
Controller = controllerModel
});
@ -153,25 +251,74 @@ namespace Microsoft.Extensions.DependencyInjection
ApplicationModelConventions.ApplyConventions(applicationModel, conventions);
}
[Fact]
public void ApplicationModelConventions_CopiesPropertyModelCollectionOnApply()
{
// Arrange
var controllerType = typeof(HelloController).GetTypeInfo();
var applicationModel = new ApplicationModel();
var controllerModel = new ControllerModel(controllerType, Array.Empty<object>())
{
Application = applicationModel
};
controllerModel.ControllerProperties.Add(
new PropertyModel(controllerType.GetProperty(nameof(HelloController.Property1)), Array.Empty<object>())
{
Controller = controllerModel
});
applicationModel.Controllers.Add(controllerModel);
var propertyModelConvention = new ParameterModelBaseConvention();
var conventions = new List<IApplicationModelConvention>();
conventions.Add(propertyModelConvention);
// Act & Assert
ApplicationModelConventions.ApplyConventions(applicationModel, conventions);
}
[Fact]
public void ApplicationModelConventions_CopiesPropertyModelCollectionOnApply_WhenAppliedViaAttributes()
{
// Arrange
var propertyModelConvention = new ParameterModelBaseConvention();
var controllerType = typeof(HelloController).GetTypeInfo();
var applicationModel = new ApplicationModel();
var controllerModel = new ControllerModel(controllerType, Array.Empty<object>())
{
Application = applicationModel
};
controllerModel.ControllerProperties.Add(
new PropertyModel(controllerType.GetProperty(nameof(HelloController.Property1)), new[] { propertyModelConvention })
{
Controller = controllerModel
});
applicationModel.Controllers.Add(controllerModel);
var conventions = new List<IApplicationModelConvention>();
// Act & Assert
ApplicationModelConventions.ApplyConventions(applicationModel, conventions);
}
[Fact]
public void ApplicationModelConventions_CopiesParameterModelCollectionOnApply()
{
// Arrange
var controllerType = typeof(HelloController).GetTypeInfo();
var app = new ApplicationModel();
var controllerModel = new ControllerModel(controllerType, new List<object>())
var controllerModel = new ControllerModel(controllerType, Array.Empty<object>())
{
Application = app
};
app.Controllers.Add(controllerModel);
var actionModel = new ActionModel(controllerType.GetMethod(nameof(HelloController.GetInfo)), new List<object>())
var actionModel = new ActionModel(controllerType.GetMethod(nameof(HelloController.GetInfo)), Array.Empty<object>())
{
Controller = controllerModel
};
controllerModel.Actions.Add(actionModel);
var parameterModel = new ParameterModel(
controllerType.GetMethod(nameof(HelloController.GetInfo)).GetParameters()[0],
new List<object>())
Array.Empty<object>())
{
Action = actionModel
};
@ -185,6 +332,37 @@ namespace Microsoft.Extensions.DependencyInjection
ApplicationModelConventions.ApplyConventions(app, conventions);
}
[Fact]
public void ApplicationModelConventions_CopiesParameterModelCollectionOnApply_WhenRegisteredViaAttribute()
{
// Arrange
var parameterModelConvention = new ParameterModelCollectionModifyingConvention();
var controllerType = typeof(HelloController).GetTypeInfo();
var app = new ApplicationModel();
var controllerModel = new ControllerModel(controllerType, Array.Empty<object>())
{
Application = app
};
app.Controllers.Add(controllerModel);
var actionModel = new ActionModel(controllerType.GetMethod(nameof(HelloController.GetInfo)), Array.Empty<object>())
{
Controller = controllerModel
};
controllerModel.Actions.Add(actionModel);
var parameterModel = new ParameterModel(
controllerType.GetMethod(nameof(HelloController.GetInfo)).GetParameters()[0],
new[] { parameterModelConvention })
{
Action = actionModel
};
actionModel.Parameters.Add(parameterModel);
var conventions = new List<IApplicationModelConvention>();
// Act & Assert
ApplicationModelConventions.ApplyConventions(app, conventions);
}
[Fact]
public void GenericRemoveType_RemovesAllOfType()
{
@ -222,6 +400,8 @@ namespace Microsoft.Extensions.DependencyInjection
private class HelloController
{
public string Property1 { get; set; }
public string GetHello()
{
return "Hello";
@ -235,6 +415,8 @@ namespace Microsoft.Extensions.DependencyInjection
private class WorldController
{
public string Property2 { get; set; }
public string GetWorld()
{
return "World!";
@ -257,6 +439,14 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
private class SimplePropertyConvention : IParameterModelBaseConvention
{
public void Apply(ParameterModelBase action)
{
action.Properties.Add("TestProperty", "TestValue");
}
}
private class SimpleControllerConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
@ -289,6 +479,15 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
private class ParameterModelBaseConvention : IParameterModelBaseConvention
{
public void Apply(ParameterModelBase modelBase)
{
var property = (PropertyModel)modelBase;
property.Controller.ControllerProperties.Remove(property);
}
}
private class ParameterModelCollectionModifyingConvention : IParameterModelConvention
{
public void Apply(ParameterModel parameter)

View File

@ -1,11 +1,13 @@
// Copyright (c) .NET Foundation. 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.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
@ -28,8 +30,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var provider2 = new Mock<IPageApplicationModelProvider>();
var sequence = 0;
var pageApplicationModel1 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
var pageApplicationModel2 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
var pageApplicationModel1 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var pageApplicationModel2 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
provider1.Setup(p => p.OnProvidersExecuting(It.IsAny<PageApplicationModelProviderContext>()))
.Callback((PageApplicationModelProviderContext c) =>
@ -103,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
.Callback((PageApplicationModelProviderContext c) =>
{
Assert.Equal(1, sequence++);
c.PageApplicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
c.PageApplicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
})
.Verifiable();
@ -148,45 +150,346 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
[Fact]
public void Load_InvokesApplicationModelConventions()
public void ApplyConventions_InvokesApplicationModelConventions()
{
// Arrange
var descriptor = new PageActionDescriptor();
var model = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var compilerProvider = GetCompilerProvider();
var model = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
var provider = new Mock<IPageApplicationModelProvider>();
provider.Setup(p => p.OnProvidersExecuting(It.IsAny<PageApplicationModelProviderContext>()))
.Callback((PageApplicationModelProviderContext c) =>
{
c.PageApplicationModel = model;
});
var providers = new[] { provider.Object };
var razorPagesOptions = Options.Create(new RazorPagesOptions());
var mvcOptions = Options.Create(new MvcOptions());
var convention = new Mock<IPageApplicationModelConvention>();
convention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>()))
.Callback((PageApplicationModel m) =>
{
Assert.Same(model, m);
});
razorPagesOptions.Value.Conventions.Add(convention.Object);
var loader = new DefaultPageLoader(
providers,
compilerProvider,
razorPagesOptions,
mvcOptions);
})
.Verifiable();
var conventionCollection = new PageConventionCollection
{
convention.Object,
};
// Act
var result = loader.Load(new PageActionDescriptor());
DefaultPageLoader.ApplyConventions(conventionCollection, model);
// Assert
convention.Verify();
}
[Fact]
public void ApplyConventions_InvokesApplicationModelConventions_SpecifiedOnHandlerType()
{
// Arrange
var descriptor = new PageActionDescriptor();
var handlerConvention = new Mock<IPageApplicationModelConvention>();
var model = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new[] { handlerConvention.Object });
var globalConvention = new Mock<IPageApplicationModelConvention>();
globalConvention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>()))
.Callback((PageApplicationModel m) =>
{
Assert.Same(model, m);
})
.Verifiable();
handlerConvention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>()))
.Callback((PageApplicationModel m) =>
{
Assert.Same(model, m);
})
.Verifiable();
var conventionCollection = new PageConventionCollection
{
globalConvention.Object,
};
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, model);
// Assert
globalConvention.Verify();
handlerConvention.Verify();
}
[Fact]
public void ApplyConventions_InvokesHandlerModelConventions()
{
// Arrange
var descriptor = new PageActionDescriptor();
var methodInfo = GetType().GetMethod(nameof(OnGet), BindingFlags.Instance | BindingFlags.NonPublic);
var handlerModelConvention = new Mock<IPageHandlerModelConvention>();
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var handlerModel = new PageHandlerModel(methodInfo, new[] { handlerModelConvention.Object });
applicationModel.HandlerMethods.Add(handlerModel);
handlerModelConvention.Setup(p => p.Apply(It.IsAny<PageHandlerModel>()))
.Callback((PageHandlerModel m) =>
{
Assert.Same(handlerModel, m);
})
.Verifiable();
var conventionCollection = new PageConventionCollection();
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
handlerModelConvention.Verify();
}
[Fact]
public void ApplyConventions_InvokesHandlerModelConventions_DefinedGlobally()
{
// Arrange
var descriptor = new PageActionDescriptor();
var methodInfo = GetType().GetMethod(nameof(OnGet), BindingFlags.Instance | BindingFlags.NonPublic);
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var handlerModel = new PageHandlerModel(methodInfo, Array.Empty<object>());
applicationModel.HandlerMethods.Add(handlerModel);
var handlerModelConvention = new Mock<IPageHandlerModelConvention>();
handlerModelConvention.Setup(p => p.Apply(It.IsAny<PageHandlerModel>()))
.Callback((PageHandlerModel m) =>
{
Assert.Same(handlerModel, m);
})
.Verifiable();
var conventionCollection = new PageConventionCollection { handlerModelConvention.Object };
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
handlerModelConvention.Verify();
}
[Fact]
public void ApplyConventions_RemovingHandlerAsPartOfHandlerModelConvention_Works()
{
// Arrange
var descriptor = new PageActionDescriptor();
var methodInfo = GetType().GetMethod(nameof(OnGet), BindingFlags.Instance | BindingFlags.NonPublic);
var handlerModelConvention = new Mock<IPageHandlerModelConvention>();
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var handlerModel = new PageHandlerModel(methodInfo, new[] { handlerModelConvention.Object })
{
Page = applicationModel,
};
applicationModel.HandlerMethods.Add(handlerModel);
handlerModelConvention.Setup(p => p.Apply(It.IsAny<PageHandlerModel>()))
.Callback((PageHandlerModel m) =>
{
m.Page.HandlerMethods.Remove(m);
})
.Verifiable();
var conventionCollection = new PageConventionCollection();
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
handlerModelConvention.Verify();
}
[Fact]
public void ApplyConventions_InvokesParameterModelConventions()
{
// Arrange
var descriptor = new PageActionDescriptor();
var methodInfo = GetType().GetMethod(nameof(OnGet), BindingFlags.Instance | BindingFlags.NonPublic);
var parameterInfo = methodInfo.GetParameters()[0];
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var handlerModel = new PageHandlerModel(methodInfo, Array.Empty<object>());
var parameterModelConvention = new Mock<IParameterModelBaseConvention>();
var parameterModel = new PageParameterModel(parameterInfo, new[] { parameterModelConvention.Object });
applicationModel.HandlerMethods.Add(handlerModel);
handlerModel.Parameters.Add(parameterModel);
parameterModelConvention.Setup(p => p.Apply(It.IsAny<ParameterModelBase>()))
.Callback((ParameterModelBase m) =>
{
Assert.Same(parameterModel, m);
})
.Verifiable();
var conventionCollection = new PageConventionCollection();
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
parameterModelConvention.Verify();
}
[Fact]
public void ApplyConventions_InvokesParameterModelConventions_DeclaredGlobally()
{
// Arrange
var descriptor = new PageActionDescriptor();
var methodInfo = GetType().GetMethod(nameof(OnGet), BindingFlags.Instance | BindingFlags.NonPublic);
var parameterInfo = methodInfo.GetParameters()[0];
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var handlerModel = new PageHandlerModel(methodInfo, Array.Empty<object>());
var parameterModel = new PageParameterModel(parameterInfo, Array.Empty<object>());
applicationModel.HandlerMethods.Add(handlerModel);
handlerModel.Parameters.Add(parameterModel);
var parameterModelConvention = new Mock<IParameterModelBaseConvention>();
parameterModelConvention.Setup(p => p.Apply(It.IsAny<ParameterModelBase>()))
.Callback((ParameterModelBase m) =>
{
Assert.Same(parameterModel, m);
})
.Verifiable();
var conventionCollection = new PageConventionCollection { parameterModelConvention.Object };
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
parameterModelConvention.Verify();
}
[Fact]
public void ApplyConventions_RemovingParameterModelAsPartOfConventionWorks()
{
// Arrange
var descriptor = new PageActionDescriptor();
var methodInfo = GetType().GetMethod(nameof(OnGet), BindingFlags.Instance | BindingFlags.NonPublic);
var parameterInfo = methodInfo.GetParameters()[0];
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var handlerModel = new PageHandlerModel(methodInfo, Array.Empty<object>());
var parameterModelConvention = new Mock<IParameterModelBaseConvention>();
var parameterModel = new PageParameterModel(parameterInfo, new[] { parameterModelConvention.Object })
{
Handler = handlerModel,
};
applicationModel.HandlerMethods.Add(handlerModel);
handlerModel.Parameters.Add(parameterModel);
parameterModelConvention.Setup(p => p.Apply(It.IsAny<ParameterModelBase>()))
.Callback((ParameterModelBase m) =>
{
var model = Assert.IsType<PageParameterModel>(m);
model.Handler.Parameters.Remove(model);
})
.Verifiable();
var conventionCollection = new PageConventionCollection();
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
parameterModelConvention.Verify();
}
[Fact]
public void ApplyConventions_InvokesPropertyModelConventions()
{
// Arrange
var descriptor = new PageActionDescriptor();
var methodInfo = GetType().GetMethod(nameof(OnGet), BindingFlags.Instance | BindingFlags.NonPublic);
var propertyInfo = GetType().GetProperty(nameof(TestProperty), BindingFlags.Instance | BindingFlags.NonPublic);
var parameterInfo = methodInfo.GetParameters()[0];
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var handlerModel = new PageHandlerModel(methodInfo, Array.Empty<object>());
var parameterModel = new PageParameterModel(parameterInfo, Array.Empty<object>());
var propertyModelConvention = new Mock<IParameterModelBaseConvention>();
var propertyModel = new PagePropertyModel(propertyInfo, new[] { propertyModelConvention.Object });
applicationModel.HandlerMethods.Add(handlerModel);
applicationModel.HandlerProperties.Add(propertyModel);
handlerModel.Parameters.Add(parameterModel);
propertyModelConvention.Setup(p => p.Apply(It.IsAny<ParameterModelBase>()))
.Callback((ParameterModelBase m) =>
{
Assert.Same(propertyModel, m);
})
.Verifiable();
var conventionCollection = new PageConventionCollection();
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
propertyModelConvention.Verify();
}
[Fact]
public void ApplyConventions_InvokesPropertyModelConventions_DeclaredGlobally()
{
// Arrange
var descriptor = new PageActionDescriptor();
var methodInfo = GetType().GetMethod(nameof(OnGet), BindingFlags.Instance | BindingFlags.NonPublic);
var propertyInfo = GetType().GetProperty(nameof(TestProperty), BindingFlags.Instance | BindingFlags.NonPublic);
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var handlerModel = new PageHandlerModel(methodInfo, Array.Empty<object>());
var propertyModel = new PagePropertyModel(propertyInfo, Array.Empty<object>());
applicationModel.HandlerMethods.Add(handlerModel);
applicationModel.HandlerProperties.Add(propertyModel);
var propertyModelConvention = new Mock<IParameterModelBaseConvention>();
propertyModelConvention.Setup(p => p.Apply(It.IsAny<ParameterModelBase>()))
.Callback((ParameterModelBase m) =>
{
Assert.Same(propertyModel, m);
})
.Verifiable();
var conventionCollection = new PageConventionCollection { propertyModelConvention.Object };
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
propertyModelConvention.Verify();
}
[Fact]
public void ApplyConventions_RemovingPropertyModelAsPartOfConvention_Works()
{
// Arrange
var descriptor = new PageActionDescriptor();
var propertyInfo = GetType().GetProperty(nameof(TestProperty), BindingFlags.Instance | BindingFlags.NonPublic);
var applicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
var propertyModelConvention = new Mock<IParameterModelBaseConvention>();
var propertyModel = new PagePropertyModel(propertyInfo, new[] { propertyModelConvention.Object })
{
Page = applicationModel,
};
applicationModel.HandlerProperties.Add(propertyModel);
propertyModelConvention.Setup(p => p.Apply(It.IsAny<ParameterModelBase>()))
.Callback((ParameterModelBase m) =>
{
var model = Assert.IsType<PagePropertyModel>(m);
model.Page.HandlerProperties.Remove(model);
})
.Verifiable();
var conventionCollection = new PageConventionCollection();
// Act
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
// Assert
propertyModelConvention.Verify();
}
private static IViewCompilerProvider GetCompilerProvider()
{
var descriptor = new CompiledViewDescriptor
@ -202,5 +505,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
.Returns(compiler.Object);
return compilerProvider.Object;
}
private void OnGet(string parameter)
{
}
private string TestProperty { get; set; }
}
}