Skip to content

Commit

Permalink
Fix nullability of TryUpdateModelAsync (#41959)
Browse files Browse the repository at this point in the history
  • Loading branch information
halter73 committed Jun 17, 2022
1 parent ab3e5e2 commit 375f5a4
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/Mvc/Mvc.Core/src/ControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2619,7 +2619,7 @@ public virtual Task<bool> TryUpdateModelAsync<TModel>(
public async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
params Expression<Func<TModel, object>>[] includeExpressions)
params Expression<Func<TModel, object?>>[] includeExpressions)
where TModel : class
{
if (model == null)
Expand Down Expand Up @@ -2710,7 +2710,7 @@ public Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
IValueProvider valueProvider,
params Expression<Func<TModel, object>>[] includeExpressions)
params Expression<Func<TModel, object?>>[] includeExpressions)
where TModel : class
{
if (model == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class DefaultPropertyFilterProvider<TModel> : IPropertyFilterProvider
/// Expressions which can be used to generate property filter which can filter model
/// properties.
/// </summary>
public virtual IEnumerable<Expression<Func<TModel, object>>>? PropertyIncludeExpressions => null;
public virtual IEnumerable<Expression<Func<TModel, object?>>>? PropertyIncludeExpressions => null;

/// <inheritdoc />
public virtual Func<ModelMetadata, bool> PropertyFilter
Expand All @@ -45,7 +45,7 @@ public virtual Func<ModelMetadata, bool> PropertyFilter
}

private static Func<ModelMetadata, bool> GetPropertyFilterFromExpression(
IEnumerable<Expression<Func<TModel, object>>> includeExpressions)
IEnumerable<Expression<Func<TModel, object?>>> includeExpressions)
{
var expression = ModelBindingHelper.GetPropertyFilterExpression(includeExpressions.ToArray());
return expression.Compile();
Expand Down
6 changes: 3 additions & 3 deletions src/Mvc/Mvc.Core/src/ModelBinding/ModelBindingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public static Task<bool> TryUpdateModelAsync<TModel>(
IModelBinderFactory modelBinderFactory,
IValueProvider valueProvider,
IObjectModelValidator objectModelValidator,
params Expression<Func<TModel, object>>[] includeExpressions)
params Expression<Func<TModel, object?>>[] includeExpressions)
where TModel : class
{
if (includeExpressions == null)
Expand Down Expand Up @@ -363,7 +363,7 @@ internal static string GetPropertyName(Expression expression)
/// <param name="expressions">Expressions identifying the properties to allow for binding.</param>
/// <returns>An expression which can be used with <see cref="IPropertyFilterProvider"/>.</returns>
public static Expression<Func<ModelMetadata, bool>> GetPropertyFilterExpression<TModel>(
Expression<Func<TModel, object>>[] expressions)
Expression<Func<TModel, object?>>[] expressions)
{
if (expressions.Length == 0)
{
Expand All @@ -385,7 +385,7 @@ public static Expression<Func<ModelMetadata, bool>> GetPropertyFilterExpression<
}

private static Expression<Func<ModelMetadata, bool>> GetPredicateExpression<TModel>(
Expression<Func<TModel, object>> expression)
Expression<Func<TModel, object?>> expression)
{
var propertyName = GetPropertyName(expression.Body);

Expand Down
6 changes: 6 additions & 0 deletions src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Microsoft.AspNetCore.Mvc.ApiBehaviorOptions.DisableImplicitFromServicesParameters.get -> bool
Microsoft.AspNetCore.Mvc.ApiBehaviorOptions.DisableImplicitFromServicesParameters.set -> void
Microsoft.AspNetCore.Mvc.ApplicationModels.InferParameterBindingInfoConvention.InferParameterBindingInfoConvention(Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider! modelMetadataProvider, Microsoft.Extensions.DependencyInjection.IServiceProviderIsService! serviceProviderIsService) -> void
*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>!
*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>!
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>!
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>!
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.TryParseModelBinderProvider
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.TryParseModelBinderProvider.GetBinder(Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderProviderContext! context) -> Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder?
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.TryParseModelBinderProvider.TryParseModelBinderProvider() -> void
Expand All @@ -15,3 +19,5 @@ Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata.ValidationMode
Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata.ValidationModelName.set -> void
virtual Microsoft.AspNetCore.Mvc.Infrastructure.ConfigureCompatibilityOptions<TOptions>.PostConfigure(string? name, TOptions! options) -> void
static Microsoft.AspNetCore.Mvc.ControllerBase.Empty.get -> Microsoft.AspNetCore.Mvc.EmptyResult!
*REMOVED*virtual Microsoft.AspNetCore.Mvc.ModelBinding.DefaultPropertyFilterProvider<TModel>.PropertyIncludeExpressions.get -> System.Collections.Generic.IEnumerable<System.Linq.Expressions.Expression<System.Func<TModel!, object!>!>!>?
virtual Microsoft.AspNetCore.Mvc.ModelBinding.DefaultPropertyFilterProvider<TModel>.PropertyIncludeExpressions.get -> System.Collections.Generic.IEnumerable<System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>!>?
63 changes: 61 additions & 2 deletions src/Mvc/Mvc.Core/test/ControllerBaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2728,8 +2728,7 @@ public async Task TryUpdateModel_IncludeExpressionOverload_UsesPassedArguments(s
[Theory]
[InlineData("")]
[InlineData("prefix")]
public async Task
TryUpdateModel_IncludeExpressionWithValueProviderOverload_UsesPassedArguments(string prefix)
public async Task TryUpdateModel_IncludeExpressionWithValueProviderOverload_UsesPassedArguments(string prefix)
{
// Arrange
var valueProvider = new Mock<IValueProvider>();
Expand Down Expand Up @@ -2758,6 +2757,57 @@ public async Task
Assert.NotEqual(0, binder.BindModelCount);
}

#nullable enable
[Fact]
public async Task TryUpdateModel_SupportsNullableExpressions()
{
// Arrange
var valueProvider = new Mock<IValueProvider>();
valueProvider.Setup(v => v.ContainsPrefix(""))
.Returns(true);

StubModelBinder CreateBinder() => new StubModelBinder(context =>
{
Assert.Same(
valueProvider.Object,
Assert.IsType<CompositeValueProvider>(context.ValueProvider)[0]);
Assert.NotNull(context.PropertyFilter);
bool InvokePropertyFilter(string propertyName)
{
var modelMetadata = context.ModelMetadata.Properties[propertyName];
Assert.NotNull(modelMetadata);
return context.PropertyFilter!(modelMetadata!);
}
Assert.True(InvokePropertyFilter("Include"));
Assert.False(InvokePropertyFilter("Exclude"));
});

var binder1 = CreateBinder();
var controller1 = GetController(binder1, valueProvider.Object);
var model1 = new MyNullableModel();

// Act
await controller1.TryUpdateModelAsync(model1, prefix: "", m => m.Include);

// Assert
Assert.NotEqual(0, binder1.BindModelCount);

// Arrange (IModelBinder overload)
var binder2 = CreateBinder();
var controller2 = GetController(binder2, valueProvider.Object);
var model2 = new MyNullableModel();

// Act (IModelBinder overload)
await controller2.TryUpdateModelAsync(model2, prefix: "", m => m.Include);

// Assert (IModelBinder overload)
Assert.NotEqual(0, binder2.BindModelCount);
}
#nullable restore

[Fact]
public async Task TryUpdateModelNonGeneric_PropertyFilterWithValueProviderOverload_UsesPassedArguments()
{
Expand Down Expand Up @@ -3114,6 +3164,15 @@ private class MyDerivedModel : MyModel
public string Property3 { get; set; }
}

#nullable enable
private class MyNullableModel
{
public string? Include { get; set; }

public string? Exclude { get; set; }
}
#nullable restore

private class TryValidateModelModel
{
public int IntegerProperty { get; set; }
Expand Down
4 changes: 2 additions & 2 deletions src/Mvc/Mvc.RazorPages/src/PageBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1397,7 +1397,7 @@ public virtual Task<bool> TryUpdateModelAsync<TModel>(
public async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
params Expression<Func<TModel, object>>[] includeExpressions)
params Expression<Func<TModel, object?>>[] includeExpressions)
where TModel : class
{
if (model == null)
Expand Down Expand Up @@ -1486,7 +1486,7 @@ public Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
IValueProvider valueProvider,
params Expression<Func<TModel, object>>[] includeExpressions)
params Expression<Func<TModel, object?>>[] includeExpressions)
where TModel : class
{
if (model == null)
Expand Down
4 changes: 2 additions & 2 deletions src/Mvc/Mvc.RazorPages/src/PageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ protected internal Task<bool> TryUpdateModelAsync<TModel>(
protected internal async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string name,
params Expression<Func<TModel, object>>[] includeExpressions)
params Expression<Func<TModel, object?>>[] includeExpressions)
where TModel : class
{
if (model == null)
Expand Down Expand Up @@ -375,7 +375,7 @@ protected internal Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string name,
IValueProvider valueProvider,
params Expression<Func<TModel, object>>[] includeExpressions)
params Expression<Func<TModel, object?>>[] includeExpressions)
where TModel : class
{
if (model == null)
Expand Down
8 changes: 8 additions & 0 deletions src/Mvc/Mvc.RazorPages/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
#nullable enable
*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>!
*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>!
*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>!
*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>!
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>!
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>!
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>!
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 comments on commit 375f5a4

Please sign in to comment.