Skip to content

Commit 375f5a4

Browse files
authored
Fix nullability of TryUpdateModelAsync (#41959)
1 parent ab3e5e2 commit 375f5a4

File tree

8 files changed

+86
-13
lines changed

8 files changed

+86
-13
lines changed

src/Mvc/Mvc.Core/src/ControllerBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,7 +2619,7 @@ public virtual Task<bool> TryUpdateModelAsync<TModel>(
26192619
public async Task<bool> TryUpdateModelAsync<TModel>(
26202620
TModel model,
26212621
string prefix,
2622-
params Expression<Func<TModel, object>>[] includeExpressions)
2622+
params Expression<Func<TModel, object?>>[] includeExpressions)
26232623
where TModel : class
26242624
{
26252625
if (model == null)
@@ -2710,7 +2710,7 @@ public Task<bool> TryUpdateModelAsync<TModel>(
27102710
TModel model,
27112711
string prefix,
27122712
IValueProvider valueProvider,
2713-
params Expression<Func<TModel, object>>[] includeExpressions)
2713+
params Expression<Func<TModel, object?>>[] includeExpressions)
27142714
where TModel : class
27152715
{
27162716
if (model == null)

src/Mvc/Mvc.Core/src/ModelBinding/DefaultPropertyFilterProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class DefaultPropertyFilterProvider<TModel> : IPropertyFilterProvider
2727
/// Expressions which can be used to generate property filter which can filter model
2828
/// properties.
2929
/// </summary>
30-
public virtual IEnumerable<Expression<Func<TModel, object>>>? PropertyIncludeExpressions => null;
30+
public virtual IEnumerable<Expression<Func<TModel, object?>>>? PropertyIncludeExpressions => null;
3131

3232
/// <inheritdoc />
3333
public virtual Func<ModelMetadata, bool> PropertyFilter
@@ -45,7 +45,7 @@ public virtual Func<ModelMetadata, bool> PropertyFilter
4545
}
4646

4747
private static Func<ModelMetadata, bool> GetPropertyFilterFromExpression(
48-
IEnumerable<Expression<Func<TModel, object>>> includeExpressions)
48+
IEnumerable<Expression<Func<TModel, object?>>> includeExpressions)
4949
{
5050
var expression = ModelBindingHelper.GetPropertyFilterExpression(includeExpressions.ToArray());
5151
return expression.Compile();

src/Mvc/Mvc.Core/src/ModelBinding/ModelBindingHelper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public static Task<bool> TryUpdateModelAsync<TModel>(
8282
IModelBinderFactory modelBinderFactory,
8383
IValueProvider valueProvider,
8484
IObjectModelValidator objectModelValidator,
85-
params Expression<Func<TModel, object>>[] includeExpressions)
85+
params Expression<Func<TModel, object?>>[] includeExpressions)
8686
where TModel : class
8787
{
8888
if (includeExpressions == null)
@@ -363,7 +363,7 @@ internal static string GetPropertyName(Expression expression)
363363
/// <param name="expressions">Expressions identifying the properties to allow for binding.</param>
364364
/// <returns>An expression which can be used with <see cref="IPropertyFilterProvider"/>.</returns>
365365
public static Expression<Func<ModelMetadata, bool>> GetPropertyFilterExpression<TModel>(
366-
Expression<Func<TModel, object>>[] expressions)
366+
Expression<Func<TModel, object?>>[] expressions)
367367
{
368368
if (expressions.Length == 0)
369369
{
@@ -385,7 +385,7 @@ public static Expression<Func<ModelMetadata, bool>> GetPropertyFilterExpression<
385385
}
386386

387387
private static Expression<Func<ModelMetadata, bool>> GetPredicateExpression<TModel>(
388-
Expression<Func<TModel, object>> expression)
388+
Expression<Func<TModel, object?>> expression)
389389
{
390390
var propertyName = GetPropertyName(expression.Body);
391391

src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
Microsoft.AspNetCore.Mvc.ApiBehaviorOptions.DisableImplicitFromServicesParameters.get -> bool
44
Microsoft.AspNetCore.Mvc.ApiBehaviorOptions.DisableImplicitFromServicesParameters.set -> void
55
Microsoft.AspNetCore.Mvc.ApplicationModels.InferParameterBindingInfoConvention.InferParameterBindingInfoConvention(Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider! modelMetadataProvider, Microsoft.Extensions.DependencyInjection.IServiceProviderIsService! serviceProviderIsService) -> void
6+
*REMOVED*Microsoft.AspNetCore.Mvc.ControllerBase.TryUpdateModelAsync<TModel>(TModel! model, string! prefix, Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider! valueProvider, params System.Linq.Expressions.Expression<System.Func<TModel!, object!>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
7+
*REMOVED*Microsoft.AspNetCore.Mvc.ControllerBase.TryUpdateModelAsync<TModel>(TModel! model, string! prefix, params System.Linq.Expressions.Expression<System.Func<TModel!, object!>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
8+
Microsoft.AspNetCore.Mvc.ControllerBase.TryUpdateModelAsync<TModel>(TModel! model, string! prefix, Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider! valueProvider, params System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
9+
Microsoft.AspNetCore.Mvc.ControllerBase.TryUpdateModelAsync<TModel>(TModel! model, string! prefix, params System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
610
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.TryParseModelBinderProvider
711
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.TryParseModelBinderProvider.GetBinder(Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderProviderContext! context) -> Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder?
812
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.TryParseModelBinderProvider.TryParseModelBinderProvider() -> void
@@ -15,3 +19,5 @@ Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata.ValidationMode
1519
Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata.ValidationModelName.set -> void
1620
virtual Microsoft.AspNetCore.Mvc.Infrastructure.ConfigureCompatibilityOptions<TOptions>.PostConfigure(string? name, TOptions! options) -> void
1721
static Microsoft.AspNetCore.Mvc.ControllerBase.Empty.get -> Microsoft.AspNetCore.Mvc.EmptyResult!
22+
*REMOVED*virtual Microsoft.AspNetCore.Mvc.ModelBinding.DefaultPropertyFilterProvider<TModel>.PropertyIncludeExpressions.get -> System.Collections.Generic.IEnumerable<System.Linq.Expressions.Expression<System.Func<TModel!, object!>!>!>?
23+
virtual Microsoft.AspNetCore.Mvc.ModelBinding.DefaultPropertyFilterProvider<TModel>.PropertyIncludeExpressions.get -> System.Collections.Generic.IEnumerable<System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>!>?

src/Mvc/Mvc.Core/test/ControllerBaseTest.cs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2728,8 +2728,7 @@ public async Task TryUpdateModel_IncludeExpressionOverload_UsesPassedArguments(s
27282728
[Theory]
27292729
[InlineData("")]
27302730
[InlineData("prefix")]
2731-
public async Task
2732-
TryUpdateModel_IncludeExpressionWithValueProviderOverload_UsesPassedArguments(string prefix)
2731+
public async Task TryUpdateModel_IncludeExpressionWithValueProviderOverload_UsesPassedArguments(string prefix)
27332732
{
27342733
// Arrange
27352734
var valueProvider = new Mock<IValueProvider>();
@@ -2758,6 +2757,57 @@ public async Task
27582757
Assert.NotEqual(0, binder.BindModelCount);
27592758
}
27602759

2760+
#nullable enable
2761+
[Fact]
2762+
public async Task TryUpdateModel_SupportsNullableExpressions()
2763+
{
2764+
// Arrange
2765+
var valueProvider = new Mock<IValueProvider>();
2766+
valueProvider.Setup(v => v.ContainsPrefix(""))
2767+
.Returns(true);
2768+
2769+
StubModelBinder CreateBinder() => new StubModelBinder(context =>
2770+
{
2771+
Assert.Same(
2772+
valueProvider.Object,
2773+
Assert.IsType<CompositeValueProvider>(context.ValueProvider)[0]);
2774+
2775+
Assert.NotNull(context.PropertyFilter);
2776+
2777+
bool InvokePropertyFilter(string propertyName)
2778+
{
2779+
var modelMetadata = context.ModelMetadata.Properties[propertyName];
2780+
Assert.NotNull(modelMetadata);
2781+
return context.PropertyFilter!(modelMetadata!);
2782+
}
2783+
2784+
Assert.True(InvokePropertyFilter("Include"));
2785+
Assert.False(InvokePropertyFilter("Exclude"));
2786+
});
2787+
2788+
var binder1 = CreateBinder();
2789+
var controller1 = GetController(binder1, valueProvider.Object);
2790+
var model1 = new MyNullableModel();
2791+
2792+
// Act
2793+
await controller1.TryUpdateModelAsync(model1, prefix: "", m => m.Include);
2794+
2795+
// Assert
2796+
Assert.NotEqual(0, binder1.BindModelCount);
2797+
2798+
// Arrange (IModelBinder overload)
2799+
var binder2 = CreateBinder();
2800+
var controller2 = GetController(binder2, valueProvider.Object);
2801+
var model2 = new MyNullableModel();
2802+
2803+
// Act (IModelBinder overload)
2804+
await controller2.TryUpdateModelAsync(model2, prefix: "", m => m.Include);
2805+
2806+
// Assert (IModelBinder overload)
2807+
Assert.NotEqual(0, binder2.BindModelCount);
2808+
}
2809+
#nullable restore
2810+
27612811
[Fact]
27622812
public async Task TryUpdateModelNonGeneric_PropertyFilterWithValueProviderOverload_UsesPassedArguments()
27632813
{
@@ -3114,6 +3164,15 @@ private class MyDerivedModel : MyModel
31143164
public string Property3 { get; set; }
31153165
}
31163166

3167+
#nullable enable
3168+
private class MyNullableModel
3169+
{
3170+
public string? Include { get; set; }
3171+
3172+
public string? Exclude { get; set; }
3173+
}
3174+
#nullable restore
3175+
31173176
private class TryValidateModelModel
31183177
{
31193178
public int IntegerProperty { get; set; }

src/Mvc/Mvc.RazorPages/src/PageBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,7 @@ public virtual Task<bool> TryUpdateModelAsync<TModel>(
13971397
public async Task<bool> TryUpdateModelAsync<TModel>(
13981398
TModel model,
13991399
string prefix,
1400-
params Expression<Func<TModel, object>>[] includeExpressions)
1400+
params Expression<Func<TModel, object?>>[] includeExpressions)
14011401
where TModel : class
14021402
{
14031403
if (model == null)
@@ -1486,7 +1486,7 @@ public Task<bool> TryUpdateModelAsync<TModel>(
14861486
TModel model,
14871487
string prefix,
14881488
IValueProvider valueProvider,
1489-
params Expression<Func<TModel, object>>[] includeExpressions)
1489+
params Expression<Func<TModel, object?>>[] includeExpressions)
14901490
where TModel : class
14911491
{
14921492
if (model == null)

src/Mvc/Mvc.RazorPages/src/PageModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ protected internal Task<bool> TryUpdateModelAsync<TModel>(
286286
protected internal async Task<bool> TryUpdateModelAsync<TModel>(
287287
TModel model,
288288
string name,
289-
params Expression<Func<TModel, object>>[] includeExpressions)
289+
params Expression<Func<TModel, object?>>[] includeExpressions)
290290
where TModel : class
291291
{
292292
if (model == null)
@@ -375,7 +375,7 @@ protected internal Task<bool> TryUpdateModelAsync<TModel>(
375375
TModel model,
376376
string name,
377377
IValueProvider valueProvider,
378-
params Expression<Func<TModel, object>>[] includeExpressions)
378+
params Expression<Func<TModel, object?>>[] includeExpressions)
379379
where TModel : class
380380
{
381381
if (model == null)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
#nullable enable
2+
*REMOVED*Microsoft.AspNetCore.Mvc.RazorPages.PageBase.TryUpdateModelAsync<TModel>(TModel! model, string! prefix, Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider! valueProvider, params System.Linq.Expressions.Expression<System.Func<TModel!, object!>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
3+
*REMOVED*Microsoft.AspNetCore.Mvc.RazorPages.PageBase.TryUpdateModelAsync<TModel>(TModel! model, string! prefix, params System.Linq.Expressions.Expression<System.Func<TModel!, object!>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
4+
*REMOVED*Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync<TModel>(TModel! model, string! name, Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider! valueProvider, params System.Linq.Expressions.Expression<System.Func<TModel!, object!>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
5+
*REMOVED*Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync<TModel>(TModel! model, string! name, params System.Linq.Expressions.Expression<System.Func<TModel!, object!>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
6+
Microsoft.AspNetCore.Mvc.RazorPages.PageBase.TryUpdateModelAsync<TModel>(TModel! model, string! prefix, Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider! valueProvider, params System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
7+
Microsoft.AspNetCore.Mvc.RazorPages.PageBase.TryUpdateModelAsync<TModel>(TModel! model, string! prefix, params System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
8+
Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync<TModel>(TModel! model, string! name, Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider! valueProvider, params System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
9+
Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync<TModel>(TModel! model, string! name, params System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!

0 commit comments

Comments
 (0)