// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; using System.Linq; using Microsoft.Web.UnitTestUtil; using Moq; using Moq.Protected; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Mvc.Test { [CLSCompliant(false)] public class DefaultModelBinderTest { [Fact] public void BindComplexElementalModelReturnsIfOnModelUpdatingReturnsFalse() { // Arrange ControllerContext controllerContext = new Mock().Object; MyModel model = new MyModel() { ReadWriteProperty = 3 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), }; Mock mockHelper = new Mock() { CallBase = true }; mockHelper.Setup(b => b.PublicOnModelUpdating(controllerContext, It.IsAny())).Returns(false); DefaultModelBinderHelper helper = mockHelper.Object; // Act helper.BindComplexElementalModel(controllerContext, bindingContext, model); // Assert Assert.Equal(3, model.ReadWriteProperty); mockHelper.Verify(); mockHelper.Verify(b => b.PublicGetModelProperties(controllerContext, It.IsAny()), Times.Never()); mockHelper.Verify(b => b.PublicBindProperty(controllerContext, It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public void BindComplexModelCanBindArrays() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int[])), ModelName = "foo", PropertyFilter = _ => false, ValueProvider = new SimpleValueProvider() { { "foo[0]", null }, { "foo[1]", null }, { "foo[2]", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(typeof(int), bc.ModelType); Assert.Equal(bindingContext.ModelState, bc.ModelState); Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture); }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(int), mockInnerBinder.Object } } }; // Act object newModel = binder.BindComplexModel(controllerContext, bindingContext); // Assert var newIntArray = Assert.IsType(newModel); Assert.Equal(new[] { 0, 1, 2 }, newIntArray); } [Fact] public void BindComplexModelCanBindCollections() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IList)), ModelName = "foo", PropertyFilter = _ => false, ValueProvider = new SimpleValueProvider() { { "foo[0]", null }, { "foo[1]", null }, { "foo[2]", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(typeof(int), bc.ModelType); Assert.Equal(bindingContext.ModelState, bc.ModelState); Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture); }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(int), mockInnerBinder.Object } } }; // Act object newModel = binder.BindComplexModel(controllerContext, bindingContext); // Assert var modelAsList = Assert.IsAssignableFrom>(newModel); Assert.Equal(new[] { 0, 1, 2 }, modelAsList.ToArray()); } [Fact] public void BindComplexModelCanBindDictionariesWithDotsNotation() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary)), ModelName = "countries", PropertyFilter = _ => true, ValueProvider = new DictionaryValueProvider(new Dictionary() { { "countries.CA.Name", "Canada" }, { "countries.CA.States[0]", "Québec" }, { "countries.CA.States[1]", "British Columbia" }, { "countries.US.Name", "United States" }, { "countries.US.States[0]", "Washington" }, { "countries.US.States[1]", "Oregon" } }, CultureInfo.CurrentCulture) }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object newModel = binder.BindComplexModel(controllerContext, bindingContext); // Assert var modelAsDictionary = Assert.IsAssignableFrom>(newModel); Assert.Equal(2, modelAsDictionary.Count); Assert.Equal("Canada", modelAsDictionary["CA"].Name); Assert.Equal("United States", modelAsDictionary["US"].Name); Assert.Equal(2, modelAsDictionary["CA"].States.Count()); Assert.True(modelAsDictionary["CA"].States.Contains("Québec")); Assert.True(modelAsDictionary["CA"].States.Contains("British Columbia")); Assert.Equal(2, modelAsDictionary["US"].States.Count()); Assert.True(modelAsDictionary["US"].States.Contains("Washington")); Assert.True(modelAsDictionary["US"].States.Contains("Oregon")); } [Fact] public void BindComplexModelCanBindDictionariesWithBracketsNotation() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary)), ModelName = "countries", PropertyFilter = _ => true, ValueProvider = new DictionaryValueProvider(new Dictionary() { { "countries[CA].Name", "Canada" }, { "countries[CA].States[0]", "Québec" }, { "countries[CA].States[1]", "British Columbia" }, { "countries[US].Name", "United States" }, { "countries[US].States[0]", "Washington" }, { "countries[US].States[1]", "Oregon" } }, CultureInfo.CurrentCulture) }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object newModel = binder.BindComplexModel(controllerContext, bindingContext); // Assert var modelAsDictionary = Assert.IsAssignableFrom>(newModel); Assert.Equal(2, modelAsDictionary.Count); Assert.Equal("Canada", modelAsDictionary["CA"].Name); Assert.Equal("United States", modelAsDictionary["US"].Name); Assert.Equal(2, modelAsDictionary["CA"].States.Count()); Assert.True(modelAsDictionary["CA"].States.Contains("Québec")); Assert.True(modelAsDictionary["CA"].States.Contains("British Columbia")); Assert.Equal(2, modelAsDictionary["US"].States.Count()); Assert.True(modelAsDictionary["US"].States.Contains("Washington")); Assert.True(modelAsDictionary["US"].States.Contains("Oregon")); } [Fact] public void BindComplexModelCanBindDictionariesWithBracketsAndDotsNotation() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary)), ModelName = "countries", PropertyFilter = _ => true, ValueProvider = new DictionaryValueProvider(new Dictionary() { { "countries[CA].Name", "Canada" }, { "countries[CA].States[0]", "Québec" }, { "countries.CA.States[1]", "British Columbia" }, { "countries.US.Name", "United States" }, { "countries.US.States[0]", "Washington" }, { "countries.US.States[1]", "Oregon" } }, CultureInfo.CurrentCulture) }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object newModel = binder.BindComplexModel(controllerContext, bindingContext); // Assert var modelAsDictionary = Assert.IsAssignableFrom>(newModel); Assert.Equal(2, modelAsDictionary.Count); Assert.Equal("Canada", modelAsDictionary["CA"].Name); Assert.Equal("United States", modelAsDictionary["US"].Name); Assert.Equal(1, modelAsDictionary["CA"].States.Count()); Assert.True(modelAsDictionary["CA"].States.Contains("Québec")); // We do not accept double notation for a same entry, so we can't find that state. Assert.False(modelAsDictionary["CA"].States.Contains("British Columbia")); Assert.Equal(2, modelAsDictionary["US"].States.Count()); Assert.True(modelAsDictionary["US"].States.Contains("Washington")); Assert.True(modelAsDictionary["US"].States.Contains("Oregon")); } [Fact] public void BindComplexModelCanBindDictionaries() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary)), ModelName = "foo", PropertyFilter = _ => false, ValueProvider = new SimpleValueProvider() { { "foo[0].key", null }, { "foo[0].value", null }, { "foo[1].key", null }, { "foo[1].value", null }, { "foo[2].key", null }, { "foo[2].value", null } } }; Mock mockIntBinder = new Mock(); mockIntBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(typeof(int), bc.ModelType); Assert.Equal(bindingContext.ModelState, bc.ModelState); Assert.Equal(new ModelBindingContext().PropertyFilter, bc.PropertyFilter); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10; }); Mock mockStringBinder = new Mock(); mockStringBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(typeof(string), bc.ModelType); Assert.Equal(bindingContext.ModelState, bc.ModelState); Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value"; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(int), mockIntBinder.Object }, { typeof(string), mockStringBinder.Object } } }; // Act object newModel = binder.BindComplexModel(controllerContext, bindingContext); // Assert var modelAsDictionary = Assert.IsAssignableFrom>(newModel); Assert.Equal(3, modelAsDictionary.Count); Assert.Equal("10Value", modelAsDictionary[10]); Assert.Equal("11Value", modelAsDictionary[11]); Assert.Equal("12Value", modelAsDictionary[12]); } [Fact] public void BindComplexModelCanBindObjects() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelWithoutBindAttribute model = new ModelWithoutBindAttribute() { Foo = "FooPreValue", Bar = "BarPreValue", Baz = "BazPreValue", }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "Foo", null }, { "Bar", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return bc.ModelName + "PostValue"; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(string), mockInnerBinder.Object } } }; // Act object updatedModel = binder.BindComplexModel(controllerContext, bindingContext); // Assert Assert.Same(model, updatedModel); Assert.Equal("FooPostValue", model.Foo); Assert.Equal("BarPostValue", model.Bar); Assert.Equal("BazPreValue", model.Baz); } [Fact] public void BindComplexModelReturnsNullArrayIfNoValuesProvided() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int[])), ModelName = "foo", ValueProvider = new SimpleValueProvider() { { "foo", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture); }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(int), mockInnerBinder.Object } } }; // Act object newModel = binder.BindComplexModel(null, bindingContext); // Assert Assert.Null(newModel); } [Fact] public void BindComplexModelWhereModelTypeContainsBindAttribute() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelWithBindAttribute model = new ModelWithBindAttribute() { Foo = "FooPreValue", Bar = "BarPreValue", Baz = "BazPreValue", }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "Foo", null }, { "Bar", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return bc.ModelName + "PostValue"; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(string), mockInnerBinder.Object } } }; // Act binder.BindComplexModel(controllerContext, bindingContext); // Assert Assert.Equal("FooPreValue", model.Foo); Assert.Equal("BarPostValue", model.Bar); Assert.Equal("BazPreValue", model.Baz); } [Fact] public void BindComplexModelWhereModelTypeDoesNotContainBindAttribute() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelWithoutBindAttribute model = new ModelWithoutBindAttribute() { Foo = "FooPreValue", Bar = "BarPreValue", Baz = "BazPreValue", }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "Foo", null }, { "Bar", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return bc.ModelName + "PostValue"; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(string), mockInnerBinder.Object } } }; // Act binder.BindComplexModel(controllerContext, bindingContext); // Assert Assert.Equal("FooPostValue", model.Foo); Assert.Equal("BarPostValue", model.Bar); Assert.Equal("BazPreValue", model.Baz); } // BindModel tests [Fact] public void BindModelCanBindObjects() { // Arrange ControllerContext controllerContext = new Mock().Object; ModelWithoutBindAttribute model = new ModelWithoutBindAttribute() { Foo = "FooPreValue", Bar = "BarPreValue", Baz = "BazPreValue", }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "Foo", null }, { "Bar", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return bc.ModelName + "PostValue"; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(string), mockInnerBinder.Object } } }; // Act object updatedModel = binder.BindModel(controllerContext, bindingContext); // Assert Assert.Same(model, updatedModel); Assert.Equal("FooPostValue", model.Foo); Assert.Equal("BarPostValue", model.Bar); Assert.Equal("BazPreValue", model.Baz); } [Fact] public void BindModelCanBindSimpleTypes() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int)), ModelName = "foo", ValueProvider = new SimpleValueProvider() { { "foo", "42" } } }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.BindModel(new ControllerContext(), bindingContext); // Assert Assert.Equal(42, updatedModel); } [Fact] public void BindModel_PerformsValidationByDefault() { // Arrange ModelMetadata metadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(string)); ControllerContext controllerContext = new ControllerContext(); controllerContext.Controller = new SimpleController(); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = metadata, ModelName = "foo", ValueProvider = new CustomUnvalidatedValueProvider() }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.BindModel(controllerContext, bindingContext); // Assert Assert.Equal("fooValidated", updatedModel); } [Fact] public void BindModel_SkipsValidationIfControllerOptsOut() { // Arrange ModelMetadata metadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(string)); ControllerContext controllerContext = new ControllerContext(); controllerContext.Controller = new SimpleController(); controllerContext.Controller.ValidateRequest = false; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = metadata, ModelName = "foo", ValueProvider = new CustomUnvalidatedValueProvider() }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.BindModel(controllerContext, bindingContext); // Assert Assert.Equal("fooUnvalidated", updatedModel); } [Fact] public void BindModel_SkipsValidationIfModelOptsOut() { // Arrange ModelMetadata metadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(string)); metadata.RequestValidationEnabled = false; ControllerContext controllerContext = new ControllerContext(); controllerContext.Controller = new SimpleController(); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = metadata, ModelName = "foo", ValueProvider = new CustomUnvalidatedValueProvider() }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.BindModel(controllerContext, bindingContext); // Assert Assert.Equal("fooUnvalidated", updatedModel); } [Fact] public void BindModelReturnsNullIfKeyNotFound() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int)), ModelName = "foo", ValueProvider = new SimpleValueProvider() }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object returnedModel = binder.BindModel(new ControllerContext(), bindingContext); // Assert Assert.Null(returnedModel); } [Fact] public void BindModelThrowsIfBindingContextIsNull() { // Arrange DefaultModelBinder binder = new DefaultModelBinder(); // Act & assert Assert.ThrowsArgumentNull( delegate { binder.BindModel(new ControllerContext(), null); }, "bindingContext"); } [Fact] public void BindModelValuesCanBeOverridden() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new ModelWithoutBindAttribute(), typeof(ModelWithoutBindAttribute)), ModelName = "", ValueProvider = new SimpleValueProvider() { { "foo", "FooPostValue" }, { "bar", "BarPostValue" }, { "baz", "BazPostValue" } } }; Mock binder = new Mock { CallBase = true }; binder.Protected().Setup("GetPropertyValue", ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) .Returns("Hello, world!"); // Act ModelWithoutBindAttribute model = (ModelWithoutBindAttribute)binder.Object.BindModel(new ControllerContext(), bindingContext); // Assert Assert.Equal("Hello, world!", model.Bar); Assert.Equal("Hello, world!", model.Baz); Assert.Equal("Hello, world!", model.Foo); } [Fact] public void BindModelWithTypeConversionErrorUpdatesModelStateMessage() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new PropertyTestingModel(), typeof(PropertyTestingModel)), ModelName = "", ValueProvider = new SimpleValueProvider() { { "IntReadWrite", "foo" } }, }; DefaultModelBinder binder = new DefaultModelBinder(); // Act PropertyTestingModel model = (PropertyTestingModel)binder.BindModel(new ControllerContext(), bindingContext); // Assert ModelState modelState = bindingContext.ModelState["IntReadWrite"]; Assert.NotNull(modelState); Assert.Single(modelState.Errors); Assert.Equal("The value 'foo' is not valid for IntReadWrite.", modelState.Errors[0].ErrorMessage); } [Fact] public void BindModelWithPrefix() { // Arrange ModelWithoutBindAttribute model = new ModelWithoutBindAttribute() { Foo = "FooPreValue", Bar = "BarPreValue", Baz = "BazPreValue", }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "prefix", ValueProvider = new SimpleValueProvider() { { "prefix.foo", "FooPostValue" }, { "prefix.bar", "BarPostValue" } } }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.BindModel(new ControllerContext(), bindingContext); // Assert Assert.Same(model, updatedModel); Assert.Equal("FooPostValue", model.Foo); Assert.Equal("BarPostValue", model.Bar); Assert.Equal("BazPreValue", model.Baz); } [Fact] public void BindModelWithPrefixAndFallback() { // Arrange ModelWithoutBindAttribute model = new ModelWithoutBindAttribute() { Foo = "FooPreValue", Bar = "BarPreValue", Baz = "BazPreValue", }; ModelBindingContext bindingContext = new ModelBindingContext() { FallbackToEmptyPrefix = true, ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "prefix", ValueProvider = new SimpleValueProvider() { { "foo", "FooPostValue" }, { "bar", "BarPostValue" } } }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.BindModel(new ControllerContext(), bindingContext); // Assert Assert.Same(model, updatedModel); Assert.Equal("FooPostValue", model.Foo); Assert.Equal("BarPostValue", model.Bar); Assert.Equal("BazPreValue", model.Baz); } [Fact] public void BindModelWithPrefixReturnsNullIfFallbackNotSpecifiedAndValueProviderContainsNoEntries() { // Arrange ModelWithoutBindAttribute model = new ModelWithoutBindAttribute() { Foo = "FooPreValue", Bar = "BarPreValue", Baz = "BazPreValue", }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "prefix", ValueProvider = new SimpleValueProvider() { { "foo", "FooPostValue" }, { "bar", "BarPostValue" } } }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.BindModel(new ControllerContext(), bindingContext); // Assert Assert.Null(updatedModel); } [Fact] public void BindModelReturnsNullIfSimpleTypeNotFound() { // DevDiv 216165: ModelBinders should not try and instantiate simple types // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(string)), ModelName = "prefix", ValueProvider = new SimpleValueProvider() { { "prefix.foo", "foo" }, { "prefix.bar", "bar" } } }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.BindModel(new ControllerContext(), bindingContext); // Assert Assert.Null(updatedModel); } // BindProperty tests [Fact] public void BindPropertyCanUpdateComplexReadOnlyProperties() { // Arrange // the Customer type contains a single read-only Address property Customer model = new Customer(); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "Address", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Address address = (Address)bc.Model; address.Street = "1 Microsoft Way"; address.Zip = "98052"; return address; }); PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["Address"]; DefaultModelBinderHelper helper = new DefaultModelBinderHelper() { Binders = new ModelBinderDictionary() { { typeof(Address), mockInnerBinder.Object } } }; // Act helper.PublicBindProperty(new ControllerContext(), bindingContext, pd); // Assert Assert.Equal("1 Microsoft Way", model.Address.Street); Assert.Equal("98052", model.Address.Zip); } [Fact] public void BindPropertyDoesNothingIfValueProviderContainsNoEntryForProperty() { // Arrange MyModel2 model = new MyModel2() { IntReadWrite = 3 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() }; PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWrite"]; DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act helper.PublicBindProperty(new ControllerContext(), bindingContext, pd); // Assert Assert.Equal(3, model.IntReadWrite); } [Fact] public void BindProperty() { // Arrange ControllerContext controllerContext = new Mock().Object; MyModel2 model = new MyModel2() { IntReadWrite = 3 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "IntReadWrite", "42" } } }; PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWrite"]; Mock mockHelper = new Mock() { CallBase = true }; mockHelper.Setup(b => b.PublicOnPropertyValidating(controllerContext, bindingContext, pd, 42)).Returns(true).Verifiable(); mockHelper.Setup(b => b.PublicSetProperty(controllerContext, bindingContext, pd, 42)).Verifiable(); mockHelper.Setup(b => b.PublicOnPropertyValidated(controllerContext, bindingContext, pd, 42)).Verifiable(); DefaultModelBinderHelper helper = mockHelper.Object; // Act helper.PublicBindProperty(controllerContext, bindingContext, pd); // Assert mockHelper.Verify(); } [Fact] public void BindPropertyReturnsIfOnPropertyValidatingReturnsFalse() { // Arrange ControllerContext controllerContext = new Mock().Object; MyModel2 model = new MyModel2() { IntReadWrite = 3 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "IntReadWrite", "42" } } }; PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWrite"]; Mock mockHelper = new Mock() { CallBase = true }; mockHelper.Setup(b => b.PublicOnPropertyValidating(controllerContext, bindingContext, pd, 42)).Returns(false); DefaultModelBinderHelper helper = mockHelper.Object; // Act helper.PublicBindProperty(controllerContext, bindingContext, pd); // Assert Assert.Equal(3, model.IntReadWrite); mockHelper.Verify(); mockHelper.Verify(b => b.PublicSetProperty(controllerContext, bindingContext, pd, 42), Times.Never()); mockHelper.Verify(b => b.PublicOnPropertyValidated(controllerContext, bindingContext, pd, 42), Times.Never()); } [Fact] public void BindPropertySetsPropertyToNullIfUserLeftTextEntryFieldBlankForOptionalValue() { // Arrange MyModel2 model = new MyModel2() { NullableIntReadWrite = 8 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "NullableIntReadWrite", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder.Setup(b => b.BindModel(new ControllerContext(), It.IsAny())).Returns((object)null); PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["NullableIntReadWrite"]; DefaultModelBinderHelper helper = new DefaultModelBinderHelper() { Binders = new ModelBinderDictionary() { { typeof(int?), mockInnerBinder.Object } } }; // Act helper.PublicBindProperty(new ControllerContext(), bindingContext, pd); // Assert Assert.Empty(bindingContext.ModelState); Assert.Null(model.NullableIntReadWrite); } [Fact] public void BindPropertyUpdatesPropertyOnFailureIfInnerBinderReturnsNonNullObject() { // Arrange MyModel2 model = new MyModel2() { IntReadWriteNonNegative = 8 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ValueProvider = new SimpleValueProvider() { { "IntReadWriteNonNegative", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { bc.ModelState.AddModelError("IntReadWriteNonNegative", "Some error text."); return 4; }); PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWriteNonNegative"]; DefaultModelBinderHelper helper = new DefaultModelBinderHelper() { Binders = new ModelBinderDictionary() { { typeof(int), mockInnerBinder.Object } } }; // Act helper.PublicBindProperty(new ControllerContext(), bindingContext, pd); // Assert Assert.Equal(false, bindingContext.ModelState.IsValidField("IntReadWriteNonNegative")); var error = Assert.Single(bindingContext.ModelState["IntReadWriteNonNegative"].Errors); Assert.Equal("Some error text.", error.ErrorMessage); Assert.Equal(4, model.IntReadWriteNonNegative); } [Fact] public void BindPropertyUpdatesPropertyOnSuccess() { // Arrange // Effectively, this is just testing updating a single property named "IntReadWrite" ControllerContext controllerContext = new Mock().Object; MyModel2 model = new MyModel2() { IntReadWrite = 3 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "foo", ModelState = new ModelStateDictionary() { { "blah", new ModelState() } }, ValueProvider = new SimpleValueProvider() { { "foo.IntReadWrite", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(3, bc.Model); Assert.Equal(typeof(int), bc.ModelType); Assert.Equal("foo.IntReadWrite", bc.ModelName); Assert.Equal(new ModelBindingContext().PropertyFilter, bc.PropertyFilter); Assert.Equal(bindingContext.ModelState, bc.ModelState); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return 4; }); PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWrite"]; DefaultModelBinderHelper helper = new DefaultModelBinderHelper() { Binders = new ModelBinderDictionary() { { typeof(int), mockInnerBinder.Object } } }; // Act helper.PublicBindProperty(controllerContext, bindingContext, pd); // Assert Assert.Equal(4, model.IntReadWrite); } // BindSimpleModel tests [Fact] public void BindSimpleModelCanReturnArrayTypes() { // Arrange ValueProviderResult result = new ValueProviderResult(42, null, null); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int[])), ModelName = "foo", }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object returnedValue = binder.BindSimpleModel(null, bindingContext, result); // Assert var returnedValueAsIntArray = Assert.IsType(returnedValue); Assert.Single(returnedValueAsIntArray); Assert.Equal(42, returnedValueAsIntArray[0]); } [Fact] public void BindSimpleModelCanReturnCollectionTypes() { // Arrange ValueProviderResult result = new ValueProviderResult(new string[] { "42", "82" }, null, null); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IEnumerable)), ModelName = "foo", }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object returnedValue = binder.BindSimpleModel(null, bindingContext, result); // Assert var returnedValueAsList = Assert.IsAssignableFrom>(returnedValue).ToList(); Assert.Equal(2, returnedValueAsList.Count); Assert.Equal(42, returnedValueAsList[0]); Assert.Equal(82, returnedValueAsList[1]); } [Fact] public void BindSimpleModelCanReturnElementalTypes() { // Arrange ValueProviderResult result = new ValueProviderResult("42", null, null); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int)), ModelName = "foo", }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object returnedValue = binder.BindSimpleModel(null, bindingContext, result); // Assert Assert.Equal(42, returnedValue); } [Fact] public void BindSimpleModelCanReturnStrings() { // Arrange ValueProviderResult result = new ValueProviderResult(new object[] { "42" }, null, null); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(string)), ModelName = "foo", }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object returnedValue = binder.BindSimpleModel(null, bindingContext, result); // Assert Assert.Equal("42", returnedValue); } [Fact] public void BindSimpleModelChecksValueProviderResultRawValueType() { // Arrange ValueProviderResult result = new ValueProviderResult(new MemoryStream(), null, null); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Stream)), ModelName = "foo", }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object returnedValue = binder.BindSimpleModel(null, bindingContext, result); // Assert Assert.Equal(result, bindingContext.ModelState["foo"].Value); Assert.Same(result.RawValue, returnedValue); } [Fact] public void BindSimpleModelPropagatesErrorsOnFailure() { // Arrange ValueProviderResult result = new ValueProviderResult("invalid", null, null); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int)), ModelName = "foo", }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object returnedValue = binder.BindSimpleModel(null, bindingContext, result); // Assert Assert.False(bindingContext.ModelState.IsValidField("foo")); Assert.IsType(bindingContext.ModelState["foo"].Errors[0].Exception); Assert.Equal("The parameter conversion from type 'System.String' to type 'System.Int32' failed. See the inner exception for more information.", bindingContext.ModelState["foo"].Errors[0].Exception.Message); Assert.Null(returnedValue); } [Fact] public void CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass() { // Arrange ModelBindingContext originalBindingContext = new ModelBindingContext() { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass_Model)), ModelName = "someName", ValueProvider = new SimpleValueProvider() }; DefaultModelBinder binder = new DefaultModelBinder(); // Act ModelBindingContext newBindingContext = binder.CreateComplexElementalModelBindingContext(new ControllerContext(), originalBindingContext, null); // Assert Assert.True(newBindingContext.PropertyFilter("foo")); Assert.False(newBindingContext.PropertyFilter("bar")); } [MetadataType(typeof(CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass_Model_BuddyClass))] private class CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass_Model { [Bind(Include = "foo")] private class CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass_Model_BuddyClass { } } [Fact] public void CreateInstanceCreatesModelInstance() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act object modelObj = helper.PublicCreateModel(null, null, typeof(Guid)); // Assert Assert.Equal(Guid.Empty, modelObj); } [Fact] public void CreateInstanceCreatesModelInstanceForGenericICollection() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act object modelObj = helper.PublicCreateModel(null, null, typeof(ICollection)); // Assert Assert.IsAssignableFrom>(modelObj); } [Fact] public void CreateInstanceCreatesModelInstanceForGenericIDictionary() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act object modelObj = helper.PublicCreateModel(null, null, typeof(IDictionary)); // Assert Assert.IsAssignableFrom>(modelObj); } [Fact] public void CreateInstanceCreatesModelInstanceForGenericIEnumerable() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act object modelObj = helper.PublicCreateModel(null, null, typeof(IEnumerable)); // Assert Assert.IsAssignableFrom>(modelObj); } [Fact] public void CreateInstanceCreatesModelInstanceForGenericIList() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act object modelObj = helper.PublicCreateModel(null, null, typeof(IList)); // Assert Assert.IsAssignableFrom>(modelObj); } [Fact] public void CreateSubIndexNameReturnsPrefixPlusIndex() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act string newName = helper.PublicCreateSubIndexName("somePrefix", 2); // Assert Assert.Equal("somePrefix[2]", newName); } [Fact] public void CreateSubPropertyNameReturnsPrefixPlusPropertyName() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act string newName = helper.PublicCreateSubPropertyName("somePrefix", "someProperty"); // Assert Assert.Equal("somePrefix.someProperty", newName); } [Fact] public void CreateSubPropertyNameReturnsPropertyNameIfPrefixIsEmpty() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act string newName = helper.PublicCreateSubPropertyName(String.Empty, "someProperty"); // Assert Assert.Equal("someProperty", newName); } [Fact] public void CreateSubPropertyNameReturnsPropertyNameIfPrefixIsNull() { // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act string newName = helper.PublicCreateSubPropertyName(null, "someProperty"); // Assert Assert.Equal("someProperty", newName); } [Fact] public void GetFilteredModelPropertiesFiltersNonUpdateableProperties() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(PropertyTestingModel)), PropertyFilter = new BindAttribute() { Exclude = "Blacklisted" }.IsPropertyAllowed }; DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act PropertyDescriptorCollection properties = new PropertyDescriptorCollection(helper.PublicGetFilteredModelProperties(null, bindingContext).ToArray()); // Assert Assert.NotNull(properties["StringReadWrite"]); Assert.Null(properties["StringReadOnly"]); Assert.NotNull(properties["IntReadWrite"]); Assert.Null(properties["IntReadOnly"]); Assert.NotNull(properties["ArrayReadWrite"]); Assert.Null(properties["ArrayReadOnly"]); Assert.NotNull(properties["AddressReadWrite"]); Assert.NotNull(properties["AddressReadOnly"]); Assert.NotNull(properties["Whitelisted"]); Assert.Null(properties["Blacklisted"]); Assert.Equal(6, properties.Count); } [Fact] public void GetModelPropertiesReturnsUnfilteredPropertyList() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(PropertyTestingModel)), PropertyFilter = new BindAttribute() { Exclude = "Blacklisted" }.IsPropertyAllowed }; DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act PropertyDescriptorCollection properties = helper.PublicGetModelProperties(null, bindingContext); // Assert Assert.NotNull(properties["StringReadWrite"]); Assert.NotNull(properties["StringReadOnly"]); Assert.NotNull(properties["IntReadWrite"]); Assert.NotNull(properties["IntReadOnly"]); Assert.NotNull(properties["ArrayReadWrite"]); Assert.NotNull(properties["ArrayReadOnly"]); Assert.NotNull(properties["AddressReadWrite"]); Assert.NotNull(properties["AddressReadOnly"]); Assert.NotNull(properties["Whitelisted"]); Assert.NotNull(properties["Blacklisted"]); Assert.Equal(10, properties.Count); } [Fact] public void IsModelValidWithNullBindingContextThrows() { // Act & Assert Assert.ThrowsArgumentNull( () => DefaultModelBinderHelper.PublicIsModelValid(null), "bindingContext"); } [Fact] public void IsModelValidReturnsModelStateIsValidWhenModelNameIsEmpty() { // Arrange ModelBindingContext contextWithNoErrors = new ModelBindingContext { ModelName = "" }; ModelBindingContext contextWithErrors = new ModelBindingContext { ModelName = "" }; contextWithErrors.ModelState.AddModelError("foo", "bar"); // Act & Assert Assert.True(DefaultModelBinderHelper.PublicIsModelValid(contextWithNoErrors)); Assert.False(DefaultModelBinderHelper.PublicIsModelValid(contextWithErrors)); } [Fact] public void IsModelValidReturnsValidityOfSubModelStateWhenModelNameIsNotEmpty() { // Arrange ModelBindingContext contextWithNoErrors = new ModelBindingContext { ModelName = "foo" }; ModelBindingContext contextWithErrors = new ModelBindingContext { ModelName = "foo" }; contextWithErrors.ModelState.AddModelError("foo.bar", "baz"); ModelBindingContext contextWithUnrelatedErrors = new ModelBindingContext { ModelName = "foo" }; contextWithUnrelatedErrors.ModelState.AddModelError("biff", "baz"); // Act & Assert Assert.True(DefaultModelBinderHelper.PublicIsModelValid(contextWithNoErrors)); Assert.False(DefaultModelBinderHelper.PublicIsModelValid(contextWithErrors)); Assert.True(DefaultModelBinderHelper.PublicIsModelValid(contextWithUnrelatedErrors)); } [Fact] public void OnModelUpdatingReturnsTrue() { // By default, this method does nothing, so we just want to make sure it returns true // Arrange DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act bool returned = helper.PublicOnModelUpdating(null, null); // Arrange Assert.True(returned); } // OnModelUpdated tests [Fact] public void OnModelUpdatedCalledWhenOnModelUpdatingReturnsTrue() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new ModelWithoutBindAttribute(), typeof(ModelWithoutBindAttribute)), ModelName = "", ValueProvider = new SimpleValueProvider() }; Mock binder = new Mock { CallBase = true }; binder.Protected().Setup("OnModelUpdating", ItExpr.IsAny(), ItExpr.IsAny()) .Returns(true); binder.Protected().Setup("OnModelUpdated", ItExpr.IsAny(), ItExpr.IsAny()) .Verifiable(); // Act binder.Object.BindModel(new ControllerContext(), bindingContext); // Assert binder.Verify(); } [Fact] public void OnModelUpdatedNotCalledWhenOnModelUpdatingReturnsFalse() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new ModelWithoutBindAttribute(), typeof(ModelWithoutBindAttribute)), ModelName = "", ValueProvider = new SimpleValueProvider() }; Mock binder = new Mock { CallBase = true }; binder.Protected().Setup("OnModelUpdating", ItExpr.IsAny(), ItExpr.IsAny()) .Returns(false); // Act binder.Object.BindModel(new ControllerContext(), bindingContext); // Assert binder.Verify(); binder.Protected().Verify("OnModelUpdated", Times.Never(), ItExpr.IsAny(), ItExpr.IsAny()); } [Fact] public void OnModelUpdatedDoesntAddNewMessagesWhenMessagesAlreadyExist() { // Arrange var binder = new TestableDefaultModelBinder(); binder.Context.ModelState.AddModelError(BASE_MODEL_NAME + ".NonNullableStringWithAttribute", "Some pre-existing error"); // Act binder.OnModelUpdated(); // Assert var modelState = binder.Context.ModelState[BASE_MODEL_NAME + ".NonNullableStringWithAttribute"]; var error = Assert.Single(modelState.Errors); Assert.Equal("Some pre-existing error", error.ErrorMessage); } [Fact] public void OnPropertyValidatingNotCalledOnPropertiesWithErrors() { // Arrange ModelWithoutBindAttribute model = new ModelWithoutBindAttribute(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "", ValueProvider = new SimpleValueProvider() { { "foo", "foo" } }, }; bindingContext.ModelState.AddModelError("foo", "Pre-existing error"); Mock binder = new Mock { CallBase = true }; // Act binder.Object.BindModel(new ControllerContext(), bindingContext); // Assert binder.Verify(); binder.Protected().Verify("OnPropertyValidating", Times.Never(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()); } public class ExtraValueModel { public int RequiredValue { get; set; } } [Fact] public void ExtraValueRequiredMessageNotAddedForAlreadyInvalidProperty() { // Arrange DefaultModelBinder binder = new DefaultModelBinder(); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(MyModel)), ModelName = "theModel", ValueProvider = new SimpleValueProvider() }; bindingContext.ModelState.AddModelError("theModel.ReadWriteProperty", "Existing Error Message"); // Act binder.BindModel(new ControllerContext(), bindingContext); // Assert ModelState modelState = bindingContext.ModelState["theModel.ReadWriteProperty"]; var error = Assert.Single(modelState.Errors); Assert.Equal("Existing Error Message", error.ErrorMessage); } [Fact] public void OnPropertyValidatingReturnsTrueOnSuccess() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(MyModel)), ModelName = "theModel" }; PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(MyModel))["ReadWriteProperty"]; DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); bindingContext.PropertyMetadata["ReadWriteProperty"].Model = 42; // Act bool returned = helper.PublicOnPropertyValidating(new ControllerContext(), bindingContext, property, 42); // Assert Assert.True(returned); Assert.Empty(bindingContext.ModelState); } [Fact] public void UpdateCollectionCreatesDefaultEntriesForInvalidElements() { // Arrange List model = new List() { 4, 5, 6, 7, 8 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "foo", ValueProvider = new SimpleValueProvider() { { "foo[0]", null }, { "foo[1]", null }, { "foo[2]", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { int fooIdx = Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture); return (fooIdx == 1) ? (object)null : fooIdx; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(int), mockInnerBinder.Object } } }; // Act object updatedModel = binder.UpdateCollection(null, bindingContext, typeof(int)); // Assert Assert.Equal(3, model.Count); Assert.Equal(false, bindingContext.ModelState.IsValidField("foo[1]")); Assert.Equal("A value is required.", bindingContext.ModelState["foo[1]"].Errors[0].ErrorMessage); Assert.Equal(0, model[0]); Assert.Equal(0, model[1]); Assert.Equal(2, model[2]); } [Fact] public void UpdateCollectionReturnsModifiedCollectionOnSuccess_ExplicitIndex() { // Arrange ControllerContext controllerContext = new Mock().Object; List model = new List() { 4, 5, 6, 7, 8 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "foo", PropertyFilter = _ => false, ValueProvider = new SimpleValueProvider() { { "foo.index", new string[] { "alpha", "bravo", "charlie" } }, // 'bravo' will be skipped { "foo[alpha]", "10" }, { "foo[charlie]", "30" } } }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.UpdateCollection(controllerContext, bindingContext, typeof(int)); // Assert Assert.Same(model, updatedModel); Assert.Equal(2, model.Count); Assert.Equal(10, model[0]); Assert.Equal(30, model[1]); } [Fact] public void UpdateCollectionReturnsModifiedCollectionOnSuccess_ZeroBased() { // Arrange ControllerContext controllerContext = new Mock().Object; List model = new List() { 4, 5, 6, 7, 8 }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "foo", PropertyFilter = _ => false, ValueProvider = new SimpleValueProvider() { { "foo[0]", null }, { "foo[1]", null }, { "foo[2]", null } } }; Mock mockInnerBinder = new Mock(); mockInnerBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(typeof(int), bc.ModelType); Assert.Equal(bindingContext.ModelState, bc.ModelState); Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture); }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(int), mockInnerBinder.Object } } }; // Act object updatedModel = binder.UpdateCollection(controllerContext, bindingContext, typeof(int)); // Assert Assert.Same(model, updatedModel); Assert.Equal(3, model.Count); Assert.Equal(0, model[0]); Assert.Equal(1, model[1]); Assert.Equal(2, model[2]); } [Fact] public void UpdateCollectionReturnsNullIfZeroIndexNotFound() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ValueProvider = new SimpleValueProvider() }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.UpdateCollection(null, bindingContext, typeof(object)); // Assert Assert.Null(updatedModel); } [Fact] public void UpdateDictionaryCreatesDefaultEntriesForInvalidValues() { // Arrange Dictionary model = new Dictionary { { "one", 1 }, { "two", 2 } }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "foo", ValueProvider = new SimpleValueProvider() { { "foo[0].key", null }, { "foo[0].value", null }, { "foo[1].key", null }, { "foo[1].value", null }, { "foo[2].key", null }, { "foo[2].value", null } } }; Mock mockStringBinder = new Mock(); mockStringBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value"; }); Mock mockIntBinder = new Mock(); mockIntBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { int fooIdx = Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture); return (fooIdx == 1) ? (object)null : fooIdx; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(string), mockStringBinder.Object }, { typeof(int), mockIntBinder.Object } } }; // Act object updatedModel = binder.UpdateDictionary(null, bindingContext, typeof(string), typeof(int)); // Assert Assert.Equal(3, model.Count); Assert.False(bindingContext.ModelState.IsValidField("foo[1].value")); Assert.Equal("A value is required.", bindingContext.ModelState["foo[1].value"].Errors[0].ErrorMessage); Assert.Equal(0, model["10Value"]); Assert.Equal(0, model["11Value"]); Assert.Equal(2, model["12Value"]); } [Fact] public void UpdateDictionaryReturnsModifiedDictionaryOnSuccess_ExplicitIndex() { // Arrange ControllerContext controllerContext = new Mock().Object; Dictionary model = new Dictionary { { 1, "one" }, { 2, "two" } }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "foo", PropertyFilter = _ => false, ValueProvider = new SimpleValueProvider() { { "foo.index", new string[] { "alpha", "bravo", "charlie" } }, // 'bravo' will be skipped { "foo[alpha].key", "10" }, { "foo[alpha].value", "ten" }, { "foo[charlie].key", "30" }, { "foo[charlie].value", "thirty" } } }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.UpdateDictionary(controllerContext, bindingContext, typeof(int), typeof(string)); // Assert Assert.Same(model, updatedModel); Assert.Equal(2, model.Count); Assert.Equal("ten", model[10]); Assert.Equal("thirty", model[30]); } [Fact] public void UpdateDictionaryReturnsModifiedDictionaryOnSuccess_ZeroBased() { // Arrange ControllerContext controllerContext = new Mock().Object; Dictionary model = new Dictionary { { 1, "one" }, { 2, "two" } }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "foo", PropertyFilter = _ => false, ValueProvider = new SimpleValueProvider() { { "foo[0].key", null }, { "foo[0].value", null }, { "foo[1].key", null }, { "foo[1].value", null }, { "foo[2].key", null }, { "foo[2].value", null } } }; Mock mockIntBinder = new Mock(); mockIntBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(typeof(int), bc.ModelType); Assert.Equal(bindingContext.ModelState, bc.ModelState); Assert.Equal(new ModelBindingContext().PropertyFilter, bc.PropertyFilter); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10; }); Mock mockStringBinder = new Mock(); mockStringBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { Assert.Equal(controllerContext, cc); Assert.Equal(typeof(string), bc.ModelType); Assert.Equal(bindingContext.ModelState, bc.ModelState); Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter); Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider); return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value"; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(int), mockIntBinder.Object }, { typeof(string), mockStringBinder.Object } } }; // Act object updatedModel = binder.UpdateDictionary(controllerContext, bindingContext, typeof(int), typeof(string)); // Assert Assert.Same(model, updatedModel); Assert.Equal(3, model.Count); Assert.Equal("10Value", model[10]); Assert.Equal("11Value", model[11]); Assert.Equal("12Value", model[12]); } [Fact] public void UpdateDictionaryReturnsNullIfNoValidElementsFound() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ValueProvider = new SimpleValueProvider() }; DefaultModelBinder binder = new DefaultModelBinder(); // Act object updatedModel = binder.UpdateDictionary(null, bindingContext, typeof(object), typeof(object)); // Assert Assert.Null(updatedModel); } [Fact] public void UpdateDictionarySkipsInvalidKeys() { // Arrange Dictionary model = new Dictionary { { 1, "one" }, { 2, "two" } }; ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "foo", ValueProvider = new SimpleValueProvider() { { "foo[0].key", null }, { "foo[0].value", null }, { "foo[1].key", null }, { "foo[1].value", null }, { "foo[2].key", null }, { "foo[2].value", null } } }; Mock mockIntBinder = new Mock(); mockIntBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { int fooIdx = Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture); return (fooIdx == 1) ? (object)null : fooIdx; }); Mock mockStringBinder = new Mock(); mockStringBinder .Setup(b => b.BindModel(It.IsAny(), It.IsAny())) .Returns( delegate(ControllerContext cc, ModelBindingContext bc) { return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value"; }); DefaultModelBinder binder = new DefaultModelBinder() { Binders = new ModelBinderDictionary() { { typeof(int), mockIntBinder.Object }, { typeof(string), mockStringBinder.Object } } }; // Act object updatedModel = binder.UpdateDictionary(null, bindingContext, typeof(int), typeof(string)); // Assert Assert.Equal(2, model.Count); Assert.False(bindingContext.ModelState.IsValidField("foo[1].key")); Assert.Equal("A value is required.", bindingContext.ModelState["foo[1].key"].Errors[0].ErrorMessage); Assert.Equal("10Value", model[0]); Assert.Equal("12Value", model[2]); } [ModelBinder(typeof(DefaultModelBinder))] private class MyModel { public int ReadOnlyProperty { get { return 4; } } public int ReadWriteProperty { get; set; } public int ReadWriteProperty2 { get; set; } } private class MyClassWithoutConverter { } [Bind(Exclude = "Alpha,Echo")] private class MyOtherModel { public string Alpha { get; set; } public string Bravo { get; set; } public string Charlie { get; set; } public string Delta { get; set; } public string Echo { get; set; } public string Foxtrot { get; set; } } public class Customer { private Address _address = new Address(); public Address Address { get { return _address; } } } public class Address { public string Street { get; set; } public string Zip { get; set; } } public class IntegerContainer { public int Integer { get; set; } public int? NullableInteger { get; set; } } [TypeConverter(typeof(CultureAwareConverter))] public class StringContainer { public StringContainer(string value) { Value = value; } public string Value { get; private set; } } private class CultureAwareConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return (sourceType == typeof(string)); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return (destinationType == typeof(string)); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { string stringValue = value as string; if (stringValue == null || stringValue.Length < 3) { throw new Exception("Value must have at least 3 characters."); } return new StringContainer(AppendCultureName(stringValue, culture)); } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { StringContainer container = value as StringContainer; if (container.Value == null || container.Value.Length < 3) { throw new Exception("Value must have at least 3 characters."); } return AppendCultureName(container.Value, culture); } private static string AppendCultureName(string value, CultureInfo culture) { string cultureName = (!String.IsNullOrEmpty(culture.Name)) ? culture.Name : culture.ThreeLetterWindowsLanguageName; return value + " (" + cultureName + ")"; } } [ModelBinder(typeof(MyStringModelBinder))] private class MyStringModel { public string Value { get; set; } } private class MyStringModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { MyStringModel castModel = bindingContext.Model as MyStringModel; if (castModel != null) { castModel.Value += "_Update"; } else { castModel = new MyStringModel() { Value = bindingContext.ModelName + "_Create" }; } return castModel; } } private class CustomUnvalidatedValueProvider : IUnvalidatedValueProvider { public ValueProviderResult GetValue(string key, bool skipValidation) { string newValue = key + ((skipValidation) ? "Unvalidated" : "Validated"); return new ValueProviderResult(newValue, newValue, null); } public bool ContainsPrefix(string prefix) { return true; } public ValueProviderResult GetValue(string key) { return GetValue(key, skipValidation: false); } } private class SimpleController : Controller { } public class DefaultModelBinderHelper : DefaultModelBinder { public virtual void PublicBindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property) { base.BindProperty(controllerContext, bindingContext, property); } protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property) { PublicBindProperty(controllerContext, bindingContext, property); } public virtual object PublicCreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { return base.CreateModel(controllerContext, bindingContext, modelType); } protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { return PublicCreateModel(controllerContext, bindingContext, modelType); } public virtual IEnumerable PublicGetFilteredModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { return base.GetFilteredModelProperties(controllerContext, bindingContext); } public virtual PropertyDescriptorCollection PublicGetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { return base.GetModelProperties(controllerContext, bindingContext); } protected override PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { return PublicGetModelProperties(controllerContext, bindingContext); } public string PublicCreateSubIndexName(string prefix, int indexName) { return CreateSubIndexName(prefix, indexName); } public string PublicCreateSubPropertyName(string prefix, string propertyName) { return CreateSubPropertyName(prefix, propertyName); } public static bool PublicIsModelValid(ModelBindingContext bindingContext) { return IsModelValid(bindingContext); } public virtual bool PublicOnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext) { return base.OnModelUpdating(controllerContext, bindingContext); } protected override bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext) { return PublicOnModelUpdating(controllerContext, bindingContext); } public virtual void PublicOnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { base.OnModelUpdated(controllerContext, bindingContext); } protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { PublicOnModelUpdated(controllerContext, bindingContext); } public virtual bool PublicOnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value) { return base.OnPropertyValidating(controllerContext, bindingContext, property, value); } protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value) { return PublicOnPropertyValidating(controllerContext, bindingContext, property, value); } public virtual void PublicOnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value) { base.OnPropertyValidated(controllerContext, bindingContext, property, value); } protected override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value) { PublicOnPropertyValidated(controllerContext, bindingContext, property, value); } public virtual void PublicSetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value) { base.SetProperty(controllerContext, bindingContext, property, value); } protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value) { PublicSetProperty(controllerContext, bindingContext, property, value); } } private class MyModel2 { private int _intReadWriteNonNegative; public int IntReadOnly { get { return 4; } } public int IntReadWrite { get; set; } public int IntReadWriteNonNegative { get { return _intReadWriteNonNegative; } set { if (value < 0) { throw new ArgumentOutOfRangeException("value", "Value must be non-negative."); } _intReadWriteNonNegative = value; } } public int? NullableIntReadWrite { get; set; } } [Bind(Exclude = "Foo")] private class ModelWithBindAttribute : ModelWithoutBindAttribute { } private class ModelWithoutBindAttribute { public string Foo { get; set; } public string Bar { get; set; } public string Baz { get; set; } } private class PropertyTestingModel { public string StringReadWrite { get; set; } public string StringReadOnly { get; private set; } public int IntReadWrite { get; set; } public int IntReadOnly { get; private set; } public object[] ArrayReadWrite { get; set; } public object[] ArrayReadOnly { get; private set; } public Address AddressReadWrite { get; set; } public Address AddressReadOnly { get; private set; } public string Whitelisted { get; set; } public string Blacklisted { get; set; } } // -------------------------------------------------------------------------------- // DataAnnotations tests public const string BASE_MODEL_NAME = "BaseModelName"; // GetModelProperties tests [MetadataType(typeof(Metadata))] class GetModelPropertiesModel { [Required] public int LocalAttributes { get; set; } public int MetadataAttributes { get; set; } [Required] public int MixedAttributes { get; set; } class Metadata { [Range(10, 100)] public int MetadataAttributes { get; set; } [Range(10, 100)] public int MixedAttributes { get; set; } } } [Fact] public void GetModelPropertiesWithLocalAttributes() { // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); // Act PropertyDescriptor property = modelBinder.GetModelProperties() .Cast() .Where(pd => pd.Name == "LocalAttributes") .Single(); // Assert Assert.True(property.Attributes.Cast().Any(a => a is RequiredAttribute)); } [Fact] public void GetModelPropertiesWithMetadataAttributes() { // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); // Act PropertyDescriptor property = modelBinder.GetModelProperties() .Cast() .Where(pd => pd.Name == "MetadataAttributes") .Single(); // Assert Assert.True(property.Attributes.Cast().Any(a => a is RangeAttribute)); } [Fact] public void GetModelPropertiesWithMixedAttributes() { // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); // Act PropertyDescriptor property = modelBinder.GetModelProperties() .Cast() .Where(pd => pd.Name == "MixedAttributes") .Single(); // Assert Assert.True(property.Attributes.Cast().Any(a => a is RequiredAttribute)); Assert.True(property.Attributes.Cast().Any(a => a is RangeAttribute)); } // GetPropertyValue tests class GetPropertyValueModel { public string NoAttribute { get; set; } [DisplayFormat(ConvertEmptyStringToNull = false)] public string AttributeWithoutConversion { get; set; } [DisplayFormat(ConvertEmptyStringToNull = true)] public string AttributeWithConversion { get; set; } } [Fact] public void GetPropertyValueWithNoAttributeConvertsEmptyStringToNull() { // Arrange TestableDefaultModelBinder binder = new TestableDefaultModelBinder(); binder.Context.ModelMetadata = binder.Context.PropertyMetadata["NoAttribute"]; // Act object result = binder.GetPropertyValue("NoAttribute", String.Empty); // Assert Assert.Null(result); } [Fact] public void GetPropertyValueWithFalseAttributeDoesNotConvertEmptyStringToNull() { // Arrange TestableDefaultModelBinder binder = new TestableDefaultModelBinder(); binder.Context.ModelMetadata = binder.Context.PropertyMetadata["AttributeWithoutConversion"]; // Act object result = binder.GetPropertyValue("AttributeWithoutConversion", String.Empty); // Assert Assert.Equal(String.Empty, result); } [Fact] public void GetPropertyValueWithTrueAttributeConvertsEmptyStringToNull() { // Arrange TestableDefaultModelBinder binder = new TestableDefaultModelBinder(); binder.Context.ModelMetadata = binder.Context.PropertyMetadata["AttributeWithConversion"]; // Act object result = binder.GetPropertyValue("AttributeWithConversion", String.Empty); // Assert Assert.Null(result); } // OnModelUpdated tests [Fact] public void OnModelUpdatedPassesNullContainerToValidate() { Mock provider = null; try { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(object)); ControllerContext context = new ControllerContext(); Mock validator = new Mock(metadata, context); provider = new Mock(); provider.Setup(p => p.GetValidators(It.IsAny(), It.IsAny())) .Returns(new ModelValidator[] { validator.Object }); ModelValidatorProviders.Providers.Add(provider.Object); object model = new object(); TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(model); // Act modelBinder.OnModelUpdated(); // Assert validator.Verify(v => v.Validate(null)); } finally { if (provider != null) { ModelValidatorProviders.Providers.Remove(provider.Object); } } } public class MinMaxValidationAttribute : ValidationAttribute { public MinMaxValidationAttribute() : base("Minimum must be less than or equal to Maximum") { } public override bool IsValid(object value) { OnModelUpdatedModelMultipleParameters model = (OnModelUpdatedModelMultipleParameters)value; return model.Minimum <= model.Maximum; } } [MinMaxValidation] public class OnModelUpdatedModelMultipleParameters { public int Minimum { get; set; } public int Maximum { get; set; } } [Fact] public void OnModelUpdatedWithValidationAttributeMultipleParameters() { // Arrange OnModelUpdatedModelMultipleParameters model = new OnModelUpdatedModelMultipleParameters { Minimum = 250, Maximum = 100 }; TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(model); // Act modelBinder.OnModelUpdated(); // Assert Assert.Single(modelBinder.ModelState); ModelState stateModel = modelBinder.ModelState[BASE_MODEL_NAME]; Assert.NotNull(stateModel); Assert.Equal("Minimum must be less than or equal to Maximum", stateModel.Errors.Single().ErrorMessage); } [Fact] public void OnModelUpdatedWithInvalidPropertyValidationWillNotRunEntityLevelValidation() { // Arrange OnModelUpdatedModelMultipleParameters model = new OnModelUpdatedModelMultipleParameters { Minimum = 250, Maximum = 100 }; TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(model); modelBinder.ModelState.AddModelError(BASE_MODEL_NAME + ".Minimum", "The minimum value was invalid."); // Act modelBinder.OnModelUpdated(); // Assert Assert.Null(modelBinder.ModelState[BASE_MODEL_NAME]); } public class AlwaysInvalidAttribute : ValidationAttribute { public AlwaysInvalidAttribute() { } public AlwaysInvalidAttribute(string message) : base(message) { } public override bool IsValid(object value) { return false; } } [AlwaysInvalid("The object just isn't right")] public class OnModelUpdatedModelNoParameters { } [Fact] public void OnModelUpdatedWithValidationAttributeNoParameters() { // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); // Act modelBinder.OnModelUpdated(); // Assert Assert.Single(modelBinder.ModelState); ModelState stateModel = modelBinder.ModelState[BASE_MODEL_NAME]; Assert.NotNull(stateModel); Assert.Equal("The object just isn't right", stateModel.Errors.Single().ErrorMessage); } [AlwaysInvalid] public class OnModelUpdatedModelNoValidationResult { } [Fact] public void OnModelUpdatedWithValidationAttributeNoValidationMessage() { // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); // Act modelBinder.OnModelUpdated(); // Assert Assert.Single(modelBinder.ModelState); ModelState stateModel = modelBinder.ModelState[BASE_MODEL_NAME]; Assert.NotNull(stateModel); Assert.Equal("The field OnModelUpdatedModelNoValidationResult is invalid.", stateModel.Errors.Single().ErrorMessage); } [Fact] public void OnModelUpdatedDoesNotPlaceErrorMessagesInModelStateWhenSubPropertiesHaveErrors() { // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); modelBinder.ModelState.AddModelError("Foo.Bar", "Foo.Bar is invalid"); modelBinder.Context.ModelName = "Foo"; // Act modelBinder.OnModelUpdated(); // Assert Assert.Null(modelBinder.ModelState["Foo"]); } // OnPropertyValidating tests class OnPropertyValidatingModel { public string NotValidated { get; set; } [Range(10, 65)] public int RangedInteger { get; set; } [Required(ErrorMessage = "Custom Required Message")] public int RequiredInteger { get; set; } } [Fact] public void OnPropertyValidatingWithoutValidationAttribute() { // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); // Act modelBinder.OnPropertyValidating("NotValidated", 42); // Assert Assert.Empty(modelBinder.ModelState); } [Fact] public void OnPropertyValidatingWithValidationAttributePassing() { // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); modelBinder.Context.PropertyMetadata["RangedInteger"].Model = 42; // Act bool result = modelBinder.OnPropertyValidating("RangedInteger", 42); // Assert Assert.True(result); Assert.Empty(modelBinder.ModelState); } // SetProperty tests [Fact] public void SetPropertyWithRequiredOnValueTypeOnlyResultsInSingleMessage() { // DDB #225150 // Arrange TestableDefaultModelBinder modelBinder = new TestableDefaultModelBinder(); modelBinder.Context.ModelMetadata.Model = new OnPropertyValidatingModel(); // Act modelBinder.SetProperty("RequiredInteger", null); // Assert ModelState modelState = modelBinder.ModelState[BASE_MODEL_NAME + ".RequiredInteger"]; ModelError modelStateError = modelState.Errors.Single(); Assert.Equal("Custom Required Message", modelStateError.ErrorMessage); } [Fact] public void SetPropertyAddsDefaultMessageForNonBindableNonNullableValueTypes() { DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; try { // Arrange TestableDefaultModelBinder> modelBinder = new TestableDefaultModelBinder>(); modelBinder.Context.ModelMetadata.Model = null; // Act modelBinder.SetProperty("Count", null); // Assert ModelState modelState = modelBinder.ModelState[BASE_MODEL_NAME + ".Count"]; ModelError modelStateError = modelState.Errors.Single(); Assert.Equal("A value is required.", modelStateError.ErrorMessage); } finally { DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = true; } } [Fact] public void SetPropertyCreatesValueRequiredErrorIfNecessary() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new MyModel(), typeof(MyModel)), ModelName = "theModel", }; PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(MyModel))["ReadWriteProperty"]; DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act helper.PublicSetProperty(new ControllerContext(), bindingContext, property, null); // Assert Assert.Equal("The ReadWriteProperty field is required.", bindingContext.ModelState["theModel.ReadWriteProperty"].Errors[0].ErrorMessage); } [Fact] public void SetPropertyWithThrowingSetter() { // Arrange TestableDefaultModelBinder binder = new TestableDefaultModelBinder(); // Act binder.SetProperty("NonNullableString", null); // Assert ModelState modelState = binder.Context.ModelState[BASE_MODEL_NAME + ".NonNullableString"]; Assert.Single(modelState.Errors); Assert.IsType(modelState.Errors[0].Exception); } [Fact] public void SetPropertyWithNullValueAndThrowingSetterWithRequiredAttribute() { // DDB #227809 // Arrange TestableDefaultModelBinder binder = new TestableDefaultModelBinder(); // Act binder.SetProperty("NonNullableStringWithAttribute", null); // Assert ModelState modelState = binder.Context.ModelState[BASE_MODEL_NAME + ".NonNullableStringWithAttribute"]; var error = Assert.Single(modelState.Errors); Assert.Equal("My custom required message", error.ErrorMessage); } [Fact] public void SetPropertyDoesNothingIfPropertyIsReadOnly() { // Arrange MyModel model = new MyModel(); ModelBindingContext bindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()), ModelName = "theModel" }; PropertyDescriptor property = TypeDescriptor.GetProperties(model)["ReadOnlyProperty"]; DefaultModelBinderHelper helper = new DefaultModelBinderHelper(); // Act helper.PublicSetProperty(new ControllerContext(), bindingContext, property, 42); // Assert Assert.Empty(bindingContext.ModelState); } [Fact] public void SetPropertySuccess() { // Arrange TestableDefaultModelBinder binder = new TestableDefaultModelBinder(); // Act binder.SetProperty("NullableString", "The new value"); // Assert Assert.Equal("The new value", ((SetPropertyModel)binder.Context.Model).NullableString); Assert.Empty(binder.Context.ModelState); } class SetPropertyModel { public string NullableString { get; set; } public string NonNullableString { get { return null; } set { if (value == null) { throw new ArgumentNullException("value"); } } } [Required(ErrorMessage = "My custom required message")] public string NonNullableStringWithAttribute { get { return null; } set { if (value == null) { throw new ArgumentNullException("value"); } } } } // Helper methods static PropertyDescriptor GetProperty(string propertyName) { return TypeDescriptor.GetProperties(typeof(T)) .Cast() .Where(p => p.Name == propertyName) .Single(); } static ICustomTypeDescriptor GetType() { return TypeDescriptor.GetProvider(typeof(T)).GetTypeDescriptor(typeof(T)); } // Helper classes class TestableDefaultModelBinder : DefaultModelBinder where TModel : new() { public TestableDefaultModelBinder() : this(new TModel()) { } public TestableDefaultModelBinder(TModel model) { ModelState = new ModelStateDictionary(); Context = new ModelBindingContext(); Context.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(TModel)); Context.ModelName = BASE_MODEL_NAME; Context.ModelState = ModelState; } public ModelBindingContext Context { get; private set; } public ModelStateDictionary ModelState { get; set; } public void BindProperty(string propertyName) { base.BindProperty(new ControllerContext(), Context, GetProperty(propertyName)); } public PropertyDescriptorCollection GetModelProperties() { return base.GetModelProperties(new ControllerContext(), Context); } public object GetPropertyValue(string propertyName, object existingValue) { Mock mockModelBinder = new Mock(); mockModelBinder.Setup(b => b.BindModel(It.IsAny(), Context)).Returns(existingValue); return base.GetPropertyValue(new ControllerContext(), Context, GetProperty(propertyName), mockModelBinder.Object); } public bool OnPropertyValidating(string propertyName, object value) { return base.OnPropertyValidating(new ControllerContext(), Context, GetProperty(propertyName), value); } public void OnPropertyValidated(string propertyName, object value) { base.OnPropertyValidated(new ControllerContext(), Context, GetProperty(propertyName), value); } public void OnModelUpdated() { base.OnModelUpdated(new ControllerContext(), Context); } public void SetProperty(string propertyName, object value) { base.SetProperty(new ControllerContext(), Context, GetProperty(propertyName), value); } } private class CountryState { public string Name { get; set; } public IEnumerable States { get; set; } } } }