Add support for top level validation to Razor Pages
This commit is contained in:
parent
d40c60d15b
commit
06e40252a0
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
|
// Note: changes made to binding behavior in type should also be made to PageBinderFactory.
|
||||||
public static class ControllerBinderDelegateProvider
|
public static class ControllerBinderDelegateProvider
|
||||||
{
|
{
|
||||||
public static ControllerBinderDelegate CreateBinderDelegate(
|
public static ControllerBinderDelegate CreateBinderDelegate(
|
||||||
|
|
@ -100,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BindingInfo[] GetParameterBindingInfo(
|
private static BinderItem[] GetParameterBindingInfo(
|
||||||
IModelBinderFactory modelBinderFactory,
|
IModelBinderFactory modelBinderFactory,
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
ControllerActionDescriptor actionDescriptor)
|
ControllerActionDescriptor actionDescriptor)
|
||||||
|
|
@ -111,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parameterBindingInfo = new BindingInfo[parameters.Count];
|
var parameterBindingInfo = new BinderItem[parameters.Count];
|
||||||
for (var i = 0; i < parameters.Count; i++)
|
for (var i = 0; i < parameters.Count; i++)
|
||||||
{
|
{
|
||||||
var parameter = parameters[i];
|
var parameter = parameters[i];
|
||||||
|
|
@ -140,13 +141,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
CacheToken = parameter,
|
CacheToken = parameter,
|
||||||
});
|
});
|
||||||
|
|
||||||
parameterBindingInfo[i] = new BindingInfo(binder, metadata);
|
parameterBindingInfo[i] = new BinderItem(binder, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parameterBindingInfo;
|
return parameterBindingInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BindingInfo[] GetPropertyBindingInfo(
|
private static BinderItem[] GetPropertyBindingInfo(
|
||||||
IModelBinderFactory modelBinderFactory,
|
IModelBinderFactory modelBinderFactory,
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
ControllerActionDescriptor actionDescriptor)
|
ControllerActionDescriptor actionDescriptor)
|
||||||
|
|
@ -157,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var propertyBindingInfo = new BindingInfo[properties.Count];
|
var propertyBindingInfo = new BinderItem[properties.Count];
|
||||||
var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
|
var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
|
||||||
for (var i = 0; i < properties.Count; i++)
|
for (var i = 0; i < properties.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -170,15 +171,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
CacheToken = property,
|
CacheToken = property,
|
||||||
});
|
});
|
||||||
|
|
||||||
propertyBindingInfo[i] = new BindingInfo(binder, metadata);
|
propertyBindingInfo[i] = new BinderItem(binder, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
return propertyBindingInfo;
|
return propertyBindingInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct BindingInfo
|
private struct BinderItem
|
||||||
{
|
{
|
||||||
public BindingInfo(IModelBinder modelBinder, ModelMetadata modelMetadata)
|
public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata)
|
||||||
{
|
{
|
||||||
ModelBinder = modelBinder;
|
ModelBinder = modelBinder;
|
||||||
ModelMetadata = modelMetadata;
|
ModelMetadata = modelMetadata;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
public static class ExecutorFactory
|
public static class ExecutorFactory
|
||||||
{
|
{
|
||||||
public static Func<object, object[], Task<IActionResult>> CreateExecutor(HandlerMethodDescriptor handlerDescriptor)
|
public static PageHandlerExecutorDelegate CreateExecutor(HandlerMethodDescriptor handlerDescriptor)
|
||||||
{
|
{
|
||||||
if (handlerDescriptor == null)
|
if (handlerDescriptor == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -154,33 +154,26 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
private async Task BindArgumentsCoreAsync()
|
private async Task BindArgumentsCoreAsync()
|
||||||
{
|
{
|
||||||
if (CacheEntry.PropertyBinder != null)
|
await CacheEntry.PropertyBinder(_pageContext, _instance);
|
||||||
{
|
|
||||||
await CacheEntry.PropertyBinder(_pageContext, _instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_handler == null)
|
if (_handler == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var valueProvider = await CompositeValueProvider.CreateAsync(_pageContext, _pageContext.ValueProviderFactories);
|
// We do two separate cache lookups, once for the binder and once for the executor.
|
||||||
|
// Reduding it to a single lookup requires a lot of code change with little value.
|
||||||
for (var i = 0; i < _handler.Parameters.Count; i++)
|
PageHandlerBinderDelegate handlerBinder = null;
|
||||||
|
for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++)
|
||||||
{
|
{
|
||||||
var parameter = _handler.Parameters[i];
|
if (object.ReferenceEquals(_handler, _actionDescriptor.HandlerMethods[i]))
|
||||||
|
|
||||||
var result = await _parameterBinder.BindModelAsync(
|
|
||||||
_pageContext,
|
|
||||||
valueProvider,
|
|
||||||
parameter,
|
|
||||||
value: null);
|
|
||||||
|
|
||||||
if (result.IsModelSet)
|
|
||||||
{
|
{
|
||||||
_arguments[parameter.Name] = result.Model;
|
handlerBinder = CacheEntry.HandlerBinders[i];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await handlerBinder(_pageContext, _arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object[] PrepareArguments(
|
private static object[] PrepareArguments(
|
||||||
|
|
@ -223,16 +216,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
var arguments = PrepareArguments(_arguments, handler);
|
var arguments = PrepareArguments(_arguments, handler);
|
||||||
|
|
||||||
Func<object, object[], Task<IActionResult>> executor = null;
|
PageHandlerExecutorDelegate executor = null;
|
||||||
for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++)
|
for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++)
|
||||||
{
|
{
|
||||||
if (object.ReferenceEquals(handler, _actionDescriptor.HandlerMethods[i]))
|
if (object.ReferenceEquals(handler, _actionDescriptor.HandlerMethods[i]))
|
||||||
{
|
{
|
||||||
executor = CacheEntry.Executors[i];
|
executor = CacheEntry.HandlerExecutors[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Debug.Assert(executor != null, "We should always find a executor for a handler");
|
||||||
|
|
||||||
_diagnosticSource.BeforeHandlerMethod(_pageContext, handler, _arguments, _instance);
|
_diagnosticSource.BeforeHandlerMethod(_pageContext, handler, _arguments, _instance);
|
||||||
_logger.ExecutingHandlerMethod(_pageContext, handler, arguments);
|
_logger.ExecutingHandlerMethod(_pageContext, handler, arguments);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
Func<PageContext, object> modelFactory,
|
Func<PageContext, object> modelFactory,
|
||||||
Action<PageContext, object> releaseModel,
|
Action<PageContext, object> releaseModel,
|
||||||
Func<PageContext, object, Task> propertyBinder,
|
Func<PageContext, object, Task> propertyBinder,
|
||||||
Func<object, object[], Task<IActionResult>>[] executors,
|
PageHandlerExecutorDelegate[] handlerExecutors,
|
||||||
|
PageHandlerBinderDelegate[] handlerBinders,
|
||||||
IReadOnlyList<Func<IRazorPage>> viewStartFactories,
|
IReadOnlyList<Func<IRazorPage>> viewStartFactories,
|
||||||
FilterItem[] cacheableFilters)
|
FilterItem[] cacheableFilters)
|
||||||
{
|
{
|
||||||
|
|
@ -33,7 +34,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
ModelFactory = modelFactory;
|
ModelFactory = modelFactory;
|
||||||
ReleaseModel = releaseModel;
|
ReleaseModel = releaseModel;
|
||||||
PropertyBinder = propertyBinder;
|
PropertyBinder = propertyBinder;
|
||||||
Executors = executors;
|
HandlerExecutors = handlerExecutors;
|
||||||
|
HandlerBinders = handlerBinders;
|
||||||
ViewStartFactories = viewStartFactories;
|
ViewStartFactories = viewStartFactories;
|
||||||
CacheableFilters = cacheableFilters;
|
CacheableFilters = cacheableFilters;
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +62,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<PageContext, object, Task> PropertyBinder { get; }
|
public Func<PageContext, object, Task> PropertyBinder { get; }
|
||||||
|
|
||||||
public Func<object, object[], Task<IActionResult>>[] Executors { get; }
|
public PageHandlerExecutorDelegate[] HandlerExecutors { get; }
|
||||||
|
|
||||||
|
public PageHandlerBinderDelegate[] HandlerBinders { get; }
|
||||||
|
|
||||||
public Func<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> ViewDataFactory { get; }
|
public Func<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> ViewDataFactory { get; }
|
||||||
|
|
||||||
|
|
@ -70,5 +74,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
public IReadOnlyList<Func<IRazorPage>> ViewStartFactories { get; }
|
public IReadOnlyList<Func<IRazorPage>> ViewStartFactories { get; }
|
||||||
|
|
||||||
public FilterItem[] CacheableFilters { get; }
|
public FilterItem[] CacheableFilters { get; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
private readonly IPageLoader _loader;
|
private readonly IPageLoader _loader;
|
||||||
private readonly IPageFactoryProvider _pageFactoryProvider;
|
private readonly IPageFactoryProvider _pageFactoryProvider;
|
||||||
private readonly IPageModelFactoryProvider _modelFactoryProvider;
|
private readonly IPageModelFactoryProvider _modelFactoryProvider;
|
||||||
|
private readonly IModelBinderFactory _modelBinderFactory;
|
||||||
private readonly IRazorPageFactoryProvider _razorPageFactoryProvider;
|
private readonly IRazorPageFactoryProvider _razorPageFactoryProvider;
|
||||||
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
||||||
private readonly IFilterProvider[] _filterProviders;
|
private readonly IFilterProvider[] _filterProviders;
|
||||||
|
|
@ -52,6 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
IEnumerable<IFilterProvider> filterProviders,
|
IEnumerable<IFilterProvider> filterProviders,
|
||||||
ParameterBinder parameterBinder,
|
ParameterBinder parameterBinder,
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
|
IModelBinderFactory modelBinderFactory,
|
||||||
ITempDataDictionaryFactory tempDataFactory,
|
ITempDataDictionaryFactory tempDataFactory,
|
||||||
IOptions<MvcOptions> mvcOptions,
|
IOptions<MvcOptions> mvcOptions,
|
||||||
IOptions<HtmlHelperOptions> htmlHelperOptions,
|
IOptions<HtmlHelperOptions> htmlHelperOptions,
|
||||||
|
|
@ -63,6 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
_loader = loader;
|
_loader = loader;
|
||||||
_pageFactoryProvider = pageFactoryProvider;
|
_pageFactoryProvider = pageFactoryProvider;
|
||||||
_modelFactoryProvider = modelFactoryProvider;
|
_modelFactoryProvider = modelFactoryProvider;
|
||||||
|
_modelBinderFactory = modelBinderFactory;
|
||||||
_razorPageFactoryProvider = razorPageFactoryProvider;
|
_razorPageFactoryProvider = razorPageFactoryProvider;
|
||||||
_collectionProvider = collectionProvider;
|
_collectionProvider = collectionProvider;
|
||||||
_filterProviders = filterProviders.ToArray();
|
_filterProviders = filterProviders.ToArray();
|
||||||
|
|
@ -172,9 +175,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor);
|
var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor);
|
||||||
var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor);
|
var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor);
|
||||||
var propertyBinder = PagePropertyBinderFactory.CreateBinder(
|
var propertyBinder = PageBinderFactory.CreatePropertyBinder(
|
||||||
_parameterBinder,
|
_parameterBinder,
|
||||||
_modelMetadataProvider,
|
_modelMetadataProvider,
|
||||||
|
_modelBinderFactory,
|
||||||
compiledActionDescriptor);
|
compiledActionDescriptor);
|
||||||
|
|
||||||
Func<PageContext, object> modelFactory = null;
|
Func<PageContext, object> modelFactory = null;
|
||||||
|
|
@ -187,7 +191,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
var viewStartFactories = GetViewStartFactories(compiledActionDescriptor);
|
var viewStartFactories = GetViewStartFactories(compiledActionDescriptor);
|
||||||
|
|
||||||
var executors = GetExecutors(compiledActionDescriptor);
|
var handlerExecutors = GetHandlerExecutors(compiledActionDescriptor);
|
||||||
|
var handlerBinders = GetHandlerBinders(compiledActionDescriptor);
|
||||||
|
|
||||||
return new PageActionInvokerCacheEntry(
|
return new PageActionInvokerCacheEntry(
|
||||||
compiledActionDescriptor,
|
compiledActionDescriptor,
|
||||||
|
|
@ -197,7 +202,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
modelFactory,
|
modelFactory,
|
||||||
modelReleaser,
|
modelReleaser,
|
||||||
propertyBinder,
|
propertyBinder,
|
||||||
executors,
|
handlerExecutors,
|
||||||
|
handlerBinders,
|
||||||
viewStartFactories,
|
viewStartFactories,
|
||||||
cachedFilters);
|
cachedFilters);
|
||||||
}
|
}
|
||||||
|
|
@ -222,14 +228,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
return viewStartFactories;
|
return viewStartFactories;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Func<object, object[], Task<IActionResult>>[] GetExecutors(CompiledPageActionDescriptor actionDescriptor)
|
private static PageHandlerExecutorDelegate[] GetHandlerExecutors(CompiledPageActionDescriptor actionDescriptor)
|
||||||
{
|
{
|
||||||
if (actionDescriptor.HandlerMethods == null || actionDescriptor.HandlerMethods.Count == 0)
|
if (actionDescriptor.HandlerMethods == null || actionDescriptor.HandlerMethods.Count == 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<Func<object, object[], Task<IActionResult>>>();
|
return Array.Empty<PageHandlerExecutorDelegate>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var results = new Func<object, object[], Task<IActionResult>>[actionDescriptor.HandlerMethods.Count];
|
var results = new PageHandlerExecutorDelegate[actionDescriptor.HandlerMethods.Count];
|
||||||
|
|
||||||
for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++)
|
for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -239,6 +245,28 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PageHandlerBinderDelegate[] GetHandlerBinders(CompiledPageActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
if (actionDescriptor.HandlerMethods == null ||actionDescriptor.HandlerMethods.Count == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<PageHandlerBinderDelegate>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = new PageHandlerBinderDelegate[actionDescriptor.HandlerMethods.Count];
|
||||||
|
|
||||||
|
for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++)
|
||||||
|
{
|
||||||
|
results[i] = PageBinderFactory.CreateHandlerBinder(
|
||||||
|
_parameterBinder,
|
||||||
|
_modelMetadataProvider,
|
||||||
|
_modelBinderFactory,
|
||||||
|
actionDescriptor,
|
||||||
|
actionDescriptor.HandlerMethods[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
internal class InnerCache
|
internal class InnerCache
|
||||||
{
|
{
|
||||||
public InnerCache(int version)
|
public InnerCache(int version)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
internal static class PageBinderFactory
|
||||||
|
{
|
||||||
|
internal static readonly Func<PageContext, object, Task> NullPropertyBinder = (context, arguments) => Task.CompletedTask;
|
||||||
|
internal static readonly PageHandlerBinderDelegate NullHandlerBinder = (context, arguments) => Task.CompletedTask;
|
||||||
|
|
||||||
|
public static Func<PageContext, object, Task> CreatePropertyBinder(
|
||||||
|
ParameterBinder parameterBinder,
|
||||||
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
|
IModelBinderFactory modelBinderFactory,
|
||||||
|
CompiledPageActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
if (parameterBinder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(parameterBinder));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionDescriptor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(actionDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
var properties = actionDescriptor.BoundProperties;
|
||||||
|
if (properties == null || properties.Count == 0)
|
||||||
|
{
|
||||||
|
return NullPropertyBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlerType = actionDescriptor.HandlerTypeInfo.AsType();
|
||||||
|
var propertyBindingInfo = new BinderItem[properties.Count];
|
||||||
|
for (var i = 0; i < properties.Count; i++)
|
||||||
|
{
|
||||||
|
var property = properties[i];
|
||||||
|
var metadata = modelMetadataProvider.GetMetadataForProperty(handlerType, property.Name);
|
||||||
|
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
|
||||||
|
{
|
||||||
|
BindingInfo = property.BindingInfo,
|
||||||
|
Metadata = metadata,
|
||||||
|
CacheToken = property,
|
||||||
|
});
|
||||||
|
|
||||||
|
propertyBindingInfo[i] = new BinderItem(binder, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Bind;
|
||||||
|
|
||||||
|
async Task Bind(PageContext pageContext, object instance)
|
||||||
|
{
|
||||||
|
var valueProvider = await CompositeValueProvider.CreateAsync(pageContext, pageContext.ValueProviderFactories);
|
||||||
|
for (var i = 0; i < properties.Count; i++)
|
||||||
|
{
|
||||||
|
var property = properties[i];
|
||||||
|
var bindingInfo = propertyBindingInfo[i];
|
||||||
|
var modelMetadata = bindingInfo.ModelMetadata;
|
||||||
|
|
||||||
|
if (!modelMetadata.IsBindingAllowed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await parameterBinder.BindModelAsync(
|
||||||
|
pageContext,
|
||||||
|
bindingInfo.ModelBinder,
|
||||||
|
valueProvider,
|
||||||
|
property,
|
||||||
|
modelMetadata,
|
||||||
|
value: null);
|
||||||
|
|
||||||
|
if (result.IsModelSet)
|
||||||
|
{
|
||||||
|
PropertyValueSetter.SetValue(bindingInfo.ModelMetadata, instance, result.Model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PageHandlerBinderDelegate CreateHandlerBinder(
|
||||||
|
ParameterBinder parameterBinder,
|
||||||
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
|
IModelBinderFactory modelBinderFactory,
|
||||||
|
CompiledPageActionDescriptor actionDescriptor,
|
||||||
|
HandlerMethodDescriptor handler)
|
||||||
|
{
|
||||||
|
if (handler.Parameters == null || handler.Parameters.Count == 0)
|
||||||
|
{
|
||||||
|
return NullHandlerBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlerType = actionDescriptor.HandlerTypeInfo.AsType();
|
||||||
|
var parameterBindingInfo = new BinderItem[handler.Parameters.Count];
|
||||||
|
for (var i = 0; i < parameterBindingInfo.Length; i++)
|
||||||
|
{
|
||||||
|
var parameter = handler.Parameters[i];
|
||||||
|
ModelMetadata metadata;
|
||||||
|
if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
|
||||||
|
{
|
||||||
|
// The default model metadata provider derives from ModelMetadataProvider
|
||||||
|
// and can therefore supply information about attributes applied to parameters.
|
||||||
|
metadata = modelMetadataProviderBase.GetMetadataForParameter(parameter.ParameterInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For backward compatibility, if there's a custom model metadata provider that
|
||||||
|
// only implements the older IModelMetadataProvider interface, access the more
|
||||||
|
// limited metadata information it supplies. In this scenario, validation attributes
|
||||||
|
// are not supported on parameters.
|
||||||
|
metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
|
||||||
|
{
|
||||||
|
BindingInfo = parameter.BindingInfo,
|
||||||
|
Metadata = metadata,
|
||||||
|
CacheToken = parameter,
|
||||||
|
});
|
||||||
|
|
||||||
|
parameterBindingInfo[i] = new BinderItem(binder, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Bind;
|
||||||
|
|
||||||
|
async Task Bind(PageContext pageContext, IDictionary<string, object> arguments)
|
||||||
|
{
|
||||||
|
var valueProvider = await CompositeValueProvider.CreateAsync(pageContext, pageContext.ValueProviderFactories);
|
||||||
|
|
||||||
|
for (var i = 0; i < parameterBindingInfo.Length; i++)
|
||||||
|
{
|
||||||
|
var parameter = handler.Parameters[i];
|
||||||
|
var bindingInfo = parameterBindingInfo[i];
|
||||||
|
var modelMetadata = bindingInfo.ModelMetadata;
|
||||||
|
|
||||||
|
if (!modelMetadata.IsBindingAllowed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await parameterBinder.BindModelAsync(
|
||||||
|
pageContext,
|
||||||
|
bindingInfo.ModelBinder,
|
||||||
|
valueProvider,
|
||||||
|
parameter,
|
||||||
|
modelMetadata,
|
||||||
|
value: null);
|
||||||
|
|
||||||
|
if (result.IsModelSet)
|
||||||
|
{
|
||||||
|
arguments[parameter.Name] = result.Model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct BinderItem
|
||||||
|
{
|
||||||
|
public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata)
|
||||||
|
{
|
||||||
|
ModelMetadata = modelMetadata;
|
||||||
|
ModelBinder = modelBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelMetadata ModelMetadata { get; }
|
||||||
|
|
||||||
|
public IModelBinder ModelBinder { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public delegate Task PageHandlerBinderDelegate(PageContext pageContext, IDictionary<string, object> arguments);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public delegate Task<IActionResult> PageHandlerExecutorDelegate(object handler, object[] arguments);
|
||||||
|
}
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
// 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.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|
||||||
{
|
|
||||||
public static class PagePropertyBinderFactory
|
|
||||||
{
|
|
||||||
public static Func<PageContext, object, Task> CreateBinder(
|
|
||||||
ParameterBinder parameterBinder,
|
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
|
||||||
CompiledPageActionDescriptor actionDescriptor)
|
|
||||||
{
|
|
||||||
if (parameterBinder == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(parameterBinder));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionDescriptor == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(actionDescriptor));
|
|
||||||
}
|
|
||||||
|
|
||||||
var properties = actionDescriptor.BoundProperties;
|
|
||||||
if (properties == null || properties.Count == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = actionDescriptor.HandlerTypeInfo.AsType();
|
|
||||||
var metadata = new ModelMetadata[properties.Count];
|
|
||||||
for (var i = 0; i < properties.Count; i++)
|
|
||||||
{
|
|
||||||
metadata[i] = modelMetadataProvider.GetMetadataForProperty(type, properties[i].Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Bind;
|
|
||||||
|
|
||||||
Task Bind(PageContext pageContext, object instance)
|
|
||||||
{
|
|
||||||
return BindPropertiesAsync(parameterBinder, pageContext, instance, properties, metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task BindPropertiesAsync(
|
|
||||||
ParameterBinder parameterBinder,
|
|
||||||
PageContext pageContext,
|
|
||||||
object instance,
|
|
||||||
IList<ParameterDescriptor> properties,
|
|
||||||
IList<ModelMetadata> metadata)
|
|
||||||
{
|
|
||||||
var valueProvider = await CompositeValueProvider.CreateAsync(pageContext, pageContext.ValueProviderFactories);
|
|
||||||
for (var i = 0; i < properties.Count; i++)
|
|
||||||
{
|
|
||||||
var result = await parameterBinder.BindModelAsync(pageContext, valueProvider, properties[i]);
|
|
||||||
if (result.IsModelSet)
|
|
||||||
{
|
|
||||||
PropertyValueSetter.SetValue(metadata[i], instance, result.Model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -463,5 +463,26 @@ Hello from /Pages/Shared/";
|
||||||
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
|
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
|
||||||
Assert.Equal("/", response.Headers.Location.ToString());
|
Assert.Equal("/", response.Headers.Location.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidationAttributes_OnTopLevelProperties()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetStringAsync("/Validation/PageWithValidation?age=71");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Contains("Name is required", response);
|
||||||
|
Assert.Contains("18 ≤ Age ≤ 60", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidationAttributes_OnHandlerParameters()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetStringAsync("/Validation/PageHandlerWithValidation");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Contains("Name is required", response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -486,6 +486,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
var parameterBinder = new ParameterBinder(
|
var parameterBinder = new ParameterBinder(
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
TestModelBinderFactory.CreateDefault(),
|
TestModelBinderFactory.CreateDefault(),
|
||||||
|
|
@ -501,6 +502,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
new IFilterProvider[0],
|
new IFilterProvider[0],
|
||||||
parameterBinder,
|
parameterBinder,
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
|
modelBinderFactory,
|
||||||
tempDataFactory.Object,
|
tempDataFactory.Object,
|
||||||
Options.Create(new MvcOptions()),
|
Options.Create(new MvcOptions()),
|
||||||
Options.Create(new HtmlHelperOptions()),
|
Options.Create(new HtmlHelperOptions()),
|
||||||
|
|
|
||||||
|
|
@ -1102,7 +1102,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
BoundProperties = new List<ParameterDescriptor>(),
|
BoundProperties = new List<ParameterDescriptor>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var handlers = new List<Func<object, object[], Task<IActionResult>>>();
|
var handlers = new List<PageHandlerExecutorDelegate>();
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
handlers.Add((obj, args) => Task.FromResult(result));
|
handlers.Add((obj, args) => Task.FromResult(result));
|
||||||
|
|
@ -1135,7 +1135,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
Func<PageContext, object> modelFactory = null,
|
Func<PageContext, object> modelFactory = null,
|
||||||
ITempDataDictionaryFactory tempDataFactory = null,
|
ITempDataDictionaryFactory tempDataFactory = null,
|
||||||
IList<IValueProviderFactory> valueProviderFactories = null,
|
IList<IValueProviderFactory> valueProviderFactories = null,
|
||||||
Func<object, object[], Task<IActionResult>>[] handlers = null,
|
PageHandlerExecutorDelegate[] handlers = null,
|
||||||
|
PageHandlerBinderDelegate[] handlerBinders = null,
|
||||||
RouteData routeData = null,
|
RouteData routeData = null,
|
||||||
ILogger logger = null,
|
ILogger logger = null,
|
||||||
TestDiagnosticListener listener = null)
|
TestDiagnosticListener listener = null)
|
||||||
|
|
@ -1178,13 +1179,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
if (handlers == null)
|
if (handlers == null)
|
||||||
{
|
{
|
||||||
handlers = new Func<object, object[], Task<IActionResult>>[actionDescriptor.HandlerMethods.Count];
|
handlers = new PageHandlerExecutorDelegate[actionDescriptor.HandlerMethods.Count];
|
||||||
for (var i = 0; i < handlers.Length; i++)
|
for (var i = 0; i < handlers.Length; i++)
|
||||||
{
|
{
|
||||||
handlers[i] = (obj, args) => Task.FromResult<IActionResult>(new PageResult());
|
handlers[i] = (obj, args) => Task.FromResult<IActionResult>(new PageResult());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlerBinders = handlerBinders ?? Array.Empty<PageHandlerBinderDelegate>();
|
||||||
|
|
||||||
if (modelFactory == null)
|
if (modelFactory == null)
|
||||||
{
|
{
|
||||||
modelFactory = _ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType());
|
modelFactory = _ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType());
|
||||||
|
|
@ -1199,6 +1202,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
(c, model) => { (model as IDisposable)?.Dispose(); },
|
(c, model) => { (model as IDisposable)?.Dispose(); },
|
||||||
null,
|
null,
|
||||||
handlers,
|
handlers,
|
||||||
|
handlerBinders,
|
||||||
null,
|
null,
|
||||||
new FilterItem[0]);
|
new FilterItem[0]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
@ -18,7 +19,7 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
public class PagePropertyBinderFactoryTest
|
public class PageBinderFactoryTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetModelBinderFactory_ReturnsNullIfPageHasNoBoundProperties()
|
public void GetModelBinderFactory_ReturnsNullIfPageHasNoBoundProperties()
|
||||||
|
|
@ -29,17 +30,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
PageTypeInfo = typeof(PageWithNoBoundProperties).GetTypeInfo(),
|
PageTypeInfo = typeof(PageWithNoBoundProperties).GetTypeInfo(),
|
||||||
};
|
};
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
var binder = new ParameterBinder(
|
var binder = new ParameterBinder(
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
TestModelBinderFactory.CreateDefault(),
|
modelBinderFactory,
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(factory);
|
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -52,18 +55,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
ModelTypeInfo = typeof(PageModelWithNoBoundProperties).GetTypeInfo(),
|
ModelTypeInfo = typeof(PageModelWithNoBoundProperties).GetTypeInfo(),
|
||||||
};
|
};
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
var binder = new ParameterBinder(
|
var binder = new ParameterBinder(
|
||||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
modelMetadataProvider,
|
||||||
TestModelBinderFactory.CreateDefault(),
|
modelBinderFactory,
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(factory);
|
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -75,17 +79,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
PageTypeInfo = typeof(PageWithNoVisibleBoundProperties).GetTypeInfo(),
|
PageTypeInfo = typeof(PageWithNoVisibleBoundProperties).GetTypeInfo(),
|
||||||
};
|
};
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
var binder = new ParameterBinder(
|
var binder = new ParameterBinder(
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
TestModelBinderFactory.CreateDefault(),
|
modelBinderFactory,
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(factory);
|
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -98,17 +104,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
ModelTypeInfo = typeof(PageModelWithNoVisibleBoundProperties).GetTypeInfo(),
|
ModelTypeInfo = typeof(PageModelWithNoVisibleBoundProperties).GetTypeInfo(),
|
||||||
};
|
};
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
var binder = new ParameterBinder(
|
var binder = new ParameterBinder(
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
TestModelBinderFactory.CreateDefault(),
|
modelBinderFactory,
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(factory);
|
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -120,17 +128,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
PageTypeInfo = typeof(PageWithReadOnlyProperties).GetTypeInfo(),
|
PageTypeInfo = typeof(PageWithReadOnlyProperties).GetTypeInfo(),
|
||||||
};
|
};
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
var binder = new ParameterBinder(
|
var binder = new ParameterBinder(
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
TestModelBinderFactory.CreateDefault(),
|
modelBinderFactory,
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(factory);
|
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -143,17 +153,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
ModelTypeInfo = typeof(PageModelWithReadOnlyProperties).GetTypeInfo(),
|
ModelTypeInfo = typeof(PageModelWithReadOnlyProperties).GetTypeInfo(),
|
||||||
};
|
};
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
var binder = new ParameterBinder(
|
var binder = new ParameterBinder(
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
TestModelBinderFactory.CreateDefault(),
|
modelBinderFactory,
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(factory);
|
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -190,6 +202,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
};
|
};
|
||||||
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
var binder = new TestParameterBinder(new Dictionary<string, object>
|
var binder = new TestParameterBinder(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
|
|
@ -197,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{ nameof(PageWithProperty.RouteDifferentValue), "route-value" }
|
{ nameof(PageWithProperty.RouteDifferentValue), "route-value" }
|
||||||
});
|
});
|
||||||
|
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
var page = new PageWithProperty
|
var page = new PageWithProperty
|
||||||
{
|
{
|
||||||
|
|
@ -255,7 +268,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
var page = new PageWithProperty
|
var page = new PageWithProperty
|
||||||
{
|
{
|
||||||
|
|
@ -303,7 +318,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
var binder = new TestParameterBinder(new Dictionary<string, object>());
|
var binder = new TestParameterBinder(new Dictionary<string, object>());
|
||||||
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
var page = new PageWithProperty
|
var page = new PageWithProperty
|
||||||
{
|
{
|
||||||
|
|
@ -364,7 +381,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
var page = new PageWithProperty
|
var page = new PageWithProperty
|
||||||
{
|
{
|
||||||
|
|
@ -421,7 +440,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
var page = new PageWithProperty
|
var page = new PageWithProperty
|
||||||
{
|
{
|
||||||
|
|
@ -440,6 +461,264 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
Assert.Equal("value", model.Default);
|
Assert.Equal("value", model.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreatePropertyBinder_SkipsBindingPropertiesWithBindNever()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(PageModelWithBindNeverProperty).GetTypeInfo();
|
||||||
|
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
BoundProperties = new[]
|
||||||
|
{
|
||||||
|
new PageBoundPropertyDescriptor()
|
||||||
|
{
|
||||||
|
Name = nameof(PageModelWithBindNeverProperty.BindNeverProperty),
|
||||||
|
ParameterType = typeof(string),
|
||||||
|
Property = type.GetProperty(nameof(PageModelWithBindNeverProperty.BindNeverProperty)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
HandlerTypeInfo = type,
|
||||||
|
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
|
||||||
|
ModelTypeInfo = type,
|
||||||
|
};
|
||||||
|
|
||||||
|
var binder = new TestParameterBinder(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ nameof(PageModelWithBindNeverProperty.BindNeverProperty), "value" },
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
|
var page = new PageWithProperty
|
||||||
|
{
|
||||||
|
PageContext = GetPageContext()
|
||||||
|
};
|
||||||
|
|
||||||
|
var model = new PageModelWithBindNeverProperty();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await factory(page.PageContext, model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(model.BindNeverProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreatePropertyBinder_ValidatesTopLevelProperties()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(PageModelWithValidation).GetTypeInfo();
|
||||||
|
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
BoundProperties = new[]
|
||||||
|
{
|
||||||
|
new PageBoundPropertyDescriptor()
|
||||||
|
{
|
||||||
|
Name = nameof(PageModelWithValidation.Validated),
|
||||||
|
ParameterType = typeof(string),
|
||||||
|
Property = type.GetProperty(nameof(PageModelWithValidation.Validated)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
HandlerTypeInfo = type,
|
||||||
|
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
|
||||||
|
ModelTypeInfo = type,
|
||||||
|
};
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();
|
||||||
|
|
||||||
|
var binder = new ParameterBinder(
|
||||||
|
modelMetadataProvider,
|
||||||
|
modelBinderFactory,
|
||||||
|
validatorProvider,
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||||
|
|
||||||
|
var page = new PageWithProperty
|
||||||
|
{
|
||||||
|
PageContext = GetPageContext()
|
||||||
|
};
|
||||||
|
|
||||||
|
var model = new PageModelWithValidation();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await factory(page.PageContext, model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = page.PageContext.ModelState;
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
Assert.Collection(
|
||||||
|
modelState,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(PageModelWithValidation.Validated), kvp.Key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateHandlerBinder_BindsHandlerParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(PageModelWithExecutors);
|
||||||
|
var actionDescriptor = GetActionDescriptorWithHandlerMethod(type, nameof(PageModelWithExecutors.OnGet));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var parameterBinder = new TestParameterBinder(new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
{ "id", "value" },
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreateHandlerBinder(
|
||||||
|
parameterBinder,
|
||||||
|
modelMetadataProvider,
|
||||||
|
modelBinderFactory,
|
||||||
|
actionDescriptor,
|
||||||
|
actionDescriptor.HandlerMethods[0]);
|
||||||
|
|
||||||
|
var page = new PageWithProperty
|
||||||
|
{
|
||||||
|
PageContext = GetPageContext()
|
||||||
|
};
|
||||||
|
|
||||||
|
var model = new PageModelWithExecutors();
|
||||||
|
var arguments = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await factory(page.PageContext, arguments);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
arguments,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("id", kvp.Key);
|
||||||
|
Assert.Equal("value", kvp.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateHandlerBinder_SkipBindingParametersThatDisallowBinding()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(PageModelWithExecutors);
|
||||||
|
var actionDescriptor = GetActionDescriptorWithHandlerMethod(type, nameof(PageModelWithExecutors.OnGetWithBindNever));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var parameterBinder = new TestParameterBinder(new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
{ "id", "value" },
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreateHandlerBinder(
|
||||||
|
parameterBinder,
|
||||||
|
modelMetadataProvider,
|
||||||
|
modelBinderFactory,
|
||||||
|
actionDescriptor,
|
||||||
|
actionDescriptor.HandlerMethods[0]);
|
||||||
|
|
||||||
|
var page = new PageWithProperty
|
||||||
|
{
|
||||||
|
PageContext = GetPageContext()
|
||||||
|
};
|
||||||
|
|
||||||
|
var model = new PageModelWithExecutors();
|
||||||
|
var arguments = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await factory(page.PageContext, arguments);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateHandlerBinder_ValidatesTopLevelParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(PageModelWithExecutors);
|
||||||
|
var actionDescriptor = GetActionDescriptorWithHandlerMethod(type, nameof(PageModelWithExecutors.OnPostWithValidation));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
|
var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();
|
||||||
|
|
||||||
|
var parameterBinder = new ParameterBinder(modelMetadataProvider, modelBinderFactory, validatorProvider, NullLoggerFactory.Instance);
|
||||||
|
|
||||||
|
var factory = PageBinderFactory.CreateHandlerBinder(
|
||||||
|
parameterBinder,
|
||||||
|
modelMetadataProvider,
|
||||||
|
modelBinderFactory,
|
||||||
|
actionDescriptor,
|
||||||
|
actionDescriptor.HandlerMethods[0]);
|
||||||
|
|
||||||
|
var page = new PageWithProperty
|
||||||
|
{
|
||||||
|
PageContext = GetPageContext()
|
||||||
|
};
|
||||||
|
|
||||||
|
var model = new PageModelWithExecutors();
|
||||||
|
var arguments = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await factory(page.PageContext, arguments);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = page.PageContext.ModelState;
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
Assert.Collection(
|
||||||
|
page.PageContext.ModelState,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("name", kvp.Key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompiledPageActionDescriptor GetActionDescriptorWithHandlerMethod(Type type, string method)
|
||||||
|
{
|
||||||
|
var handlerMethodInfo = type.GetMethod(method);
|
||||||
|
var parameterInfo = handlerMethodInfo.GetParameters()[0];
|
||||||
|
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
HandlerTypeInfo = type.GetTypeInfo(),
|
||||||
|
HandlerMethods = new[]
|
||||||
|
{
|
||||||
|
new HandlerMethodDescriptor
|
||||||
|
{
|
||||||
|
HttpMethod = "Post",
|
||||||
|
MethodInfo = handlerMethodInfo,
|
||||||
|
Parameters = new[]
|
||||||
|
{
|
||||||
|
new HandlerParameterDescriptor
|
||||||
|
{
|
||||||
|
ParameterInfo = parameterInfo,
|
||||||
|
ParameterType = parameterInfo.ParameterType,
|
||||||
|
Name = parameterInfo.Name
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return actionDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
private PageContext GetPageContext(string httpMethod = null)
|
private PageContext GetPageContext(string httpMethod = null)
|
||||||
{
|
{
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
|
|
@ -477,7 +756,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
public IList<ParameterDescriptor> Descriptors { get; } = new List<ParameterDescriptor>();
|
public IList<ParameterDescriptor> Descriptors { get; } = new List<ParameterDescriptor>();
|
||||||
|
|
||||||
public override Task<ModelBindingResult> BindModelAsync(ActionContext actionContext, IValueProvider valueProvider, ParameterDescriptor parameter, object value)
|
public override Task<ModelBindingResult> BindModelAsync(
|
||||||
|
ActionContext actionContext,
|
||||||
|
IModelBinder modelBinder,
|
||||||
|
IValueProvider valueProvider,
|
||||||
|
ParameterDescriptor parameter,
|
||||||
|
ModelMetadata metadata,
|
||||||
|
object value)
|
||||||
{
|
{
|
||||||
Descriptors.Add(parameter);
|
Descriptors.Add(parameter);
|
||||||
|
|
||||||
|
|
@ -596,5 +881,34 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
public string Default { get; set; }
|
public string Default { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PageModelWithBindNeverProperty
|
||||||
|
{
|
||||||
|
[BindNever]
|
||||||
|
public string BindNeverProperty { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PageModelWithValidation
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Validated { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PageModelWithExecutors
|
||||||
|
{
|
||||||
|
public void OnGetWithBindNever([BindNever] string id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGet(string id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPostWithValidation([Required] string name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
@page
|
||||||
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
@functions
|
||||||
|
{
|
||||||
|
public void OnGet([Required(ErrorMessage = "Name is required")] string name)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<div asp-validation-summary="All"></div>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace RazorPagesWebSite
|
||||||
|
{
|
||||||
|
public class PageWithValidation : PageModel
|
||||||
|
{
|
||||||
|
[BindProperty(SupportsGet = true)]
|
||||||
|
[Required(ErrorMessage = "Name is required.")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[BindProperty(SupportsGet = true)]
|
||||||
|
[Range(18, 60, ErrorMessage = "18 ≤ Age ≤ 60")]
|
||||||
|
public int Age { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
@page
|
||||||
|
@model PageWithValidation
|
||||||
|
<div asp-validation-summary="All"></div>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
|
||||||
Loading…
Reference in New Issue