Add integration and functional tests of `[BindRequired]` on page properties (#8677)
- #7353
This commit is contained in:
parent
f2af66bc31
commit
a6199bbfba
|
|
@ -4,4 +4,5 @@
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[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")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -420,14 +419,16 @@ Hello from /Pages/Shared/";
|
||||||
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
|
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
|
||||||
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
|
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
|
||||||
|
|
||||||
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel");
|
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel")
|
||||||
message.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
|
||||||
{
|
{
|
||||||
["__RequestVerificationToken"] = token,
|
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
["ConfirmPassword"] = "",
|
{
|
||||||
["Password"] = "",
|
["__RequestVerificationToken"] = token,
|
||||||
["Email"] = ""
|
["ConfirmPassword"] = "",
|
||||||
});
|
["Password"] = "",
|
||||||
|
["Email"] = ""
|
||||||
|
})
|
||||||
|
};
|
||||||
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
|
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -443,18 +444,20 @@ Hello from /Pages/Shared/";
|
||||||
public async Task PageConventions_CustomizedModelCanWorkWithModelState()
|
public async Task PageConventions_CustomizedModelCanWorkWithModelState()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var getPage = await Client.GetAsync("/CustomModelTypeModel");
|
var getPage = await Client.GetAsync("/CustomModelTypeModel?Attempts=0");
|
||||||
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
|
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
|
||||||
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
|
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
|
||||||
|
|
||||||
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel");
|
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel?Attempts=3")
|
||||||
message.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
|
||||||
{
|
{
|
||||||
["__RequestVerificationToken"] = token,
|
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
["Email"] = "javi@example.com",
|
{
|
||||||
["Password"] = "Password.12$",
|
["__RequestVerificationToken"] = token,
|
||||||
["ConfirmPassword"] = "Password.12$",
|
["Email"] = "javi@example.com",
|
||||||
});
|
["Password"] = "Password.12$",
|
||||||
|
["ConfirmPassword"] = "Password.12$",
|
||||||
|
})
|
||||||
|
};
|
||||||
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
|
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -465,6 +468,37 @@ Hello from /Pages/Shared/";
|
||||||
Assert.Equal("/", response.Headers.Location.ToString());
|
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 'Attempts' parameter or property was not provided.",
|
||||||
|
responseText);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ValidationAttributes_OnTopLevelProperties()
|
public async Task ValidationAttributes_OnTopLevelProperties()
|
||||||
{
|
{
|
||||||
|
|
@ -642,10 +676,12 @@ Hello from /Pages/Shared/";
|
||||||
|
|
||||||
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(response);
|
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(response);
|
||||||
|
|
||||||
var content = new MultipartFormDataContent();
|
var content = new MultipartFormDataContent
|
||||||
content.Add(new StringContent("property1-value"), property1);
|
{
|
||||||
content.Add(new StringContent("test-value1"), file1, "test1.txt");
|
{ new StringContent("property1-value"), property1 },
|
||||||
content.Add(new StringContent("test-value2"), file3, "test2.txt");
|
{ new StringContent("test-value1"), file1, "test1.txt" },
|
||||||
|
{ new StringContent("test-value2"), file3, "test2.txt" }
|
||||||
|
};
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, url)
|
var request = new HttpRequestMessage(HttpMethod.Post, url)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
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;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
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]
|
[Theory]
|
||||||
[InlineData("RequiredAndStringLengthProp", null, false)]
|
[InlineData("RequiredAndStringLengthProp", null, false)]
|
||||||
[InlineData("RequiredAndStringLengthProp", "", false)]
|
[InlineData("RequiredAndStringLengthProp", "", false)]
|
||||||
|
|
@ -231,12 +301,18 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestController
|
private class TestController
|
||||||
{
|
{
|
||||||
[BindNever] public string BindNeverProp { get; set; }
|
[BindNever] public string BindNeverProp { get; set; }
|
||||||
[BindRequired] public int BindRequiredProp { get; set; }
|
[BindRequired] public int BindRequiredProp { get; set; }
|
||||||
[Required, StringLength(3)] public string RequiredAndStringLengthProp { get; set; }
|
[Required, StringLength(3)] public string RequiredAndStringLengthProp { get; set; }
|
||||||
[DisplayName("My Display Name"), StringLength(3)] public string DisplayNameStringLengthProp { get; set; }
|
[DisplayName("My Display Name"), StringLength(3)] public string DisplayNameStringLengthProp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestPage : PageModel
|
||||||
|
{
|
||||||
|
[BindRequired]
|
||||||
|
public int BindRequiredProperty { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -13,6 +14,10 @@ namespace RazorPagesWebSite
|
||||||
|
|
||||||
public string ReturnUrl { get; set; }
|
public string ReturnUrl { get; set; }
|
||||||
|
|
||||||
|
[BindRequired]
|
||||||
|
[FromQuery(Name = nameof(Attempts))]
|
||||||
|
public int Attempts { get; set; }
|
||||||
|
|
||||||
public class InputModel
|
public class InputModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
|
|
@ -69,10 +74,13 @@ namespace RazorPagesWebSite
|
||||||
{
|
{
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
|
Attempts++;
|
||||||
|
RouteData.Values.Add(nameof(Attempts), Attempts);
|
||||||
|
|
||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Redirect("~/");
|
return Redirect("~/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue