File upload model binder

- Support for binding posted file to type IFormFile
- Support for multipart/form-data in FormValueProviderFactory
- Updated Mvc Sample
- Added relevant unit and functional tests
This commit is contained in:
Ajay Bhargav Baaskaran 2014-12-08 12:56:46 -08:00
parent 6a824a4394
commit 437eb93bde
17 changed files with 587 additions and 55 deletions

View File

@ -2,7 +2,10 @@
// 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 System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
@ -89,6 +92,26 @@ namespace MvcSample.Web
return View("MyView", user);
}
[Activate]
public IHostingEnvironment HostingEnvironment { get; set; }
/// <summary>
/// Action that shows multiple file upload.
/// </summary>
public async Task<ActionResult> PostFile(IList<IFormFile> files)
{
if (!ModelState.IsValid)
{
return View("MyView");
}
foreach (var f in files)
{
await f.SaveAsAsync(Path.Combine(HostingEnvironment.WebRoot, "test-file" + files.IndexOf(f)));
}
return View();
}
/// <summary>
/// Action that exercises input formatter
/// </summary>

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>File Upload Successful</title>
</head>
<body>
<h2>File upload successful.</h2>
</body>
</html>

View File

@ -51,6 +51,13 @@
<h1>ASP.NET</h1>
<p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
<p><a href="http://asp.net" class="btn btn-primary btn-large">Learn more &raquo;</a></p>
File Upload Demo: <br/>
<form action="Home/PostFile" method="post" enctype="multipart/form-data">
<input type="file" name="files" id="file1" />
<input type="file" name="files" id="file2" />
<input type="submit" value="submit" />
</form>
</div>
<div class="row">
<h3 title="@(Model?.Name)" class="@nullValue">Hello @Html.DisplayTextFor(User => User)! Happy @(Model?.Age) birthday.</h3>

View File

@ -0,0 +1,81 @@
// 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.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// Modelbinder to bind posted files to <see cref="IFormFile"/>.
/// </summary>
public class FormFileModelBinder : IModelBinder
{
/// <inheritdoc />
public async Task<bool> BindModelAsync([NotNull] ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(IFormFile))
{
var postedFiles = await GetFormFilesAsync(bindingContext);
var value = postedFiles.FirstOrDefault();
if (value != null)
{
bindingContext.Model = value;
}
return true;
}
else if (typeof(IEnumerable<IFormFile>).GetTypeInfo().IsAssignableFrom(
bindingContext.ModelType.GetTypeInfo()))
{
var postedFiles = await GetFormFilesAsync(bindingContext);
var value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles);
if (value != null)
{
bindingContext.Model = value;
}
return true;
}
return false;
}
private async Task<List<IFormFile>> GetFormFilesAsync(ModelBindingContext bindingContext)
{
var request = bindingContext.OperationBindingContext.HttpContext.Request;
var postedFiles = new List<IFormFile>();
if (request.HasFormContentType)
{
var form = await request.ReadFormAsync();
foreach (var file in form.Files)
{
ContentDispositionHeaderValue parsedContentDisposition;
ContentDispositionHeaderValue.TryParse(file.ContentDisposition, out parsedContentDisposition);
// If there is an <input type="file" ... /> in the form and is left blank.
if (parsedContentDisposition == null ||
(file.Length == 0 && string.IsNullOrEmpty(parsedContentDisposition.FileName)))
{
continue;
}
var modelName = parsedContentDisposition.Name;
if (modelName.Equals(bindingContext.ModelName, StringComparison.OrdinalIgnoreCase))
{
postedFiles.Add(file);
}
}
}
return postedFiles;
}
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -31,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var values = request.Headers.GetCommaSeparatedValues(bindingContext.ModelName);
if (values != null)
{
bindingContext.Model = ConvertValuesToCollectionType(bindingContext.ModelType, values);
bindingContext.Model = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, values);
}
return Task.FromResult(true);
@ -39,43 +40,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return Task.FromResult(false);
}
private object ConvertValuesToCollectionType(Type modelType, IList<string> values)
{
// There's a limited set of collection types we can support here.
//
// For the simple cases - choose a string[] or List<string> if the destination type supports
// it.
//
// For more complex cases, if the destination type is a class and implements ICollection<string>
// then activate it and add the values.
//
// Otherwise just give up.
if (typeof(List<string>).IsAssignableFrom(modelType))
{
return new List<string>(values);
}
else if (typeof(string[]).IsAssignableFrom(modelType))
{
return values.ToArray();
}
else if (
modelType.GetTypeInfo().IsClass &&
!modelType.GetTypeInfo().IsAbstract &&
typeof(ICollection<string>).IsAssignableFrom(modelType))
{
var result = (ICollection<string>)Activator.CreateInstance(modelType);
foreach (var value in values)
{
result.Add(value);
}
return result;
}
else
{
return null;
}
}
}
}

