From f6e701b2b2d01ae068b144416aeac080c623bc17 Mon Sep 17 00:00:00 2001 From: Kirthi Krishnamraju Date: Thu, 5 Feb 2015 11:27:32 -0800 Subject: [PATCH] Reverting #1837 and add non-generic overloads for TryUpdateModel --- src/Microsoft.AspNet.Mvc.Core/Controller.cs | 76 ++++++ .../ParameterBinding/ModelBindingHelper.cs | 110 ++++++++- .../Properties/Resources.Designer.cs | 8 + src/Microsoft.AspNet.Mvc.Core/Resources.resx | 3 + .../ControllerTests.cs | 109 +++++++++ .../ModelBindingHelperTest.cs | 216 ++++++++++++++++++ .../ModelBindingTest.cs | 48 +++- .../Controllers/TryUpdateModelController.cs | 60 ++++- 8 files changed, 621 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index 79c2e8b36e..5fe319e4cb 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -1104,6 +1104,82 @@ namespace Microsoft.AspNet.Mvc predicate); } + /// + /// Updates the specified instance using values from the controller's current + /// and a . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The prefix to use when looking up values in the current + /// + /// A that on completion returns true if the update is successful + [NonAction] + public virtual async Task TryUpdateModelAsync([NotNull] object model, + [NotNull] Type modelType, + string prefix) + { + if (BindingContext == null) + { + var message = Resources.FormatPropertyOfTypeCannotBeNull( + nameof(BindingContext), + typeof(Controller).FullName); + throw new InvalidOperationException(message); + } + + return await ModelBindingHelper.TryUpdateModelAsync( + model, + modelType, + prefix, + ActionContext.HttpContext, + ModelState, + MetadataProvider, + BindingContext.ModelBinder, + BindingContext.ValueProvider, + ObjectValidator, + BindingContext.ValidatorProvider); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The prefix to use when looking up values in the + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful + [NonAction] + public async Task TryUpdateModelAsync( + [NotNull] object model, + [NotNull] Type modelType, + string prefix, + [NotNull] IValueProvider valueProvider, + [NotNull] Func predicate) + { + if (BindingContext == null) + { + var message = Resources.FormatPropertyOfTypeCannotBeNull( + nameof(BindingContext), + typeof(Controller).FullName); + throw new InvalidOperationException(message); + } + + return await ModelBindingHelper.TryUpdateModelAsync( + model, + modelType, + prefix, + ActionContext.HttpContext, + ModelState, + MetadataProvider, + BindingContext.ModelBinder, + valueProvider, + ObjectValidator, + BindingContext.ValidatorProvider, + predicate); + } + /// /// Validates the specified instance. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs index 91b0ee26b5..c7b7a92caf 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs @@ -128,14 +128,14 @@ namespace Microsoft.AspNet.Mvc /// The provider used for reading metadata for the model type. /// The used for binding. /// The used for looking up values. - /// /// The used for validating the + /// The used for validating the /// bound values. /// The used for executing validation /// on the model instance. /// A predicate which can be used to /// filter properties(for inclusion/exclusion) at runtime. /// A that on completion returns true if the update is successful - public static async Task TryUpdateModelAsync( + public static Task TryUpdateModelAsync( [NotNull] TModel model, [NotNull] string prefix, [NotNull] HttpContext httpContext, @@ -148,9 +148,113 @@ namespace Microsoft.AspNet.Mvc [NotNull] Func predicate) where TModel : class { + return TryUpdateModelAsync( + model, + typeof(TModel), + prefix, + httpContext, + modelState, + metadataProvider, + modelBinder, + valueProvider, + objectModelValidator, + validatorProvider, + predicate: predicate); + } + + /// + /// Updates the specified instance using the specified + /// and the specified and executes validation using the specified + /// . + /// + /// The model instance to update and validate. + /// The type of model instance to update and validate. + /// The prefix to use when looking up values in the . + /// + /// The for the current executing request. + /// The used for maintaining state and + /// results of model-binding validation. + /// The provider used for reading metadata for the model type. + /// The used for binding. + /// The used for looking up values. + /// The used for validating the + /// bound values. + /// The used for executing validation + /// on the model instance. + /// A that on completion returns true if the update is successful + public static Task TryUpdateModelAsync( + [NotNull] object model, + [NotNull] Type modelType, + [NotNull] string prefix, + [NotNull] HttpContext httpContext, + [NotNull] ModelStateDictionary modelState, + [NotNull] IModelMetadataProvider metadataProvider, + [NotNull] IModelBinder modelBinder, + [NotNull] IValueProvider valueProvider, + [NotNull] IObjectModelValidator objectModelValidator, + [NotNull] IModelValidatorProvider validatorProvider) + { + // Includes everything by default. + return TryUpdateModelAsync( + model, + modelType, + prefix, + httpContext, + modelState, + metadataProvider, + modelBinder, + valueProvider, + objectModelValidator, + validatorProvider, + predicate: (context, propertyName) => true); + } + + /// + /// Updates the specified instance using the specified + /// and the specified and executes validation using the specified + /// . + /// + /// The model instance to update and validate. + /// The type of model instance to update and validate. + /// The prefix to use when looking up values in the . + /// + /// The for the current executing request. + /// The used for maintaining state and + /// results of model-binding validation. + /// The provider used for reading metadata for the model type. + /// The used for binding. + /// The used for looking up values. + /// The used for validating the + /// bound values. + /// The used for executing validation + /// on the model instance. + /// A predicate which can be used to + /// filter properties(for inclusion/exclusion) at runtime. + /// A that on completion returns true if the update is successful + public static async Task TryUpdateModelAsync( + [NotNull] object model, + [NotNull] Type modelType, + [NotNull] string prefix, + [NotNull] HttpContext httpContext, + [NotNull] ModelStateDictionary modelState, + [NotNull] IModelMetadataProvider metadataProvider, + [NotNull] IModelBinder modelBinder, + [NotNull] IValueProvider valueProvider, + [NotNull] IObjectModelValidator objectModelValidator, + [NotNull] IModelValidatorProvider validatorProvider, + [NotNull] Func predicate) + { + if (!modelType.IsAssignableFrom(model.GetType())) + { + var message = Resources.FormatModelType_WrongType( + model.GetType().FullName, + modelType.FullName); + throw new ArgumentException(message, nameof(modelType)); + } + var modelMetadata = metadataProvider.GetMetadataForType( modelAccessor: () => model, - modelType: model.GetType()); + modelType: modelType); var operationBindingContext = new OperationBindingContext { diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 4954301d8f..292432e9ff 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1706,6 +1706,14 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("CacheProfileNotFound"), p0); } + /// + /// The model type '{0}' does not match the '{1}' type parameter. + /// + internal static string FormatModelType_WrongType(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ModelType_WrongType"), p0, p1); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index c5434b5443..20099dea90 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -445,4 +445,7 @@ The '{0}' cache profile is not defined. + + The model's runtime type '{0}' is not assignable to the type '{1}'. + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index 78a96eae8d..f200ccd6ec 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -1140,6 +1140,110 @@ namespace Microsoft.AspNet.Mvc.Test binder.Verify(); } + [Fact] + public async Task TryUpdateModelNonGeneric_PredicateWithValueProviderOverload_UsesPassedArguments() + { + // Arrange + var modelName = "mymodel"; + + Func includePredicate = + (context, propertyName) => string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase); + + var binder = new Mock(); + var valueProvider = Mock.Of(); + binder.Setup(b => b.BindModelAsync(It.IsAny())) + .Callback((ModelBindingContext context) => + { + Assert.Equal(modelName, context.ModelName); + Assert.Same(valueProvider, context.ValueProvider); + + Assert.True(context.PropertyFilter(context, "include1")); + Assert.True(context.PropertyFilter(context, "include2")); + + Assert.False(context.PropertyFilter(context, "exclude1")); + Assert.False(context.PropertyFilter(context, "exclude2")); + }) + .Returns(Task.FromResult(null)) + .Verifiable(); + + var controller = GetController(binder.Object, provider: null); + + var model = new MyModel(); + + // Act + await controller.TryUpdateModelAsync(model, model.GetType(), modelName, valueProvider, includePredicate); + + // Assert + binder.Verify(); + } + + [Fact] + public async Task TryUpdateModelNonGeneric_ModelTypeOverload_UsesPassedArguments() + { + // Arrange + var modelName = "mymodel"; + + var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var valueProvider = Mock.Of(); + var binder = new Mock(); + binder.Setup(b => b.BindModelAsync(It.IsAny())) + .Callback((ModelBindingContext context) => + { + Assert.Equal(modelName, context.ModelName); + Assert.Same(valueProvider, context.ValueProvider); + + // Include and exclude should be null, resulting in property + // being included. + Assert.True(context.PropertyFilter(context, "Property1")); + Assert.True(context.PropertyFilter(context, "Property2")); + }) + .Returns(Task.FromResult(null)) + .Verifiable(); + + var controller = GetController(binder.Object, valueProvider); + var model = new MyModel(); + + // Act + var result = await controller.TryUpdateModelAsync(model, model.GetType(), modelName); + + // Assert + binder.Verify(); + } + + [Fact] + public async Task TryUpdateModelNonGeneric_BindToBaseDeclaredType_ModelTypeOverload() + { + // Arrange + var modelName = "mymodel"; + + var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var valueProvider = Mock.Of(); + var binder = new Mock(); + binder.Setup(b => b.BindModelAsync(It.IsAny())) + .Callback((ModelBindingContext context) => + { + Assert.Equal(modelName, context.ModelName); + Assert.Same(valueProvider, context.ValueProvider); + + // Include and exclude should be null, resulting in property + // being included. + Assert.True(context.PropertyFilter(context, "Property1")); + Assert.True(context.PropertyFilter(context, "Property2")); + }) + .Returns(Task.FromResult(null)) + .Verifiable(); + + var controller = GetController(binder.Object, valueProvider); + MyModel model = new MyDerivedModel(); + + // Act + var result = await controller.TryUpdateModelAsync(model, model.GetType(), modelName); + + // Assert + binder.Verify(); + } + #endif [Fact] @@ -1366,6 +1470,11 @@ namespace Microsoft.AspNet.Mvc.Test public string Property2 { get; set; } } + private class MyDerivedModel : MyModel + { + public string Property3 { get; set; } + } + private class User { public User(int id) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs index 16acb3f3fd..c9494920cf 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs @@ -479,6 +479,222 @@ namespace Microsoft.AspNet.Mvc.Core.Test ex.Message); } + [Fact] + public async Task TryUpdateModelNonGeneric_PredicateOverload_ReturnsFalse_IfBinderReturnsFalse() + { + // Arrange + var metadataProvider = new Mock(); + metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny>(), It.IsAny())) + .Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null)) + .Verifiable(); + + var binder = new Mock(); + binder.Setup(b => b.BindModelAsync(It.IsAny())) + .Returns(Task.FromResult(null)); + var model = new MyModel(); + Func includePredicate = + (context, propertyName) => true; + // Act + var result = await ModelBindingHelper.TryUpdateModelAsync( + model, + model.GetType(), + prefix: null, + httpContext: Mock.Of(), + modelState: new ModelStateDictionary(), + metadataProvider: metadataProvider.Object, + modelBinder: GetCompositeBinder(binder.Object), + valueProvider: Mock.Of(), + objectModelValidator: Mock.Of(), + validatorProvider: Mock.Of(), + predicate: includePredicate); + + // Assert + Assert.False(result); + Assert.Null(model.MyProperty); + Assert.Null(model.IncludedProperty); + Assert.Null(model.ExcludedProperty); + metadataProvider.Verify(); + } + + [Fact] + public async Task TryUpdateModelNonGeneric_PredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully() + { + // Arrange + var binders = new IModelBinder[] + { + new TypeConverterModelBinder(), + new ComplexModelDtoModelBinder(), + new MutableObjectModelBinder() + }; + + var validator = new DataAnnotationsModelValidatorProvider(); + var model = new MyModel + { + MyProperty = "Old-Value", + IncludedProperty = "Old-IncludedPropertyValue", + ExcludedProperty = "Old-ExcludedPropertyValue" + }; + + var modelStateDictionary = new ModelStateDictionary(); + var values = new Dictionary + { + { "", null }, + { "MyProperty", "MyPropertyValue" }, + { "IncludedProperty", "IncludedPropertyValue" }, + { "ExcludedProperty", "ExcludedPropertyValue" } + }; + + Func includePredicate = + (context, propertyName) => + string.Equals(propertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase); + + var valueProvider = new TestValueProvider(values); + var metadataProvider = new DataAnnotationsModelMetadataProvider(); + + // Act + var result = await ModelBindingHelper.TryUpdateModelAsync( + model, + model.GetType(), + "", + Mock.Of(), + modelStateDictionary, + metadataProvider, + GetCompositeBinder(binders), + valueProvider, + new DefaultObjectValidator( + Mock.Of(), + metadataProvider), + validator, + includePredicate); + + // Assert + Assert.True(result); + Assert.Equal("MyPropertyValue", model.MyProperty); + Assert.Equal("IncludedPropertyValue", model.IncludedProperty); + Assert.Equal("Old-ExcludedPropertyValue", model.ExcludedProperty); + } + + [Fact] + public async Task TryUpdateModelNonGeneric_ModelTypeOverload_ReturnsFalse_IfBinderReturnsFalse() + { + // Arrange + var metadataProvider = new Mock(); + metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny>(), It.IsAny())) + .Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null)) + .Verifiable(); + + var binder = new Mock(); + binder.Setup(b => b.BindModelAsync(It.IsAny())) + .Returns(Task.FromResult(null)); + var model = new MyModel(); + + // Act + var result = await ModelBindingHelper.TryUpdateModelAsync( + model, + modelType: model.GetType(), + prefix: null, + httpContext: Mock.Of(), + modelState: new ModelStateDictionary(), + metadataProvider: metadataProvider.Object, + modelBinder: GetCompositeBinder(binder.Object), + valueProvider: Mock.Of(), + objectModelValidator: Mock.Of(), + validatorProvider: Mock.Of()); + + // Assert + Assert.False(result); + Assert.Null(model.MyProperty); + metadataProvider.Verify(); + } + + [Fact] + public async Task TryUpdateModelNonGeneric_ModelTypeOverload_ReturnsTrue_IfModelBindsAndValidatesSuccessfully() + { + // Arrange + var binders = new IModelBinder[] + { + new TypeConverterModelBinder(), + new ComplexModelDtoModelBinder(), + new MutableObjectModelBinder() + }; + + var validator = new DataAnnotationsModelValidatorProvider(); + var model = new MyModel { MyProperty = "Old-Value" }; + var modelStateDictionary = new ModelStateDictionary(); + var values = new Dictionary + { + { "", null }, + { "MyProperty", "MyPropertyValue" } + }; + var valueProvider = new TestValueProvider(values); + var metadataProvider = new DataAnnotationsModelMetadataProvider(); + + // Act + var result = await ModelBindingHelper.TryUpdateModelAsync( + model, + model.GetType(), + "", + Mock.Of(), + modelStateDictionary, + new DataAnnotationsModelMetadataProvider(), + GetCompositeBinder(binders), + valueProvider, + new DefaultObjectValidator( + Mock.Of(), + metadataProvider), + validator); + + // Assert + Assert.True(result); + Assert.Equal("MyPropertyValue", model.MyProperty); + } + + [Fact] + public async Task TryUpdataModel_ModelTypeDifferentFromModel_Throws() + { + // Arrange + var metadataProvider = new Mock(); + metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny())) + .Returns(new ModelMetadata(metadataProvider.Object, + containerType: null, + modelAccessor: null, + modelType: typeof(MyModel), + propertyName: null)) + .Verifiable(); + + var binder = new Mock(); + binder.Setup(b => b.BindModelAsync(It.IsAny())) + .Returns(Task.FromResult(null)); + var model = new MyModel(); + Func includePredicate = + (context, propertyName) => true; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => ModelBindingHelper.TryUpdateModelAsync( + model, + typeof(User), + null, + Mock.Of(), + new ModelStateDictionary(), + metadataProvider.Object, + GetCompositeBinder(binder.Object), + Mock.Of(), + new DefaultObjectValidator( + Mock.Of(), + metadataProvider.Object), + Mock.Of(), + includePredicate)); + + var expectedMessage = string.Format("The model's runtime type '{0}' is not assignable to the type '{1}'." + + Environment.NewLine + + "Parameter name: modelType", + model.GetType().FullName, + typeof(User).FullName); + Assert.Equal(expectedMessage, exception.Message); + } + private static IModelBinder GetCompositeBinder(params IModelBinder[] binders) { return new CompositeModelBinder(binders); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs index a715fc02f8..86ffbfb80d 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs @@ -398,7 +398,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("WA_Query", user.ShippingAddress.State); Assert.Equal(3, user.ShippingAddress.Street); Assert.Equal(4, user.ShippingAddress.Zip); - } [Fact] @@ -1573,6 +1572,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests [Fact] public async Task ModelBinder_FormatsDontMatch_ThrowsUserFriendlyException() + { // Arrange var server = TestServer.Create(_services, _app); @@ -1644,6 +1644,29 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(expectedDictionary, dictionary); } + [Fact] + public async Task TryUpdateModelNonGeneric_IncludesAllProperties_CanBind() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + + "GetUserAsync_ModelType_IncludeAll" + + "?id=123&RegisterationMonth=March&Key=123&UserName=SomeName"); + + // Assert + var user = JsonConvert.DeserializeObject(response); + + // Should not update any not explicitly mentioned properties. + Assert.Equal("SomeName", user.UserName); + Assert.Equal(123, user.Key); + + // Should Update all included properties. + Assert.Equal("March", user.RegisterationMonth); + } + [Fact] public async Task FormCollectionModelBinder_CanBind_FormValues() { @@ -1731,5 +1754,28 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var fileContent = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedContent, fileContent); } + + [Fact] + public async Task TryUpdateModelNonGenericIncludesAllProperties_ByDefault() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + + "GetUserAsync_ModelType_IncludeAllByDefault" + + "?id=123&RegisterationMonth=March&Key=123&UserName=SomeName"); + + // Assert + var user = JsonConvert.DeserializeObject(response); + + // Should not update any not explicitly mentioned properties. + Assert.Equal("SomeName", user.UserName); + Assert.Equal(123, user.Key); + + // Should Update all included properties. + Assert.Equal("March", user.RegisterationMonth); + } } } \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Controllers/TryUpdateModelController.cs b/test/WebSites/ModelBindingWebSite/Controllers/TryUpdateModelController.cs index 77fb8539f7..fe5d89d543 100644 --- a/test/WebSites/ModelBindingWebSite/Controllers/TryUpdateModelController.cs +++ b/test/WebSites/ModelBindingWebSite/Controllers/TryUpdateModelController.cs @@ -2,9 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.ModelBinding; +using System.Collections.Generic; +using Microsoft.AspNet.Http.Core.Collections; +using Microsoft.AspNet.Http; namespace ModelBindingWebSite.Controllers { @@ -100,12 +104,58 @@ namespace ModelBindingWebSite.Controllers public async Task GetEmployeeAsync_BindToBaseDeclaredType() { - var employee = new Employee(); - await TryUpdateModelAsync( - employee, - prefix: string.Empty); + var backingStore = new ReadableStringCollection( + new Dictionary + { + { "Parent.Name", new[] { "fatherName"} }, + { "Parent.Parent.Name", new[] {"grandFatherName" } }, + { "Department", new[] {"Sales" } } + }); - return employee; + Person employee = new Employee(); + await TryUpdateModelAsync( + employee, + employee.GetType(), + prefix: string.Empty, + valueProvider: new ReadableStringCollectionValueProvider( + BindingSource.Query, + backingStore, + CultureInfo.CurrentCulture), + predicate: (content, propertyName) => true); + + return (Employee)employee; + } + + public async Task GetUserAsync_ModelType_IncludeAll(int id) + { + var backingStore = new ReadableStringCollection( + new Dictionary + { + { "Key", new[] { "123"} }, + { "RegisterationMonth", new[] {"March" } }, + { "UserName", new[] {"SomeName" } } + }); + + var user = GetUser(id); + + await TryUpdateModelAsync(user, + typeof(User), + prefix: string.Empty, + valueProvider: new ReadableStringCollectionValueProvider( + BindingSource.Query, + backingStore, + CultureInfo.CurrentCulture), + predicate: (content, propertyName) => true); + + return user; + } + + public async Task GetUserAsync_ModelType_IncludeAllByDefault(int id) + { + var user = GetUser(id); + + await TryUpdateModelAsync(user, user.GetType(), prefix: string.Empty); + return user; } private User GetUser(int id)