aspnetcore/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormFileModelBinder.cs

119 lines
4.7 KiB
C#

// Copyright (c) .NET Foundation. 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.Diagnostics;
using System.Linq;
#if DNXCORE50
using System.Reflection;
#endif
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// <see cref="IModelBinder"/> implementation to bind posted files to <see cref="IFormFile"/>.
/// </summary>
public class FormFileModelBinder : IModelBinder
{
/// <inheritdoc />
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// This method is optimized to use cached tasks when possible and avoid allocating
// using Task.FromResult. If you need to make changes of this nature, profile
// allocations afterwards and look for Task<ModelBindingResult>.
if (bindingContext.ModelType != typeof(IFormFile) &&
!typeof(IEnumerable<IFormFile>).IsAssignableFrom(bindingContext.ModelType))
{
return ModelBindingResult.NoResultAsync;
}
return BindModelCoreAsync(bindingContext);
}
private async Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext)
{
object value;
if (bindingContext.ModelType == typeof(IFormFile))
{
var postedFiles = await GetFormFilesAsync(bindingContext);
value = postedFiles.FirstOrDefault();
}
else if (typeof(IEnumerable<IFormFile>).IsAssignableFrom(bindingContext.ModelType))
{
var postedFiles = await GetFormFilesAsync(bindingContext);
value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles);
}
else
{
// This binder does not support the requested type.
Debug.Fail("We shouldn't be called without a matching type.");
return ModelBindingResult.NoResult;
}
if (value == null)
{
return ModelBindingResult.Failed(bindingContext.ModelName);
}
else
{
bindingContext.ValidationState.Add(value, new ValidationStateEntry() { SuppressValidation = true });
bindingContext.ModelState.SetModelValue(
bindingContext.ModelName,
rawValue: null,
attemptedValue: null);
return ModelBindingResult.Success(bindingContext.ModelName, value);
}
}
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();
// If we're at the top level, then use the FieldName (paramter or property name).
// This handles the fact that there will be nothing in the ValueProviders for this parameter
// and so we'll do the right thing even though we 'fell-back' to the empty prefix.
var modelName = bindingContext.IsTopLevelObject
? bindingContext.FieldName
: bindingContext.ModelName;
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(HeaderUtilities.RemoveQuotes(parsedContentDisposition.FileName))))
{
continue;
}
var fileName = HeaderUtilities.RemoveQuotes(parsedContentDisposition.Name);
if (fileName.Equals(modelName, StringComparison.OrdinalIgnoreCase))
{
postedFiles.Add(file);
}
}
}
return postedFiles;
}
}
}