diff --git a/src/CommonAssemblyInfo.cs b/src/CommonAssemblyInfo.cs index 3ec09c0..a96cd59 100644 --- a/src/CommonAssemblyInfo.cs +++ b/src/CommonAssemblyInfo.cs @@ -1,5 +1,5 @@ -using System.Runtime.CompilerServices; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyCompany("Mark Rendle")] [assembly: AssemblyProduct("Simple.Web")] diff --git a/src/Sandbox/GetEnumerable.cs b/src/Sandbox/GetEnumerable.cs new file mode 100644 index 0000000..814876a --- /dev/null +++ b/src/Sandbox/GetEnumerable.cs @@ -0,0 +1,27 @@ +namespace Sandbox +{ + using System.Collections.Generic; + using System.Linq; + + using Simple.Web; + using Simple.Web.Behaviors; + + [UriTemplate("/enumerable")] + public class GetEnumerable : IGet, IOutput + { + public Status Get() + { + this.Output = new EnumerableModel { Messages = this.Message.ToArray() }; + return 200; + } + + public IEnumerable Message { get; set; } + + public EnumerableModel Output { get; set; } + } + + public class EnumerableModel + { + public string[] Messages { get; set; } + } +} diff --git a/src/Sandbox/Sandbox.csproj b/src/Sandbox/Sandbox.csproj index f563531..5665326 100644 --- a/src/Sandbox/Sandbox.csproj +++ b/src/Sandbox/Sandbox.csproj @@ -99,6 +99,7 @@ + @@ -160,6 +161,9 @@ Always + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/Sandbox/Views/EnumerableQSArg.cshtml b/src/Sandbox/Views/EnumerableQSArg.cshtml new file mode 100644 index 0000000..6c69901 --- /dev/null +++ b/src/Sandbox/Views/EnumerableQSArg.cshtml @@ -0,0 +1,17 @@ +@model Sandbox.EnumerableModel + + + + + + title + + +
+ @foreach (var message in Model.Messages) + { +
@message + } +
+ + diff --git a/src/Simple.Web.Autofac.Tests/HandlerFactoryBuilderTests.cs b/src/Simple.Web.Autofac.Tests/HandlerFactoryBuilderTests.cs index eade741..53a8b37 100644 --- a/src/Simple.Web.Autofac.Tests/HandlerFactoryBuilderTests.cs +++ b/src/Simple.Web.Autofac.Tests/HandlerFactoryBuilderTests.cs @@ -1,11 +1,11 @@ -using System; +using Autofac; +using System; using System.Collections.Generic; -using Autofac; namespace Simple.Web.Autofac.Tests { - using System.Reflection; using CodeGeneration; + using System.Reflection; using Xunit; public class HandlerFactoryBuilderTests diff --git a/src/Simple.Web.Autofac.Tests/HandlersModuleTests.cs b/src/Simple.Web.Autofac.Tests/HandlersModuleTests.cs index ccd62aa..ff28a6d 100644 --- a/src/Simple.Web.Autofac.Tests/HandlersModuleTests.cs +++ b/src/Simple.Web.Autofac.Tests/HandlersModuleTests.cs @@ -1,5 +1,5 @@ -using System.Reflection; -using Autofac; +using Autofac; +using System.Reflection; using Xunit; namespace Simple.Web.Autofac.Tests diff --git a/src/Simple.Web.Tests/HandlerBuilderFactoryTests.cs b/src/Simple.Web.Tests/HandlerBuilderFactoryTests.cs index 38f461c..b6d1a52 100644 --- a/src/Simple.Web.Tests/HandlerBuilderFactoryTests.cs +++ b/src/Simple.Web.Tests/HandlerBuilderFactoryTests.cs @@ -11,7 +11,7 @@ public class HandlerBuilderFactoryTests public void CreatesTypeWithParameterlessConstructorUsingDefaultContainer() { var target = new HandlerBuilderFactory(new Configuration()); - var actualFunc = target.BuildHandlerBuilder(typeof (ParameterlessConstructorType)); + var actualFunc = target.BuildHandlerBuilder(typeof(ParameterlessConstructorType)); var actual = actualFunc(new Dictionary()); Assert.IsType(actual.Handler); } @@ -20,7 +20,7 @@ public void CreatesTypeWithParameterlessConstructorUsingDefaultContainer() public void CreatingTypeWithNoParameterlessConstructorUsingDefaultContainerThrowsInvalidOperationException() { var target = new HandlerBuilderFactory(new Configuration()); - var actualFunc = target.BuildHandlerBuilder(typeof (NoParameterlessConstructorType)); + var actualFunc = target.BuildHandlerBuilder(typeof(NoParameterlessConstructorType)); Assert.Throws(() => actualFunc(new Dictionary())); } @@ -29,25 +29,109 @@ public void SetsGuidPropertyCorrectly() { var guid = new Guid("FA37E0B4-2DB9-4471-BC6C-229748F417CA"); var target = new HandlerBuilderFactory(new Configuration()); - var actualFunc = target.BuildHandlerBuilder(typeof (GuidHolder)); - var actual = (GuidHolder)actualFunc(new Dictionary{{"Guid",guid.ToString()}}).Handler; + var actualFunc = target.BuildHandlerBuilder(typeof(GuidHolder)); + var actual = (GuidHolder)actualFunc(new Dictionary { { "Guid", guid.ToString() } }).Handler; Assert.Equal(guid, actual.Guid); } + + [Fact] + public void SetsEnumerableGuidPropertyCorrectly() + { + var guidCollection = new[] + { + new Guid("FA37E0B4-2DB9-4471-BC6C-229748F417CA"), + new Guid("47A210D9-7E5D-480A-9300-B2CF1443C496") + }; + var target = new HandlerBuilderFactory(new Configuration()); + var actualFunc = target.BuildHandlerBuilder(typeof(EnumerableGuidHolder)); + var actual = (EnumerableGuidHolder)actualFunc(new Dictionary { { "Guids", "FA37E0B4-2DB9-4471-BC6C-229748F417CA\t47A210D9-7E5D-480A-9300-B2CF1443C496" } }).Handler; + Assert.Equal(guidCollection, actual.Guids); + } + + [Fact] + public void SetsEnumerableStringPropertyCorrectly() + { + var stringCollection = new[] + { + "hello", + "world" + }; + var target = new HandlerBuilderFactory(new Configuration()); + var actualFunc = target.BuildHandlerBuilder(typeof(EnumerableStringHolder)); + var actual = (EnumerableStringHolder)actualFunc(new Dictionary { { "Strings", "hello\tworld" } }).Handler; + Assert.Equal((IEnumerable)stringCollection, (IEnumerable)actual.Strings); + } + + [Fact] + public void SetsEnumerableEnumPropertyCorrectly() + { + var enumCollection = new[] + { + Enterprise.Kirk, + Enterprise.Spock + }; + var target = new HandlerBuilderFactory(new Configuration()); + var actualFunc = target.BuildHandlerBuilder(typeof(EnumerableEnumHolder)); + var actual = (EnumerableEnumHolder)actualFunc(new Dictionary { { "Trekkers", "Kirk\tSpock" } }).Handler; + Assert.Equal((IEnumerable)enumCollection, (IEnumerable)actual.Trekkers); + } + + [Fact] + public void SetsSingleEnumPropertyCorrectly() + { + var target = new HandlerBuilderFactory(new Configuration()); + var actualFunc = target.BuildHandlerBuilder(typeof(SingleEnumHolder)); + var actual = (SingleEnumHolder)actualFunc(new Dictionary { { "Trekker", "Uhura" } }).Handler; + Assert.Equal(Enterprise.Uhura, actual.Trekker); + } } class NoParameterlessConstructorType { public NoParameterlessConstructorType(int n) { - + } } + class ParameterlessConstructorType { - + } + class GuidHolder { public Guid? Guid { get; set; } } + + class EnumerableGuidHolder + { + public IEnumerable Guids { get; set; } + } + + class EnumerableStringHolder + { + public IEnumerable Strings { get; set; } + } + + enum Enterprise + { + Kirk, + McCoy, + Scott, + Spock, + Uhura, + Chekov, + Zulu + } + + class EnumerableEnumHolder + { + public IEnumerable Trekkers { get; set; } + } + + class SingleEnumHolder + { + public Enterprise Trekker { get; set; } + } } \ No newline at end of file diff --git a/src/Simple.Web/CodeGeneration/PropertySetterBuilder.cs b/src/Simple.Web/CodeGeneration/PropertySetterBuilder.cs index 5cdab07..452f662 100644 --- a/src/Simple.Web/CodeGeneration/PropertySetterBuilder.cs +++ b/src/Simple.Web/CodeGeneration/PropertySetterBuilder.cs @@ -3,6 +3,7 @@ namespace Simple.Web.CodeGeneration { using System; + using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -46,12 +47,20 @@ private bool PropertyIsPrimitive() public static bool PropertyIsPrimitive(PropertyInfo property) { - return property.PropertyType.IsPrimitive || property.PropertyType == typeof(string) || - property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTimeOffset) || - property.PropertyType == typeof(Guid) || property.PropertyType == typeof(byte[]) || - property.PropertyType.IsEnum || - (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)); - + return TypeIsPrimitive(property.PropertyType); + } + + private static bool TypeIsPrimitive(Type type) + { + return type.IsPrimitive || type == typeof(string) || type == typeof(DateTime) || type == typeof(DateTimeOffset) + || type == typeof(Guid) || type == typeof(byte[]) || type.IsEnum + || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + || IsPrimitiveEnumerable(type); + } + + private static bool IsPrimitiveEnumerable(Type type) + { + return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) && TypeIsPrimitive(type.GetGenericArguments()[0]); } private void CreatePropertyExpressions() @@ -70,49 +79,74 @@ private CatchBlock CreateCatchBlock() private TryExpression CreateTrySimpleAssign() { - var changeTypeMethod = typeof(PropertySetterBuilder).GetMethod("SafeConvert", - BindingFlags.Static | BindingFlags.NonPublic); + var changeTypeMethod = typeof(PropertySetterBuilder).GetMethod( + "SafeConvert", BindingFlags.Static | BindingFlags.NonPublic); MethodCallExpression callConvert; if (_property.PropertyType.IsEnum) { - callConvert = Expression.Call(changeTypeMethod, _itemProperty, - Expression.Constant(_property.PropertyType.GetEnumUnderlyingType())); + callConvert = Expression.Call( + changeTypeMethod, _itemProperty, Expression.Constant(_property.PropertyType.GetEnumUnderlyingType())); } else if (_property.PropertyType.IsNullable()) { - callConvert = Expression.Call(changeTypeMethod, _itemProperty, - Expression.Constant(_property.PropertyType.GetGenericArguments().Single())); + callConvert = Expression.Call( + changeTypeMethod, + _itemProperty, + Expression.Constant(_property.PropertyType.GetGenericArguments().Single())); + } + else if (IsEnumerable(_property.PropertyType)) + { + changeTypeMethod = typeof(PropertySetterBuilder).GetMethod( + "SafeConvertEnumerable", BindingFlags.Static | BindingFlags.NonPublic); + callConvert = Expression.Call( + changeTypeMethod, _itemProperty, Expression.Constant(_property.PropertyType)); } else { - callConvert = Expression.Call(changeTypeMethod, _itemProperty, - Expression.Constant(_property.PropertyType)); + callConvert = Expression.Call( + changeTypeMethod, _itemProperty, Expression.Constant(_property.PropertyType)); } var assign = Expression.Assign(_nameProperty, Expression.Convert(callConvert, _property.PropertyType)); if (_property.PropertyType.IsEnum) { - return Expression.TryCatch( // try { - Expression.IfThenElse(Expression.TypeIs(_itemProperty, typeof(string)), - Expression.Assign(_nameProperty, - Expression.Convert(Expression.Call(typeof(Enum).GetMethod("Parse", new[] { typeof(Type), typeof(string), typeof(bool) }), - Expression.Constant(_property.PropertyType), - Expression.Call(_itemProperty, typeof(object).GetMethod("ToString")), Expression.Constant(true)), _property.PropertyType)), - assign), Expression.Catch(typeof(Exception), Expression.Empty())); + return Expression.TryCatch( + // try { + Expression.IfThenElse( + Expression.TypeIs(_itemProperty, typeof(string)), + Expression.Assign( + _nameProperty, + Expression.Convert( + Expression.Call( + typeof(Enum).GetMethod( + "Parse", new[] { typeof(Type), typeof(string), typeof(bool) }), + Expression.Constant(_property.PropertyType), + Expression.Call(_itemProperty, typeof(object).GetMethod("ToString")), + Expression.Constant(true)), + _property.PropertyType)), + assign), + Expression.Catch(typeof(Exception), Expression.Empty())); } - if (_property.PropertyType == typeof (Guid) || _property.PropertyType == typeof(Guid?)) + if (IsAGuid(_property.PropertyType)) { - return Expression.TryCatch( // try { - Expression.IfThenElse(Expression.TypeIs(_itemProperty, typeof(string)), - Expression.Assign(_nameProperty, - Expression.Convert(Expression.Call(typeof(Guid).GetMethod("Parse", new[] { typeof(string) }), - Expression.Call(_itemProperty, typeof(object).GetMethod("ToString"))), _property.PropertyType)), - assign), Expression.Catch(typeof(Exception), Expression.Empty())); + return Expression.TryCatch( + // try { + Expression.IfThenElse( + Expression.TypeIs(_itemProperty, typeof(string)), + Expression.Assign( + _nameProperty, + Expression.Convert( + Expression.Call( + typeof(Guid).GetMethod("Parse", new[] { typeof(string) }), + Expression.Call(_itemProperty, typeof(object).GetMethod("ToString"))), + _property.PropertyType)), + assign), + Expression.Catch(typeof(Exception), Expression.Empty())); } - return Expression.TryCatch( // try { - assign, - CreateCatchBlock()); + return Expression.TryCatch( + // try { + assign, CreateCatchBlock()); } private static object SafeConvert(object source, Type targetType) @@ -120,6 +154,59 @@ private static object SafeConvert(object source, Type targetType) return ReferenceEquals(source, null) ? null : Convert.ChangeType(source, targetType); } + private static object SafeConvertEnumerable(object source, Type targetType) + { + if (source == null) return null; + + var tmpSource = (string)source; + + var destinationType = targetType.GetGenericArguments()[0]; + var collection = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(destinationType)); + if (!tmpSource.Contains("\t")) + { + // Single value IEnumerable element + collection.Add(Cast(source, destinationType)); + } + else + { + // Multi value IEnumerable element + var parts = tmpSource.Split('\t'); + foreach (var part in parts) + { + collection.Add(Cast(part, destinationType)); + } + } + + return (IEnumerable)collection; + } + + private static object Cast(object value, Type destinationType) + { + if (IsAGuid(destinationType)) + { + var tmp = (string)value; + return Guid.Parse(tmp); + } + + if (destinationType.IsEnum) + { + var tmp = (string)value; + return Enum.Parse(destinationType, tmp, true); + } + return Convert.ChangeType(value, destinationType); + } + + private static bool IsAGuid(Type type) + { + return type == typeof(Guid) || type == typeof(Guid?); + } + + private static bool IsEnumerable(Type type) + { + return typeof(IEnumerable).IsAssignableFrom(type) + && (typeof(string) != type); + } + public static BlockExpression MakePropertySetterBlock(Type type, ParameterExpression variables, ParameterExpression instance, BinaryExpression construct) { @@ -137,11 +224,11 @@ private static object SafeConvert(object source, Type targetType) var block = Expression.Block(type, new[] { instance }, lines); return block; } - + public static BlockExpression MakePropertySetterBlock(Type type, MethodCallExpression getVariables, ParameterExpression instance, BinaryExpression construct) { - var variables = Expression.Variable(typeof (IDictionary)); + var variables = Expression.Variable(typeof(IDictionary)); var lines = new List { construct }; lines.Add(Expression.Assign(variables, getVariables)); diff --git a/src/Simple.Web/Priority.cs b/src/Simple.Web/Priority.cs index a8503e9..068e6b0 100644 --- a/src/Simple.Web/Priority.cs +++ b/src/Simple.Web/Priority.cs @@ -12,26 +12,26 @@ public enum Priority /// /// Higher than high, but not highest. /// - Higher = 0x40000000, + Higher = -0x40000000, /// /// High. /// - High = 0x20000000, + High = -0x20000000, /// /// Normal, the default level. /// - Normal = 0, + Normal = 0, /// /// Low. /// - Low = 0x20000000, + Low = 0x20000000, /// /// Lower than low, but not lowest. /// - Lower = 0x40000000, + Lower = 0x40000000, /// /// The lowest priority. Things here happen last. /// - Lowest = 0x60000000 + Lowest = 0x60000000 } } \ No newline at end of file diff --git a/src/Simple.Web/Routing/MatchData.cs b/src/Simple.Web/Routing/MatchData.cs index 97a5735..f7a7dc3 100644 --- a/src/Simple.Web/Routing/MatchData.cs +++ b/src/Simple.Web/Routing/MatchData.cs @@ -10,7 +10,7 @@ class MatchData private bool _set; private HandlerTypeInfo _single; private List _list; - private HandlerTypeInfo[] _prioritised; + private HandlerTypeInfo[] _prioritised; private IDictionary _variables; public IDictionary Variables @@ -68,7 +68,15 @@ public void SetVariable(string key, string value) { _variables = new Dictionary(StringComparer.OrdinalIgnoreCase); } - _variables[key] = value; + if (_variables.ContainsKey(key)) + { + // Append this value with a delimiter + _variables[key] += "\t" + value; + } + else + { + _variables.Add(key, value); + } } public Type ResolveByMediaTypes(string contentType, IList acceptTypes)