View File

@ -2,7 +2,9 @@
// 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.Globalization;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
@ -117,5 +119,47 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
}
return false;
}
public static object ConvertValuesToCollectionType<T>(Type modelType, IList<T> values)
{
// There's a limited set of collection types we can support here.
//
// For the simple cases - choose a T[] or List<T> if the destination type supports
// it.
//
// For more complex cases, if the destination type is a class and implements ICollection<T>
// then activate it and add the values.
//
// Otherwise just give up.
if (typeof(List<T>).IsAssignableFrom(modelType))
{
return new List<T>(values);
}
else if (typeof(T[]).IsAssignableFrom(modelType))
{
return values.ToArray();
}
else if (
modelType.GetTypeInfo().IsClass &&
!modelType.GetTypeInfo().IsAbstract &&
typeof(ICollection<T>).IsAssignableFrom(modelType))
{
var result = (ICollection<T>)Activator.CreateInstance(modelType);
foreach (var value in values)
{
result.Add(value);
}
return result;
}
else if (typeof(IEnumerable<T>).IsAssignableFrom(modelType))
{
return values;
}
else
{
return null;
}
}
}
}

View File

@ -3,22 +3,19 @@
using System.Globalization;
using Microsoft.AspNet.Http;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class FormValueProviderFactory : IValueProviderFactory
{
private static MediaTypeHeaderValue _formEncodedContentType =
MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context)
{
var request = context.HttpContext.Request;
if (IsSupportedContentType(request))
if (request.HasFormContentType)
{
var culture = GetCultureInfo(request);
return new ReadableStringCollectionValueProvider<IFormDataValueProviderMetadata>(
async () => await request.ReadFormAsync(),
culture);
@ -27,13 +24,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return null;
}
private bool IsSupportedContentType(HttpRequest request)
{
MediaTypeHeaderValue requestContentType = null;
return MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType) &&
_formEncodedContentType.IsSubsetOf(requestContentType);
}
private static CultureInfo GetCultureInfo(HttpRequest request)
{
return CultureInfo.CurrentCulture;

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private IReadableStringCollection _values;
/// <summary>
/// Creates a NameValuePairsProvider wrapping an existing set of key value pairs.
/// Creates a provider for <see cref="IReadableStringCollection"/> wrapping an existing set of key value pairs.
/// </summary>
/// <param name="values">The key value pairs to wrap.</param>
/// <param name="culture">The culture to return with ValueProviderResult instances.</param>
@ -31,6 +31,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
_culture = culture;
}
/// <summary>
/// Creates a provider for <see cref="IReadableStringCollection"/> wrapping an
/// existing set of key value pairs provided by the delegate.
/// </summary>
/// <param name="values">The delegate that provides the key value pairs to wrap.</param>
/// <param name="culture">The culture to return with ValueProviderResult instances.</param>
public ReadableStringCollectionValueProvider([NotNull] Func<Task<IReadableStringCollection>> valuesFactory,
CultureInfo culture)
{
@ -46,18 +52,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public override async Task<bool> ContainsPrefixAsync(string prefix)
{
var prefixContainer = await GetPrefixContainerAsync();
return prefixContainer.ContainsPrefix(prefix);
}
/// <inheritdoc />
public virtual async Task<IDictionary<string, string>> GetKeysFromPrefixAsync([NotNull] string prefix)
{
var prefixContainer = await GetPrefixContainerAsync();
return prefixContainer.GetKeysFromPrefix(prefix);
}
/// <inheritdoc />
public override async Task<ValueProviderResult> GetValueAsync([NotNull] string key)
{
var collection = await GetValueCollectionAsync();

View File

@ -6,6 +6,7 @@
},
"dependencies": {
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.Net.Http.Headers": "1.0.0-*",

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNet.Mvc
options.ModelBinders.Add(new TypeMatchModelBinder());
options.ModelBinders.Add(new CancellationTokenModelBinder());
options.ModelBinders.Add(new ByteArrayModelBinder());
options.ModelBinders.Add(new FormFileModelBinder());
options.ModelBinders.Add(typeof(GenericModelBinder));
options.ModelBinders.Add(new MutableObjectModelBinder());
options.ModelBinders.Add(new ComplexModelDtoModelBinder());

View File

@ -7,7 +7,6 @@ using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@ -1327,5 +1326,111 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var body = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedContent, body);
}
[Fact]
public async Task FormFileModelBinder_CanBind_SingleFile()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
var url = "http://localhost/FileUpload/UploadSingle";
var formData = new MultipartFormDataContent("Upload----");
formData.Add(new StringContent("Test Content"), "file", "test.txt");
// Act
var response = await client.PostAsync(url, formData);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var fileDetails = JsonConvert.DeserializeObject<FileDetails>(
await response.Content.ReadAsStringAsync());
Assert.Equal("test.txt", fileDetails.Filename);
Assert.Equal("Test Content", fileDetails.Content);
}
[Fact]
public async Task FormFileModelBinder_CanBind_MultipleFiles()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
var url = "http://localhost/FileUpload/UploadMultiple";
var formData = new MultipartFormDataContent("Upload----");
formData.Add(new StringContent("Test Content 1"), "files", "test1.txt");
formData.Add(new StringContent("Test Content 2"), "files", "test2.txt");
// Act
var response = await client.PostAsync(url, formData);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var fileDetailsArray = JsonConvert.DeserializeObject<FileDetails[]>(
await response.Content.ReadAsStringAsync());
Assert.Equal(2, fileDetailsArray.Length);
Assert.Equal("test1.txt", fileDetailsArray[0].Filename);
Assert.Equal("Test Content 1", fileDetailsArray[0].Content);
Assert.Equal("test2.txt", fileDetailsArray[1].Filename);
Assert.Equal("Test Content 2", fileDetailsArray[1].Content);
}
[Fact]
public async Task FormFileModelBinder_CanBind_MultipleListOfFiles()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
var url = "http://localhost/FileUpload/UploadMultipleList";
var formData = new MultipartFormDataContent("Upload----");
formData.Add(new StringContent("Test Content 1"), "filelist1", "test1.txt");
formData.Add(new StringContent("Test Content 2"), "filelist1", "test2.txt");
formData.Add(new StringContent("Test Content 3"), "filelist2", "test3.txt");
formData.Add(new StringContent("Test Content 4"), "filelist2", "test4.txt");
// Act
var response = await client.PostAsync(url, formData);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var fileDetailsLookup = JsonConvert.DeserializeObject<IDictionary<string, IList<FileDetails>>>(
await response.Content.ReadAsStringAsync());
Assert.Equal(2, fileDetailsLookup.Count);
var fileDetailsList1 = fileDetailsLookup["filelist1"];
var fileDetailsList2 = fileDetailsLookup["filelist2"];
Assert.Equal(2, fileDetailsList1.Count);
Assert.Equal(2, fileDetailsList2.Count);
Assert.Equal("test1.txt", fileDetailsList1[0].Filename);
Assert.Equal("Test Content 1", fileDetailsList1[0].Content);
Assert.Equal("test2.txt", fileDetailsList1[1].Filename);
Assert.Equal("Test Content 2", fileDetailsList1[1].Content);
Assert.Equal("test3.txt", fileDetailsList2[0].Filename);
Assert.Equal("Test Content 3", fileDetailsList2[0].Content);
Assert.Equal("test4.txt", fileDetailsList2[1].Filename);
Assert.Equal("Test Content 4", fileDetailsList2[1].Content);
}
[Fact]
public async Task FormFileModelBinder_CanBind_FileInsideModel()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
var url = "http://localhost/FileUpload/UploadModelWithFile";
var formData = new MultipartFormDataContent("Upload----");
formData.Add(new StringContent("Test Book"), "Name");
formData.Add(new StringContent("Test Content"), "File", "test.txt");
// Act
var response = await client.PostAsync(url, formData);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var book = JsonConvert.DeserializeObject<KeyValuePair<string, FileDetails>>(
await response.Content.ReadAsStringAsync());
var bookName = book.Key;
var fileDetails = book.Value;
Assert.Equal("Test Book", bookName);
Assert.Equal("test.txt", fileDetails.Filename);
Assert.Equal("Test Content", fileDetails.Content);
}
}
}

