diff options
author | Stephen Halter <halter73@gmail.com> | 2022-06-01 04:19:46 +0300 |
---|---|---|
committer | Stephen Halter <halter73@gmail.com> | 2022-06-01 04:20:15 +0300 |
commit | 1e71d1eec4b278960d5d2752a8fcb57e0bf1196b (patch) | |
tree | fdda23bc9f8ebc655a5f5f30c1b07e56801c1844 | |
parent | 978998cdf15d5a17740629c134095e653cbb6f3e (diff) |
Fix nullability of TryUpdateModelAsynchalter73/41886
5 files changed, 74 insertions, 9 deletions
diff --git a/src/Mvc/Mvc.Core/src/ControllerBase.cs b/src/Mvc/Mvc.Core/src/ControllerBase.cs index d1c9a840d7..5fddac183d 100644 --- a/src/Mvc/Mvc.Core/src/ControllerBase.cs +++ b/src/Mvc/Mvc.Core/src/ControllerBase.cs @@ -2619,7 +2619,7 @@ public abstract class ControllerBase 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) @@ -2710,7 +2710,7 @@ public abstract class ControllerBase TModel model, string prefix, IValueProvider valueProvider, - params Expression<Func<TModel, object>>[] includeExpressions) + params Expression<Func<TModel, object?>>[] includeExpressions) where TModel : class { if (model == null) diff --git a/src/Mvc/Mvc.Core/src/ModelBinding/DefaultPropertyFilterProvider.cs b/src/Mvc/Mvc.Core/src/ModelBinding/DefaultPropertyFilterProvider.cs index 95324dc180..d30748e7de 100644 --- a/src/Mvc/Mvc.Core/src/ModelBinding/DefaultPropertyFilterProvider.cs +++ b/src/Mvc/Mvc.Core/src/ModelBinding/DefaultPropertyFilterProvider.cs @@ -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 @@ -45,7 +45,7 @@ public class DefaultPropertyFilterProvider<TModel> : IPropertyFilterProvider } 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(); diff --git a/src/Mvc/Mvc.Core/src/ModelBinding/ModelBindingHelper.cs b/src/Mvc/Mvc.Core/src/ModelBinding/ModelBindingHelper.cs index 236a243d7b..34dc03f9c3 100644 --- a/src/Mvc/Mvc.Core/src/ModelBinding/ModelBindingHelper.cs +++ b/src/Mvc/Mvc.Core/src/ModelBinding/ModelBindingHelper.cs @@ -82,7 +82,7 @@ internal static class ModelBindingHelper IModelBinderFactory modelBinderFactory, IValueProvider valueProvider, IObjectModelValidator objectModelValidator, - params Expression<Func<TModel, object>>[] includeExpressions) + params Expression<Func<TModel, object?>>[] includeExpressions) where TModel : class { if (includeExpressions == null) @@ -363,7 +363,7 @@ internal static class ModelBindingHelper /// <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) { @@ -385,7 +385,7 @@ internal static class ModelBindingHelper } private static Expression<Func<ModelMetadata, bool>> GetPredicateExpression<TModel>( - Expression<Func<TModel, object>> expression) + Expression<Func<TModel, object?>> expression) { var propertyName = GetPropertyName(expression.Body); diff --git a/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt index 80b80b3520..fabf768487 100644 --- a/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt @@ -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 @@ -29,3 +33,5 @@ Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadataProvider.Defa Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactory.ModelBinderFactory(Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider! metadataProvider, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Mvc.MvcOptions!>! options, System.IServiceProvider! serviceProvider) -> void Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.ParameterBinder(Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider! modelMetadataProvider, Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory! modelBinderFactory, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator! validator, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Mvc.MvcOptions!>! mvcOptions, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> 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?>!>!>? diff --git a/src/Mvc/Mvc.Core/test/ControllerBaseTest.cs b/src/Mvc/Mvc.Core/test/ControllerBaseTest.cs index deeda17dd2..3b6020a52d 100644 --- a/src/Mvc/Mvc.Core/test/ControllerBaseTest.cs +++ b/src/Mvc/Mvc.Core/test/ControllerBaseTest.cs @@ -2728,8 +2728,7 @@ public class ControllerBaseTest [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>(); @@ -2758,6 +2757,57 @@ public class ControllerBaseTest 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() { @@ -3114,6 +3164,15 @@ public class ControllerBaseTest 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; } |