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

Commit

Permalink
Collection Converter handles Array,ICollection<T>,IEnumerable<T> types
Browse files Browse the repository at this point in the history
  • Loading branch information
grumpydev committed Apr 1, 2011
1 parent 17f4937 commit 0d43e49
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 7 deletions.
@@ -1,5 +1,8 @@
namespace Nancy.Tests.Unit.ModelBinding.DefaultConverters
{
using System.Collections.Generic;
using System.Linq;
using FakeItEasy;
using Nancy.ModelBinding;
using Nancy.ModelBinding.DefaultConverters;

Expand All @@ -8,24 +11,77 @@ namespace Nancy.Tests.Unit.ModelBinding.DefaultConverters
public class CollectionConverterFixture
{
private ITypeConverter converter;
private BindingContext context;
private ITypeConverter mockStringTypeConverter;

/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public CollectionConverterFixture()
{
this.converter = new CollectionConverter();
this.context = new BindingContext() { TypeConverters = new[] { new FallbackConverter() } };

this.mockStringTypeConverter = A.Fake<ITypeConverter>();
A.CallTo(() => mockStringTypeConverter.CanConvertTo(null)).WithAnyArguments().Returns(true);
A.CallTo(() => mockStringTypeConverter.Convert(null, null, null)).WithAnyArguments().Returns(string.Empty);
}

[Fact]
public void Should_handle_array_types()
{
var input = "one,two,three";
const string input = "one,two,three";

var output = (string[])converter.Convert(input, typeof(string[]), null);
var output = (string[])converter.Convert(input, typeof(string[]), this.context);

output.ShouldNotBeNull();
output.Length.ShouldEqual(3);
}

[Fact]
public void Array_type_conversion_should_use_type_converter()
{
const string input = "one,two,three";
var mockContext = new BindingContext() { TypeConverters = new[] { this.mockStringTypeConverter } };

converter.Convert(input, typeof(string[]), mockContext);

A.CallTo(() => this.mockStringTypeConverter.Convert(null, null, null)).WithAnyArguments()
.MustHaveHappened(Repeated.Exactly.Times(3));
}

[Fact]
public void Should_handle_collection_types()
{
const string input = "one,two,three";

var output = (List<string>)converter.Convert(input, typeof(List<string>), this.context);

output.ShouldNotBeNull();
output.Count.ShouldEqual(3);
}

[Fact]
public void Collection_type_conversion_should_use_type_converter()
{
const string input = "one,two,three";
var mockContext = new BindingContext() { TypeConverters = new[] { this.mockStringTypeConverter } };

converter.Convert(input, typeof(List<string>), mockContext);

A.CallTo(() => this.mockStringTypeConverter.Convert(null, null, null)).WithAnyArguments()
.MustHaveHappened(Repeated.Exactly.Times(3));
}

[Fact]
public void Should_handle_IEnumerable_types()
{
const string input = "one,two,three";

var output = (IEnumerable<string>)converter.Convert(input, typeof(IEnumerable<string>), this.context);

output.ShouldNotBeNull();
output.Count().ShouldEqual(3);
}
}
}
2 changes: 1 addition & 1 deletion src/Nancy/ModelBinding/DefaultBinder.cs
Expand Up @@ -87,7 +87,7 @@ private BindingContext CreateBindingContext(NancyContext context, Type modelType
Model = this.CreateModel(modelType),
ValidModelProperties = this.GetProperties(modelType, blackList),
FormFields = this.GetFormFields(context),
TypeConverters = this.typeConverters.Concat(this.defaults.DefaultTypeConverters);
TypeConverters = this.typeConverters.Concat(this.defaults.DefaultTypeConverters),
};
}

Expand Down
116 changes: 112 additions & 4 deletions src/Nancy/ModelBinding/DefaultConverters/CollectionConverter.cs
Expand Up @@ -4,12 +4,18 @@ namespace Nancy.ModelBinding.DefaultConverters
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

