Skip to content
This repository has been archived by the owner on Jan 24, 2021. It is now read-only.

Commit

Permalink
Updated model binding to support fields as well as properties.
Browse files Browse the repository at this point in the history
Added ModelBinding/BindingMemberInfo.cs to Nancy.csproj, migrating into it the logic DefaultBinder.cs uses to enumerate properties and expanding it to support fields as well. Added unit testing class Unit/ModelBinding/BindingMemberInfoFixture.cs to Nancy.Tests.csproj.
Updated ModelBinding/BindingContext.cs to store an enumerable sequence of BindingMemberInfo objects instead of directly referencing System.Reflection.PropertyInfo. Updated all unit tests referencing this field accordingly.
Updated ModelBinding/DefaultBinder.cs to work with BindingMemberInfo objects wherever it refers to model properties. The primary impact of this change is that .GetValue and .SetValue calls no longer need to supply a dummy 'null' parameter for the property's index arguments, as this detail (which does not apply, in any case, to fields) is now hidden within BindingMemberInfo.
Updated the initialization of BindingContext objects in ModelBinding/DefaultBinder.cs to convert the enumerable list of properties to a list before stashing it in the ValidModelProperties member, because it does get enumerated more than once in some circumstances.
Reworked the GetProperties helper method in ModelBinding/DefaultBinder.cs to leverage the new BindingMemberInfo.Collect method, leaving only the blacklist to be implemented within the method.
Updated the Unit/ModelBinding/DefaultBinderFixture.cs and Unit/ModelBinding/DefaultBodyDeserializers/JsonBodyDeserializerFixture.cs unit test classes in Nancy.Tests to verify the functionality of the BindingMemberInfo objects on both properties and fields.
Refactored the copy/paste overloads of BindProperty in ModelBinding/DefaultBinder.cs so that one overload calls the other.
Fixed a bug in the BindProperty overload in ModelBinding/DefaultBinder.cs that takes a specified instance for the target of the binding that may have caused certain assignments to go to the wrong object.
Renamed GetProperties to GetBindingMembers, BindProperty to BindValue and SetPropertyValue to SetBindingMemberValue in ModelBinding/DefaultBinder.cs.
  • Loading branch information
logiclrd committed Jul 31, 2014
1 parent 3266990 commit 315866b
Show file tree
Hide file tree
Showing 9 changed files with 601 additions and 121 deletions.
1 change: 1 addition & 0 deletions src/Nancy.Tests/Nancy.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
<Compile Include="Unit\FormatterExtensionsFixture.cs" />
<Compile Include="Unit\Helpers\HttpUtilityFixture.cs" />
<Compile Include="Unit\MimeTypesFixture.cs" />
<Compile Include="Unit\ModelBinding\BindingMemberInfoFixture.cs" />
<Compile Include="Unit\ModelBinding\ModelBindingExceptionFixture.cs" />
<Compile Include="Unit\ModelBinding\PropertyBindingExceptionFixture.cs" />
<Compile Include="Unit\NamedPipelineBaseFixture.cs" />
Expand Down
235 changes: 235 additions & 0 deletions src/Nancy.Tests/Unit/ModelBinding/BindingMemberInfoFixture.cs
Original file line number Diff line number Diff line change
@@ -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<TestModel>();

// 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<TestModel>().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<TestModel>().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<TestModel>().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<TestModel>().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<BiggerTestModel>();

// 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<int> BindableEnumerableField;
[XmlIgnore]
public IEnumerable<int> BindableEnumerableProperty { get; set; }
public List<string> BindableListField;
public List<string> 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 { }
}
}
}
}
Loading

0 comments on commit 315866b

Please sign in to comment.