Add integration and functional tests of `[BindRequired]` on page properties (#8677)

- #7353
This commit is contained in:
Doug Bunting 2018-10-31 14:15:14 -07:00 committed by GitHub
parent f2af66bc31
commit a6199bbfba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 145 additions and 24 deletions

View File

@ -4,4 +4,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Xunit;
@ -420,14 +419,16 @@ Hello from /Pages/Shared/";
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel");
message.Content = new FormUrlEncodedContent(new Dictionary<string, string>
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel")
{
["__RequestVerificationToken"] = token,
["ConfirmPassword"] = "",
["Password"] = "",
["Email"] = ""
});
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["__RequestVerificationToken"] = token,
["ConfirmPassword"] = "",
["Password"] = "",
["Email"] = ""
})
};
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
// Act
@ -443,18 +444,20 @@ Hello from /Pages/Shared/";
public async Task PageConventions_CustomizedModelCanWorkWithModelState()
{
// Arrange
var getPage = await Client.GetAsync("/CustomModelTypeModel");
var getPage = await Client.GetAsync("/CustomModelTypeModel?Attempts=0");
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel");
message.Content = new FormUrlEncodedContent(new Dictionary<string, string>
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel?Attempts=3")
{
["__RequestVerificationToken"] = token,
["Email"] = "javi@example.com",
["Password"] = "Password.12$",
["ConfirmPassword"] = "Password.12$",
});
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["__RequestVerificationToken"] = token,
["Email"] = "javi@example.com",
["Password"] = "Password.12$",
["ConfirmPassword"] = "Password.12$",
})
};
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
// Act
@ -465,6 +468,37 @@ Hello from /Pages/Shared/";
Assert.Equal("/", response.Headers.Location.ToString());
}
[Fact]
public async Task PageConventions_CustomizedModelCanWorkWithModelState_EnforcesBindRequired()
{
// Arrange
var getPage = await Client.GetAsync("/CustomModelTypeModel?Attempts=0");
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel")
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["__RequestVerificationToken"] = token,
["Email"] = "javi@example.com",
["Password"] = "Password.12$",
["ConfirmPassword"] = "Password.12$",
})
};
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
// Act
var response = await Client.SendAsync(message);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var responseText = await response.Content.ReadAsStringAsync();
Assert.Contains(
"A value for the &#x27;Attempts&#x27; parameter or property was not provided.",
responseText);
}
[Fact]
public async Task ValidationAttributes_OnTopLevelProperties()
{
@ -642,10 +676,12 @@ Hello from /Pages/Shared/";
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(response);
var content = new MultipartFormDataContent();
content.Add(new StringContent("property1-value"), property1);
content.Add(new StringContent("test-value1"), file1, "test1.txt");
content.Add(new StringContent("test-value2"), file3, "test2.txt");
var content = new MultipartFormDataContent
{
{ new StringContent("property1-value"), property1 },
{ new StringContent("test-value1"), file1, "test1.txt" },
{ new StringContent("test-value2"), file3, "test2.txt" }
};
var request = new HttpRequestMessage(HttpMethod.Post, url)
{

View File

@ -1,15 +1,17 @@
// 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.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
@ -179,6 +181,74 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
}
[Theory]
[InlineData(null, false)]
[InlineData(123, true)]
public async Task BindModelAsync_WithBindPageProperty_EnforcesBindRequired(int? input, bool isValid)
{
// Arrange
var propertyInfo = typeof(TestPage).GetProperty(nameof(TestPage.BindRequiredProperty));
var propertyDescriptor = new PageBoundPropertyDescriptor
{
BindingInfo = BindingInfo.GetBindingInfo(new[]
{
new FromQueryAttribute { Name = propertyInfo.Name },
}),
Name = propertyInfo.Name,
ParameterType = propertyInfo.PropertyType,
Property = propertyInfo,
};
var typeInfo = typeof(TestPage).GetTypeInfo();
var actionDescriptor = new CompiledPageActionDescriptor
{
BoundProperties = new[] { propertyDescriptor },
HandlerTypeInfo = typeInfo,
ModelTypeInfo = typeInfo,
PageTypeInfo = typeInfo,
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.Method = "POST";
if (input.HasValue)
{
request.QueryString = new QueryString($"?{propertyDescriptor.Name}={input.Value}");
}
});
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
var modelBinderFactory = ModelBindingTestHelper.GetModelBinderFactory(modelMetadataProvider);
var modelMetadata = modelMetadataProvider
.GetMetadataForProperty(typeof(TestPage), propertyDescriptor.Name);
var pageBinder = PageBinderFactory.CreatePropertyBinder(
parameterBinder,
modelMetadataProvider,
modelBinderFactory,
actionDescriptor);
var pageContext = new PageContext
{
ActionDescriptor = actionDescriptor,
HttpContext = testContext.HttpContext,
RouteData = testContext.RouteData,
ValueProviderFactories = testContext.ValueProviderFactories,
};
var page = new TestPage();
// Act
await pageBinder(pageContext, page);
// Assert
Assert.Equal(isValid, pageContext.ModelState.IsValid);
if (isValid)
{
Assert.Equal(input.Value, page.BindRequiredProperty);
}
}
[Theory]
[InlineData("RequiredAndStringLengthProp", null, false)]
[InlineData("RequiredAndStringLengthProp", "", false)]
@ -231,12 +301,18 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
}
class TestController
private class TestController
{
[BindNever] public string BindNeverProp { get; set; }
[BindRequired] public int BindRequiredProp { get; set; }
[Required, StringLength(3)] public string RequiredAndStringLengthProp { get; set; }
[DisplayName("My Display Name"), StringLength(3)] public string DisplayNameStringLengthProp { get; set; }
}
private class TestPage : PageModel
{
[BindRequired]
public int BindRequiredProperty { get; set; }
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
@ -13,6 +14,10 @@ namespace RazorPagesWebSite
public string ReturnUrl { get; set; }
[BindRequired]
[FromQuery(Name = nameof(Attempts))]
public int Attempts { get; set; }
public class InputModel
{
[Required]
@ -69,10 +74,13 @@ namespace RazorPagesWebSite
{
if (!ModelState.IsValid)
{
Attempts++;
RouteData.Values.Add(nameof(Attempts), Attempts);
return Page();
}
return Redirect("~/");
}
}
}
}