diff --git a/src/Nancy.Tests/Nancy.Tests.csproj b/src/Nancy.Tests/Nancy.Tests.csproj
index c5d8147cb9..55cf2d1d26 100644
--- a/src/Nancy.Tests/Nancy.Tests.csproj
+++ b/src/Nancy.Tests/Nancy.Tests.csproj
@@ -147,6 +147,7 @@
+
diff --git a/src/Nancy.Tests/Unit/ModelBinding/BindingMemberInfoFixture.cs b/src/Nancy.Tests/Unit/ModelBinding/BindingMemberInfoFixture.cs
new file mode 100644
index 0000000000..39a344e6ea
--- /dev/null
+++ b/src/Nancy.Tests/Unit/ModelBinding/BindingMemberInfoFixture.cs
@@ -0,0 +1,235 @@
+namespace Nancy.Tests.Unit.ModelBinding
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Xml.Serialization;
+ using Nancy.ModelBinding;
+ using Xunit;
+ using Xunit.Sdk;
+
+ public class BindingMemberInfoFixture
+ {
+ [Fact]
+ public void Should_return_MemberInfo_for_properties_or_fields()
+ {
+ // Given
+ var type = typeof(TestModel);
+ var underlyingFieldInfo = type.GetFields().First();
+ var underlyingPropertyInfo = type.GetProperties().First();
+
+ // When
+ var fieldInfo = new BindingMemberInfo(underlyingFieldInfo);
+ var propertyInfo = new BindingMemberInfo(underlyingPropertyInfo);
+
+ // Then
+ fieldInfo.MemberInfo.ShouldEqual(underlyingFieldInfo);
+ propertyInfo.MemberInfo.ShouldEqual(underlyingPropertyInfo);
+ }
+
+ [Fact]
+ public void Should_return_Name_for_properties_or_fields()
+ {
+ // Given
+ var type = typeof(TestModel);
+ var underlyingFieldInfo = type.GetFields().First();
+ var underlyingPropertyInfo = type.GetProperties().First();
+
+ // When
+ var fieldInfo = new BindingMemberInfo(underlyingFieldInfo);
+ var propertyInfo = new BindingMemberInfo(underlyingPropertyInfo);
+
+ // Then
+ fieldInfo.Name.ShouldEqual(underlyingFieldInfo.Name);
+ propertyInfo.Name.ShouldEqual(underlyingPropertyInfo.Name);
+ }
+
+ [Fact]
+ public void Should_return_PropertyType_for_properties_or_fields()
+ {
+ // Given
+ var properties = BindingMemberInfo.Collect();
+
+ // When
+
+ // Then
+ properties.ShouldHaveCount(4);
+
+ foreach (var propInfo in properties)
+ {
+ if (propInfo.Name.StartsWith("Int"))
+ {
+ propInfo.PropertyType.ShouldEqual(typeof(int));
+ }
+ else if (propInfo.Name.StartsWith("String"))
+ {
+ propInfo.PropertyType.ShouldEqual(typeof(string));
+ }
+ else
+ {
+ throw new AssertException("Internal error in unit test: Test model property/field name does not follow the expected convention: " + propInfo.Name);
+ }
+ }
+ }
+
+ [Fact]
+ public void Should_get_fields()
+ {
+ // Given
+ var propInfo = BindingMemberInfo.Collect().Where(prop => prop.Name.EndsWith("Field"));
+ var model = new TestModel();
+
+ // When
+ model.IntField = 669;
+ model.StringField = "testing";
+
+ // Then
+ propInfo.Single(prop => prop.PropertyType == typeof(int))
+ .GetValue(model)
+ .ShouldEqual(669);
+
+ propInfo.Single(prop => prop.PropertyType == typeof(string))
+ .GetValue(model)
+ .ShouldEqual("testing");
+ }
+
+ [Fact]
+ public void Should_set_fields()
+ {
+ // Given
+ var propInfo = BindingMemberInfo.Collect().Where(prop => prop.Name.EndsWith("Field"));
+ var model = new TestModel();
+
+ // When
+ propInfo.Single(prop => prop.PropertyType == typeof(int))
+ .SetValue(model, 42);
+
+ propInfo.Single(prop => prop.PropertyType == typeof(string))
+ .SetValue(model, "nineteen");
+
+ // Then
+ model.IntField.ShouldEqual(42);
+ model.StringField.ShouldEqual("nineteen");
+ }
+
+ [Fact]
+ public void Should_get_properties()
+ {
+ // Given
+ var propInfo = BindingMemberInfo.Collect().Where(prop => prop.Name.EndsWith("Property"));
+ var model = new TestModel();
+
+ // When
+ model.IntProperty = 1701;
+ model.StringProperty = "NancyFX Unit Testing";
+
+ // Then
+ propInfo.Single(prop => prop.PropertyType == typeof(int))
+ .GetValue(model)
+ .ShouldEqual(1701);
+
+ propInfo.Single(prop => prop.PropertyType == typeof(string))
+ .GetValue(model)
+ .ShouldEqual("NancyFX Unit Testing");
+ }
+
+ [Fact]
+ public void Should_set_properties()
+ {
+ // Given
+ var propInfo = BindingMemberInfo.Collect().Where(prop => prop.Name.EndsWith("Property"));
+ var model = new TestModel();
+
+ // When
+ propInfo.Single(prop => prop.PropertyType == typeof(int))
+ .SetValue(model, 2600);
+
+ propInfo.Single(prop => prop.PropertyType == typeof(string))
+ .SetValue(model, "R2D2");
+
+ // Then
+ model.IntProperty.ShouldEqual(2600);
+ model.StringProperty.ShouldEqual("R2D2");
+ }
+
+ [Fact]
+ public void Should_collect_all_bindable_members_and_skip_all_others()
+ {
+ // Given
+
+ // When
+ var properties = BindingMemberInfo.Collect();
+
+ // Then
+ properties.ShouldHaveCount(16);
+
+ foreach (var property in properties)
+ property.Name.ShouldStartWith("Bindable");
+ }
+
+ public class TestModel
+ {
+ public int IntField;
+ public string StringField;
+
+ public int IntProperty { get; set; }
+ public string StringProperty { get; set; }
+ }
+
+ public class BiggerTestModel
+ {
+ public int BindableIntField;
+ public int BindableIntProperty { get; set; }
+ public string BindableStringField;
+ public string BindableStringProperty { get; set; }
+ public TestModel BindableTestModelField;
+ public TestModel BindableTestModelProperty { get; set; }
+ public BiggerTestModel BindableBiggerTestModelField;
+ public BiggerTestModel BindableBiggerTestModelProperty { get; set; }
+ [XmlIgnore]
+ public IEnumerable BindableEnumerableField;
+ [XmlIgnore]
+ public IEnumerable BindableEnumerableProperty { get; set; }
+ public List BindableListField;
+ public List BindableListProperty { get; set; }
+ public double[] BindableArrayField;
+ public double[] BindableArrayProperty { get; set; }
+ public TestModel[] BindableArrayOfObjectsField;
+ public TestModel[] BindableArrayOfObjectsProperty { get; set; }
+
+ public int this[int index]
+ {
+ get { return 0; }
+ set { }
+ }
+
+ public int this[string index, int index2]
+ {
+ get { return 0; }
+ set { }
+ }
+
+ public readonly int UnbindableReadOnlyField = 74205;
+
+ public string UnbindableReadOnlyProperty
+ {
+ get { return "hi"; }
+ }
+
+ public string UnbindableWriteOnlyProperty
+ {
+ set { }
+ }
+
+ private int UnbindablePrivateField;
+
+ public static int UnbindableStaticField;
+
+ public static int UnbindableStaticProperty
+ {
+ get { return 0; }
+ set { }
+ }
+ }
+ }
+}
diff --git a/src/Nancy.Tests/Unit/ModelBinding/DefaultBinderFixture.cs b/src/Nancy.Tests/Unit/ModelBinding/DefaultBinderFixture.cs
index 475af207b8..f027a3cb6f 100644
--- a/src/Nancy.Tests/Unit/ModelBinding/DefaultBinderFixture.cs
+++ b/src/Nancy.Tests/Unit/ModelBinding/DefaultBinderFixture.cs
@@ -195,7 +195,13 @@ public void Should_use_object_from_deserializer_if_one_returned_and_overwrite_wh
public void Should_use_object_from_deserializer_if_one_returned_and_not_overwrite_when_not_allowed()
{
// Given
- var modelObject = new TestModel { StringPropertyWithDefaultValue = "Hello!" };
+ var modelObject =
+ new TestModel()
+ {
+ StringPropertyWithDefaultValue = "Hello!",
+ StringFieldWithDefaultValue = "World!",
+ };
+
var deserializer = A.Fake();
A.CallTo(() => deserializer.CanDeserialize(null, A._)).WithAnyArguments().Returns(true);
A.CallTo(() => deserializer.Deserialize(null, null, null)).WithAnyArguments().Returns(modelObject);
@@ -208,7 +214,8 @@ public void Should_use_object_from_deserializer_if_one_returned_and_not_overwrit
// Then
result.ShouldBeOfType();
- ((TestModel)result).StringPropertyWithDefaultValue.ShouldEqual("Default Value");
+ ((TestModel)result).StringPropertyWithDefaultValue.ShouldEqual("Default Property Value");
+ ((TestModel)result).StringFieldWithDefaultValue.ShouldEqual("Default Field Value");
}
[Fact]
@@ -350,7 +357,7 @@ public void Should_ignore_indexer_properties()
binder.Bind(context, typeof(TestModel), null, BindingConfig.Default);
// Then
- validProperties.ShouldEqual(11);
+ validProperties.ShouldEqual(22);
}
[Fact]
@@ -697,10 +704,14 @@ public void Should_be_able_to_bind_more_than_once_should_ignore_non_list_propert
context.Request.Form["IntProperty"] = "3";
context.Request.Form["NestedIntProperty[0]"] = "1";
+ context.Request.Form["NestedIntField[0]"] = "2";
context.Request.Form["NestedStringProperty[0]"] = "one";
+ context.Request.Form["NestedStringField[0]"] = "two";
- context.Request.Form["NestedIntProperty[1]"] = "2";
- context.Request.Form["NestedStringProperty[1]"] = "two";
+ context.Request.Form["NestedIntProperty[1]"] = "3";
+ context.Request.Form["NestedIntField[1]"] = "4";
+ context.Request.Form["NestedStringProperty[1]"] = "three";
+ context.Request.Form["NestedStringField[1]"] = "four";
// When
var result = (TestModel)binder.Bind(context, typeof(TestModel), null, BindingConfig.Default);
@@ -710,10 +721,15 @@ public void Should_be_able_to_bind_more_than_once_should_ignore_non_list_propert
result.StringProperty.ShouldEqual("Test");
result.IntProperty.ShouldEqual(3);
+ result2.ShouldHaveCount(2);
result2.First().NestedIntProperty.ShouldEqual(1);
+ result2.First().NestedIntField.ShouldEqual(2);
result2.First().NestedStringProperty.ShouldEqual("one");
- result2.Last().NestedIntProperty.ShouldEqual(2);
- result2.Last().NestedStringProperty.ShouldEqual("two");
+ result2.First().NestedStringField.ShouldEqual("two");
+ result2.Last().NestedIntProperty.ShouldEqual(3);
+ result2.Last().NestedIntField.ShouldEqual(4);
+ result2.Last().NestedStringProperty.ShouldEqual("three");
+ result2.Last().NestedStringField.ShouldEqual("four");
}
[Fact]
@@ -756,13 +772,17 @@ public void Should_bind_to_IEnumerable_from_Form()
var context = CreateContextWithHeader("Content-Type", new[] { "application/x-www-form-urlencoded" });
- context.Request.Form["IntValues"] = "1,2,3,4";
+ context.Request.Form["IntValuesProperty"] = "1,2,3,4";
+ context.Request.Form["IntValuesField"] = "5,6,7,8";
// When
var result = (TestModel)binder.Bind(context, typeof(TestModel), null, BindingConfig.Default);
// Then
- result.IntValues.ShouldHaveCount(4);
+ result.IntValuesProperty.ShouldHaveCount(4);
+ result.IntValuesProperty.ShouldEqualSequence(new[] { 1, 2, 3, 4 });
+ result.IntValuesField.ShouldHaveCount(4);
+ result.IntValuesField.ShouldEqualSequence(new[] { 5, 6, 7, 8 });
}
[Fact]
@@ -774,17 +794,24 @@ public void Should_bind_to_IEnumerable_from_Form_with_multiple_inputs()
var context = CreateContextWithHeader("Content-Type", new[] { "application/x-www-form-urlencoded" });
- context.Request.Form["IntValues_0"] = "1,2,3,4";
- context.Request.Form["IntValues_1"] = "5,6,7,8";
+ context.Request.Form["IntValuesProperty_0"] = "1,2,3,4";
+ context.Request.Form["IntValuesField_0"] = "5,6,7,8";
+ context.Request.Form["IntValuesProperty_1"] = "9,10,11,12";
+ context.Request.Form["IntValuesField_1"] = "13,14,15,16";
// When
var result = (List)binder.Bind(context, typeof(List), null, BindingConfig.Default);
// Then
- result.First().IntValues.ShouldHaveCount(4);
- result.First().IntValues.ShouldEqualSequence(new[] { 1, 2, 3, 4 });
- result.Last().IntValues.ShouldHaveCount(4);
- result.Last().IntValues.ShouldEqualSequence(new[] { 5, 6, 7, 8 });
+ result.ShouldHaveCount(2);
+ result.First().IntValuesProperty.ShouldHaveCount(4);
+ result.First().IntValuesProperty.ShouldEqualSequence(new[] { 1, 2, 3, 4 });
+ result.First().IntValuesField.ShouldHaveCount(4);
+ result.First().IntValuesField.ShouldEqualSequence(new[] { 5, 6, 7, 8 });
+ result.Last().IntValuesProperty.ShouldHaveCount(4);
+ result.Last().IntValuesProperty.ShouldEqualSequence(new[] { 9, 10, 11, 12 });
+ result.Last().IntValuesField.ShouldHaveCount(4);
+ result.Last().IntValuesField.ShouldEqualSequence(new[] { 13, 14, 15, 16 });
}
@@ -797,18 +824,26 @@ public void Should_bind_to_IEnumerable_from_Form_with_multiple_inputs_using_brac
var context = CreateContextWithHeader("Content-Type", new[] { "application/x-www-form-urlencoded" });
- context.Request.Form["IntValues[0]"] = "1,2,3,4";
- context.Request.Form["IntValues[1]"] = "5,6,7,8";
+ context.Request.Form["IntValuesProperty[0]"] = "1,2,3,4";
+ context.Request.Form["IntValuesField[0]"] = "5,6,7,8";
+ context.Request.Form["IntValuesProperty[1]"] = "9,10,11,12";
+ context.Request.Form["IntValuesField[1]"] = "13,14,15,16";
// When
- var result = (List)binder.Bind(context, typeof(List), new List { new TestModel {AnotherStringProprety = "Test"} }, new BindingConfig { Overwrite = false});
+ var result = (List)binder.Bind(context, typeof(List), new List { new TestModel {AnotherStringProperty = "Test"} }, new BindingConfig { Overwrite = false});
// Then
- result.First().AnotherStringProprety.ShouldEqual("Test");
- result.First().IntValues.ShouldHaveCount(4);
- result.First().IntValues.ShouldEqualSequence(new[] { 1, 2, 3, 4 });
- result.Last().IntValues.ShouldHaveCount(4);
- result.Last().IntValues.ShouldEqualSequence(new[] { 5, 6, 7, 8 });
+ result.ShouldHaveCount(2);
+ result.First().AnotherStringProperty.ShouldEqual("Test");
+ result.First().IntValuesProperty.ShouldHaveCount(4);
+ result.First().IntValuesProperty.ShouldEqualSequence(new[] { 1, 2, 3, 4 });
+ result.First().IntValuesField.ShouldHaveCount(4);
+ result.First().IntValuesField.ShouldEqualSequence(new[] { 5, 6, 7, 8 });
+ result.Last().AnotherStringProperty.ShouldBeNull();
+ result.Last().IntValuesProperty.ShouldHaveCount(4);
+ result.Last().IntValuesProperty.ShouldEqualSequence(new[] { 9, 10, 11, 12 });
+ result.Last().IntValuesField.ShouldHaveCount(4);
+ result.Last().IntValuesField.ShouldEqualSequence(new[] { 13, 14, 15, 16 });
}
[Fact]
@@ -820,17 +855,24 @@ public void Should_bind_to_IEnumerable_from_Form_with_multiple_inputs_using_brac
var context = CreateContextWithHeader("Content-Type", new[] { "application/x-www-form-urlencoded" });
- context.Request.Form["IntValues[0]"] = "1,2,3,4";
- context.Request.Form["IntValues[1]"] = "5,6,7,8";
+ context.Request.Form["IntValuesProperty[0]"] = "1,2,3,4";
+ context.Request.Form["IntValuesField[0]"] = "5,6,7,8";
+ context.Request.Form["IntValuesProperty[1]"] = "9,10,11,12";
+ context.Request.Form["IntValuesField[1]"] = "13,14,15,16";
// When
var result = (List)binder.Bind(context, typeof(List), null, BindingConfig.Default);
// Then
- result.First().IntValues.ShouldHaveCount(4);
- result.First().IntValues.ShouldEqualSequence(new[] { 1, 2, 3, 4 });
- result.Last().IntValues.ShouldHaveCount(4);
- result.Last().IntValues.ShouldEqualSequence(new[] { 5, 6, 7, 8 });
+ result.ShouldHaveCount(2);
+ result.First().IntValuesProperty.ShouldHaveCount(4);
+ result.First().IntValuesProperty.ShouldEqualSequence(new[] { 1, 2, 3, 4 });
+ result.First().IntValuesField.ShouldHaveCount(4);
+ result.First().IntValuesField.ShouldEqualSequence(new[] { 5, 6, 7, 8 });
+ result.Last().IntValuesProperty.ShouldHaveCount(4);
+ result.Last().IntValuesProperty.ShouldEqualSequence(new[] { 9, 10, 11, 12 });
+ result.Last().IntValuesField.ShouldHaveCount(4);
+ result.Last().IntValuesField.ShouldEqualSequence(new[] { 13, 14, 15, 16 });
}
[Fact]
@@ -851,6 +893,15 @@ public void Should_bind_collections_regardless_of_case()
context.Request.Form["LowercaseIntproperty[6]"] = "7";
context.Request.Form["LowercaseIntproperty[7]"] = "8";
+ context.Request.Form["lowercaseintfield[0]"] = "9";
+ context.Request.Form["lowercaseintfield[1]"] = "10";
+ context.Request.Form["lowercaseIntfield[2]"] = "11";
+ context.Request.Form["lowercaseIntfield[3]"] = "12";
+ context.Request.Form["Lowercaseintfield[4]"] = "13";
+ context.Request.Form["Lowercaseintfield[5]"] = "14";
+ context.Request.Form["LowercaseIntfield[6]"] = "15";
+ context.Request.Form["LowercaseIntfield[7]"] = "16";
+
// When
var result = (List)binder.Bind(context, typeof(List), null, BindingConfig.Default);
@@ -858,6 +909,8 @@ public void Should_bind_collections_regardless_of_case()
result.ShouldHaveCount(8);
result.First().lowercaseintproperty.ShouldEqual(1);
result.Last().lowercaseintproperty.ShouldEqual(8);
+ result.First().lowercaseintfield.ShouldEqual(9);
+ result.Last().lowercaseintfield.ShouldEqual(16);
}
[Fact]
@@ -1151,7 +1204,7 @@ public void Should_bind_ienumerable_model_with_instance_from_body()
var context = CreateContextWithHeaderAndBody("Content-Type", new[] { "application/json" }, body);
var then = DateTime.Now;
- var instance = new List { new TestModel{ DateProperty = then }, new TestModel { IntProperty = 9, AnotherStringProprety = "Bananas" } };
+ var instance = new List { new TestModel{ DateProperty = then }, new TestModel { IntProperty = 9, AnotherStringProperty = "Bananas" } };
// When
var result = (IEnumerable)binder.Bind(context, typeof(IEnumerable), instance, new BindingConfig{Overwrite = false});
@@ -1161,7 +1214,7 @@ public void Should_bind_ienumerable_model_with_instance_from_body()
result.First().DateProperty.ShouldEqual(then);
result.Last().StringProperty.ShouldEqual("AnotherTest");
result.Last().IntProperty.ShouldEqual(9);
- result.Last().AnotherStringProprety.ShouldEqual("Bananas");
+ result.Last().AnotherStringProperty.ShouldEqual("Bananas");
}
[Fact]
@@ -1174,7 +1227,7 @@ public void Should_bind_model_with_instance_from_body()
var context = CreateContextWithHeaderAndBody("Content-Type", new[] { "application/xml" }, body);
var then = DateTime.Now;
- var instance = new TestModel { DateProperty = then, IntProperty = 6, AnotherStringProprety = "Beers" };
+ var instance = new TestModel { DateProperty = then, IntProperty = 6, AnotherStringProperty = "Beers" };
// Wham
var result = (TestModel)binder.Bind(context, typeof(TestModel), instance, new BindingConfig { Overwrite = false });
@@ -1183,7 +1236,7 @@ public void Should_bind_model_with_instance_from_body()
result.StringProperty.ShouldEqual("Test");
result.DateProperty.ShouldEqual(then);
result.IntProperty.ShouldEqual(6);
- result.AnotherStringProprety.ShouldEqual("Beers");
+ result.AnotherStringProperty.ShouldEqual("Beers");
}
[Fact]
@@ -1192,16 +1245,24 @@ public void Should_bind_model_from_body_that_contains_an_array()
//Given
var typeConverters = new ITypeConverter[] { new CollectionConverter(), new FallbackConverter() };
var binder = this.GetBinder(typeConverters, new List { new JsonBodyDeserializer() });
- var body = serializer.Serialize(new TestModel {StringProperty = "Test", SomeStrings = new[] {"E", "A", "D", "G", "B", "E"}});
-
+ var body = serializer.Serialize(
+ new TestModel
+ {
+ StringProperty = "Test",
+ SomeStringsProperty = new[] { "E", "A", "D", "G", "B", "E" },
+ SomeStringsField = new[] { "G", "D", "A", "E" },
+ });
+
var context = CreateContextWithHeaderAndBody("Content-Type", new[] { "application/json" }, body);
// When
var result = (TestModel)binder.Bind(context, typeof(TestModel), null, BindingConfig.Default);
// Then
- result.SomeStrings.ShouldHaveCount(6);
- result.SomeStrings.ShouldEqualSequence(new[] { "E", "A", "D", "G", "B", "E" });
+ result.SomeStringsProperty.ShouldHaveCount(6);
+ result.SomeStringsProperty.ShouldEqualSequence(new[] { "E", "A", "D", "G", "B", "E" });
+ result.SomeStringsField.ShouldHaveCount(4);
+ result.SomeStringsField.ShouldEqualSequence(new[] { "G", "D", "A", "E" });
}
@@ -1213,18 +1274,31 @@ public void Should_bind_array_model_from_body_that_contains_an_array()
var body =
serializer.Serialize(new[]
{
- new TestModel {StringProperty = "Test", SomeStrings = new[] {"E", "A", "D", "G", "B", "E"}},
- new TestModel {StringProperty = "AnotherTest", SomeStrings = new[] {"E", "A", "D", "G", "B", "E"}}
+ new TestModel()
+ {
+ StringProperty = "Test",
+ SomeStringsProperty = new[] {"E", "A", "D", "G", "B", "E"},
+ SomeStringsField = new[] { "G", "D", "A", "E" },
+ },
+ new TestModel()
+ {
+ StringProperty = "AnotherTest",
+ SomeStringsProperty = new[] {"E", "A", "D", "G", "B", "E"},
+ SomeStringsField = new[] { "G", "D", "A", "E" },
+ }
});
var context = CreateContextWithHeaderAndBody("Content-Type", new[] { "application/json" }, body);
// When
- var result = (TestModel[])binder.Bind(context, typeof(TestModel[]), null, BindingConfig.Default, "SomeStrings");
+ var result = (TestModel[])binder.Bind(context, typeof(TestModel[]), null, BindingConfig.Default, "SomeStringsProperty", "SomeStringsField");
// Then
- result.First().SomeStrings.ShouldBeNull();
- result.Last().SomeStrings.ShouldBeNull();
+ result.ShouldHaveCount(2);
+ result.First().SomeStringsProperty.ShouldBeNull();
+ result.First().SomeStringsField.ShouldBeNull();
+ result.Last().SomeStringsProperty.ShouldBeNull();
+ result.Last().SomeStringsField.ShouldBeNull();
}
@@ -1241,14 +1315,14 @@ public void Form_request_and_context_properties_should_take_precedence_over_body
context.Request.Form["StringProperty"] = "From form";
context.Request.Query["IntProperty"] = "1";
- context.Parameters["AnotherStringProprety"] = "From context";
+ context.Parameters["AnotherStringProperty"] = "From context";
// When
var result = (TestModel)binder.Bind(context, typeof(TestModel), null, BindingConfig.Default);
// Then
result.StringProperty.ShouldEqual("From form");
- result.AnotherStringProprety.ShouldEqual("From context");
+ result.AnotherStringProperty.ShouldEqual("From context");
result.IntProperty.ShouldEqual(1);
}
@@ -1265,14 +1339,14 @@ public void Form_request_and_context_properties_should_be_ignored_in_body_only_m
context.Request.Form["StringProperty"] = "From form";
context.Request.Query["IntProperty"] = "1";
- context.Parameters["AnotherStringProprety"] = "From context";
+ context.Parameters["AnotherStringProperty"] = "From context";
// When
var result = (TestModel)binder.Bind(context, typeof(TestModel), null, new BindingConfig { BodyOnly = true });
// Then
result.StringProperty.ShouldEqual("From body");
- result.AnotherStringProprety.ShouldBeNull(); // not in body, so default value
+ result.AnotherStringProperty.ShouldBeNull(); // not in body, so default value
result.IntProperty.ShouldEqual(2);
}
@@ -1286,14 +1360,14 @@ public void Form_request_and_context_properties_should_NOT_be_used_in_body_only_
var context = CreateContextWithHeader("Content-Type", new[] { "application/xml" });
context.Request.Form["StringProperty"] = "From form";
context.Request.Query["IntProperty"] = "1";
- context.Parameters["AnotherStringProprety"] = "From context";
+ context.Parameters["AnotherStringProperty"] = "From context";
// When
var result = (TestModel)binder.Bind(context, typeof(TestModel), null, new BindingConfig { BodyOnly = true });
// Then
result.StringProperty.ShouldEqual(null);
- result.AnotherStringProprety.ShouldEqual(null);
+ result.AnotherStringProperty.ShouldEqual(null);
result.IntProperty.ShouldEqual(0);
}
@@ -1309,7 +1383,7 @@ public void Should_be_able_to_bind_body_request_form_and_context_properties()
context.Request.Form["IntProperty"] = "0";
context.Request.Query["StringProperty"] = "From Query";
- context.Parameters["AnotherStringProprety"] = "From Context";
+ context.Parameters["AnotherStringProperty"] = "From Context";
// When
var result = (TestModel)binder.Bind(context, typeof(TestModel), null, BindingConfig.Default);
@@ -1318,7 +1392,7 @@ public void Should_be_able_to_bind_body_request_form_and_context_properties()
result.StringProperty.ShouldEqual("From Query");
result.IntProperty.ShouldEqual(0);
result.DateProperty.ShouldEqual(new DateTime(2012, 8, 16));
- result.AnotherStringProprety.ShouldEqual("From Context");
+ result.AnotherStringProperty.ShouldEqual("From Context");
}
[Fact]
@@ -1413,29 +1487,51 @@ public class TestModel
{
public TestModel()
{
- this.StringPropertyWithDefaultValue = "Default Value";
+ this.StringPropertyWithDefaultValue = "Default Property Value";
+ this.StringFieldWithDefaultValue = "Default Field Value";
}
public string StringProperty { get; set; }
- public string AnotherStringProprety { get; set; }
+ public string AnotherStringProperty { get; set; }
+
+ public string StringField;
+
+ public string AnotherStringField;
public int IntProperty { get; set; }
public int AnotherIntProperty { get; set; }
+ public int IntField;
+
+ public int AnotherIntField;
+
public int lowercaseintproperty { get; set; }
+ public int lowercaseintfield;
+
public DateTime DateProperty { get; set; }
+ public DateTime DateField;
+
public string StringPropertyWithDefaultValue { get; set; }
+ public string StringFieldWithDefaultValue;
+
public double DoubleProperty { get; set; }
+ public double DoubleField;
+
[XmlIgnore]
- public IEnumerable IntValues { get; set; }
+ public IEnumerable IntValuesProperty { get; set; }
- public string[] SomeStrings { get; set; }
+ [XmlIgnore]
+ public IEnumerable IntValuesField;
+
+ public string[] SomeStringsProperty { get; set; }
+
+ public string[] SomeStringsField;
public int this[int index]
{
@@ -1443,7 +1539,9 @@ public int this[int index]
set { }
}
- public List Models { get; set; }
+ public List ModelsProperty { get; set; }
+
+ public List ModelsField;
}
public class AnotherTestModel
@@ -1451,6 +1549,10 @@ public class AnotherTestModel
public string NestedStringProperty { get; set; }
public int NestedIntProperty { get; set; }
public double NestedDoubleProperty { get; set; }
+
+ public string NestedStringField;
+ public int NestedIntField;
+ public double NestedDoubleField;
}
}
diff --git a/src/Nancy.Tests/Unit/ModelBinding/DefaultBodyDeserializers/JsonBodyDeserializerFixture.cs b/src/Nancy.Tests/Unit/ModelBinding/DefaultBodyDeserializers/JsonBodyDeserializerFixture.cs
index fdb9bacd9d..7538f2fe91 100644
--- a/src/Nancy.Tests/Unit/ModelBinding/DefaultBodyDeserializers/JsonBodyDeserializerFixture.cs
+++ b/src/Nancy.Tests/Unit/ModelBinding/DefaultBodyDeserializers/JsonBodyDeserializerFixture.cs
@@ -115,7 +115,7 @@ public void Should_deserialize_timespan()
var context = new BindingContext()
{
DestinationType = typeof(TimeSpan),
- ValidModelProperties = typeof(TimeSpan).GetProperties(),
+ ValidModelProperties = BindingMemberInfo.Collect().ToList(),
};
// When
@@ -135,13 +135,14 @@ public void Should_deserialize_list_of_primitives()
var context = new BindingContext()
{
DestinationType = typeof(TestModel),
- ValidModelProperties = typeof(TestModel).GetProperties(),
+ ValidModelProperties = BindingMemberInfo.Collect().ToList(),
};
var model =
new TestModel
{
- ListOfPrimitivesProperty = new List { 1, 3, 5 }
+ ListOfPrimitivesProperty = new List { 1, 3, 5 },
+ ListOfPrimitivesField = new List { 2, 4, 6 },
};
var s = new JavaScriptSerializer();
@@ -159,6 +160,11 @@ public void Should_deserialize_list_of_primitives()
result.ListOfPrimitivesProperty[0].ShouldEqual(1);
result.ListOfPrimitivesProperty[1].ShouldEqual(3);
result.ListOfPrimitivesProperty[2].ShouldEqual(5);
+
+ result.ListOfPrimitivesField.ShouldHaveCount(3);
+ result.ListOfPrimitivesField[0].ShouldEqual(2);
+ result.ListOfPrimitivesField[1].ShouldEqual(4);
+ result.ListOfPrimitivesField[2].ShouldEqual(6);
}
[Fact]
@@ -168,7 +174,7 @@ public void Should_deserialize_list_of_complex_objects()
var context = new BindingContext()
{
DestinationType = typeof(TestModel),
- ValidModelProperties = typeof(TestModel).GetProperties(),
+ ValidModelProperties = BindingMemberInfo.Collect().ToList(),
};
var model =
@@ -178,6 +184,11 @@ public void Should_deserialize_list_of_complex_objects()
{
new ModelWithStringValues() { Value1 = "one", Value2 = "two"},
new ModelWithStringValues() { Value1 = "three", Value2 = "four"}
+ },
+ ListOfComplexObjectsField = new List
+ {
+ new ModelWithStringValues() { Value1 = "five", Value2 = "six"},
+ new ModelWithStringValues() { Value1 = "seven", Value2 = "eight"}
}
};
@@ -197,21 +208,29 @@ public void Should_deserialize_list_of_complex_objects()
result.ListOfComplexObjectsProperty[0].Value2.ShouldEqual("two");
result.ListOfComplexObjectsProperty[1].Value1.ShouldEqual("three");
result.ListOfComplexObjectsProperty[1].Value2.ShouldEqual("four");
+ result.ListOfComplexObjectsField.ShouldHaveCount(2);
+ result.ListOfComplexObjectsField[0].Value1.ShouldEqual("five");
+ result.ListOfComplexObjectsField[0].Value2.ShouldEqual("six");
+ result.ListOfComplexObjectsField[1].Value1.ShouldEqual("seven");
+ result.ListOfComplexObjectsField[1].Value2.ShouldEqual("eight");
}
[Fact]
public void Should_Deserialize_Signed_And_Unsigned_Nullable_Numeric_Types()
{
//Given
- const string json = "{F1: 1, F2: 2, F3: 3}";
+ const string json = "{P1: 1, P2: 2, P3: 3, F1: 4, F2: 5, F3: 6}";
//When
var model = this.serializer.Deserialize (json);
//Should
- Assert.Equal (1, model.F1);
- Assert.Equal ((uint)2, model.F2);
- Assert.Equal ((uint)3, model.F3);
+ Assert.Equal (1, model.P1);
+ Assert.Equal ((uint)2, model.P2);
+ Assert.Equal ((uint)3, model.P3);
+ Assert.Equal (4, model.F1);
+ Assert.Equal ((uint)5, model.F2);
+ Assert.Equal ((uint)6, model.F3);
}
#if !__MonoCS__
@@ -286,8 +305,12 @@ public class TestModel : IEquatable
public List ListOfPrimitivesProperty { get; set; }
+ public List ListOfPrimitivesField;
+
public List ListOfComplexObjectsProperty { get; set; }
+ public List ListOfComplexObjectsField { get; set; }
+
public bool Equals(TestModel other)
{
if (ReferenceEquals(null, other))
@@ -354,21 +377,25 @@ public class ModelWithStringValues
{
public string Value1 { get; set; }
- public string Value2 { get; set; }
+ public string Value2;
}
public class ModelWithDoubleValues
{
public double Latitude { get; set; }
- public double Longitude { get; set; }
+ public double Longitude;
}
public class ModelWithNullables
{
- public int? F1 { get; set; }
- public uint F2 { get; set; }
- public uint? F3 { get; set; }
+ public int? P1 { get; set; }
+ public uint P2 { get; set; }
+ public uint? P3 { get; set; }
+
+ public int? F1;
+ public uint F2;
+ public uint? F3;
}
}
\ No newline at end of file
diff --git a/src/Nancy.Tests/Unit/ModelBinding/DefaultBodyDeserializers/XmlBodyDeserializerfixture.cs b/src/Nancy.Tests/Unit/ModelBinding/DefaultBodyDeserializers/XmlBodyDeserializerfixture.cs
index 458198f039..9785472333 100644
--- a/src/Nancy.Tests/Unit/ModelBinding/DefaultBodyDeserializers/XmlBodyDeserializerfixture.cs
+++ b/src/Nancy.Tests/Unit/ModelBinding/DefaultBodyDeserializers/XmlBodyDeserializerfixture.cs
@@ -1,6 +1,7 @@
namespace Nancy.Tests.Unit.ModelBinding.DefaultBodyDeserializers
{
using System.IO;
+ using System.Linq;
using System.Text;
using System.Xml.Serialization;
using FakeItEasy;
@@ -81,7 +82,7 @@ public void Should_deserialize_xml_model()
var context = new BindingContext()
{
DestinationType = typeof(TestModel),
- ValidModelProperties = typeof(TestModel).GetProperties(),
+ ValidModelProperties = BindingMemberInfo.Collect().ToArray(),
};
// When
diff --git a/src/Nancy/ModelBinding/BindingContext.cs b/src/Nancy/ModelBinding/BindingContext.cs
index bc7412a812..1493061f4b 100644
--- a/src/Nancy/ModelBinding/BindingContext.cs
+++ b/src/Nancy/ModelBinding/BindingContext.cs
@@ -37,7 +37,7 @@ public class BindingContext
///
/// DestinationType properties that are not black listed
///
- public IEnumerable ValidModelProperties { get; set; }
+ public IEnumerable ValidModelProperties { get; set; }
///
/// The incoming data fields
diff --git a/src/Nancy/ModelBinding/BindingMemberInfo.cs b/src/Nancy/ModelBinding/BindingMemberInfo.cs
new file mode 100644
index 0000000000..a2cb169a02
--- /dev/null
+++ b/src/Nancy/ModelBinding/BindingMemberInfo.cs
@@ -0,0 +1,140 @@
+namespace Nancy.ModelBinding
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+
+ ///
+ /// Represents a bindable member of a type, which can be a property or a field.
+ ///
+ public class BindingMemberInfo
+ {
+ PropertyInfo propertyInfo;
+ FieldInfo fieldInfo;
+
+ ///
+ /// Gets a reference to the MemberInfo that this BindingMemberInfo represents. This can be a property or a field.
+ ///
+ public MemberInfo MemberInfo
+ {
+ get { return this.propertyInfo ?? (MemberInfo)this.fieldInfo; }
+ }
+
+ ///
+ /// Gets the name of the property or field represented by this BindingMemberInfo.
+ ///
+ public string Name
+ {
+ get { return this.MemberInfo.Name; }
+ }
+
+ ///
+ /// Gets the data type of the property or field represented by this BindingMemberInfo.
+ ///
+ public Type PropertyType
+ {
+ get
+ {
+ if (this.propertyInfo != null)
+ {
+ return this.propertyInfo.PropertyType;
+ }
+ else
+ {
+ return this.fieldInfo.FieldType;
+ }
+ }
+ }
+
+ ///
+ /// Constructs a BindingMemberInfo instance for a property.
+ ///
+ /// The bindable property to represent.
+ public BindingMemberInfo(PropertyInfo propertyInfo)
+ {
+ if (propertyInfo == null)
+ {
+ throw new ArgumentNullException("propertyInfo");
+ }
+
+ this.propertyInfo = propertyInfo;
+ }
+
+ ///
+ /// Constructs a BindingMemberInfo instance for a field.
+ ///
+ /// The bindable field to represent.
+ public BindingMemberInfo(FieldInfo fieldInfo)
+ {
+ if (fieldInfo == null)
+ {
+ throw new ArgumentNullException("fieldInfo");
+ }
+
+ this.fieldInfo = fieldInfo;
+ }
+
+ ///
+ /// Gets the value from a specified object associated with the property or field represented by this BindingMemberInfo.
+ ///
+ /// The object whose property or field should be retrieved.
+ /// The value for this BindingMemberInfo's property or field in the specified object.
+ public object GetValue(object sourceObject)
+ {
+ if (this.propertyInfo != null)
+ {
+ return this.propertyInfo.GetValue(sourceObject, null);
+ }
+ else
+ {
+ return this.fieldInfo.GetValue(sourceObject);
+ }
+ }
+
+ ///
+ /// Sets the value from a specified object associated with the property or field represented by this BindingMemberInfo.
+ ///
+ /// The object whose property or field should be assigned.
+ /// The value to assign in the specified object to this BindingMemberInfo's property or field.
+ public void SetValue(object destinationObject, object newValue)
+ {
+ if (this.propertyInfo != null)
+ {
+ this.propertyInfo.SetValue(destinationObject, newValue, null);
+ }
+ else
+ {
+ this.fieldInfo.SetValue(destinationObject, newValue);
+ }
+ }
+
+ ///
+ /// Returns an enumerable sequence of bindable properties for the specified type.
+ ///
+ /// The type to enumerate.
+ /// Bindable properties.
+ public static IEnumerable Collect()
+ {
+ return Collect(typeof(T));
+ }
+
+ ///
+ /// Returns an enumerable sequence of bindable properties for the specified type.
+ ///
+ /// The type to enumerate.
+ /// Bindable properties.
+ public static IEnumerable Collect(Type type)
+ {
+ var fromProperties = type
+ .GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite)
+ .Where(property => !property.GetIndexParameters().Any())
+ .Select(property => new BindingMemberInfo(property));
+
+ var fromFields = type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => !f.IsInitOnly)
+ .Select(field => new BindingMemberInfo(field));
+
+ return fromProperties.Concat(fromFields);
+ }
+ }
+}
diff --git a/src/Nancy/ModelBinding/DefaultBinder.cs b/src/Nancy/ModelBinding/DefaultBinder.cs
index 3731e97b93..b45de22aa3 100644
--- a/src/Nancy/ModelBinding/DefaultBinder.cs
+++ b/src/Nancy/ModelBinding/DefaultBinder.cs
@@ -64,7 +64,7 @@ public DefaultBinder(IEnumerable typeConverters, IEnumerableModel type to bind to
/// Optional existing instance
/// The that should be applied during binding.
- /// Blacklisted property names
+ /// Blacklisted binding property names
/// Bound model
public object Bind(NancyContext context, Type modelType, object instance, BindingConfig configuration, params string[] blackList)
{
@@ -123,7 +123,7 @@ public object Bind(NancyContext context, Type modelType, object instance, Bindin
foreach (var modelProperty in bindingContext.ValidModelProperties)
{
- var existingCollectionValue = modelProperty.GetValue(genericinstance, null);
+ var existingCollectionValue = modelProperty.GetValue(genericinstance);
var collectionStringValue = GetValue(modelProperty.Name, bindingContext, i);
@@ -132,7 +132,7 @@ public object Bind(NancyContext context, Type modelType, object instance, Bindin
{
try
{
- BindProperty(modelProperty, collectionStringValue, bindingContext, genericinstance);
+ BindValue(modelProperty, collectionStringValue, bindingContext, genericinstance);
}
catch (PropertyBindingException ex)
{
@@ -146,7 +146,7 @@ public object Bind(NancyContext context, Type modelType, object instance, Bindin
{
foreach (var modelProperty in bindingContext.ValidModelProperties)
{
- var existingValue = modelProperty.GetValue(bindingContext.Model, null);
+ var existingValue = modelProperty.GetValue(bindingContext.Model);
var stringValue = GetValue(modelProperty.Name, bindingContext);
@@ -154,7 +154,7 @@ public object Bind(NancyContext context, Type modelType, object instance, Bindin
{
try
{
- BindProperty(modelProperty, stringValue, bindingContext);
+ BindValue(modelProperty, stringValue, bindingContext);
}
catch (PropertyBindingException ex)
{
@@ -178,7 +178,7 @@ public object Bind(NancyContext context, Type modelType, object instance, Bindin
return bindingContext.Model;
}
- private bool BindingValueIsValid(string bindingValue, object existingValue, PropertyInfo modelProperty, BindingContext bindingContext)
+ private bool BindingValueIsValid(string bindingValue, object existingValue, BindingMemberInfo modelProperty, BindingContext bindingContext)
{
return (!String.IsNullOrEmpty(bindingValue) &&
(IsDefaultValue(existingValue, modelProperty.PropertyType) ||
@@ -264,7 +264,7 @@ private static void UpdateModelWithDeserializedModel(object bodyDeserializedMode
foreach (var modelProperty in bindingContext.ValidModelProperties)
{
var existingValue =
- modelProperty.GetValue(bindingContext.Model, null);
+ modelProperty.GetValue(bindingContext.Model);
if (IsDefaultValue(existingValue, modelProperty.PropertyType) || bindingContext.Configuration.Overwrite)
{
@@ -301,7 +301,7 @@ private static void HandleReferenceTypeCollectionElement(BindingContext bindingC
foreach (var modelProperty in bindingContext.ValidModelProperties)
{
- var existingValue = modelProperty.GetValue(genericTypeInstance, null);
+ var existingValue = modelProperty.GetValue(genericTypeInstance);
if (IsDefaultValue(existingValue, modelProperty.PropertyType) || bindingContext.Configuration.Overwrite)
{
@@ -310,11 +310,11 @@ private static void HandleReferenceTypeCollectionElement(BindingContext bindingC
}
}
- private static void CopyValue(PropertyInfo modelProperty, object source, object destination)
+ private static void CopyValue(BindingMemberInfo modelProperty, object source, object destination)
{
- var newValue = modelProperty.GetValue(source, null);
+ var newValue = modelProperty.GetValue(source);
- modelProperty.SetValue(destination, newValue, null);
+ modelProperty.SetValue(destination, newValue);
}
private static bool IsDefaultValue(object existingValue, Type propertyType)
@@ -332,7 +332,7 @@ private BindingContext CreateBindingContext(NancyContext context, Type modelType
Context = context,
DestinationType = modelType,
Model = CreateModel(modelType, genericType, instance),
- ValidModelProperties = GetProperties(modelType, genericType, blackList),
+ ValidModelProperties = GetBindingMembers(modelType, genericType, blackList).ToList(),
RequestData = this.GetDataFields(context),
GenericType = genericType,
TypeConverters = this.typeConverters.Concat(this.defaults.DefaultTypeConverters),
@@ -363,31 +363,12 @@ private IDictionary ConvertDynamicDictionary(DynamicDictionary d
memberName => (string)dictionary[memberName]);
}
- private static void BindProperty(PropertyInfo modelProperty, string stringValue, BindingContext context)
+ private static void BindValue(BindingMemberInfo modelProperty, string stringValue, BindingContext context)
{
- var destinationType = modelProperty.PropertyType;
-
- var typeConverter =
- context.TypeConverters.FirstOrDefault(c => c.CanConvertTo(destinationType, context));
-
- if (typeConverter != null)
- {
- try
- {
- SetPropertyValue(modelProperty, context.Model, typeConverter.Convert(stringValue, destinationType, context));
- }
- catch (Exception e)
- {
- throw new PropertyBindingException(modelProperty.Name, stringValue, e);
- }
- }
- else if (destinationType == typeof(string))
- {
- SetPropertyValue(modelProperty, context.Model, stringValue);
- }
+ BindValue(modelProperty, stringValue, context, context.Model);
}
- private static void BindProperty(PropertyInfo modelProperty, string stringValue, BindingContext context, object genericInstance)
+ private static void BindValue(BindingMemberInfo modelProperty, string stringValue, BindingContext context, object targetInstance)
{
var destinationType = modelProperty.PropertyType;
@@ -398,7 +379,7 @@ private static void BindProperty(PropertyInfo modelProperty, string stringValue,
{
try
{
- SetPropertyValue(modelProperty, genericInstance, typeConverter.Convert(stringValue, destinationType, context));
+ SetBindingMemberValue(modelProperty, targetInstance, typeConverter.Convert(stringValue, destinationType, context));
}
catch (Exception e)
{
@@ -407,30 +388,22 @@ private static void BindProperty(PropertyInfo modelProperty, string stringValue,
}
else if (destinationType == typeof(string))
{
- SetPropertyValue(modelProperty, context.Model, stringValue);
+ SetBindingMemberValue(modelProperty, targetInstance, stringValue);
}
}
- private static void SetPropertyValue(PropertyInfo modelProperty, object model, object value)
+ private static void SetBindingMemberValue(BindingMemberInfo modelProperty, object model, object value)
{
// TODO - catch reflection exceptions?
- modelProperty.SetValue(model, value, null);
+ modelProperty.SetValue(model, value);
}
- private static IEnumerable GetProperties(Type modelType, Type genericType, IEnumerable blackList)
+ private static IEnumerable GetBindingMembers(Type modelType, Type genericType, IEnumerable blackList)
{
- if (genericType != null)
- {
- return genericType
- .GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanWrite && !blackList.Contains(p.Name, StringComparer.InvariantCulture))
- .Where(property => !property.GetIndexParameters().Any());
- }
- else
- {
- return modelType
- .GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanWrite && !blackList.Contains(p.Name, StringComparer.InvariantCulture))
- .Where(property => !property.GetIndexParameters().Any());
- }
+ var blackListHash = new HashSet(blackList, StringComparer.InvariantCulture);
+
+ return BindingMemberInfo.Collect(genericType ?? modelType)
+ .Where(member => !blackListHash.Contains(member.Name));
}
private static object CreateModel(Type modelType, Type genericType, object instance)
diff --git a/src/Nancy/Nancy.csproj b/src/Nancy/Nancy.csproj
index de0871bb5f..b921815be8 100644
--- a/src/Nancy/Nancy.csproj
+++ b/src/Nancy/Nancy.csproj
@@ -179,6 +179,7 @@
+