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:
parent
6a824a4394
commit
437eb93bde
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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 »</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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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-*",
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue