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;
|
||||
|
||||
[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")]
|
||||
|
|
|
|||
|
|
@ -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 'Attempts' 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("~/");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue