Parity between Controller and Page/PageModel

This commit is contained in:
Ryan Brandenburg 2017-04-18 11:57:57 -07:00
parent 3e8cd1e7c9
commit c9bfd2296d
11 changed files with 1170 additions and 54 deletions

View File

@ -52,7 +52,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
}
var factories = controllerContext.ValueProviderFactories;
var valueProviderFactoryContext = new ValueProviderFactoryContext(controllerContext);
return await CreateAsync(controllerContext, factories);
}
/// <summary>
/// Asynchronously creates a <see cref="CompositeValueProvider"/> using the provided
/// <paramref name="actionContext"/>.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/> associated with the current request.</param>
/// <param name="factories">The <see cref="IValueProviderFactory"/> to be applied to the context.</param>
/// <returns>
/// A <see cref="Task{TResult}"/> which, when completed, asynchronously returns a
/// <see cref="CompositeValueProvider"/>.
/// </returns>
public static async Task<CompositeValueProvider> CreateAsync(
ActionContext actionContext,
IList<IValueProviderFactory> factories)
{
var valueProviderFactoryContext = new ValueProviderFactoryContext(actionContext);
for (var i = 0; i < factories.Count; i++)
{
var factory = factories[i];

View File

@ -4,14 +4,19 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
@ -23,6 +28,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public abstract class Page : RazorPageBase, IRazorPage
{
private PageArgumentBinder _binder;
private IObjectModelValidator _objectValidator;
private IModelMetadataProvider _metadataProvider;
private IModelBinderFactory _modelBinderFactory;
/// <summary>
/// The <see cref="RazorPages.PageContext"/>.
@ -80,11 +88,61 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
}
}
/// <summary>
/// Gets the <see cref="AspNetCore.Routing.RouteData"/> for the executing action.
/// </summary>
public RouteData RouteData
{
get
{
return PageContext.RouteData;
}
}
/// <summary>
/// Gets the <see cref="ModelStateDictionary"/>.
/// </summary>
public ModelStateDictionary ModelState => PageContext?.ModelState;
private IObjectModelValidator ObjectValidator
{
get
{
if (_objectValidator == null)
{
_objectValidator = HttpContext?.RequestServices?.GetRequiredService<IObjectModelValidator>();
}
return _objectValidator;
}
}
private IModelMetadataProvider MetadataProvider
{
get
{
if (_metadataProvider == null)
{
_metadataProvider = HttpContext?.RequestServices?.GetRequiredService<IModelMetadataProvider>();
}
return _metadataProvider;
}
}
private IModelBinderFactory ModelBinderFactory
{
get
{
if (_modelBinderFactory == null)
{
_modelBinderFactory = HttpContext?.RequestServices?.GetRequiredService<IModelBinderFactory>();
}
return _modelBinderFactory;
}
}
/// <inheritdoc />
public override void EnsureRenderedBodyOrSections()
{
@ -1130,5 +1188,384 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
return new PageViewResult(this);
}
#endregion Factory methods
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="Page"/>'s current
/// <see cref="IValueProvider"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public virtual Task<bool> TryUpdateModelAsync<TModel>(
TModel model)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
return TryUpdateModelAsync(model, prefix: string.Empty);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="Page"/>'s current
/// <see cref="IValueProvider"/> and a <paramref name="prefix"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>.
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public virtual async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (prefix == null)
{
throw new ArgumentNullException(nameof(prefix));
}
var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories);
return await TryUpdateModelAsync(model, prefix, valueProvider);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
/// <paramref name="prefix"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
/// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public virtual Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
IValueProvider valueProvider)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (prefix == null)
{
throw new ArgumentNullException(nameof(prefix));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
return ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="Page"/>'s current
/// <see cref="IValueProvider"/> and a <paramref name="prefix"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>.
/// </param>
/// <param name="includeExpressions"> <see cref="Expression"/>(s) which represent top-level properties
/// which need to be included for the current model.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
params Expression<Func<TModel, object>>[] includeExpressions)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (includeExpressions == null)
{
throw new ArgumentNullException(nameof(includeExpressions));
}
var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
includeExpressions);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="Page"/>'s current
/// <see cref="IValueProvider"/> and a <paramref name="prefix"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>.
/// </param>
/// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
Func<ModelMetadata, bool> propertyFilter)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (propertyFilter == null)
{
throw new ArgumentNullException(nameof(propertyFilter));
}
var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
propertyFilter);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
/// <paramref name="prefix"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
/// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <param name="includeExpressions"> <see cref="Expression"/>(s) which represent top-level properties
/// which need to be included for the current model.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
IValueProvider valueProvider,
params Expression<Func<TModel, object>>[] includeExpressions)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
if (includeExpressions == null)
{
throw new ArgumentNullException(nameof(includeExpressions));
}
return ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
includeExpressions);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
/// <paramref name="prefix"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
/// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
IValueProvider valueProvider,
Func<ModelMetadata, bool> propertyFilter)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
if (propertyFilter == null)
{
throw new ArgumentNullException(nameof(propertyFilter));
}
return ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
propertyFilter);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="Page"/>'s current
/// <see cref="IValueProvider"/> and a <paramref name="prefix"/>.
/// </summary>
/// <param name="model">The model instance to update.</param>
/// <param name="modelType">The type of model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>.
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public virtual async Task<bool> TryUpdateModelAsync(
object model,
Type modelType,
string prefix)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (modelType == null)
{
throw new ArgumentNullException(nameof(modelType));
}
var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
modelType,
prefix,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
/// <paramref name="prefix"/>.
/// </summary>
/// <param name="model">The model instance to update.</param>
/// <param name="modelType">The type of model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
/// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
public Task<bool> TryUpdateModelAsync(
object model,
Type modelType,
string prefix,
IValueProvider valueProvider,
Func<ModelMetadata, bool> propertyFilter)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (modelType == null)
{
throw new ArgumentNullException(nameof(modelType));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
if (propertyFilter == null)
{
throw new ArgumentNullException(nameof(propertyFilter));
}
return ModelBindingHelper.TryUpdateModelAsync(
model,
modelType,
prefix,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
propertyFilter);
}
/// <summary>
/// Validates the specified <paramref name="model"/> instance.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <returns><c>true</c> if the <see cref="ModelState"/> is valid; <c>false</c> otherwise.</returns>
public virtual bool TryValidateModel(
object model)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
return TryValidateModel(model, prefix: null);
}
/// <summary>
/// Validates the specified <paramref name="model"/> instance.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <param name="prefix">The key to use when looking up information in <see cref="ModelState"/>.
/// </param>
/// <returns><c>true</c> if the <see cref="ModelState"/> is valid;<c>false</c> otherwise.</returns>
public virtual bool TryValidateModel(
object model,
string prefix)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
ObjectValidator.Validate(
PageContext,
validationState: null,
prefix: prefix ?? string.Empty,
model: model);
return ModelState.IsValid;
}
}
}

View File

@ -3,16 +3,21 @@
using System;
using System.IO;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
@ -21,6 +26,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public abstract class PageModel
{
private PageArgumentBinder _binder;
private IObjectModelValidator _objectValidator;
private IModelMetadataProvider _metadataProvider;
private IModelBinderFactory _modelBinderFactory;
private IUrlHelper _urlHelper;
/// <summary>
@ -106,68 +114,405 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary>
public HttpResponse Response => HttpContext?.Response;
/// <summary>
/// Gets the <see cref="AspNetCore.Routing.RouteData"/> for the executing action.
/// </summary>
public RouteData RouteData => PageContext.RouteData;
/// <summary>
/// Gets the <see cref="ModelStateDictionary"/>.
/// </summary>
public ModelStateDictionary ModelState => PageContext.ModelState;
/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User => HttpContext?.User;
/// <summary>
/// Gets the <see cref="ITempDataDictionary"/> from the <see cref="PageContext"/>.
/// </summary>
/// <remarks>Returns null if <see cref="PageContext"/> is null.</remarks>
public ITempDataDictionary TempData => PageContext?.TempData;
private IObjectModelValidator ObjectValidator
{
get
{
if (_objectValidator == null)
{
_objectValidator = HttpContext?.RequestServices?.GetRequiredService<IObjectModelValidator>();
}
return _objectValidator;
}
}
private IModelMetadataProvider MetadataProvider
{
get
{
if (_metadataProvider == null)
{
_metadataProvider = HttpContext?.RequestServices?.GetRequiredService<IModelMetadataProvider>();
}
return _metadataProvider;
}
}
private IModelBinderFactory ModelBinderFactory
{
get
{
if (_modelBinderFactory == null)
{
_modelBinderFactory = HttpContext?.RequestServices?.GetRequiredService<IModelBinderFactory>();
}
return _modelBinderFactory;
}
}
/// <summary>
/// Gets the <see cref="ViewDataDictionary"/>.
/// </summary>
public ViewDataDictionary ViewData => PageContext?.ViewData;
/// <summary>
/// Binds the model with the specified <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="name">The model name.</param>
/// <returns>A <see cref="Task"/> that on completion returns the bound model.</returns>
protected internal Task<TModel> BindAsync<TModel>(string name)
{
return Binder.BindModelAsync<TModel>(PageContext, name);
}
/// <summary>
/// Binds the model with the specified <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="name">The model name.</param>
/// <param name="default">The default model value.</param>
/// <returns>A <see cref="Task"/> that on completion returns the bound model.</returns>
protected internal Task<TModel> BindAsync<TModel>(TModel @default, string name)
{
return Binder.BindModelAsync(PageContext, @default, name);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the pageModel's current
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="PageModel"/>'s current
/// <see cref="IValueProvider"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal Task<bool> TryUpdateModelAsync<TModel>(TModel model)
where TModel : class
{
return Binder.TryUpdateModelAsync(PageContext, model);
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
return TryUpdateModelAsync(model, name: string.Empty);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the pageModel's current
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="PageModel"/>'s current
/// <see cref="IValueProvider"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="name">The model name.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal Task<bool> TryUpdateModelAsync<TModel>(TModel model, string name)
protected internal async Task<bool> TryUpdateModelAsync<TModel>(TModel model, string name)
where TModel : class
{
return Binder.TryUpdateModelAsync(PageContext, model, name);
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories);
return await TryUpdateModelAsync(model, name, valueProvider);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
/// <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="name">The name to use when looking up values in the <paramref name="valueProvider"/>.
/// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string name,
IValueProvider valueProvider)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
return ModelBindingHelper.TryUpdateModelAsync(
model,
name,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="PageModel"/>'s current
/// <see cref="IValueProvider"/> and a <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="name">The name to use when looking up values in the current <see cref="IValueProvider"/>.
/// </param>
/// <param name="includeExpressions"> <see cref="Expression"/>(s) which represent top-level properties
/// which need to be included for the current model.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string name,
params Expression<Func<TModel, object>>[] includeExpressions)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (includeExpressions == null)
{
throw new ArgumentNullException(nameof(includeExpressions));
}
var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
name,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
includeExpressions);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="PageModel"/>'s current
/// <see cref="IValueProvider"/> and a <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="name">The name to use when looking up values in the current <see cref="IValueProvider"/>.
/// </param>
/// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string name,
Func<ModelMetadata, bool> propertyFilter)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (propertyFilter == null)
{
throw new ArgumentNullException(nameof(propertyFilter));
}
var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
name,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
propertyFilter);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
/// <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="name">The name to use when looking up values in the <paramref name="valueProvider"/>.
/// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <param name="includeExpressions"> <see cref="Expression"/>(s) which represent top-level properties
/// which need to be included for the current model.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string name,
IValueProvider valueProvider,
params Expression<Func<TModel, object>>[] includeExpressions)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
if (includeExpressions == null)
{
throw new ArgumentNullException(nameof(includeExpressions));
}
return ModelBindingHelper.TryUpdateModelAsync(
model,
name,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
includeExpressions);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
/// <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="name">The name to use when looking up values in the <paramref name="valueProvider"/>.
/// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string name,
IValueProvider valueProvider,
Func<ModelMetadata, bool> propertyFilter)
where TModel : class
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
if (propertyFilter == null)
{
throw new ArgumentNullException(nameof(propertyFilter));
}
return ModelBindingHelper.TryUpdateModelAsync(
model,
name,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
propertyFilter);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="PageModel"/>'s current
/// <see cref="IValueProvider"/> and a <paramref name="name"/>.
/// </summary>
/// <param name="model">The model instance to update.</param>
/// <param name="modelType">The type of model instance to update.</param>
/// <param name="name">The name to use when looking up values in the current <see cref="IValueProvider"/>.
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal async Task<bool> TryUpdateModelAsync(
object model,
Type modelType,
string name)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (modelType == null)
{
throw new ArgumentNullException(nameof(modelType));
}
var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
modelType,
name,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator);
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
/// <paramref name="name"/>.
/// </summary>
/// <param name="model">The model instance to update.</param>
/// <param name="modelType">The type of model instance to update.</param>
/// <param name="name">The name to use when looking up values in the <paramref name="valueProvider"/>.
/// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal Task<bool> TryUpdateModelAsync(
object model,
Type modelType,
string name,
IValueProvider valueProvider,
Func<ModelMetadata, bool> propertyFilter)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (modelType == null)
{
throw new ArgumentNullException(nameof(modelType));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
if (propertyFilter == null)
{
throw new ArgumentNullException(nameof(propertyFilter));
}
return ModelBindingHelper.TryUpdateModelAsync(
model,
modelType,
name,
PageContext,
MetadataProvider,
ModelBinderFactory,
valueProvider,
ObjectValidator,
propertyFilter);
}
#region Factory methods
@ -1176,5 +1521,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
return new PageViewResult(Page);
}
#endregion Factory methods
/// <summary>
/// Validates the specified <paramref name="model"/> instance.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <returns><c>true</c> if the <see cref="ModelState"/> is valid; <c>false</c> otherwise.</returns>
public virtual bool TryValidateModel(
object model)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
return TryValidateModel(model, name: null);
}
/// <summary>
/// Validates the specified <paramref name="model"/> instance.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <param name="name">The key to use when looking up information in <see cref="ModelState"/>.
/// </param>
/// <returns><c>true</c> if the <see cref="ModelState"/> is valid;<c>false</c> otherwise.</returns>
public virtual bool TryValidateModel(
object model,
string name)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
ObjectValidator.Validate(
PageContext,
validationState: null,
prefix: name ?? string.Empty,
model: model);
return ModelState.IsValid;
}
}
}

View File

@ -0,0 +1,185 @@
// 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.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class RazorPageModelTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.Startup>>
{
public RazorPageModelTest(MvcTestFixture<RazorPagesWebSite.Startup> fixture)
{
Client = fixture.Client;
}
public HttpClient Client { get; }
[Fact]
public async Task Page_TryUpdateModelAsync_Success()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Post, "Pages/TryUpdateModel/10")
{
Content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("Name", "Overriden"),
new KeyValuePair<string, string>("Age", "25"),
})
};
await AddAntiforgeryHeaders(request);
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Updated: True", content);
Assert.Contains("Name = Overriden", content);
}
[Fact]
public async Task Page_TryValidateModel_Success()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Post, "Pages/TryValidateModel/10")
{
Content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("Name", "Foo"),
new KeyValuePair<string, string>("Age", "25"),
})
};
await AddAntiforgeryHeaders(request);
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Validation: success", content.Trim());
}
[Fact]
public async Task Page_TryValidateModel_TooLong()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Post, "Pages/TryValidateModel/10")
{
Content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("Name", "Foo"),
new KeyValuePair<string, string>("Age", "200"),
})
};
await AddAntiforgeryHeaders(request);
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Validation: fail", content);
Assert.Contains("The field Age must be between 0 and 99.", content);
}
[Fact]
public async Task PageModel_TryUpdateModelAsync_Success()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Post, "Pages/TryUpdateModelPageModel/10")
{
Content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("Name", "Overriden"),
new KeyValuePair<string, string>("Age", "25"),
})
};
await AddAntiforgeryHeaders(request);
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Updated: True", content);
Assert.Contains("Name = Overriden", content);
}
[Fact]
public async Task PageModel_TryValidateModel_Success()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Post, "Pages/TryValidateModelPageModel/10")
{
Content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("Name", "Foo"),
new KeyValuePair<string, string>("Age", "25"),
})
};
await AddAntiforgeryHeaders(request);
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Validation: success", content.Trim());
}
[Fact]
public async Task PageModel_TryValidateModel_TooLong()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Post, "Pages/TryValidateModelPageModel/10")
{
Content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("Name", "Foo"),
new KeyValuePair<string, string>("Age", "200"),
})
};
await AddAntiforgeryHeaders(request);
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Validation: fail", content);
Assert.Contains("The field Age must be between 0 and 99.", content);
}
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
var getResponseBody = await getResponse.Content.ReadAsStringAsync();
var formToken = AntiforgeryTestHelper.RetrieveAntiforgeryToken(getResponseBody, "");
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getResponse);
request.Headers.Add("Cookie", cookie.Key + "=" + cookie.Value);
request.Headers.Add("RequestVerificationToken", formToken);
}
}
}

View File

@ -1463,31 +1463,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
Assert.Same(urlHelper, pageModel.Url);
}
[Fact]
public async Task BindModel_InvokesBindOnPageArgumentBinder()
{
// Arrange
var httpContext = new DefaultHttpContext();
var binder = new TestPageArgumentBinder();
httpContext.RequestServices = new ServiceCollection()
.AddSingleton<PageArgumentBinder>(binder)
.BuildServiceProvider();
var pageContext = new PageContext
{
HttpContext = httpContext,
};
var pageModel = new TestPageModel
{
PageContext = pageContext,
};
// Act
var result = await pageModel.BindAsync<Guid>("test-name");
// Assert
Assert.NotNull(result);
}
[Fact]
public void Redirect_ReturnsARedirectResult()
{

View File

@ -0,0 +1,27 @@
@page "{id:int}"
@using RazorPagesWebSite
@functions
{
public UserModel UserModel { get; set; }
[FromRoute]
public int Id { get; set; }
public async Task OnPost()
{
var user = new UserModel();
Updated = await TryUpdateModelAsync(user);
UserModel = user;
}
public bool Updated { get; set; }
}
@Html.ValidationSummary()
<form action="">
@Html.AntiForgeryToken()
</form>
Updated: @Updated
Name = @UserModel?.Name

View File

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite.Pages
{
public class TryUpdateModelPageModel : PageModel
{
public UserModel UserModel { get; set; }
public bool Updated { get; set; }
public async Task OnPost()
{
var user = new UserModel();
Updated = await TryUpdateModelAsync(user);
UserModel = user;
}
}
}

View File

@ -0,0 +1,10 @@
@page "{id:int}"
@using RazorPagesWebSite.Pages
@model TryUpdateModelPageModel
@Html.ValidationSummary()
<form action="">
@Html.AntiForgeryToken()
</form>
Updated: @Model.Updated
Name = @Model.UserModel?.Name

View File

@ -0,0 +1,26 @@
@page "{id:int}"
@using RazorPagesWebSite
@functions
{
[ModelBinder]
public UserModel UserModel { get; set; }
[FromRoute]
public int Id { get; set; }
public void OnPost(UserModel user)
{
Valid = TryValidateModel(user);
UserModel = user;
}
public bool Valid { get; set; }
}
@Html.ValidationSummary()
<form action="">
@Html.AntiForgeryToken()
</form>
Validation: @(Valid ? "success" : "fail!" )

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite.Pages
{
public class TryValidateModelPageModel : PageModel
{
[ModelBinder]
public UserModel UserModel { get; set; }
public bool Validate { get; set; }
public void OnPost(UserModel user)
{
Validate = TryValidateModel(user);
}
}
}

View File

@ -0,0 +1,9 @@
@page "{id:int}"
@using RazorPagesWebSite.Pages
@model TryValidateModelPageModel
@Html.ValidationSummary()
<form action="">
@Html.AntiForgeryToken()
</form>
Validation: @(Model.Validate ? "success" : "fail!" )