Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit a6199bb

Browse files
authored
Add integration and functional tests of [BindRequired] on page properties (#8677)
- #7353
1 parent f2af66b commit a6199bb

File tree

4 files changed

+145
-24
lines changed

4 files changed

+145
-24
lines changed

src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
using System.Runtime.CompilerServices;
55

66
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
7+
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
78
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Collections.Generic;
55
using System.Net;
66
using System.Net.Http;
7-
using System.Net.Http.Headers;
87
using System.Threading.Tasks;
98
using Xunit;
109

@@ -420,14 +419,16 @@ public async Task PageConventions_CustomizedModelCanPostToHandlers()
420419
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
421420
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
422421

423-
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel");
424-
message.Content = new FormUrlEncodedContent(new Dictionary<string, string>
422+
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel")
425423
{
426-
["__RequestVerificationToken"] = token,
427-
["ConfirmPassword"] = "",
428-
["Password"] = "",
429-
["Email"] = ""
430-
});
424+
Content = new FormUrlEncodedContent(new Dictionary<string, string>
425+
{
426+
["__RequestVerificationToken"] = token,
427+
["ConfirmPassword"] = "",
428+
["Password"] = "",
429+
["Email"] = ""
430+
})
431+
};
431432
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
432433

433434
// Act
@@ -443,18 +444,20 @@ public async Task PageConventions_CustomizedModelCanPostToHandlers()
443444
public async Task PageConventions_CustomizedModelCanWorkWithModelState()
444445
{
445446
// Arrange
446-
var getPage = await Client.GetAsync("/CustomModelTypeModel");
447+
var getPage = await Client.GetAsync("/CustomModelTypeModel?Attempts=0");
447448
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
448449
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
449450

450-
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel");
451-
message.Content = new FormUrlEncodedContent(new Dictionary<string, string>
451+
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel?Attempts=3")
452452
{
453-
["__RequestVerificationToken"] = token,
454-
["Email"] = "javi@example.com",
455-
["Password"] = "Password.12$",
456-
["ConfirmPassword"] = "Password.12$",
457-
});
453+
Content = new FormUrlEncodedContent(new Dictionary<string, string>
454+
{
455+
["__RequestVerificationToken"] = token,
456+
["Email"] = "javi@example.com",
457+
["Password"] = "Password.12$",
458+
["ConfirmPassword"] = "Password.12$",
459+
})
460+
};
458461
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
459462

460463
// Act
@@ -465,6 +468,37 @@ public async Task PageConventions_CustomizedModelCanWorkWithModelState()
465468
Assert.Equal("/", response.Headers.Location.ToString());
466469
}
467470

