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
|
||||
{
|
||||
// Note: changes made to binding behavior in type should also be made to PageBinderFactory.
|
||||
public static class ControllerBinderDelegateProvider
|
||||
{
|
||||
public static ControllerBinderDelegate CreateBinderDelegate(
|
||||
|
|
@ -100,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
|
||||
private static BindingInfo[] GetParameterBindingInfo(
|
||||
private static BinderItem[] GetParameterBindingInfo(
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
ControllerActionDescriptor actionDescriptor)
|
||||
|
|
@ -111,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return null;
|
||||
}
|
||||
|
||||
var parameterBindingInfo = new BindingInfo[parameters.Count];
|
||||
var parameterBindingInfo = new BinderItem[parameters.Count];
|
||||
for (var i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
|
|
@ -140,13 +141,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
CacheToken = parameter,
|
||||
});
|
||||
|
||||
parameterBindingInfo[i] = new BindingInfo(binder, metadata);
|
||||
parameterBindingInfo[i] = new BinderItem(binder, metadata);
|
||||
}
|
||||
|
||||
return parameterBindingInfo;
|
||||
}
|
||||
|
||||
private static BindingInfo[] GetPropertyBindingInfo(
|
||||
private static BinderItem[] GetPropertyBindingInfo(
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
ControllerActionDescriptor actionDescriptor)
|
||||
|
|
@ -157,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return null;
|
||||
}
|
||||
|
||||
var propertyBindingInfo = new BindingInfo[properties.Count];
|
||||
var propertyBindingInfo = new BinderItem[properties.Count];
|
||||
var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
|
||||
for (var i = 0; i < properties.Count; i++)
|
||||
{
|
||||
|
|
@ -170,15 +171,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
CacheToken = property,
|
||||
});
|
||||
|
||||
propertyBindingInfo[i] = new BindingInfo(binder, metadata);
|
||||
propertyBindingInfo[i] = new BinderItem(binder, metadata);
|
||||
}
|
||||
|
||||
return propertyBindingInfo;
|
||||
}
|
||||
|
||||
private struct BindingInfo
|
||||
private struct BinderItem
|
||||
{
|
||||
public BindingInfo(IModelBinder modelBinder, ModelMetadata modelMetadata)
|
||||
public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata)
|
||||
{
|
||||
ModelBinder = modelBinder;
|
||||
ModelMetadata = modelMetadata;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
public static class ExecutorFactory
|
||||
{
|
||||
public static Func<object, object[], Task<IActionResult>> CreateExecutor(HandlerMethodDescriptor handlerDescriptor)
|
||||
public static PageHandlerExecutorDelegate CreateExecutor(HandlerMethodDescriptor handlerDescriptor)
|
||||
{
|
||||
if (handlerDescriptor == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -154,33 +154,26 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
private async Task BindArgumentsCoreAsync()
|
||||
{
|
||||
if (CacheEntry.PropertyBinder != null)
|
||||
{
|
||||
await CacheEntry.PropertyBinder(_pageContext, _instance);
|
||||
}
|
||||
await CacheEntry.PropertyBinder(_pageContext, _instance);
|
||||
|
||||
if (_handler == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var valueProvider = await CompositeValueProvider.CreateAsync(_pageContext, _pageContext.ValueProviderFactories);
|
||||
|
||||
for (var i = 0; i < _handler.Parameters.Count; i++)
|
||||
// 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.
|
||||
PageHandlerBinderDelegate handlerBinder = null;
|
||||
for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++)
|
||||
{
|
||||
var parameter = _handler.Parameters[i];
|
||||
|
||||
var result = await _parameterBinder.BindModelAsync(
|
||||
_pageContext,
|
||||
valueProvider,
|
||||
parameter,
|
||||
value: null);
|
||||
|
||||
if (result.IsModelSet)
|
||||
if (object.ReferenceEquals(_handler, _actionDescriptor.HandlerMethods[i]))
|
||||
{
|
||||
_arguments[parameter.Name] = result.Model;
|
||||
handlerBinder = CacheEntry.HandlerBinders[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await handlerBinder(_pageContext, _arguments);
|
||||
}
|
||||
|
||||
private static object[] PrepareArguments(
|
||||
|
|
@ -223,16 +216,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
var arguments = PrepareArguments(_arguments, handler);
|
||||
|
||||
Func<object, object[], Task<IActionResult>> executor = null;
|
||||
PageHandlerExecutorDelegate executor = null;
|
||||
for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++)
|
||||
{
|
||||
if (object.ReferenceEquals(handler, _actionDescriptor.HandlerMethods[i]))
|
||||
{
|
||||
executor = CacheEntry.Executors[i];
|
||||
executor = CacheEntry.HandlerExecutors[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(executor != null, "We should always find a executor for a handler");
|
||||
|
||||
_diagnosticSource.BeforeHandlerMethod(_pageContext, handler, _arguments, _instance);
|
||||
_logger.ExecutingHandlerMethod(_pageContext, handler, arguments);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
Func<PageContext, object> modelFactory,
|
||||
Action<PageContext, object> releaseModel,
|
||||
Func<PageContext, object, Task> propertyBinder,
|
||||
Func<object, object[], Task<IActionResult>>[] executors,
|
||||
PageHandlerExecutorDelegate[] handlerExecutors,
|
||||
PageHandlerBinderDelegate[] handlerBinders,
|
||||
IReadOnlyList<Func<IRazorPage>> viewStartFactories,
|
||||
FilterItem[] cacheableFilters)
|
||||
{
|
||||
|
|
@ -33,7 +34,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
ModelFactory = modelFactory;
|
||||
ReleaseModel = releaseModel;
|
||||
PropertyBinder = propertyBinder;
|
||||
Executors = executors;
|
||||
HandlerExecutors = handlerExecutors;
|
||||
HandlerBinders = handlerBinders;
|
||||
ViewStartFactories = viewStartFactories;
|
||||
CacheableFilters = cacheableFilters;
|
||||
}
|
||||
|
|
@ -60,7 +62,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
/// </summary>
|
||||
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; }
|
||||
|
||||
|
|
@ -70,5 +74,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public IReadOnlyList<Func<IRazorPage>> ViewStartFactories { get; }
|
||||
|
||||
public FilterItem[] CacheableFilters { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
private readonly IPageLoader _loader;
|
||||
private readonly IPageFactoryProvider _pageFactoryProvider;
|
||||
private readonly IPageModelFactoryProvider _modelFactoryProvider;
|
||||
private readonly IModelBinderFactory _modelBinderFactory;
|
||||
private readonly IRazorPageFactoryProvider _razorPageFactoryProvider;
|
||||
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
||||
private readonly IFilterProvider[] _filterProviders;
|
||||
|
|
@ -52,6 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
IEnumerable<IFilterProvider> filterProviders,
|
||||
ParameterBinder parameterBinder,
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
ITempDataDictionaryFactory tempDataFactory,
|
||||
IOptions<MvcOptions> mvcOptions,
|
||||
IOptions<HtmlHelperOptions> htmlHelperOptions,
|
||||
|
|
@ -63,6 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
_loader = loader;
|
||||
_pageFactoryProvider = pageFactoryProvider;
|
||||
_modelFactoryProvider = modelFactoryProvider;
|
||||
_modelBinderFactory = modelBinderFactory;
|
||||
_razorPageFactoryProvider = razorPageFactoryProvider;
|
||||
_collectionProvider = collectionProvider;
|
||||
_filterProviders = filterProviders.ToArray();
|
||||
|
|
@ -172,9 +175,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor);
|
||||
var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor);
|
||||
var propertyBinder = PagePropertyBinderFactory.CreateBinder(
|
||||
var propertyBinder = PageBinderFactory.CreatePropertyBinder(
|
||||
_parameterBinder,
|
||||
_modelMetadataProvider,
|
||||
_modelBinderFactory,
|
||||
compiledActionDescriptor);
|
||||
|
||||
Func<PageContext, object> modelFactory = null;
|
||||
|
|
@ -187,7 +191,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
var viewStartFactories = GetViewStartFactories(compiledActionDescriptor);
|
||||
|
||||
var executors = GetExecutors(compiledActionDescriptor);
|
||||
var handlerExecutors = GetHandlerExecutors(compiledActionDescriptor);
|
||||
var handlerBinders = GetHandlerBinders(compiledActionDescriptor);
|
||||
|
||||
return new PageActionInvokerCacheEntry(
|
||||
compiledActionDescriptor,
|
||||
|
|
@ -197,7 +202,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
modelFactory,
|
||||
modelReleaser,
|
||||
propertyBinder,
|
||||
executors,
|
||||
handlerExecutors,
|
||||
handlerBinders,
|
||||
viewStartFactories,
|
||||
cachedFilters);
|
||||
}
|
||||
|
|
@ -222,14 +228,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
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)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
|
|
@ -239,6 +245,28 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
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
|
||||
{
|
||||
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("/", 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 modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
var parameterBinder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
|
|
@ -501,6 +502,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
new IFilterProvider[0],
|
||||
parameterBinder,
|
||||
modelMetadataProvider,
|
||||
modelBinderFactory,
|
||||
tempDataFactory.Object,
|
||||
Options.Create(new MvcOptions()),
|
||||
Options.Create(new HtmlHelperOptions()),
|
||||
|
|
|
|||
|
|
@ -1102,7 +1102,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
BoundProperties = new List<ParameterDescriptor>(),
|
||||
};
|
||||
|
||||
var handlers = new List<Func<object, object[], Task<IActionResult>>>();
|
||||
var handlers = new List<PageHandlerExecutorDelegate>();
|
||||
if (result != null)
|
||||
{
|
||||
handlers.Add((obj, args) => Task.FromResult(result));
|
||||
|
|
@ -1135,7 +1135,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
Func<PageContext, object> modelFactory = null,
|
||||
ITempDataDictionaryFactory tempDataFactory = null,
|
||||
IList<IValueProviderFactory> valueProviderFactories = null,
|
||||
Func<object, object[], Task<IActionResult>>[] handlers = null,
|
||||
PageHandlerExecutorDelegate[] handlers = null,
|
||||
PageHandlerBinderDelegate[] handlerBinders = null,
|
||||
RouteData routeData = null,
|
||||
ILogger logger = null,
|
||||
TestDiagnosticListener listener = null)
|
||||
|
|
@ -1178,13 +1179,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
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++)
|
||||
{
|
||||
handlers[i] = (obj, args) => Task.FromResult<IActionResult>(new PageResult());
|
||||
}
|
||||
}
|
||||
|
||||
handlerBinders = handlerBinders ?? Array.Empty<PageHandlerBinderDelegate>();
|
||||
|
||||
if (modelFactory == null)
|
||||
{
|
||||
modelFactory = _ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType());
|
||||
|
|
@ -1199,6 +1202,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
(c, model) => { (model as IDisposable)?.Dispose(); },
|
||||
null,
|
||||
handlers,
|
||||
handlerBinders,
|
||||
null,
|
||||
new FilterItem[0]);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -18,7 +19,7 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class PagePropertyBinderFactoryTest
|
||||
public class PageBinderFactoryTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetModelBinderFactory_ReturnsNullIfPageHasNoBoundProperties()
|
||||
|
|
@ -29,17 +30,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
PageTypeInfo = typeof(PageWithNoBoundProperties).GetTypeInfo(),
|
||||
};
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
|
||||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
modelBinderFactory,
|
||||
Mock.Of<IModelValidatorProvider>(),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Null(factory);
|
||||
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -52,18 +55,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
ModelTypeInfo = typeof(PageModelWithNoBoundProperties).GetTypeInfo(),
|
||||
};
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
|
||||
var binder = new ParameterBinder(
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
modelMetadataProvider,
|
||||
modelBinderFactory,
|
||||
Mock.Of<IModelValidatorProvider>(),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Null(factory);
|
||||
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -75,17 +79,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
PageTypeInfo = typeof(PageWithNoVisibleBoundProperties).GetTypeInfo(),
|
||||
};
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
|
||||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
modelBinderFactory,
|
||||
Mock.Of<IModelValidatorProvider>(),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Null(factory);
|
||||
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -98,17 +104,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
ModelTypeInfo = typeof(PageModelWithNoVisibleBoundProperties).GetTypeInfo(),
|
||||
};
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
|
||||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
modelBinderFactory,
|
||||
Mock.Of<IModelValidatorProvider>(),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Null(factory);
|
||||
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -120,17 +128,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
PageTypeInfo = typeof(PageWithReadOnlyProperties).GetTypeInfo(),
|
||||
};
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
|
||||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
modelBinderFactory,
|
||||
Mock.Of<IModelValidatorProvider>(),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Null(factory);
|
||||
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -143,17 +153,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
ModelTypeInfo = typeof(PageModelWithReadOnlyProperties).GetTypeInfo(),
|
||||
};
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
|
||||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
modelBinderFactory,
|
||||
Mock.Of<IModelValidatorProvider>(),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Null(factory);
|
||||
Assert.Same(PageBinderFactory.NullPropertyBinder, factory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -190,6 +202,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
};
|
||||
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
|
||||
var binder = new TestParameterBinder(new Dictionary<string, object>
|
||||
{
|
||||
|
|
@ -197,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{ nameof(PageWithProperty.RouteDifferentValue), "route-value" }
|
||||
});
|
||||
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
|
||||
|
||||
var page = new PageWithProperty
|
||||
{
|
||||
|
|
@ -255,7 +268,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
});
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -303,7 +318,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var binder = new TestParameterBinder(new Dictionary<string, object>());
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -364,7 +381,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
});
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -421,7 +440,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
});
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -440,6 +461,264 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
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)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
|
@ -477,7 +756,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
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);
|
||||
|
||||
|
|
@ -596,5 +881,34 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
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