Skip to content

Commit

Permalink
Add Bind support for form
Browse files Browse the repository at this point in the history
  • Loading branch information
jchannon committed Feb 26, 2018
1 parent 17934b8 commit 4210b36
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/Botwin.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<VersionPrefix>3.2.0</VersionPrefix>
<VersionPrefix>3.3.0</VersionPrefix>
<Authors>Jonathan Channon</Authors>
<Description>Botwin is a library that allows Nancy-esque routing for use with ASP.Net Core.</Description>
<PackageTags>asp.net core;nancy;.net core;routing;botwin</PackageTags>
Expand Down
29 changes: 27 additions & 2 deletions src/ModelBinding/BindExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ namespace Botwin.ModelBinding
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Botwin.Request;
using FluentValidation.Results;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public static class BindExtensions
{
Expand All @@ -28,7 +29,7 @@ public static (ValidationResult ValidationResult, T Data) BindAndValidate<T>(thi
model = Activator.CreateInstance<T>();
}

var validationResult = request.Validate(model);
var validationResult = request.Validate(model);
return (validationResult, model);
}

Expand All @@ -40,6 +41,30 @@ public static (ValidationResult ValidationResult, T Data) BindAndValidate<T>(thi
/// <returns>Bound model</returns>
public static T Bind<T>(this HttpRequest request)
{
if (request.HasFormContentType)
{
var res = JObject.FromObject(request.Form.ToDictionary(key => key.Key, val =>
{
var type = typeof(T);
var propertyType = type.GetProperty(val.Key).PropertyType;
if (propertyType.IsArray() || propertyType.IsCollection() || propertyType.IsEnumerable())
{
var colType = propertyType.GetElementType();
if (colType == null)
{
colType = propertyType.GetGenericArguments().FirstOrDefault();
}
return val.Value.Select(y => Convert.ChangeType(y, colType));
}
//int, double etc
return Convert.ChangeType(val.Value[0], propertyType);
}));

var instance = res.ToObject<T>();
return instance;
}

using (var streamReader = new StreamReader(request.Body))
using (var jsonTextReader = new JsonTextReader(streamReader))
{
Expand Down
26 changes: 24 additions & 2 deletions src/Request/ConvertExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.Linq;

internal static class ConvertExtensions
{
Expand All @@ -12,7 +13,7 @@ public static T ConvertTo<T>(this object value)

if (underlyingType != null)
{
if (value == null) return default(T);
if (value == null) return default;

type = underlyingType;
}
Expand All @@ -33,10 +34,31 @@ public static IEnumerable<T> ConvertMultipleTo<T>(this IEnumerable<string> value
foreach (var value in values)
{
if (value == null)
yield return default(T);
yield return default;
else
yield return (T)Convert.ChangeType(value, type);
}
}

public static bool IsArray(this Type source)
{
return source.BaseType == typeof(Array);
}

public static bool IsCollection(this Type source)
{
var collectionType = typeof(ICollection<>);

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

public static bool IsEnumerable(this Type source)
{
var enumerableType = typeof(IEnumerable<>);

return source.IsGenericType && source.GetGenericTypeDefinition() == enumerableType;
}
}
}
2 changes: 1 addition & 1 deletion template/BotwinTemplate.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package>
<metadata>
<id>BotwinTemplate</id>
<version>3.2.0</version>
<version>3.3.0</version>
<title>Botwin Template for dotnet-new</title>
<summary>A dotnet-new template for Botwin applications.</summary>
<description>A dotnet-new template for Botwin applications.</description>
Expand Down
2 changes: 1 addition & 1 deletion template/content/BotwinTemplate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.0.0" />
<PackageReference Include="Botwin" Version="3.2.0" />
<PackageReference Include="Botwin" Version="3.3.0" />
</ItemGroup>
</Project>
51 changes: 49 additions & 2 deletions test/Botwin.Tests/BindTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public async Task Should_return_validation_failure_result_when_no_validator_foun
[Fact]
public async Task Should_throw_exception_when_multiple_validators_found()
{
var ex = await Record.ExceptionAsync(async () => await this.httpClient.PostAsync("/duplicatevalidator", new StringContent("{\"MyIntProperty\":\"-1\",\"MyStringProperty\":\"\"}", Encoding.UTF8, "application/json")));
var ex = await Record.ExceptionAsync(async () =>
await this.httpClient.PostAsync("/duplicatevalidator", new StringContent("{\"MyIntProperty\":\"-1\",\"MyStringProperty\":\"\"}", Encoding.UTF8, "application/json")));

Assert.IsType<InvalidOperationException>(ex);
}
Expand Down Expand Up @@ -186,13 +187,56 @@ public async Task Should_create_file_with_custom_filename()
Directory.Delete(model.Path, true);
}
}

[Fact]
public async Task Should_bind_form_data()
{
//Given
var res = await this.httpClient.PostAsync("/bindandvalidate",
new FormUrlEncodedContent(
new[]
{
new KeyValuePair<string, string>("MyIntProperty", "1"),
new KeyValuePair<string, string>("MyStringProperty", "hi there"),
new KeyValuePair<string, string>("MyDoubleProperty", "2.3"),
new KeyValuePair<string, string>("MyArrayProperty", "1"),
new KeyValuePair<string, string>("MyArrayProperty", "2"),
new KeyValuePair<string, string>("MyArrayProperty", "3"),
new KeyValuePair<string, string>("MyIntArrayProperty", "1"),
new KeyValuePair<string, string>("MyIntArrayProperty", "2"),
new KeyValuePair<string, string>("MyIntArrayProperty", "3"),
new KeyValuePair<string, string>("MyIntListProperty", "1"),
new KeyValuePair<string, string>("MyIntListProperty", "2"),
new KeyValuePair<string, string>("MyIntListProperty", "3")
}));

//When
var body = await res.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<TestModel>(body);

//Then
Assert.Equal(1, model.MyIntProperty);
Assert.Equal("hi there", model.MyStringProperty);
Assert.Equal(2.3, model.MyDoubleProperty);
Assert.Equal(new[] { "1", "2", "3" }, model.MyArrayProperty);
Assert.Equal(Enumerable.Range(1,3), model.MyIntArrayProperty);
Assert.Equal(Enumerable.Range(1,3), model.MyIntListProperty);
}
}

public class TestModel
{
public int MyIntProperty { get; set; }

public string MyStringProperty { get; set; }

public double MyDoubleProperty { get; set; }

public string[] MyArrayProperty { get; set; }

public IEnumerable<int> MyIntArrayProperty { get; set; }

public IEnumerable<int> MyIntListProperty { get; set; }
}

public class TestModelValidator : AbstractValidator<TestModel>
Expand Down Expand Up @@ -246,6 +290,7 @@ public BindModule()
await res.Negotiate(model.ValidationResult.Errors);
return;
}
await res.Negotiate(model.Data);
});

Expand All @@ -257,6 +302,7 @@ public BindModule()
await res.Negotiate(model.ValidationResult.Errors.Select(x => new { x.PropertyName, x.ErrorMessage }));
return;
}
await res.Negotiate(model);
});

Expand All @@ -268,6 +314,7 @@ public BindModule()
await res.Negotiate(model.ValidationResult.Errors.Select(x => new { x.PropertyName, x.ErrorMessage }));
return;
}
await res.Negotiate(model.Data);
});

Expand All @@ -290,4 +337,4 @@ public BindModule()
});
}
}
}
}

0 comments on commit 4210b36

Please sign in to comment.