View File

@ -0,0 +1,171 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.PipelineCore.Collections;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class FormFileModelBinderTest
{
[Fact]
public async Task FormFileModelBinder_ExpectMultipleFiles_BindSuccessful()
{
// Arrange
var formFiles = new FormFileCollection();
formFiles.Add(GetMockFormFile("file", "file1.txt"));
formFiles.Add(GetMockFormFile("file", "file2.txt"));
var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles));
var bindingContext = GetBindingContext(typeof(IEnumerable<IFormFile>), httpContext);
var binder = new FormFileModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(result);
var files = Assert.IsAssignableFrom<IList<IFormFile>>(bindingContext.Model);
Assert.Equal(2, files.Count);
}
[Fact]
public async Task FormFileModelBinder_ExpectSingleFile_BindFirstFile()
{
// Arrange
var formFiles = new FormFileCollection();
formFiles.Add(GetMockFormFile("file", "file1.txt"));
formFiles.Add(GetMockFormFile("file", "file2.txt"));
var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles));
var bindingContext = GetBindingContext(typeof(IFormFile), httpContext);
var binder = new FormFileModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(result);
var file = Assert.IsAssignableFrom<IFormFile>(bindingContext.Model);
Assert.Equal("form-data; name=file; filename=file1.txt",
file.ContentDisposition);
}
[Fact]
public async Task FormFileModelBinder_ReturnsNull_WhenNoFilePosted()
{
// Arrange
var formFiles = new FormFileCollection();
var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles));
var bindingContext = GetBindingContext(typeof(IFormFile), httpContext);
var binder = new FormFileModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(result);
Assert.Null(bindingContext.Model);
}
[Fact]
public async Task FormFileModelBinder_ReturnsNull_WhenNamesDontMatch()
{
// Arrange
var formFiles = new FormFileCollection();
formFiles.Add(GetMockFormFile("different name", "file1.txt"));
var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles));
var bindingContext = GetBindingContext(typeof(IFormFile), httpContext);
var binder = new FormFileModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(result);
Assert.Null(bindingContext.Model);
}
[Fact]
public async Task FormFileModelBinder_ReturnsNull_WithEmptyContentDisposition()
{
// Arrange
var formFiles = new FormFileCollection();
formFiles.Add(new Mock<IFormFile>().Object);
var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles));
var bindingContext = GetBindingContext(typeof(IFormFile), httpContext);
var binder = new FormFileModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(result);
Assert.Null(bindingContext.Model);
}
[Fact]
public async Task FormFileModelBinder_ReturnsNull_WithNoFileNameAndZeroLength()
{
// Arrange
var formFiles = new FormFileCollection();
formFiles.Add(GetMockFormFile("file", ""));
var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles));
var bindingContext = GetBindingContext(typeof(IFormFile), httpContext);
var binder = new FormFileModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(result);
Assert.Null(bindingContext.Model);
}
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 FormFileModelBinder(),
MetadataProvider = metadataProvider,
HttpContext = httpContext,
}
};
return bindingContext;
}
private static HttpContext GetMockHttpContext(IFormCollection formCollection)
{
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(true);
return httpContext.Object;
}
private static IFormCollection GetMockFormCollection(FormFileCollection formFiles)
{
var formCollection = new Mock<IFormCollection>();
formCollection.Setup(f => f.Files).Returns(formFiles);
return formCollection.Object;
}
private static IFormFile GetMockFormFile(string modelName, string filename)
{
var formFile = new Mock<IFormFile>();
formFile.Setup(f => f.ContentDisposition)
.Returns(string.Format("form-data; name={0}; filename={1}", modelName, filename));
return formFile.Object;
}
}
}

