Files
linux-packaging-mono/external/aspnetwebstack/test/System.Web.Mvc.Test/Test/DefaultModelBinderTest.cs
Pat Tullmann 0cb742dafb binfmt-detector-cli: rewrite to support PE32+ binaries (#38)
Rewrite with hard-coded offsets into the PE file format to discern
if a binary is PE32 or PE32+, and then to determine if it contains
a "CLR Data Directory" entry that looks valid.

Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native
binaries, and a random assortment of garbage files.

Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
2023-10-16 20:16:47 +02:00

2907 lines
114 KiB
C#

// 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<ControllerContext>().Object;
MyModel model = new MyModel() { ReadWriteProperty = 3 };
ModelBindingContext bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
};
Mock<DefaultModelBinderHelper> mockHelper = new Mock<DefaultModelBinderHelper>() { CallBase = true };
mockHelper.Setup(b => b.PublicOnModelUpdating(controllerContext, It.IsAny<ModelBindingContext>())).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<ModelBindingContext>()), Times.Never());
mockHelper.Verify(b => b.PublicBindProperty(controllerContext, It.IsAny<ModelBindingContext>(), It.IsAny<PropertyDescriptor>()), Times.Never());
}
[Fact]
public void BindComplexModelCanBindArrays()
{
// Arrange
ControllerContext controllerContext = new Mock<ControllerContext>().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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<int[]>(newModel);
Assert.Equal(new[] { 0, 1, 2 }, newIntArray);
}
[Fact]
public void BindComplexModelCanBindCollections()
{
// Arrange
ControllerContext controllerContext = new Mock<ControllerContext>().Object;
ModelBindingContext bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IList<int>)),
ModelName = "foo",
PropertyFilter = _ => false,
ValueProvider = new SimpleValueProvider()
{
{ "foo[0]", null },
{ "foo[1]", null },
{ "foo[2]", null }
}
};
Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<IList<int>>(newModel);
Assert.Equal(new[] { 0, 1, 2 }, modelAsList.ToArray());
}
[Fact]
public void BindComplexModelCanBindDictionariesWithDotsNotation()
{
// Arrange
ControllerContext controllerContext = new Mock<ControllerContext>().Object;
ModelBindingContext bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary<string, CountryState>)),
ModelName = "countries",
PropertyFilter = _ => true,
ValueProvider = new DictionaryValueProvider<object>(new Dictionary<string, object>()
{
{ "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<IDictionary<string, CountryState>>(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<ControllerContext>().Object;
ModelBindingContext bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary<string, CountryState>)),
ModelName = "countries",
PropertyFilter = _ => true,
ValueProvider = new DictionaryValueProvider<object>(new Dictionary<string, object>()
{
{ "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<IDictionary<string, CountryState>>(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<ControllerContext>().Object;
ModelBindingContext bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary<string, CountryState>)),
ModelName = "countries",
PropertyFilter = _ => true,
ValueProvider = new DictionaryValueProvider<object>(new Dictionary<string, object>()
{
{ "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<IDictionary<string, CountryState>>(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<ControllerContext>().Object;
ModelBindingContext bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary<int, string>)),
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<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
mockStringBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<IDictionary<int, string>>(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<ControllerContext>().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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<ControllerContext>().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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<ControllerContext>().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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<ControllerContext>().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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<DefaultModelBinder> binder = new Mock<DefaultModelBinder> { CallBase = true };
binder.Protected().Setup<object>("GetPropertyValue",
ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>(),
ItExpr.IsAny<PropertyDescriptor>(), ItExpr.IsAny<IModelBinder>())
.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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<ControllerContext>().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<DefaultModelBinderHelper> mockHelper = new Mock<DefaultModelBinderHelper>() { 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<ControllerContext>().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<DefaultModelBinderHelper> mockHelper = new Mock<DefaultModelBinderHelper>() { 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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder.Setup(b => b.BindModel(new ControllerContext(), It.IsAny<ModelBindingContext>())).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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<ControllerContext>().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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<int[]>(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<int>)),
ModelName = "foo",
};
DefaultModelBinder binder = new DefaultModelBinder();
// Act
object returnedValue = binder.BindSimpleModel(null, bindingContext, result);
// Assert
var returnedValueAsList = Assert.IsAssignableFrom<IEnumerable<int>>(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<InvalidOperationException>(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<Guid>));
// Assert
Assert.IsAssignableFrom<ICollection<Guid>>(modelObj);
}
[Fact]
public void CreateInstanceCreatesModelInstanceForGenericIDictionary()
{
// Arrange
DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
// Act
object modelObj = helper.PublicCreateModel(null, null, typeof(IDictionary<string, Guid>));
// Assert
Assert.IsAssignableFrom<IDictionary<string, Guid>>(modelObj);
}
[Fact]
public void CreateInstanceCreatesModelInstanceForGenericIEnumerable()
{
// Arrange
DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
// Act
object modelObj = helper.PublicCreateModel(null, null, typeof(IEnumerable<Guid>));
// Assert
Assert.IsAssignableFrom<ICollection<Guid>>(modelObj);
}
[Fact]
public void CreateInstanceCreatesModelInstanceForGenericIList()
{
// Arrange
DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
// Act
object modelObj = helper.PublicCreateModel(null, null, typeof(IList<Guid>));
// Assert
Assert.IsAssignableFrom<IList<Guid>>(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<DefaultModelBinder> binder = new Mock<DefaultModelBinder> { CallBase = true };
binder.Protected().Setup<bool>("OnModelUpdating",
ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>())
.Returns(true);
binder.Protected().Setup("OnModelUpdated",
ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>())
.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<DefaultModelBinder> binder = new Mock<DefaultModelBinder> { CallBase = true };
binder.Protected().Setup<bool>("OnModelUpdating",
ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>())
.Returns(false);
// Act
binder.Object.BindModel(new ControllerContext(), bindingContext);
// Assert
binder.Verify();
binder.Protected().Verify("OnModelUpdated", Times.Never(),
ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>());
}
[Fact]
public void OnModelUpdatedDoesntAddNewMessagesWhenMessagesAlreadyExist()
{
// Arrange
var binder = new TestableDefaultModelBinder<SetPropertyModel>();
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<DefaultModelBinder> binder = new Mock<DefaultModelBinder> { CallBase = true };
// Act
binder.Object.BindModel(new ControllerContext(), bindingContext);
// Assert
binder.Verify();
binder.Protected().Verify("OnPropertyValidating", Times.Never(),
ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>(),
ItExpr.IsAny<PropertyDescriptor>(), ItExpr.IsAny<object>());
}
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<int> model = new List<int>() { 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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<ControllerContext>().Object;
List<int> model = new List<int>() { 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<ControllerContext>().Object;
List<int> model = new List<int>() { 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<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
mockInnerBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<string, int> model = new Dictionary<string, int>
{
{ "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<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
mockStringBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.Returns(
delegate(ControllerContext cc, ModelBindingContext bc) { return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value"; });
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<ControllerContext>().Object;
Dictionary<int, string> model = new Dictionary<int, string>
{
{ 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<ControllerContext>().Object;
Dictionary<int, string> model = new Dictionary<int, string>
{
{ 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<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
mockStringBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<int, string> model = new Dictionary<int, string>
{
{ 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<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.Returns(
delegate(ControllerContext cc, ModelBindingContext bc)
{
int fooIdx = Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture);
return (fooIdx == 1) ? (object)null : fooIdx;
});
Mock<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
mockStringBinder
.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
.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<PropertyDescriptor> 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<GetModelPropertiesModel> modelBinder = new TestableDefaultModelBinder<GetModelPropertiesModel>();
// Act
PropertyDescriptor property = modelBinder.GetModelProperties()
.Cast<PropertyDescriptor>()
.Where(pd => pd.Name == "LocalAttributes")
.Single();
// Assert
Assert.True(property.Attributes.Cast<Attribute>().Any(a => a is RequiredAttribute));
}
[Fact]
public void GetModelPropertiesWithMetadataAttributes()
{
// Arrange
TestableDefaultModelBinder<GetModelPropertiesModel> modelBinder = new TestableDefaultModelBinder<GetModelPropertiesModel>();
// Act
PropertyDescriptor property = modelBinder.GetModelProperties()
.Cast<PropertyDescriptor>()
.Where(pd => pd.Name == "MetadataAttributes")
.Single();
// Assert
Assert.True(property.Attributes.Cast<Attribute>().Any(a => a is RangeAttribute));
}
[Fact]
public void GetModelPropertiesWithMixedAttributes()
{
// Arrange
TestableDefaultModelBinder<GetModelPropertiesModel> modelBinder = new TestableDefaultModelBinder<GetModelPropertiesModel>();
// Act
PropertyDescriptor property = modelBinder.GetModelProperties()
.Cast<PropertyDescriptor>()
.Where(pd => pd.Name == "MixedAttributes")
.Single();
// Assert
Assert.True(property.Attributes.Cast<Attribute>().Any(a => a is RequiredAttribute));
Assert.True(property.Attributes.Cast<Attribute>().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<GetPropertyValueModel> binder = new TestableDefaultModelBinder<GetPropertyValueModel>();
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<GetPropertyValueModel> binder = new TestableDefaultModelBinder<GetPropertyValueModel>();
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<GetPropertyValueModel> binder = new TestableDefaultModelBinder<GetPropertyValueModel>();
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<ModelValidatorProvider> provider = null;
try
{
// Arrange
ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(object));
ControllerContext context = new ControllerContext();
Mock<ModelValidator> validator = new Mock<ModelValidator>(metadata, context);
provider = new Mock<ModelValidatorProvider>();
provider.Setup(p => p.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
.Returns(new ModelValidator[] { validator.Object });
ModelValidatorProviders.Providers.Add(provider.Object);
object model = new object();
TestableDefaultModelBinder<object> modelBinder = new TestableDefaultModelBinder<object>(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<OnModelUpdatedModelMultipleParameters> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelMultipleParameters>(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<OnModelUpdatedModelMultipleParameters> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelMultipleParameters>(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<OnModelUpdatedModelNoParameters> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelNoParameters>();
// 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<OnModelUpdatedModelNoValidationResult> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelNoValidationResult>();
// 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<OnModelUpdatedModelNoValidationResult> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelNoValidationResult>();
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<OnPropertyValidatingModel> modelBinder = new TestableDefaultModelBinder<OnPropertyValidatingModel>();
// Act
modelBinder.OnPropertyValidating("NotValidated", 42);
// Assert
Assert.Empty(modelBinder.ModelState);
}
[Fact]
public void OnPropertyValidatingWithValidationAttributePassing()
{
// Arrange
TestableDefaultModelBinder<OnPropertyValidatingModel> modelBinder = new TestableDefaultModelBinder<OnPropertyValidatingModel>();
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<OnPropertyValidatingModel> modelBinder = new TestableDefaultModelBinder<OnPropertyValidatingModel>();
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<List<String>> modelBinder = new TestableDefaultModelBinder<List<String>>();
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<SetPropertyModel> binder = new TestableDefaultModelBinder<SetPropertyModel>();
// Act
binder.SetProperty("NonNullableString", null);
// Assert
ModelState modelState = binder.Context.ModelState[BASE_MODEL_NAME + ".NonNullableString"];
Assert.Single(modelState.Errors);
Assert.IsType<ArgumentNullException>(modelState.Errors[0].Exception);
}
[Fact]
public void SetPropertyWithNullValueAndThrowingSetterWithRequiredAttribute()
{ // DDB #227809
// Arrange
TestableDefaultModelBinder<SetPropertyModel> binder = new TestableDefaultModelBinder<SetPropertyModel>();
// 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<SetPropertyModel> binder = new TestableDefaultModelBinder<SetPropertyModel>();
// 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<T>(string propertyName)
{
return TypeDescriptor.GetProperties(typeof(T))
.Cast<PropertyDescriptor>()
.Where(p => p.Name == propertyName)
.Single();
}
static ICustomTypeDescriptor GetType<T>()
{
return TypeDescriptor.GetProvider(typeof(T)).GetTypeDescriptor(typeof(T));
}
// Helper classes
class TestableDefaultModelBinder<TModel> : 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<TModel>(propertyName));
}
public PropertyDescriptorCollection GetModelProperties()
{
return base.GetModelProperties(new ControllerContext(), Context);
}
public object GetPropertyValue(string propertyName, object existingValue)
{
Mock<IModelBinder> mockModelBinder = new Mock<IModelBinder>();
mockModelBinder.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), Context)).Returns(existingValue);
return base.GetPropertyValue(new ControllerContext(), Context, GetProperty<TModel>(propertyName), mockModelBinder.Object);
}
public bool OnPropertyValidating(string propertyName, object value)
{
return base.OnPropertyValidating(new ControllerContext(), Context, GetProperty<TModel>(propertyName), value);
}
public void OnPropertyValidated(string propertyName, object value)
{
base.OnPropertyValidated(new ControllerContext(), Context, GetProperty<TModel>(propertyName), value);
}
public void OnModelUpdated()
{
base.OnModelUpdated(new ControllerContext(), Context);
}
public void SetProperty(string propertyName, object value)
{
base.SetProperty(new ControllerContext(), Context, GetProperty<TModel>(propertyName), value);
}
}
private class CountryState
{
public string Name { get; set; }
public IEnumerable<string> States { get; set; }
}
}
}