471+
[Fact]
472+
public async Task PageConventions_CustomizedModelCanWorkWithModelState_EnforcesBindRequired()
473+
{
474+
// Arrange
475+
var getPage = await Client.GetAsync("/CustomModelTypeModel?Attempts=0");
476+
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
477+
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
478+
479+
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel")
480+
{
481+
Content = new FormUrlEncodedContent(new Dictionary<string, string>
482+
{
483+
["__RequestVerificationToken"] = token,
484+
["Email"] = "javi@example.com",
485+
["Password"] = "Password.12$",
486+
["ConfirmPassword"] = "Password.12$",
487+
})
488+
};
489+
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
490+
491+
// Act
492+
var response = await Client.SendAsync(message);
493+
494+
// Assert
495+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
496+
var responseText = await response.Content.ReadAsStringAsync();
497+
Assert.Contains(
498+
"A value for the &#x27;Attempts&#x27; parameter or property was not provided.",
499+
responseText);
500+
}
501+
468502
[Fact]
469503
public async Task ValidationAttributes_OnTopLevelProperties()
470504
{
@@ -642,10 +676,12 @@ public async Task RoundTrippingFormFileInputWorks()
642676

643677
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(response);
644678

645-
var content = new MultipartFormDataContent();
646-
content.Add(new StringContent("property1-value"), property1);
647-
content.Add(new StringContent("test-value1"), file1, "test1.txt");
648-
content.Add(new StringContent("test-value2"), file3, "test2.txt");
679+
var content = new MultipartFormDataContent
680+
{
681+
{ new StringContent("property1-value"), property1 },
682+
{ new StringContent("test-value1"), file1, "test1.txt" },
683+
{ new StringContent("test-value2"), file3, "test2.txt" }
684+
};
649685

650686
var request = new HttpRequestMessage(HttpMethod.Post, url)
651687
{

test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindPropertyIntegrationTest.cs

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Collections.Generic;
54
using System.ComponentModel;
65
using System.ComponentModel.DataAnnotations;
76
using System.Linq;
7+
using System.Reflection;
88
using System.Threading.Tasks;
99
using Microsoft.AspNetCore.Http;
1010
using Microsoft.AspNetCore.Mvc.Abstractions;
1111
using Microsoft.AspNetCore.Mvc.ModelBinding;
12-
using Microsoft.Extensions.Primitives;
12+
using Microsoft.AspNetCore.Mvc.RazorPages;
13+
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
14+
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
1315
using Xunit;
1416

1517
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
@@ -179,6 +181,74 @@ public async Task BindModelAsync_WithBindProperty_EnforcesBindRequired(int? inpu
179181
}
180182
}
181183

184+
[Theory]
185+
[InlineData(null, false)]
186+
[InlineData(123, true)]
187+
public async Task BindModelAsync_WithBindPageProperty_EnforcesBindRequired(int? input, bool isValid)
188+
{
189+
// Arrange
190+
var propertyInfo = typeof(TestPage).GetProperty(nameof(TestPage.BindRequiredProperty));
191+
var propertyDescriptor = new PageBoundPropertyDescriptor
192+
{
193+
BindingInfo = BindingInfo.GetBindingInfo(new[]
194+
{
195+
new FromQueryAttribute { Name = propertyInfo.Name },
196+
}),
197+
Name = propertyInfo.Name,
198+
ParameterType = propertyInfo.PropertyType,
199+
Property = propertyInfo,
200+
};
201+
202+
var typeInfo = typeof(TestPage).GetTypeInfo();
203+
var actionDescriptor = new CompiledPageActionDescriptor
204+
{
205+
BoundProperties = new[] { propertyDescriptor },
206+
HandlerTypeInfo = typeInfo,
207+
ModelTypeInfo = typeInfo,
208+
PageTypeInfo = typeInfo,
209+
};
210+
211+
var testContext = ModelBindingTestHelper.GetTestContext(request =>
212+
{
213+
request.Method = "POST";
214+
if (input.HasValue)
215+
{
216+
request.QueryString = new QueryString($"?{propertyDescriptor.Name}={input.Value}");
217+
}
218+
});
219+
220+
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
221+
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
222+
var modelBinderFactory = ModelBindingTestHelper.GetModelBinderFactory(modelMetadataProvider);
223+
var modelMetadata = modelMetadataProvider
224+
.GetMetadataForProperty(typeof(TestPage), propertyDescriptor.Name);
225+
226+
var pageBinder = PageBinderFactory.CreatePropertyBinder(
227+
parameterBinder,
228+
modelMetadataProvider,
229+
modelBinderFactory,
230+
actionDescriptor);
231+
var pageContext = new PageContext
232+
{
233+
ActionDescriptor = actionDescriptor,
234+
HttpContext = testContext.HttpContext,
235+
RouteData = testContext.RouteData,
236+
ValueProviderFactories = testContext.ValueProviderFactories,
237+
};
238+
239+
var page = new TestPage();
240+
241+
// Act
242+
await pageBinder(pageContext, page);
243+
244+
// Assert
245+
Assert.Equal(isValid, pageContext.ModelState.IsValid);
246+
if (isValid)
247+
{
248+
Assert.Equal(input.Value, page.BindRequiredProperty);
249+
}
250+
}
251+
182252
[Theory]
183253
[InlineData("RequiredAndStringLengthProp", null, false)]
184254
[InlineData("RequiredAndStringLengthProp", "", false)]
@@ -231,12 +301,18 @@ public async Task BindModelAsync_WithBindProperty_EnforcesDataAnnotationsAttribu
231301
}
232302
}
233303

234-
class TestController
304+
private class TestController
235305
{
236306
[BindNever] public string BindNeverProp { get; set; }
237307
[BindRequired] public int BindRequiredProp { get; set; }
238308
[Required, StringLength(3)] public string RequiredAndStringLengthProp { get; set; }
239309
[DisplayName("My Display Name"), StringLength(3)] public string DisplayNameStringLengthProp { get; set; }
240310
}
311+
312+
private class TestPage : PageModel
313+
{
314+
[BindRequired]
315+
public int BindRequiredProperty { get; set; }
316+
}
241317
}
242318
}

test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.ComponentModel.DataAnnotations;
33
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.AspNetCore.Mvc.ModelBinding;
45
using Microsoft.AspNetCore.Mvc.RazorPages;
56
using Microsoft.Extensions.Logging;
67

@@ -13,6 +14,10 @@ public class CustomModelTypeModel : PageModel
1314

1415
public string ReturnUrl { get; set; }
1516

17+
[BindRequired]
18+
[FromQuery(Name = nameof(Attempts))]
19+
public int Attempts { get; set; }
20+
1621
public class InputModel
1722
{
1823
[Required]
@@ -69,10 +74,13 @@ public override IActionResult OnPostAsync(string returnUrl = null)
6974
{
7075
if (!ModelState.IsValid)
7176
{
77+
Attempts++;
78+
RouteData.Values.Add(nameof(Attempts), Attempts);
79+
7280
return Page();
7381
}
7482

7583
return Redirect("~/");
7684
}
7785
}
78-
}
86+
}

0 commit comments

Comments
 (0)