View File

@ -8,6 +8,7 @@ using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.PipelineCore;
using Moq;
using Xunit;
@ -32,7 +33,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
[Theory]
[InlineData("application/x-www-form-urlencoded")]
[InlineData("application/x-www-form-urlencoded;charset=utf-8")]
public void GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture(string contentType)
[InlineData("multipart/form-data")]
[InlineData("multipart/form-data;charset=utf-8")]
public void GetValueProvider_ReturnsValueProviderInstanceWithInvariantCulture(string contentType)
{
// Arrange
var context = CreateContext(contentType);
@ -52,6 +55,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var request = new Mock<HttpRequest>();
request.Setup(f => f.ReadFormAsync(CancellationToken.None)).Returns(Task.FromResult(collection));
request.SetupGet(r => r.ContentType).Returns(contentType);
request.SetupGet(r => r.HasFormContentType).Returns(new FormFeature(request.Object).HasFormContentType);
var context = new Mock<HttpContext>();
context.SetupGet(c => c.Request).Returns(request.Object);

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc
// Assert
var i = 0;
Assert.Equal(11, mvcOptions.ModelBinders.Count);
Assert.Equal(12, mvcOptions.ModelBinders.Count);
Assert.Equal(typeof(BinderTypeBasedModelBinder), mvcOptions.ModelBinders[i++].OptionType);
Assert.Equal(typeof(ServicesModelBinder), mvcOptions.ModelBinders[i++].OptionType);
Assert.Equal(typeof(BodyModelBinder), mvcOptions.ModelBinders[i++].OptionType);
@ -48,6 +48,7 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(typeof(TypeMatchModelBinder), mvcOptions.ModelBinders[i++].OptionType);
Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[i++].OptionType);
Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[i++].OptionType);
Assert.Equal(typeof(FormFileModelBinder), mvcOptions.ModelBinders[i++].OptionType);
Assert.Equal(typeof(GenericModelBinder), mvcOptions.ModelBinders[i++].OptionType);
Assert.Equal(typeof(MutableObjectModelBinder), mvcOptions.ModelBinders[i++].OptionType);
Assert.Equal(typeof(ComplexModelDtoModelBinder), mvcOptions.ModelBinders[i++].OptionType);

View File

@ -0,0 +1,95 @@
// 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 System.Linq;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Http;
using Microsoft.Net.Http.Headers;
namespace ModelBindingWebSite.Controllers
{
public class FileUploadController : Controller
{
public FileDetails UploadSingle(IFormFile file)
{
FileDetails fileDetails;
using (var reader = new StreamReader(file.OpenReadStream()))
{
var fileContent = reader.ReadToEnd();
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
}
return fileDetails;
}
public FileDetails[] UploadMultiple(IEnumerable<IFormFile> files)
{
var fileDetailsList = new List<FileDetails>();
foreach (var file in files)
{
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
using (var reader = new StreamReader(file.OpenReadStream()))
{
var fileContent = reader.ReadToEnd();
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
fileDetailsList.Add(fileDetails);
}
}
return fileDetailsList.ToArray();
}
public IDictionary<string, IList<FileDetails>> UploadMultipleList(IEnumerable<IFormFile> filelist1,
IEnumerable<IFormFile> filelist2)
{
var fileDetailsDict = new Dictionary<string, IList<FileDetails>>
{
{ "filelist1", new List<FileDetails>() },
{ "filelist2", new List<FileDetails>() }
};
var fileDetailsList = new List<FileDetails>();
foreach (var file in filelist1.Concat(filelist2))
{
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
using (var reader = new StreamReader(file.OpenReadStream()))
{
var fileContent = reader.ReadToEnd();
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
fileDetailsDict[parsedContentDisposition.Name].Add(fileDetails);
}
}
return fileDetailsDict;
}
public KeyValuePair<string, FileDetails> UploadModelWithFile(Book book)
{
var file = book.File;
var reader = new StreamReader(file.OpenReadStream());
var fileContent = reader.ReadToEnd();
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
return new KeyValuePair<string, FileDetails>(book.Name, fileDetails);
}
}
}

View File

@ -0,0 +1,14 @@
// 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 Microsoft.AspNet.Http;
namespace ModelBindingWebSite
{
public class Book
{
public string Name { get; set; }
public IFormFile File { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// 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.
namespace ModelBindingWebSite
{
public class FileDetails
{
public string Filename { get; set; }
public string Content { get; set; }
}
}