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

Commit

Permalink
Now working for basic types and calling type converters and body dese…
Browse files Browse the repository at this point in the history
…rializers
  • Loading branch information
grumpydev committed Apr 1, 2011
1 parent 0eda931 commit ec88d3e
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/Nancy.Demo.ModelBinding/Views/Customers.spark
Expand Up @@ -8,7 +8,7 @@
<p>Current customers:</p>
<ul>
<li each="var v in Model">
<a href="customer/${v.Id}"> ${v.Name} - ${v.RenewalDate} </a>
<a href="customer/${v.Id}"> ${v.Name} - ${v.RenewalDate.ToShortDateString()} </a>
</li>
</ul>

Expand Down
84 changes: 84 additions & 0 deletions src/Nancy.Tests/Unit/ModelBinding/DefaultBinderFixture.cs
Expand Up @@ -88,10 +88,94 @@ public void Should_return_object_from_deserializer_if_one_returned()
result.ShouldBeSameAs(modelObject);
}

[Fact]
public void Should_see_if_a_type_converter_is_available_for_each_property_on_the_model_where_incoming_value_exists()
{
var typeConverter = A.Fake<ITypeConverter>();
A.CallTo(() => typeConverter.CanConvertTo(null)).WithAnyArguments().Returns(false);
var binder = this.GetBinder(typeConverters: new[] { typeConverter });
var context = new NancyContext { Request = new FakeRequest("GET", "/") };
context.Request.Form["StringProperty"] = "Test";
context.Request.Form["IntProperty"] = "12";

binder.Bind(context, typeof(TestModel));

A.CallTo(() => typeConverter.CanConvertTo(null)).WithAnyArguments()
.MustHaveHappened(Repeated.Exactly.Times(2));
}

[Fact]
public void Should_call_convert_on_type_converter_if_available()
{
var typeConverter = A.Fake<ITypeConverter>();
A.CallTo(() => typeConverter.CanConvertTo(typeof(string))).WithAnyArguments().Returns(true);
A.CallTo(() => typeConverter.Convert(null, null, null)).WithAnyArguments().Returns(null);
var binder = this.GetBinder(typeConverters: new[] { typeConverter });
var context = new NancyContext { Request = new FakeRequest("GET", "/") };
context.Request.Form["StringProperty"] = "Test";

binder.Bind(context, typeof(TestModel));

A.CallTo(() => typeConverter.Convert(null, null, null)).WithAnyArguments()
.MustHaveHappened(Repeated.Exactly.Once);
}

[Fact]
public void Should_convert_basic_types()
{
var binder = this.GetBinder();
var context = new NancyContext { Request = new FakeRequest("GET", "/") };
context.Request.Form["StringProperty"] = "Test";
context.Request.Form["IntProperty"] = "12";
var now = DateTime.Now;
context.Request.Form["DateProperty"] = now.ToString();

var result = (TestModel)binder.Bind(context, typeof(TestModel));

result.StringProperty.ShouldEqual("Test");
result.IntProperty.ShouldEqual(12);
result.DateProperty.ShouldEqual(now);
}

[Fact]
public void Should_ignore_properties_that_cannot_be_converted()
{
var binder = this.GetBinder();
var context = new NancyContext { Request = new FakeRequest("GET", "/") };
context.Request.Form["StringProperty"] = "Test";
context.Request.Form["IntProperty"] = "12";
context.Request.Form["DateProperty"] = "Broken";

var result = (TestModel)binder.Bind(context, typeof(TestModel));

result.StringProperty.ShouldEqual("Test");
result.IntProperty.ShouldEqual(12);
result.DateProperty.ShouldEqual(default(DateTime));
}

private IBinder GetBinder(IEnumerable<ITypeConverter> typeConverters = null, IEnumerable<IBodyDeserializer> bodyDeserializers = null)
{
return new DefaultBinder(
typeConverters ?? new ITypeConverter[] { }, bodyDeserializers ?? new IBodyDeserializer[] { });
}

public class TestModel
{
public string StringProperty { get; set; }

public int IntProperty { get; set; }

public DateTime DateProperty { get; set; }
}

public class BrokenModel
{
private string broken;
public string Broken
{
get { return this.broken; }
set { throw new NotImplementedException(); }
}
}
}
}
88 changes: 70 additions & 18 deletions src/Nancy/ModelBinding/DefaultBinder.cs
@@ -1,7 +1,9 @@
namespace Nancy.ModelBinding
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

Expand Down Expand Up @@ -39,43 +41,93 @@ public DefaultBinder(IEnumerable<ITypeConverter> typeConverters, IEnumerable<IBo
/// <returns>Bound model</returns>
public object Bind(NancyContext context, Type modelType)
{
// TODO - Blacklist
// TODO - Name conversion conventions? Might not want to exactly match name in form fields
// TODO - Collections support

var result = this.DeserializeRequestBody(context, modelType);

if (result != null)
{
return result;
}

// Currently this is *extremely* dumb and PoC only :-)
// Models must have a default constructor and public settable properties
// for each member. Also only supports very basic types and only works
// if the input type is directly assignable to the type in the object.
var model = Activator.CreateInstance(modelType);
var model = this.CreateModel(modelType);

var modelProperties = model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanWrite);
DynamicDictionary formDictionary = context.Request.Form;
var modelProperties = this.GetProperties(modelType);

foreach (var modelProperty in modelProperties)
{
var dictionaryItem = (DynamicDictionaryValue)formDictionary[modelProperty.Name];
var stringValue = this.GetValue(modelProperty.Name, context);

// TODO - Use type converters if they are available
if (dictionaryItem.HasValue)
if (!String.IsNullOrEmpty(stringValue))
{
if (modelProperty.PropertyType.IsAssignableFrom(dictionaryItem.Value.GetType()))
{
modelProperty.SetValue(model, dictionaryItem.Value, null);
}
else
{
//modelProperty.SetValue(model, dictionaryItem, null);
}
this.BindProperty(model, modelProperty, stringValue, context);
}
}

return model;
}

private void BindProperty(object model, PropertyInfo modelProperty, string stringValue, NancyContext context)
{
var destinationType = modelProperty.PropertyType;

var typeConverter =
this.typeConverters.Where(c => c.CanConvertTo(destinationType)).FirstOrDefault();

if (typeConverter != null)
{
this.SetPropertyValue(modelProperty, model, typeConverter.Convert(stringValue, destinationType, context));
return;
}

if (destinationType == typeof(string))
{
this.SetPropertyValue(modelProperty, model, stringValue);
return;
}

var converter = TypeDescriptor.GetConverter(modelProperty.PropertyType);

if (converter == null || !converter.CanConvertFrom(typeof(string)))
{
return;
}

try
{
this.SetPropertyValue(modelProperty, model, converter.ConvertFrom(stringValue));
}
catch (FormatException)
{
}
}

private void SetPropertyValue(PropertyInfo modelProperty, object model, object value)
{
// TODO - catch reflection exceptions?
modelProperty.SetValue(model, value, null);
}

private IEnumerable<PropertyInfo> GetProperties(Type modelType)
{
return modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanWrite);
}

private object CreateModel(Type modelType)
{
return Activator.CreateInstance(modelType);
}

private string GetValue(string propertyName, NancyContext context)
{
// TODO - check captured variables too if possible
var dictionaryItem = (DynamicDictionaryValue)context.Request.Form[propertyName];

return dictionaryItem.HasValue ? dictionaryItem : String.Empty;
}

private object DeserializeRequestBody(NancyContext context, Type modelType)
{
if (context == null || context.Request == null)
Expand Down

0 comments on commit ec88d3e

Please sign in to comment.