Added support for binding FormCollection
- Added FormCollectionModelBinder - Added relevant unit and functional tests
This commit is contained in:
parent
a33e83f363
commit
088bb18eed
|
|
@ -56,7 +56,6 @@
|
||||||
<input type="file" name="files" id="file1" />
|
<input type="file" name="files" id="file1" />
|
||||||
<input type="file" name="files" id="file2" />
|
<input type="file" name="files" id="file2" />
|
||||||
<input type="submit" value="submit" />
|
<input type="submit" value="submit" />
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Core.Collections;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Modelbinder to bind form values to <see cref="IFormCollection"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class FormCollectionModelBinder : IModelBinder
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<bool> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
if (bindingContext.ModelType != typeof(IFormCollection) &&
|
||||||
|
bindingContext.ModelType != typeof(FormCollection))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||||
|
if (request.HasFormContentType)
|
||||||
|
{
|
||||||
|
var form = await request.ReadFormAsync();
|
||||||
|
if (bindingContext.ModelType.IsAssignableFrom(form.GetType()))
|
||||||
|
{
|
||||||
|
bindingContext.Model = form;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var formValuesLookup = form.ToDictionary(p => p.Key,
|
||||||
|
p => p.Value);
|
||||||
|
bindingContext.Model = new FormCollection(formValuesLookup, form.Files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bindingContext.Model = new FormCollection(new Dictionary<string, string[]>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"warningsAsErrors": true
|
"warningsAsErrors": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Http": "1.0.0-*",
|
"Microsoft.AspNet.Http.Core": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
options.ModelBinders.Add(new CancellationTokenModelBinder());
|
options.ModelBinders.Add(new CancellationTokenModelBinder());
|
||||||
options.ModelBinders.Add(new ByteArrayModelBinder());
|
options.ModelBinders.Add(new ByteArrayModelBinder());
|
||||||
options.ModelBinders.Add(new FormFileModelBinder());
|
options.ModelBinders.Add(new FormFileModelBinder());
|
||||||
|
options.ModelBinders.Add(new FormCollectionModelBinder());
|
||||||
options.ModelBinders.Add(typeof(GenericModelBinder));
|
options.ModelBinders.Add(typeof(GenericModelBinder));
|
||||||
options.ModelBinders.Add(new MutableObjectModelBinder());
|
options.ModelBinders.Add(new MutableObjectModelBinder());
|
||||||
options.ModelBinders.Add(new ComplexModelDtoModelBinder());
|
options.ModelBinders.Add(new ComplexModelDtoModelBinder());
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Core.Collections;
|
||||||
using Microsoft.AspNet.TestHost;
|
using Microsoft.AspNet.TestHost;
|
||||||
using ModelBindingWebSite;
|
using ModelBindingWebSite;
|
||||||
using ModelBindingWebSite.ViewModels;
|
using ModelBindingWebSite.ViewModels;
|
||||||
|
|
@ -1530,6 +1532,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(expectedContent, body);
|
Assert.Equal(expectedContent, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
public async Task ModelBinder_FormatsDontMatch_ThrowsUserFriendlyException()
|
public async Task ModelBinder_FormatsDontMatch_ThrowsUserFriendlyException()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -1601,5 +1604,93 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var dictionary = JsonConvert.DeserializeObject<IDictionary<string, string>>(responseContent);
|
var dictionary = JsonConvert.DeserializeObject<IDictionary<string, string>>(responseContent);
|
||||||
Assert.Equal(expectedDictionary, dictionary);
|
Assert.Equal(expectedDictionary, dictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FormCollectionModelBinder_CanBind_FormValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var url = "http://localhost/FormCollection/ReturnValuesAsList";
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("field1", "value1"),
|
||||||
|
new KeyValuePair<string, string>("field2", "value2"),
|
||||||
|
};
|
||||||
|
var formData = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsync(url, formData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var valuesList = JsonConvert.DeserializeObject<IList<string>>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Equal(new List<string> { "value1", "value2" }, valuesList);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FormCollectionModelBinder_CanBind_FormValuesWithDuplicateKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var url = "http://localhost/FormCollection/ReturnValuesAsList";
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("field1", "value1"),
|
||||||
|
new KeyValuePair<string, string>("field2", "value2"),
|
||||||
|
new KeyValuePair<string, string>("field1", "value3"),
|
||||||
|
};
|
||||||
|
var formData = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsync(url, formData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var valuesList = JsonConvert.DeserializeObject<IList<string>>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Equal(new List<string> { "value1,value3", "value2" }, valuesList);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FormCollectionModelBinder_CannotBind_NonFormValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var url = "http://localhost/FormCollection/ReturnCollectionCount";
|
||||||
|
var data = new StringContent("Non form content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsync(url, data);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var collectionCount = JsonConvert.DeserializeObject<int>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Equal(0, collectionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FormCollectionModelBinder_CanBind_FormWithFile()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var url = "http://localhost/FormCollection/ReturnFileContent";
|
||||||
|
var expectedContent = "Test Content";
|
||||||
|
var formData = new MultipartFormDataContent("Upload----");
|
||||||
|
formData.Add(new StringContent(expectedContent), "File", "test.txt");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsync(url, formData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var fileContent = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expectedContent, fileContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
#if ASPNET50
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Core.Collections;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public class FormCollectionModelBinderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task FormCollectionModelBinder_ValidType_BindSuccessful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formCollection = new FormCollection(new Dictionary<string, string[]>
|
||||||
|
{
|
||||||
|
{ "field1", new string[] { "value1" } },
|
||||||
|
{ "field2", new string[] { "value2" } }
|
||||||
|
});
|
||||||
|
var httpContext = GetMockHttpContext(formCollection);
|
||||||
|
var bindingContext = GetBindingContext(typeof(FormCollection), httpContext);
|
||||||
|
var binder = new FormCollectionModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
var form = Assert.IsAssignableFrom<IFormCollection>(bindingContext.Model);
|
||||||
|
Assert.Equal(2, form.Count);
|
||||||
|
Assert.Equal("value1", form["field1"]);
|
||||||
|
Assert.Equal("value2", form["field2"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FormCollectionModelBinder_InvalidType_BindFails()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formCollection = new FormCollection(new Dictionary<string, string[]>
|
||||||
|
{
|
||||||
|
{ "field1", new string[] { "value1" } },
|
||||||
|
{ "field2", new string[] { "value2" } }
|
||||||
|
});
|
||||||
|
var httpContext = GetMockHttpContext(formCollection);
|
||||||
|
var bindingContext = GetBindingContext(typeof(string), httpContext);
|
||||||
|
var binder = new FormCollectionModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FormCollectionModelBinder_NoForm_BindSuccessful_ReturnsEmptyFormCollection()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = GetMockHttpContext(null, hasForm: false);
|
||||||
|
var bindingContext = GetBindingContext(typeof(IFormCollection), httpContext);
|
||||||
|
var binder = new FormCollectionModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.IsType(typeof(FormCollection), bindingContext.Model);
|
||||||
|
Assert.Empty((FormCollection)bindingContext.Model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FormCollectionModelBinder_CustomFormCollection_BindSuccessful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formCollection = new MyFormCollection(new Dictionary<string, string[]>
|
||||||
|
{
|
||||||
|
{ "field1", new string[] { "value1" } },
|
||||||
|
{ "field2", new string[] { "value2" } }
|
||||||
|
});
|
||||||
|
var httpContext = GetMockHttpContext(formCollection);
|
||||||
|
var bindingContext = GetBindingContext(typeof(FormCollection), httpContext);
|
||||||
|
var binder = new FormCollectionModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
var form = Assert.IsAssignableFrom<IFormCollection>(bindingContext.Model);
|
||||||
|
Assert.Equal(2, form.Count);
|
||||||
|
Assert.Equal("value1", form["field1"]);
|
||||||
|
Assert.Equal("value2", form["field2"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpContext GetMockHttpContext(IFormCollection formCollection, bool hasForm = true)
|
||||||
|
{
|
||||||
|
var httpContext = new Mock<HttpContext>();
|
||||||
|
httpContext.Setup(h => h.Request.ReadFormAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(formCollection));
|
||||||
|
httpContext.Setup(h => h.Request.HasFormContentType).Returns(hasForm);
|
||||||
|
return httpContext.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelBindingContext GetBindingContext(Type modelType, HttpContext httpContext)
|
||||||
|
{
|
||||||
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
|
||||||
|
ModelName = "file",
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ModelBinder = new FormCollectionModelBinder(),
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
HttpContext = httpContext,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return bindingContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyFormCollection : ReadableStringCollection, IFormCollection
|
||||||
|
{
|
||||||
|
public MyFormCollection(IDictionary<string, string[]> store) : this(store, new FormFileCollection())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyFormCollection(IDictionary<string, string[]> store, IFormFileCollection files) : base(store)
|
||||||
|
{
|
||||||
|
Files = files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFormFileCollection Files { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var i = 0;
|
var i = 0;
|
||||||
Assert.Equal(12, mvcOptions.ModelBinders.Count);
|
Assert.Equal(13, mvcOptions.ModelBinders.Count);
|
||||||
Assert.Equal(typeof(BinderTypeBasedModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(BinderTypeBasedModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
Assert.Equal(typeof(ServicesModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(ServicesModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
Assert.Equal(typeof(BodyModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(BodyModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
|
|
@ -49,6 +49,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
Assert.Equal(typeof(FormFileModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(FormFileModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
|
Assert.Equal(typeof(FormCollectionModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
Assert.Equal(typeof(GenericModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(GenericModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
Assert.Equal(typeof(MutableObjectModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(MutableObjectModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
Assert.Equal(typeof(ComplexModelDtoModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
Assert.Equal(typeof(ComplexModelDtoModelBinder), mvcOptions.ModelBinders[i++].OptionType);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Core.Collections;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class FormCollectionController : Controller
|
||||||
|
{
|
||||||
|
public IList<string> ReturnValuesAsList(IFormCollection form)
|
||||||
|
{
|
||||||
|
var valuesList = new List<string>();
|
||||||
|
|
||||||
|
valuesList.Add(form["field1"]);
|
||||||
|
valuesList.Add(form["field2"]);
|
||||||
|
|
||||||
|
return valuesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ReturnCollectionCount(IFormCollection form)
|
||||||
|
{
|
||||||
|
return form.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionResult ReturnFileContent(FormCollection form)
|
||||||
|
{
|
||||||
|
var file = form.Files.GetFile("File");
|
||||||
|
using (var reader = new StreamReader(file.OpenReadStream()))
|
||||||
|
{
|
||||||
|
var fileContent = reader.ReadToEnd();
|
||||||
|
|
||||||
|
return Content(fileContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue