Introduce IPageApplicationModelPartsProvider (#9066)
* Resolved #6919. It might be required to rework unit tests.
This commit is contained in:
parent
3fbf3ac791
commit
1d5d144c12
|
|
@ -18,6 +18,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
{
|
{
|
||||||
void Apply(Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel model);
|
void Apply(Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel model);
|
||||||
}
|
}
|
||||||
|
public partial interface IPageApplicationModelPartsProvider
|
||||||
|
{
|
||||||
|
Microsoft.AspNetCore.Mvc.ApplicationModels.PageHandlerModel CreateHandlerModel(System.Reflection.MethodInfo method);
|
||||||
|
Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel CreateParameterModel(System.Reflection.ParameterInfo parameter);
|
||||||
|
Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel CreatePropertyModel(System.Reflection.PropertyInfo property);
|
||||||
|
bool IsHandler(System.Reflection.MethodInfo methodInfo);
|
||||||
|
}
|
||||||
public partial interface IPageApplicationModelProvider
|
public partial interface IPageApplicationModelProvider
|
||||||
{
|
{
|
||||||
int Order { get; }
|
int Order { get; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
// 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 System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
|
{
|
||||||
|
internal class DefaultPageApplicationModelPartsProvider: IPageApplicationModelPartsProvider
|
||||||
|
{
|
||||||
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
|
|
||||||
|
private readonly Func<ActionContext, bool> _supportsAllRequests;
|
||||||
|
private readonly Func<ActionContext, bool> _supportsNonGetRequests;
|
||||||
|
|
||||||
|
|
||||||
|
public DefaultPageApplicationModelPartsProvider(IModelMetadataProvider modelMetadataProvider)
|
||||||
|
{
|
||||||
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
|
|
||||||
|
_supportsAllRequests = _ => true;
|
||||||
|
_supportsNonGetRequests = context => !HttpMethods.IsGet(context.HttpContext.Request.Method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="PageHandlerModel"/> for the specified <paramref name="method"/>.s
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The <see cref="MethodInfo"/>.</param>
|
||||||
|
/// <returns>The <see cref="PageHandlerModel"/>.</returns>
|
||||||
|
public PageHandlerModel CreateHandlerModel(MethodInfo method)
|
||||||
|
{
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsHandler(method))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryParseHandlerMethod(method.Name, out var httpMethod, out var handlerName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlerModel = new PageHandlerModel(
|
||||||
|
method,
|
||||||
|
method.GetCustomAttributes(inherit: true))
|
||||||
|
{
|
||||||
|
Name = method.Name,
|
||||||
|
HandlerName = handlerName,
|
||||||
|
HttpMethod = httpMethod,
|
||||||
|
};
|
||||||
|
|
||||||
|
var methodParameters = handlerModel.MethodInfo.GetParameters();
|
||||||
|
|
||||||
|
for (var i = 0; i < methodParameters.Length; i++)
|
||||||
|
{
|
||||||
|
var parameter = methodParameters[i];
|
||||||
|
var parameterModel = CreateParameterModel(parameter);
|
||||||
|
parameterModel.Handler = handlerModel;
|
||||||
|
|
||||||
|
handlerModel.Parameters.Add(parameterModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlerModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="PageParameterModel"/> for the specified <paramref name="parameter"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The <see cref="ParameterInfo"/>.</param>
|
||||||
|
/// <returns>The <see cref="PageParameterModel"/>.</returns>
|
||||||
|
public PageParameterModel CreateParameterModel(ParameterInfo parameter)
|
||||||
|
{
|
||||||
|
if (parameter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(parameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes = parameter.GetCustomAttributes(inherit: true);
|
||||||
|
|
||||||
|
BindingInfo bindingInfo;
|
||||||
|
if (_modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
|
||||||
|
{
|
||||||
|
var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameter);
|
||||||
|
bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bindingInfo = BindingInfo.GetBindingInfo(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PageParameterModel(parameter, attributes)
|
||||||
|
{
|
||||||
|
BindingInfo = bindingInfo,
|
||||||
|
ParameterName = parameter.Name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="PagePropertyModel"/> for the <paramref name="property"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="property">The <see cref="PropertyInfo"/>.</param>
|
||||||
|
/// <returns>The <see cref="PagePropertyModel"/>.</returns>
|
||||||
|
public PagePropertyModel CreatePropertyModel(PropertyInfo property)
|
||||||
|
{
|
||||||
|
if (property == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyAttributes = property.GetCustomAttributes(inherit: true);
|
||||||
|
|
||||||
|
// BindingInfo for properties can be either specified by decorating the property with binding-specific attributes.
|
||||||
|
// ModelMetadata also adds information from the property's type and any configured IBindingMetadataProvider.
|
||||||
|
var propertyMetadata = _modelMetadataProvider.GetMetadataForProperty(property.DeclaringType, property.Name);
|
||||||
|
var bindingInfo = BindingInfo.GetBindingInfo(propertyAttributes, propertyMetadata);
|
||||||
|
|
||||||
|
if (bindingInfo == null)
|
||||||
|
{
|
||||||
|
// Look for BindPropertiesAttribute on the handler type if no BindingInfo was inferred for the property.
|
||||||
|
// This allows a user to enable model binding on properties by decorating the controller type with BindPropertiesAttribute.
|
||||||
|
var declaringType = property.DeclaringType;
|
||||||
|
var bindPropertiesAttribute = declaringType.GetCustomAttribute<BindPropertiesAttribute>(inherit: true);
|
||||||
|
if (bindPropertiesAttribute != null)
|
||||||
|
{
|
||||||
|
var requestPredicate = bindPropertiesAttribute.SupportsGet ? _supportsAllRequests : _supportsNonGetRequests;
|
||||||
|
bindingInfo = new BindingInfo
|
||||||
|
{
|
||||||
|
RequestPredicate = requestPredicate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var model = new PagePropertyModel(property, propertyAttributes)
|
||||||
|
{
|
||||||
|
PropertyName = property.Name,
|
||||||
|
BindingInfo = bindingInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the specified <paramref name="methodInfo"/> is a handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodInfo">The <see cref="MethodInfo"/>.</param>
|
||||||
|
/// <returns><c>true</c> if the <paramref name="methodInfo"/> is a handler. Otherwise <c>false</c>.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Override this method to provide custom logic to determine which methods are considered handlers.
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsHandler(MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
// The SpecialName bit is set to flag members that are treated in a special way by some compilers
|
||||||
|
// (such as property accessors and operator overloading methods).
|
||||||
|
if (methodInfo.IsSpecialName)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overridden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
|
||||||
|
if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodInfo.IsStatic)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodInfo.IsAbstract)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodInfo.IsConstructor)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodInfo.IsGenericMethod)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!methodInfo.IsPublic)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodInfo.IsDefined(typeof(NonHandlerAttribute)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude the whole hierarchy of Page.
|
||||||
|
var declaringType = methodInfo.DeclaringType;
|
||||||
|
if (declaringType == typeof(Page) ||
|
||||||
|
declaringType == typeof(PageBase) ||
|
||||||
|
declaringType == typeof(RazorPageBase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude methods declared on PageModel
|
||||||
|
if (declaringType == typeof(PageModel))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool TryParseHandlerMethod(string methodName, out string httpMethod, out string handler)
|
||||||
|
{
|
||||||
|
httpMethod = null;
|
||||||
|
handler = null;
|
||||||
|
|
||||||
|
// Handler method names always start with "On"
|
||||||
|
if (!methodName.StartsWith("On") || methodName.Length <= "On".Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we parse the method name according to our conventions to determine the required HTTP method
|
||||||
|
// and optional 'handler name'.
|
||||||
|
//
|
||||||
|
// Valid names look like:
|
||||||
|
// - OnGet
|
||||||
|
// - OnPost
|
||||||
|
// - OnFooBar
|
||||||
|
// - OnTraceAsync
|
||||||
|
// - OnPostEditAsync
|
||||||
|
|
||||||
|
var start = "On".Length;
|
||||||
|
var length = methodName.Length;
|
||||||
|
if (methodName.EndsWith("Async", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
length -= "Async".Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start == length)
|
||||||
|
{
|
||||||
|
// There are no additional characters. This is "On" or "OnAsync".
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The http method follows "On" and is required to be at least one character. We use casing
|
||||||
|
// to determine where it ends.
|
||||||
|
var handlerNameStart = start + 1;
|
||||||
|
for (; handlerNameStart < length; handlerNameStart++)
|
||||||
|
{
|
||||||
|
if (char.IsUpper(methodName[handlerNameStart]))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpMethod = methodName.Substring(start, handlerNameStart - start);
|
||||||
|
|
||||||
|
// The handler name follows the http method and is optional. It includes everything up to the end
|
||||||
|
// excluding the "Async" suffix (if present).
|
||||||
|
handler = handlerNameStart == length ? null : methodName.Substring(handlerNameStart, length - handlerNameStart);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,19 +22,18 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
private readonly PageHandlerResultFilter _pageHandlerResultFilter = new PageHandlerResultFilter();
|
private readonly PageHandlerResultFilter _pageHandlerResultFilter = new PageHandlerResultFilter();
|
||||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
private readonly RazorPagesOptions _razorPagesOptions;
|
private readonly RazorPagesOptions _razorPagesOptions;
|
||||||
private readonly Func<ActionContext, bool> _supportsAllRequests;
|
private readonly IPageApplicationModelPartsProvider _pageApplicationModelPartsProvider;
|
||||||
private readonly Func<ActionContext, bool> _supportsNonGetRequests;
|
|
||||||
private readonly HandleOptionsRequestsPageFilter _handleOptionsRequestsFilter;
|
private readonly HandleOptionsRequestsPageFilter _handleOptionsRequestsFilter;
|
||||||
|
|
||||||
public DefaultPageApplicationModelProvider(
|
public DefaultPageApplicationModelProvider(
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
IOptions<RazorPagesOptions> razorPagesOptions)
|
IOptions<RazorPagesOptions> razorPagesOptions,
|
||||||
|
IPageApplicationModelPartsProvider pageApplicationModelPartsProvider)
|
||||||
{
|
{
|
||||||
_modelMetadataProvider = modelMetadataProvider;
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
_razorPagesOptions = razorPagesOptions.Value;
|
_razorPagesOptions = razorPagesOptions.Value;
|
||||||
|
_pageApplicationModelPartsProvider = pageApplicationModelPartsProvider;
|
||||||
|
|
||||||
_supportsAllRequests = _ => true;
|
|
||||||
_supportsNonGetRequests = context => !HttpMethods.IsGet(context.HttpContext.Request.Method);
|
|
||||||
_handleOptionsRequestsFilter = new HandleOptionsRequestsPageFilter();
|
_handleOptionsRequestsFilter = new HandleOptionsRequestsPageFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,7 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
|
|
||||||
for (var i = 0; i < properties.Length; i++)
|
for (var i = 0; i < properties.Length; i++)
|
||||||
{
|
{
|
||||||
var propertyModel = CreatePropertyModel(properties[i].Property);
|
var propertyModel = _pageApplicationModelPartsProvider.CreatePropertyModel(properties[i].Property);
|
||||||
if (propertyModel != null)
|
if (propertyModel != null)
|
||||||
{
|
{
|
||||||
propertyModel.Page = pageModel;
|
propertyModel.Page = pageModel;
|
||||||
|
|
@ -149,7 +148,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
|
|
||||||
for (var i = 0; i < methods.Length; i++)
|
for (var i = 0; i < methods.Length; i++)
|
||||||
{
|
{
|
||||||
var handler = CreateHandlerModel(methods[i]);
|
var handler = _pageApplicationModelPartsProvider.CreateHandlerModel(methods[i]);
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
pageModel.HandlerMethods.Add(handler);
|
pageModel.HandlerMethods.Add(handler);
|
||||||
|
|
@ -181,250 +180,5 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
|
|
||||||
pageModel.Filters.Add(_handleOptionsRequestsFilter);
|
pageModel.Filters.Add(_handleOptionsRequestsFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a <see cref="PageHandlerModel"/> for the specified <paramref name="method"/>.s
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The <see cref="MethodInfo"/>.</param>
|
|
||||||
/// <returns>The <see cref="PageHandlerModel"/>.</returns>
|
|
||||||
protected virtual PageHandlerModel CreateHandlerModel(MethodInfo method)
|
|
||||||
{
|
|
||||||
if (method == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(method));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsHandler(method))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryParseHandlerMethod(method.Name, out var httpMethod, out var handlerName))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var handlerModel = new PageHandlerModel(
|
|
||||||
method,
|
|
||||||
method.GetCustomAttributes(inherit: true))
|
|
||||||
{
|
|
||||||
Name = method.Name,
|
|
||||||
HandlerName = handlerName,
|
|
||||||
HttpMethod = httpMethod,
|
|
||||||
};
|
|
||||||
|
|
||||||
var methodParameters = handlerModel.MethodInfo.GetParameters();
|
|
||||||
|
|
||||||
for (var i = 0; i < methodParameters.Length; i++)
|
|
||||||
{
|
|
||||||
var parameter = methodParameters[i];
|
|
||||||
var parameterModel = CreateParameterModel(parameter);
|
|
||||||
parameterModel.Handler = handlerModel;
|
|
||||||
|
|
||||||
handlerModel.Parameters.Add(parameterModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
return handlerModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a <see cref="PageParameterModel"/> for the specified <paramref name="parameter"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parameter">The <see cref="ParameterInfo"/>.</param>
|
|
||||||
/// <returns>The <see cref="PageParameterModel"/>.</returns>
|
|
||||||
protected virtual PageParameterModel CreateParameterModel(ParameterInfo parameter)
|
|
||||||
{
|
|
||||||
if (parameter == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(parameter));
|
|
||||||
}
|
|
||||||
|
|
||||||
var attributes = parameter.GetCustomAttributes(inherit: true);
|
|
||||||
|
|
||||||
BindingInfo bindingInfo;
|
|
||||||
if (_modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
|
|
||||||
{
|
|
||||||
var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameter);
|
|
||||||
bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bindingInfo = BindingInfo.GetBindingInfo(attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PageParameterModel(parameter, attributes)
|
|
||||||
{
|
|
||||||
BindingInfo = bindingInfo,
|
|
||||||
ParameterName = parameter.Name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a <see cref="PagePropertyModel"/> for the <paramref name="property"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="property">The <see cref="PropertyInfo"/>.</param>
|
|
||||||
/// <returns>The <see cref="PagePropertyModel"/>.</returns>
|
|
||||||
protected virtual PagePropertyModel CreatePropertyModel(PropertyInfo property)
|
|
||||||
{
|
|
||||||
if (property == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(property));
|
|
||||||
}
|
|
||||||
|
|
||||||
var propertyAttributes = property.GetCustomAttributes(inherit: true);
|
|
||||||
|
|
||||||
// BindingInfo for properties can be either specified by decorating the property with binding-specific attributes.
|
|
||||||
// ModelMetadata also adds information from the property's type and any configured IBindingMetadataProvider.
|
|
||||||
var propertyMetadata = _modelMetadataProvider.GetMetadataForProperty(property.DeclaringType, property.Name);
|
|
||||||
var bindingInfo = BindingInfo.GetBindingInfo(propertyAttributes, propertyMetadata);
|
|
||||||
|
|
||||||
if (bindingInfo == null)
|
|
||||||
{
|
|
||||||
// Look for BindPropertiesAttribute on the handler type if no BindingInfo was inferred for the property.
|
|
||||||
// This allows a user to enable model binding on properties by decorating the controller type with BindPropertiesAttribute.
|
|
||||||
var declaringType = property.DeclaringType;
|
|
||||||
var bindPropertiesAttribute = declaringType.GetCustomAttribute<BindPropertiesAttribute>(inherit: true);
|
|
||||||
if (bindPropertiesAttribute != null)
|
|
||||||
{
|
|
||||||
var requestPredicate = bindPropertiesAttribute.SupportsGet ? _supportsAllRequests : _supportsNonGetRequests;
|
|
||||||
bindingInfo = new BindingInfo
|
|
||||||
{
|
|
||||||
RequestPredicate = requestPredicate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var model = new PagePropertyModel(property, propertyAttributes)
|
|
||||||
{
|
|
||||||
PropertyName = property.Name,
|
|
||||||
BindingInfo = bindingInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the specified <paramref name="methodInfo"/> is a handler.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="methodInfo">The <see cref="MethodInfo"/>.</param>
|
|
||||||
/// <returns><c>true</c> if the <paramref name="methodInfo"/> is a handler. Otherwise <c>false</c>.</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// Override this method to provide custom logic to determine which methods are considered handlers.
|
|
||||||
/// </remarks>
|
|
||||||
protected virtual bool IsHandler(MethodInfo methodInfo)
|
|
||||||
{
|
|
||||||
// The SpecialName bit is set to flag members that are treated in a special way by some compilers
|
|
||||||
// (such as property accessors and operator overloading methods).
|
|
||||||
if (methodInfo.IsSpecialName)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overridden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
|
|
||||||
if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodInfo.IsStatic)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodInfo.IsAbstract)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodInfo.IsConstructor)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodInfo.IsGenericMethod)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!methodInfo.IsPublic)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodInfo.IsDefined(typeof(NonHandlerAttribute)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exclude the whole hierarchy of Page.
|
|
||||||
var declaringType = methodInfo.DeclaringType;
|
|
||||||
if (declaringType == typeof(Page) ||
|
|
||||||
declaringType == typeof(PageBase) ||
|
|
||||||
declaringType == typeof(RazorPageBase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exclude methods declared on PageModel
|
|
||||||
if (declaringType == typeof(PageModel))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool TryParseHandlerMethod(string methodName, out string httpMethod, out string handler)
|
|
||||||
{
|
|
||||||
httpMethod = null;
|
|
||||||
handler = null;
|
|
||||||
|
|
||||||
// Handler method names always start with "On"
|
|
||||||
if (!methodName.StartsWith("On") || methodName.Length <= "On".Length)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we parse the method name according to our conventions to determine the required HTTP method
|
|
||||||
// and optional 'handler name'.
|
|
||||||
//
|
|
||||||
// Valid names look like:
|
|
||||||
// - OnGet
|
|
||||||
// - OnPost
|
|
||||||
// - OnFooBar
|
|
||||||
// - OnTraceAsync
|
|
||||||
// - OnPostEditAsync
|
|
||||||
|
|
||||||
var start = "On".Length;
|
|
||||||
var length = methodName.Length;
|
|
||||||
if (methodName.EndsWith("Async", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
length -= "Async".Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start == length)
|
|
||||||
{
|
|
||||||
// There are no additional characters. This is "On" or "OnAsync".
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The http method follows "On" and is required to be at least one character. We use casing
|
|
||||||
// to determine where it ends.
|
|
||||||
var handlerNameStart = start + 1;
|
|
||||||
for (; handlerNameStart < length; handlerNameStart++)
|
|
||||||
{
|
|
||||||
if (char.IsUpper(methodName[handlerNameStart]))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
httpMethod = methodName.Substring(start, handlerNameStart - start);
|
|
||||||
|
|
||||||
// The handler name follows the http method and is optional. It includes everything up to the end
|
|
||||||
// excluding the "Async" suffix (if present).
|
|
||||||
handler = handlerNameStart == length ? null : methodName.Substring(handlerNameStart, length - handlerNameStart);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// 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 System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides parts that are used to construct a <see cref="PageApplicationModel" /> instance
|
||||||
|
/// </summary>
|
||||||
|
public interface IPageApplicationModelPartsProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="PageHandlerModel"/> for the specified <paramref name="method"/>.s
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The <see cref="MethodInfo"/>.</param>
|
||||||
|
/// <returns>The <see cref="PageHandlerModel"/>.</returns>
|
||||||
|
PageHandlerModel CreateHandlerModel(MethodInfo method);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="PageParameterModel"/> for the specified <paramref name="parameter"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The <see cref="ParameterInfo"/>.</param>
|
||||||
|
/// <returns>The <see cref="PageParameterModel"/>.</returns>
|
||||||
|
PageParameterModel CreateParameterModel(ParameterInfo parameter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="PagePropertyModel"/> for the <paramref name="property"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="property">The <see cref="PropertyInfo"/>.</param>
|
||||||
|
/// <returns>The <see cref="PagePropertyModel"/>.</returns>
|
||||||
|
PagePropertyModel CreatePropertyModel(PropertyInfo property);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the specified <paramref name="methodInfo"/> is a handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodInfo">The <see cref="MethodInfo"/>.</param>
|
||||||
|
/// <returns><c>true</c> if the <paramref name="methodInfo"/> is a handler. Otherwise <c>false</c>.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Override this method to provide custom logic to determine which methods are considered handlers.
|
||||||
|
/// </remarks>
|
||||||
|
bool IsHandler(MethodInfo methodInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -113,6 +113,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Singleton<IPageApplicationModelProvider, ResponseCacheFilterApplicationModelProvider>());
|
ServiceDescriptor.Singleton<IPageApplicationModelProvider, ResponseCacheFilterApplicationModelProvider>());
|
||||||
|
|
||||||
|
services.TryAddSingleton<IPageApplicationModelPartsProvider, DefaultPageApplicationModelPartsProvider>();
|
||||||
|
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Singleton<IActionInvokerProvider, PageActionInvokerProvider>());
|
ServiceDescriptor.Singleton<IActionInvokerProvider, PageActionInvokerProvider>());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,9 +188,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
|
|
||||||
private static PageApplicationModelProviderContext GetApplicationProviderContext(TypeInfo typeInfo)
|
private static PageApplicationModelProviderContext GetApplicationProviderContext(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
|
||||||
var defaultProvider = new DefaultPageApplicationModelProvider(
|
var defaultProvider = new DefaultPageApplicationModelProvider(
|
||||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
modelMetadataProvider,
|
||||||
Options.Create(new RazorPagesOptions()));
|
Options.Create(new RazorPagesOptions()),
|
||||||
|
new DefaultPageApplicationModelPartsProvider(modelMetadataProvider));
|
||||||
|
|
||||||
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
|
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
|
||||||
defaultProvider.OnProvidersExecuting(context);
|
defaultProvider.OnProvidersExecuting(context);
|
||||||
|
|
|
||||||
|
|
@ -971,7 +971,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
public void TryParseHandler_ParsesHandlerNames_InvalidData(string methodName)
|
public void TryParseHandler_ParsesHandlerNames_InvalidData(string methodName)
|
||||||
{
|
{
|
||||||
// Act
|
// Act
|
||||||
var result = DefaultPageApplicationModelProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
|
var result = DefaultPageApplicationModelPartsProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
@ -993,7 +993,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = DefaultPageApplicationModelProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
|
var result = DefaultPageApplicationModelPartsProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
|
|
@ -1169,9 +1169,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
|
|
||||||
private static DefaultPageApplicationModelProvider CreateProvider()
|
private static DefaultPageApplicationModelProvider CreateProvider()
|
||||||
{
|
{
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
|
||||||
return new DefaultPageApplicationModelProvider(
|
return new DefaultPageApplicationModelProvider(
|
||||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
modelMetadataProvider,
|
||||||
Options.Create(new RazorPagesOptions()));
|
Options.Create(new RazorPagesOptions()),
|
||||||
|
new DefaultPageApplicationModelPartsProvider(modelMetadataProvider));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,9 +142,12 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
||||||
|
|
||||||
private static PageApplicationModelProviderContext GetApplicationProviderContext(TypeInfo typeInfo)
|
private static PageApplicationModelProviderContext GetApplicationProviderContext(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
|
||||||
var defaultProvider = new DefaultPageApplicationModelProvider(
|
var defaultProvider = new DefaultPageApplicationModelProvider(
|
||||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
modelMetadataProvider,
|
||||||
Options.Create(new RazorPagesOptions()));
|
Options.Create(new RazorPagesOptions()),
|
||||||
|
new DefaultPageApplicationModelPartsProvider(modelMetadataProvider));
|
||||||
|
|
||||||
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
|
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
|
||||||
defaultProvider.OnProvidersExecuting(context);
|
defaultProvider.OnProvidersExecuting(context);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue