|
18 | 18 | using Microsoft.AspNetCore.Mvc.ModelBinding; |
19 | 19 | using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; |
20 | 20 | using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; |
| 21 | +using Microsoft.Extensions.DependencyInjection; |
| 22 | +using Microsoft.Extensions.Options; |
21 | 23 | using Newtonsoft.Json; |
22 | 24 | using Newtonsoft.Json.Linq; |
23 | 25 | using Xunit; |
@@ -2437,10 +2439,105 @@ public async Task Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParame |
2437 | 2439 |
|
2438 | 2440 | private static void Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParameterMethod([Required] RecursiveModel model) { } |
2439 | 2441 |
|
| 2442 | + [Fact] |
| 2443 | + public async Task Validation_ModelWithNonNullableReferenceTypes_DoesNotValidateNonNullablePropertiesOnFrameworkTypes() |
| 2444 | + { |
| 2445 | + // Arrange |
| 2446 | + var parameterInfo = GetType().GetMethod(nameof(Validation_ModelWithNonNullableReferenceTypes_DoesNotValidateNonNullablePropertiesOnFrameworkTypesAction), BindingFlags.NonPublic | BindingFlags.Static) |
| 2447 | + .GetParameters() |
| 2448 | + .First(); |
| 2449 | + |
| 2450 | + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); |
| 2451 | + var services = ModelBindingTestHelper.GetServices(modelMetadataProvider); |
| 2452 | + var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo); |
| 2453 | + var options = services.GetRequiredService<IOptions<MvcOptions>>().Value; |
| 2454 | + var validator = new RecordingObjectValidator( |
| 2455 | + modelMetadataProvider, |
| 2456 | + TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders, |
| 2457 | + options); |
| 2458 | + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider, mvcOptions: options, validator: validator); |
| 2459 | + |
| 2460 | + var parameter = new ParameterDescriptor() |
| 2461 | + { |
| 2462 | + Name = parameterInfo.Name, |
| 2463 | + ParameterType = parameterInfo.ParameterType, |
| 2464 | + }; |
| 2465 | + |
| 2466 | + var testContext = ModelBindingTestHelper.GetTestContext(request => |
| 2467 | + { |
| 2468 | + request.QueryString = new QueryString("?Name=CoolName"); |
| 2469 | + }); |
| 2470 | + var modelState = testContext.ModelState; |
| 2471 | + |
| 2472 | + // Act |
| 2473 | + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata); |
| 2474 | + |
| 2475 | + // Assert |
| 2476 | + Assert.True(modelBindingResult.IsModelSet); |
| 2477 | + |
| 2478 | + var model = Assert.IsType<ModelWithNonNullableReferenceTypeProperties>(modelBindingResult.Model); |
| 2479 | + Assert.Equal("CoolName", model.Name); |
| 2480 | + |
| 2481 | + Assert.True(modelState.IsValid); |
| 2482 | + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); |
| 2483 | + |
| 2484 | + var visited = validator.ValidationVisitor.Visited; |
| 2485 | + Assert.Collection( |
| 2486 | + visited, |
| 2487 | + v => Assert.Equal(typeof(ModelWithNonNullableReferenceTypeProperties), v.ModelType), |
| 2488 | + v => Assert.Equal(typeof(string), v.ModelType), |
| 2489 | + v => Assert.Equal(typeof(Delegate), v.ModelType)); |
| 2490 | + } |
| 2491 | + |
| 2492 | +#nullable enable |
| 2493 | + private static void Validation_ModelWithNonNullableReferenceTypes_DoesNotValidateNonNullablePropertiesOnFrameworkTypesAction(ModelWithNonNullableReferenceTypeProperties model) { } |
| 2494 | + |
| 2495 | + public class ModelWithNonNullableReferenceTypeProperties |
| 2496 | + { |
| 2497 | + public string Name { get; set; } = default!; |
| 2498 | + |
| 2499 | + public Delegate Delegate { get; set; } = typeof(ModelWithNonNullableReferenceTypeProperties).GetMethod(nameof(SomeMethod))!.CreateDelegate<Action>(); |
| 2500 | + |
| 2501 | + public static void SomeMethod() { } |
| 2502 | + } |
| 2503 | +#nullable restore |
| 2504 | + |
2440 | 2505 | private static void AssertRequiredError(string key, ModelError error) |
2441 | 2506 | { |
2442 | 2507 | Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage(key), error.ErrorMessage); |
2443 | 2508 | Assert.Null(error.Exception); |
2444 | 2509 | } |
| 2510 | + |
| 2511 | + private class RecordingObjectValidator : DefaultObjectValidator |
| 2512 | + { |
| 2513 | + public RecordingObjectValidator(IModelMetadataProvider modelMetadataProvider, IList<IModelValidatorProvider> validatorProviders, MvcOptions mvcOptions) |
| 2514 | + : base(modelMetadataProvider, validatorProviders, mvcOptions) |
| 2515 | + { |
| 2516 | + } |
| 2517 | + |
| 2518 | + public RecordingValidationVisitor ValidationVisitor { get; private set; } |
| 2519 | + |
| 2520 | + public override ValidationVisitor GetValidationVisitor(ActionContext actionContext, IModelValidatorProvider validatorProvider, ValidatorCache validatorCache, IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState) |
| 2521 | + { |
| 2522 | + ValidationVisitor = new RecordingValidationVisitor(actionContext, validatorProvider, validatorCache, metadataProvider, validationState); |
| 2523 | + return ValidationVisitor; |
| 2524 | + } |
| 2525 | + } |
| 2526 | + |
| 2527 | + private class RecordingValidationVisitor : ValidationVisitor |
| 2528 | + { |
| 2529 | + public RecordingValidationVisitor(ActionContext actionContext, IModelValidatorProvider validatorProvider, ValidatorCache validatorCache, IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState) |
| 2530 | + : base(actionContext, validatorProvider, validatorCache, metadataProvider, validationState) |
| 2531 | + { |
| 2532 | + } |
| 2533 | + |
| 2534 | + public List<ModelMetadata> Visited = new(); |
| 2535 | + |
| 2536 | + protected override bool Visit(ModelMetadata metadata, string key, object model) |
| 2537 | + { |
| 2538 | + Visited.Add(metadata); |
| 2539 | + return base.Visit(metadata, key, model); |
| 2540 | + } |
| 2541 | + } |
2445 | 2542 | } |
2446 | 2543 | } |
0 commit comments