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 namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
/// <summary> /// <summary>
/// Allows customization of the <see cref="ControllerModel"/>. /// Allows customization of the <see cref="ParameterModel"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// To use this interface, create an <see cref="System.Attribute"/> class which implements the interface and /// 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> /// </remarks>
public interface IParameterModelConvention 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); void Apply(ParameterModel parameter);
} }
} }

View File

@ -5,33 +5,22 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
[DebuggerDisplay("ParameterModel: Name={ParameterName}")] [DebuggerDisplay("ParameterModel: Name={ParameterName}")]
public class ParameterModel : ICommonModel, IBindingModel public class ParameterModel : ParameterModelBase, ICommonModel
{ {
public ParameterModel( public ParameterModel(
ParameterInfo parameterInfo, ParameterInfo parameterInfo,
IReadOnlyList<object> attributes) IReadOnlyList<object> attributes)
: base(parameterInfo?.ParameterType, attributes)
{ {
if (parameterInfo == null) ParameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
{
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);
} }
public ParameterModel(ParameterModel other) public ParameterModel(ParameterModel other)
: base(other)
{ {
if (other == null) if (other == null)
{ {
@ -39,27 +28,23 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
} }
Action = other.Action; Action = other.Action;
Attributes = new List<object>(other.Attributes);
BindingInfo = other.BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
ParameterInfo = other.ParameterInfo; ParameterInfo = other.ParameterInfo;
ParameterName = other.ParameterName;
Properties = new Dictionary<object, object>(other.Properties);
} }
public ActionModel Action { get; set; } 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; MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member;
string ICommonModel.Name => ParameterName;
public ParameterInfo ParameterInfo { get; } public ParameterInfo ParameterInfo { get; }
public string ParameterName { get; set; } public string ParameterName
{
public BindingInfo BindingInfo { get; set; } 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"/>. /// A type which is used to represent a property in a <see cref="ControllerModel"/>.
/// </summary> /// </summary>
[DebuggerDisplay("PropertyModel: Name={PropertyName}")] [DebuggerDisplay("PropertyModel: Name={PropertyName}")]
public class PropertyModel : ICommonModel, IBindingModel public class PropertyModel : ParameterModelBase, ICommonModel, IBindingModel
{ {
/// <summary> /// <summary>
/// Creates a new instance of <see cref="PropertyModel"/>. /// Creates a new instance of <see cref="PropertyModel"/>.
@ -23,20 +23,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public PropertyModel( public PropertyModel(
PropertyInfo propertyInfo, PropertyInfo propertyInfo,
IReadOnlyList<object> attributes) IReadOnlyList<object> attributes)
: base(propertyInfo?.PropertyType, attributes)
{ {
if (propertyInfo == null) PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
{
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);
} }
/// <summary> /// <summary>
@ -44,6 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary> /// </summary>
/// <param name="other">The <see cref="PropertyModel"/> which needs to be copied.</param> /// <param name="other">The <see cref="PropertyModel"/> which needs to be copied.</param>
public PropertyModel(PropertyModel other) public PropertyModel(PropertyModel other)
: base(other)
{ {
if (other == null) if (other == null)
{ {
@ -51,11 +41,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
} }
Controller = other.Controller; Controller = other.Controller;
Attributes = new List<object>(other.Attributes);
BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo); BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
PropertyInfo = other.PropertyInfo; PropertyInfo = other.PropertyInfo;
PropertyName = other.PropertyName;
Properties = new Dictionary<object, object>(other.Properties);
} }
/// <summary> /// <summary>
@ -63,30 +50,18 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary> /// </summary>
public ControllerModel Controller { get; set; } 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; MemberInfo ICommonModel.MemberInfo => PropertyInfo;
string ICommonModel.Name => PropertyName; public new IDictionary<object, object> Properties => base.Properties;
/// <summary> public new IReadOnlyList<object> Attributes => base.Attributes;
/// 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; } public PropertyInfo PropertyInfo { get; }
/// <summary> public string PropertyName
/// Gets or sets the name of the property represented by this model. {
/// </summary> get => Name;
public string PropertyName { get; set; } 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
@ -18,7 +18,8 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary> /// </summary>
/// <param name="list">The list of <see cref="IApplicationModelConvention"/>s.</param> /// <param name="list">The list of <see cref="IApplicationModelConvention"/>s.</param>
/// <typeparam name="TApplicationModelConvention">The type to remove.</typeparam> /// <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) if (list == null)
{ {
@ -127,17 +128,36 @@ namespace Microsoft.Extensions.DependencyInjection
conventions.Add(new ParameterApplicationModelConvention(parameterModelConvention)); 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 class ParameterApplicationModelConvention : IApplicationModelConvention
{ {
private readonly IParameterModelConvention _parameterModelConvention; private readonly IParameterModelConvention _parameterModelConvention;
public ParameterApplicationModelConvention(IParameterModelConvention parameterModelConvention) public ParameterApplicationModelConvention(IParameterModelConvention parameterModelConvention)
{ {
if (parameterModelConvention == null)
{
throw new ArgumentNullException(nameof(parameterModelConvention));
}
_parameterModelConvention = 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 class ActionApplicationModelConvention : IApplicationModelConvention
{ {
private readonly IActionModelConvention _actionModelConvention; private readonly IActionModelConvention _actionModelConvention;

View File

@ -39,8 +39,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
convention.Apply(applicationModel); convention.Apply(applicationModel);
} }
var controllers = applicationModel.Controllers.ToArray();
// First apply the conventions from attributes in decreasing order of scope. // 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 // ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it. // while iterating it.
@ -54,7 +55,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controllerConvention.Apply(controller); 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 // ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it. // while iterating it.
@ -68,7 +70,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
actionConvention.Apply(action); 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 // ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it. // while iterating it.
@ -81,9 +84,35 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
parameterConvention.Apply(parameter); 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.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
[DebuggerDisplay("PageParameterModel: Name={ParameterName}")] [DebuggerDisplay("PageParameterModel: Name={ParameterName}")]
public class PageParameterModel : ICommonModel, IBindingModel public class PageParameterModel : ParameterModelBase, ICommonModel, IBindingModel
{ {
public PageParameterModel( public PageParameterModel(
ParameterInfo parameterInfo, ParameterInfo parameterInfo,
IReadOnlyList<object> attributes) 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) if (attributes == null)
{ {
throw new ArgumentNullException(nameof(attributes)); throw new ArgumentNullException(nameof(attributes));
} }
Properties = new Dictionary<object, object>(); ParameterInfo = parameterInfo;
Attributes = new List<object>(attributes);
} }
public PageParameterModel(PageParameterModel other) public PageParameterModel(PageParameterModel other)
: base(other)
{ {
if (other == null) if (other == null)
{ {
@ -35,27 +38,19 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
} }
Handler = other.Handler; Handler = other.Handler;
Attributes = new List<object>(other.Attributes);
BindingInfo = other.BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
ParameterInfo = other.ParameterInfo; ParameterInfo = other.ParameterInfo;
ParameterName = other.ParameterName;
Properties = new Dictionary<object, object>(other.Properties);
} }
public PageHandlerModel Handler { get; set; } public PageHandlerModel Handler { get; set; }
public IReadOnlyList<object> Attributes { get; }
public IDictionary<object, object> Properties { get; }
MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member; MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member;
string ICommonModel.Name => ParameterName;
public ParameterInfo ParameterInfo { get; } public ParameterInfo ParameterInfo { get; }
public string ParameterName { get; set; } public string ParameterName
{
public BindingInfo BindingInfo { get; set; } get => Name;
set => Name = value;
}
} }
} }

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// Represents a property in a <see cref="PageApplicationModel"/>. /// Represents a property in a <see cref="PageApplicationModel"/>.
/// </summary> /// </summary>
[DebuggerDisplay("PagePropertyModel: Name={PropertyName}")] [DebuggerDisplay("PagePropertyModel: Name={PropertyName}")]
public class PagePropertyModel : ICommonModel, IBindingModel public class PagePropertyModel : ParameterModelBase, ICommonModel
{ {
/// <summary> /// <summary>
/// Creates a new instance of <see cref="PagePropertyModel"/>. /// Creates a new instance of <see cref="PagePropertyModel"/>.
@ -23,10 +23,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public PagePropertyModel( public PagePropertyModel(
PropertyInfo propertyInfo, PropertyInfo propertyInfo,
IReadOnlyList<object> attributes) IReadOnlyList<object> attributes)
: base(propertyInfo?.PropertyType, attributes)
{ {
PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
Properties = new Dictionary<object, object>();
Attributes = new List<object>(attributes) ?? throw new ArgumentNullException(nameof(attributes));
} }
/// <summary> /// <summary>
@ -34,6 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary> /// </summary>
/// <param name="other">The <see cref="PagePropertyModel"/> which needs to be copied.</param> /// <param name="other">The <see cref="PagePropertyModel"/> which needs to be copied.</param>
public PagePropertyModel(PagePropertyModel other) public PagePropertyModel(PagePropertyModel other)
: base(other)
{ {
if (other == null) if (other == null)
{ {
@ -41,11 +41,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
} }
Page = other.Page; Page = other.Page;
Attributes = new List<object>(other.Attributes);
BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo); BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
PropertyInfo = other.PropertyInfo; PropertyInfo = other.PropertyInfo;
PropertyName = other.PropertyName;
Properties = new Dictionary<object, object>(other.Properties);
} }
/// <summary> /// <summary>
@ -53,31 +50,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary> /// </summary>
public PageApplicationModel Page { get; set; } 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; 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; 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> /// <summary>
/// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified area folder. /// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified area folder.
/// </summary> /// </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 IPageApplicationModelProvider[] _applicationModelProviders;
private readonly IViewCompilerProvider _viewCompilerProvider; private readonly IViewCompilerProvider _viewCompilerProvider;
private readonly IPageApplicationModelConvention[] _conventions; private readonly PageConventionCollection _conventions;
private readonly FilterCollection _globalFilters; private readonly FilterCollection _globalFilters;
public DefaultPageLoader( public DefaultPageLoader(
@ -30,9 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
.OrderBy(p => p.Order) .OrderBy(p => p.Order)
.ToArray(); .ToArray();
_viewCompilerProvider = viewCompilerProvider; _viewCompilerProvider = viewCompilerProvider;
_conventions = pageOptions.Value.Conventions _conventions = pageOptions.Value.Conventions;
.OfType<IPageApplicationModelConvention>()
.ToArray();
_globalFilters = mvcOptions.Value.Filters; _globalFilters = mvcOptions.Value.Filters;
} }
@ -60,12 +58,58 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_applicationModelProviders[i].OnProvidersExecuted(context); _applicationModelProviders[i].OnProvidersExecuted(context);
} }
for (var i = 0; i < _conventions.Length; i++) ApplyConventions(_conventions, context.PageApplicationModel);
{
_conventions[i].Apply(context.PageApplicationModel);
}
return CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel, _globalFilters); 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 // Arrange
var app = new ApplicationModel(); var app = new ApplicationModel();
app.Controllers.Add(new ControllerModel(typeof(HelloController).GetTypeInfo(), new List<object>())); var controllerType = typeof(HelloController);
app.Controllers.Add(new ControllerModel(typeof(WorldController).GetTypeInfo(), new List<object>())); 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(); var options = new MvcOptions();
options.Conventions.Add(new SimpleParameterConvention()); options.Conventions.Add(new SimpleParameterConvention());
@ -28,18 +36,9 @@ namespace Microsoft.Extensions.DependencyInjection
options.Conventions[0].Apply(app); options.Conventions[0].Apply(app);
// Assert // Assert
foreach (var controller in app.Controllers) var kvp = Assert.Single(parameterModel.Properties);
{ Assert.Equal("TestProperty", kvp.Key);
foreach (var action in controller.Actions) Assert.Equal("TestValue", kvp.Value);
{
foreach (var parameter in action.Parameters)
{
var kvp = Assert.Single(parameter.Properties);
Assert.Equal("TestProperty", kvp.Key);
Assert.Equal("TestValue", kvp.Value);
}
}
}
} }
[Fact] [Fact]
@ -47,8 +46,28 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
// Arrange // Arrange
var app = new ApplicationModel(); var app = new ApplicationModel();
app.Controllers.Add(new ControllerModel(typeof(HelloController).GetTypeInfo(), new List<object>())); var controllerType1 = typeof(HelloController).GetTypeInfo();
app.Controllers.Add(new ControllerModel(typeof(WorldController).GetTypeInfo(), new List<object>())); 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(); var options = new MvcOptions();
options.Conventions.Add(new SimpleActionConvention()); options.Conventions.Add(new SimpleActionConvention());
@ -57,15 +76,76 @@ namespace Microsoft.Extensions.DependencyInjection
options.Conventions[0].Apply(app); options.Conventions[0].Apply(app);
// Assert // 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); new PropertyModel(property1, Array.Empty<object>()),
Assert.Equal("TestProperty", kvp.Key); },
Assert.Equal("TestValue", kvp.Value); 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] [Fact]
@ -74,8 +154,8 @@ namespace Microsoft.Extensions.DependencyInjection
// Arrange // Arrange
var options = new MvcOptions(); var options = new MvcOptions();
var app = new ApplicationModel(); var app = new ApplicationModel();
app.Controllers.Add(new ControllerModel(typeof(HelloController).GetTypeInfo(), new List<object>())); app.Controllers.Add(new ControllerModel(typeof(HelloController).GetTypeInfo(), Array.Empty<object>()));
app.Controllers.Add(new ControllerModel(typeof(WorldController).GetTypeInfo(), new List<object>())); app.Controllers.Add(new ControllerModel(typeof(WorldController).GetTypeInfo(), Array.Empty<object>()));
options.Conventions.Add(new SimpleControllerConvention()); options.Conventions.Add(new SimpleControllerConvention());
// Act // Act
@ -115,7 +195,7 @@ namespace Microsoft.Extensions.DependencyInjection
// Arrange // Arrange
var applicationModel = new ApplicationModel(); var applicationModel = new ApplicationModel();
applicationModel.Controllers.Add( applicationModel.Controllers.Add(
new ControllerModel(typeof(HelloController).GetTypeInfo(), new List<object>()) new ControllerModel(typeof(HelloController).GetTypeInfo(), Array.Empty<object>())
{ {
Application = applicationModel Application = applicationModel
}); });
@ -128,18 +208,36 @@ namespace Microsoft.Extensions.DependencyInjection
ApplicationModelConventions.ApplyConventions(applicationModel, conventions); 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] [Fact]
public void ApplicationModelConventions_CopiesActionModelCollectionOnApply() public void ApplicationModelConventions_CopiesActionModelCollectionOnApply()
{ {
// Arrange // Arrange
var controllerType = typeof(HelloController).GetTypeInfo(); var controllerType = typeof(HelloController).GetTypeInfo();
var applicationModel = new ApplicationModel(); var applicationModel = new ApplicationModel();
var controllerModel = new ControllerModel(controllerType, new List<object>()) var controllerModel = new ControllerModel(controllerType, Array.Empty<object>())
{ {
Application = applicationModel Application = applicationModel
}; };
controllerModel.Actions.Add( 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 Controller = controllerModel
}); });
@ -153,25 +251,74 @@ namespace Microsoft.Extensions.DependencyInjection
ApplicationModelConventions.ApplyConventions(applicationModel, conventions); 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] [Fact]
public void ApplicationModelConventions_CopiesParameterModelCollectionOnApply() public void ApplicationModelConventions_CopiesParameterModelCollectionOnApply()
{ {
// Arrange // Arrange
var controllerType = typeof(HelloController).GetTypeInfo(); var controllerType = typeof(HelloController).GetTypeInfo();
var app = new ApplicationModel(); var app = new ApplicationModel();
var controllerModel = new ControllerModel(controllerType, new List<object>()) var controllerModel = new ControllerModel(controllerType, Array.Empty<object>())
{ {
Application = app Application = app
}; };
app.Controllers.Add(controllerModel); 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 Controller = controllerModel
}; };
controllerModel.Actions.Add(actionModel); controllerModel.Actions.Add(actionModel);
var parameterModel = new ParameterModel( var parameterModel = new ParameterModel(
controllerType.GetMethod(nameof(HelloController.GetInfo)).GetParameters()[0], controllerType.GetMethod(nameof(HelloController.GetInfo)).GetParameters()[0],
new List<object>()) Array.Empty<object>())
{ {
Action = actionModel Action = actionModel
}; };
@ -185,6 +332,37 @@ namespace Microsoft.Extensions.DependencyInjection
ApplicationModelConventions.ApplyConventions(app, conventions); 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] [Fact]
public void GenericRemoveType_RemovesAllOfType() public void GenericRemoveType_RemovesAllOfType()
{ {
@ -222,6 +400,8 @@ namespace Microsoft.Extensions.DependencyInjection
private class HelloController private class HelloController
{ {
public string Property1 { get; set; }
public string GetHello() public string GetHello()
{ {
return "Hello"; return "Hello";
@ -235,6 +415,8 @@ namespace Microsoft.Extensions.DependencyInjection
private class WorldController private class WorldController
{ {
public string Property2 { get; set; }
public string GetWorld() public string GetWorld()
{ {
return "World!"; 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 private class SimpleControllerConvention : IControllerModelConvention
{ {
public void Apply(ControllerModel controller) 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 private class ParameterModelCollectionModifyingConvention : IParameterModelConvention
{ {
public void Apply(ParameterModel parameter) public void Apply(ParameterModel parameter)

View File

@ -1,11 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
@ -28,8 +30,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var provider2 = new Mock<IPageApplicationModelProvider>(); var provider2 = new Mock<IPageApplicationModelProvider>();
var sequence = 0; var sequence = 0;
var pageApplicationModel1 = 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(), new object[0]); var pageApplicationModel2 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
provider1.Setup(p => p.OnProvidersExecuting(It.IsAny<PageApplicationModelProviderContext>())) provider1.Setup(p => p.OnProvidersExecuting(It.IsAny<PageApplicationModelProviderContext>()))
.Callback((PageApplicationModelProviderContext c) => .Callback((PageApplicationModelProviderContext c) =>
@ -103,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
.Callback((PageApplicationModelProviderContext c) => .Callback((PageApplicationModelProviderContext c) =>
{ {
Assert.Equal(1, sequence++); 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(); .Verifiable();
@ -148,45 +150,346 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
} }
[Fact] [Fact]
public void Load_InvokesApplicationModelConventions() public void ApplyConventions_InvokesApplicationModelConventions()
{ {
// Arrange // Arrange
var descriptor = new PageActionDescriptor(); 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>(); var convention = new Mock<IPageApplicationModelConvention>();
convention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>())) convention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>()))
.Callback((PageApplicationModel m) => .Callback((PageApplicationModel m) =>
{ {
Assert.Same(model, m); Assert.Same(model, m);
}); })
razorPagesOptions.Value.Conventions.Add(convention.Object); .Verifiable();
var conventionCollection = new PageConventionCollection
var loader = new DefaultPageLoader( {
providers, convention.Object,
compilerProvider, };
razorPagesOptions,
mvcOptions);
// Act // Act
var result = loader.Load(new PageActionDescriptor()); DefaultPageLoader.ApplyConventions(conventionCollection, model);
// Assert // Assert
convention.Verify(); 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() private static IViewCompilerProvider GetCompilerProvider()
{ {
var descriptor = new CompiledViewDescriptor var descriptor = new CompiledViewDescriptor
@ -202,5 +505,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
.Returns(compiler.Object); .Returns(compiler.Object);
return compilerProvider.Object; return compilerProvider.Object;
} }
private void OnGet(string parameter)
{
}
private string TestProperty { get; set; }
} }
} }