/// <summary>
/// Converter for handling enumerable types
/// </summary>
public class CollectionConverter : ITypeConverter
{
private readonly MethodInfo enumerableCastMethod = typeof(Enumerable).GetMethod("Cast", BindingFlags.Public | BindingFlags.Static);
private readonly MethodInfo enumerableToArrayMethod = typeof(Enumerable).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Static);
private readonly MethodInfo enumerableAsEnumerableMethod = typeof(Enumerable).GetMethod("AsEnumerable", BindingFlags.Public | BindingFlags.Static);

/// <summary>
/// Whether the converter can convert to the destination type
/// </summary>
Expand All @@ -29,16 +35,118 @@ public bool CanConvertTo(Type destinationType)
/// <returns>Converted object of the destination type</returns>
public object Convert(string input, Type destinationType, BindingContext context)
{
var collection = new List<string>();
// TODO - Lots of reflection in here, should probably cache the methodinfos
if (string.IsNullOrEmpty(input))
{
return null;
}

var items = input.Split(',');

if (IsCollection(destinationType))
{
return ConvertCollection(items, destinationType, context);
}

if (IsArray(destinationType))
{
return ConvertArray(items, destinationType, context);
}

if (IsEnumerable(destinationType))
{
return ConvertEnumerable(items, destinationType, context);
}

return null;
}

private bool IsCollection(Type destinationType)
{
var collectionType = typeof(ICollection<>);

return destinationType.IsGenericType && destinationType.GetInterfaces().
Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == collectionType).Any();
}

private bool IsArray(Type destinationType)
{
return destinationType.BaseType == typeof(Array);
}

private bool IsEnumerable(Type destinationType)
{
var enumerableType = typeof(IEnumerable<>);

return destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == enumerableType;
}

private object ConvertCollection(string[] items, Type destinationType, BindingContext context)
{
var genericType = destinationType.GetGenericArguments().First();
var returnCollection = Activator.CreateInstance(destinationType);

var converter = context.TypeConverters.Where(c => c.CanConvertTo(genericType)).FirstOrDefault();
if (converter == null)
{
return null;
}

var collectionAddMethod = destinationType.GetMethod("Add", BindingFlags.Public | BindingFlags.Instance);

foreach (var item in items)
{
collectionAddMethod.Invoke(returnCollection, new[] { converter.Convert(item, genericType, context) });
}

return returnCollection;
}

private object ConvertArray(string[] items, Type destinationType, BindingContext context)
{
var elementType = destinationType.GetElementType();

if (elementType == null)
{
return null;
}

var converter = context.TypeConverters.Where(c => c.CanConvertTo(elementType)).FirstOrDefault();

if (converter == null)
{
return null;
}

var returnArray = items.Select(s => converter.Convert(s, elementType, context));

var genericCastMethod = this.enumerableCastMethod.MakeGenericMethod(new[] { elementType });
var generictoArrayMethod = this.enumerableToArrayMethod.MakeGenericMethod(new[] { elementType });

var castArray = genericCastMethod.Invoke(null, new object[] { returnArray });

return generictoArrayMethod.Invoke(null, new[] { castArray });
}

private object ConvertEnumerable(string[] items, Type destinationType, BindingContext context)
{
var genericType = destinationType.GetGenericArguments().First();

var converter = TypeDescriptor.GetConverter(destinationType);
var converter = context.TypeConverters.Where(c => c.CanConvertTo(genericType)).FirstOrDefault();

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

throw new NotImplementedException();
var returnArray = items.Select(s => converter.Convert(s, genericType, context));

var genericCastMethod = this.enumerableCastMethod.MakeGenericMethod(new[] { genericType });
var genericAsEnumerableMethod = this.enumerableAsEnumerableMethod.MakeGenericMethod(new[] { genericType });

var castArray = genericCastMethod.Invoke(null, new object[] { returnArray });

return genericAsEnumerableMethod.Invoke(null, new[] { castArray });
}
}
}

0 comments on commit 0d43e49

Please sign in to comment.