diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs index 88a66204fa..e5eb302fe6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs @@ -52,7 +52,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } var factories = controllerContext.ValueProviderFactories; - var valueProviderFactoryContext = new ValueProviderFactoryContext(controllerContext); + + return await CreateAsync(controllerContext, factories); + } + + /// + /// Asynchronously creates a using the provided + /// . + /// + /// The associated with the current request. + /// The to be applied to the context. + /// + /// A which, when completed, asynchronously returns a + /// . + /// + public static async Task CreateAsync( + ActionContext actionContext, + IList factories) + { + var valueProviderFactoryContext = new ValueProviderFactoryContext(actionContext); + for (var i = 0; i < factories.Count; i++) { var factory = factories[i]; diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs index 3ad708a7b1..5b388a516b 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs @@ -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; /// /// The . @@ -80,11 +88,61 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages } } + /// + /// Gets the for the executing action. + /// + public RouteData RouteData + { + get + { + return PageContext.RouteData; + } + } + /// /// Gets the . /// public ModelStateDictionary ModelState => PageContext?.ModelState; + private IObjectModelValidator ObjectValidator + { + get + { + if (_objectValidator == null) + { + _objectValidator = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _objectValidator; + } + } + + private IModelMetadataProvider MetadataProvider + { + get + { + if (_metadataProvider == null) + { + _metadataProvider = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _metadataProvider; + } + } + + private IModelBinderFactory ModelBinderFactory + { + get + { + if (_modelBinderFactory == null) + { + _modelBinderFactory = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _modelBinderFactory; + } + } + /// public override void EnsureRenderedBodyOrSections() { @@ -1130,5 +1188,384 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages return new PageViewResult(this); } #endregion Factory methods + + /// + /// Updates the specified instance using values from the 's current + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// A that on completion returns true if the update is successful. + public virtual Task TryUpdateModelAsync( + TModel model) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryUpdateModelAsync(model, prefix: string.Empty); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A that on completion returns true if the update is successful. + public virtual async Task TryUpdateModelAsync( + 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); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A that on completion returns true if the update is successful. + public virtual Task TryUpdateModelAsync( + 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); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + public async Task TryUpdateModelAsync( + TModel model, + string prefix, + params Expression>[] 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); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + public async Task TryUpdateModelAsync( + TModel model, + string prefix, + Func 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); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + public Task TryUpdateModelAsync( + TModel model, + string prefix, + IValueProvider valueProvider, + params Expression>[] 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); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + public Task TryUpdateModelAsync( + TModel model, + string prefix, + IValueProvider valueProvider, + Func 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); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A that on completion returns true if the update is successful. + public virtual async Task 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); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + public Task TryUpdateModelAsync( + object model, + Type modelType, + string prefix, + IValueProvider valueProvider, + Func 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); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// true if the is valid; false otherwise. + public virtual bool TryValidateModel( + object model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryValidateModel(model, prefix: null); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// The key to use when looking up information in . + /// + /// true if the is valid;false otherwise. + 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; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs index dc8ab8b044..ffc414ca24 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs @@ -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; /// @@ -106,68 +114,405 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// public HttpResponse Response => HttpContext?.Response; + /// + /// Gets the for the executing action. + /// + public RouteData RouteData => PageContext.RouteData; + /// /// Gets the . /// public ModelStateDictionary ModelState => PageContext.ModelState; + /// + /// Gets the for user associated with the executing action. + /// + public ClaimsPrincipal User => HttpContext?.User; + /// /// Gets the from the . /// /// Returns null if is null. public ITempDataDictionary TempData => PageContext?.TempData; + private IObjectModelValidator ObjectValidator + { + get + { + if (_objectValidator == null) + { + _objectValidator = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _objectValidator; + } + } + + private IModelMetadataProvider MetadataProvider + { + get + { + if (_metadataProvider == null) + { + _metadataProvider = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _metadataProvider; + } + } + + private IModelBinderFactory ModelBinderFactory + { + get + { + if (_modelBinderFactory == null) + { + _modelBinderFactory = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _modelBinderFactory; + } + } + /// /// Gets the . /// public ViewDataDictionary ViewData => PageContext?.ViewData; /// - /// Binds the model with the specified . - /// - /// The model type. - /// The model name. - /// A that on completion returns the bound model. - protected internal Task BindAsync(string name) - { - return Binder.BindModelAsync(PageContext, name); - } - - /// - /// Binds the model with the specified . - /// - /// The model type. - /// The model name. - /// The default model value. - /// A that on completion returns the bound model. - protected internal Task BindAsync(TModel @default, string name) - { - return Binder.BindModelAsync(PageContext, @default, name); - } - - /// - /// Updates the specified instance using values from the pageModel's current + /// Updates the specified instance using values from the 's current /// . /// /// The type of the model object. /// The model instance to update. /// A that on completion returns true if the update is successful. protected internal Task TryUpdateModelAsync(TModel model) + where TModel : class { - return Binder.TryUpdateModelAsync(PageContext, model); + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryUpdateModelAsync(model, name: string.Empty); } /// - /// Updates the specified instance using values from the pageModel's current + /// Updates the specified instance using values from the 's current /// . /// /// The type of the model object. /// The model instance to update. /// The model name. /// A that on completion returns true if the update is successful. - protected internal Task TryUpdateModelAsync(TModel model, string name) + protected internal async Task TryUpdateModelAsync(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); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the . + /// + /// The used for looking up values. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync( + 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); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the current . + /// + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + protected internal async Task TryUpdateModelAsync( + TModel model, + string name, + params Expression>[] 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); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the current . + /// + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + protected internal async Task TryUpdateModelAsync( + TModel model, + string name, + Func 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); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the . + /// + /// The used for looking up values. + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync( + TModel model, + string name, + IValueProvider valueProvider, + params Expression>[] 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); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync( + TModel model, + string name, + IValueProvider valueProvider, + Func 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); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The name to use when looking up values in the current . + /// + /// A that on completion returns true if the update is successful. + protected internal async Task 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); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The name to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync( + object model, + Type modelType, + string name, + IValueProvider valueProvider, + Func 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 + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// true if the is valid; false otherwise. + public virtual bool TryValidateModel( + object model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryValidateModel(model, name: null); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// The key to use when looking up information in . + /// + /// true if the is valid;false otherwise. + 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; + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageModelTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageModelTest.cs new file mode 100644 index 0000000000..fa9ae59b8d --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageModelTest.cs @@ -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> + { + public RazorPageModelTest(MvcTestFixture 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[] + { + new KeyValuePair("Name", "Overriden"), + new KeyValuePair("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[] + { + new KeyValuePair("Name", "Foo"), + new KeyValuePair("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[] + { + new KeyValuePair("Name", "Foo"), + new KeyValuePair("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[] + { + new KeyValuePair("Name", "Overriden"), + new KeyValuePair("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[] + { + new KeyValuePair("Name", "Foo"), + new KeyValuePair("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[] + { + new KeyValuePair("Name", "Foo"), + new KeyValuePair("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); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs index e5019878b8..1560095f6c 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs @@ -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(binder) - .BuildServiceProvider(); - var pageContext = new PageContext - { - HttpContext = httpContext, - }; - var pageModel = new TestPageModel - { - PageContext = pageContext, - }; - - // Act - var result = await pageModel.BindAsync("test-name"); - - // Assert - Assert.NotNull(result); - } - [Fact] public void Redirect_ReturnsARedirectResult() { diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModel.cshtml b/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModel.cshtml new file mode 100644 index 0000000000..35938102fd --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModel.cshtml @@ -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() +
+ @Html.AntiForgeryToken() +
+Updated: @Updated +Name = @UserModel?.Name diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cs b/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cs new file mode 100644 index 0000000000..05f7785c58 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cs @@ -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; + } + } +} diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cshtml b/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cshtml new file mode 100644 index 0000000000..c4183b5a5d --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cshtml @@ -0,0 +1,10 @@ +@page "{id:int}" +@using RazorPagesWebSite.Pages +@model TryUpdateModelPageModel + +@Html.ValidationSummary() +
+ @Html.AntiForgeryToken() +
+Updated: @Model.Updated +Name = @Model.UserModel?.Name diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryValidateModel.cshtml b/test/WebSites/RazorPagesWebSite/Pages/TryValidateModel.cshtml new file mode 100644 index 0000000000..1aaa0b637c --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/TryValidateModel.cshtml @@ -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() +
+ @Html.AntiForgeryToken() +
+Validation: @(Valid ? "success" : "fail!" ) diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cs b/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cs new file mode 100644 index 0000000000..bde677cb98 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cs @@ -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); + } + } +} diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cshtml b/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cshtml new file mode 100644 index 0000000000..08d31d73d8 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cshtml @@ -0,0 +1,9 @@ +@page "{id:int}" +@using RazorPagesWebSite.Pages +@model TryValidateModelPageModel + +@Html.ValidationSummary() +
+ @Html.AntiForgeryToken() +
+Validation: @(Model.Validate ? "success" : "fail!" )