Skip to content

Commit 6d334ff

Browse files
authored
Exclude additional types from being validated (#37150)
Prevent 'System.Delegate' and some reflection types from being validated. Fixes #36919
1 parent 52921ed commit 6d334ff

File tree

4 files changed

+136
-5
lines changed

4 files changed

+136
-5
lines changed

src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Collections.Generic;
88
using System.IO;
9+
using System.Reflection;
910
using System.Threading;
1011
using Microsoft.AspNetCore.Http;
1112
using Microsoft.AspNetCore.Mvc.Formatters;
@@ -134,6 +135,11 @@ internal static void ConfigureAdditionalModelMetadataDetailsProviders(IList<IMet
134135

135136
// Add types to be excluded from Validation
136137
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Type)));
138+
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Delegate)));
139+
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(MethodInfo)));
140+
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(MemberInfo)));
141+
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(ParameterInfo)));
142+
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Assembly)));
137143
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Uri)));
138144
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(CancellationToken)));
139145
modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormFile)));

src/Mvc/Mvc/test/MvcOptionsSetupTest.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,31 @@ public void Setup_SetsUpMetadataDetailsProviders()
211211
Assert.Equal(typeof(Type), excludeFilter.Type);
212212
},
213213
provider =>
214+
{
215+
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
216+
Assert.Equal(typeof(Delegate), excludeFilter.Type);
217+
},
218+
provider =>
219+
{
220+
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
221+
Assert.Equal(typeof(MethodInfo), excludeFilter.Type);
222+
},
223+
provider =>
224+
{
225+
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
226+
Assert.Equal(typeof(MemberInfo), excludeFilter.Type);
227+
},
228+
provider =>
229+
{
230+
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
231+
Assert.Equal(typeof(ParameterInfo), excludeFilter.Type);
232+
},
233+
provider =>
234+
{
235+
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
236+
Assert.Equal(typeof(Assembly), excludeFilter.Type);
237+
},
238+
provider =>
214239
{
215240
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
216241
Assert.Equal(typeof(Uri), excludeFilter.Type);

src/Mvc/test/Mvc.IntegrationTests/ModelBindingTestHelper.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public static ParameterBinder GetParameterBinder(IServiceProvider serviceProvide
8787
public static ParameterBinder GetParameterBinder(
8888
IModelMetadataProvider metadataProvider,
8989
IModelBinderProvider binderProvider = null,
90-
MvcOptions mvcOptions = null)
90+
MvcOptions mvcOptions = null,
91+
ObjectModelValidator validator = null)
9192
{
9293
var services = GetServices(metadataProvider, mvcOptions: mvcOptions);
9394
var options = services.GetRequiredService<IOptions<MvcOptions>>();
@@ -97,13 +98,15 @@ public static ParameterBinder GetParameterBinder(
9798
options.Value.ModelBinderProviders.Insert(0, binderProvider);
9899
}
99100

101+
validator ??= new DefaultObjectValidator(
102+
metadataProvider,
103+
new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) },
104+
options.Value);
105+
100106
return new ParameterBinder(
101107
metadataProvider,
102108
new ModelBinderFactory(metadataProvider, options, services),
103-
new DefaultObjectValidator(
104-
metadataProvider,
105-
new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) },
106-
options.Value),
109+
validator,
107110
options,
108111
NullLoggerFactory.Instance);
109112
}

src/Mvc/test/Mvc.IntegrationTests/ValidationIntegrationTests.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
using Microsoft.AspNetCore.Mvc.ModelBinding;
1919
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
2020
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
21+
using Microsoft.Extensions.DependencyInjection;
22+
using Microsoft.Extensions.Options;
2123
using Newtonsoft.Json;
2224
using Newtonsoft.Json.Linq;
2325
using Xunit;
@@ -2437,10 +2439,105 @@ public async Task Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParame
24372439

24382440
private static void Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParameterMethod([Required] RecursiveModel model) { }
24392441

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+
24402505
private static void AssertRequiredError(string key, ModelError error)
24412506
{
24422507
Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage(key), error.ErrorMessage);
24432508
Assert.Null(error.Exception);
24442509
}
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+
}
24452542
}
24462543
}

0 commit comments

Comments
 (0)