【问题标题】:How to validate uploaded file in ASP.Net Core如何在 ASP.Net Core 中验证上传的文件
【发布时间】:2019-10-28 13:03:18
【问题描述】:

我正在使用 ASP.NET Core 2.2,并且正在使用模型绑定来上传文件。

这是我的 UserViewModel

public class UserViewModel
{
    [Required(ErrorMessage = "Please select a file.")]
    [DataType(DataType.Upload)]
    public IFormFile Photo { get; set; }
}

这是我的视图

@model UserViewModel

<form method="post"
      asp-action="UploadPhoto"
      asp-controller="TestFileUpload"
      enctype="multipart/form-data">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>

    <input asp-for="Photo" />
    <span asp-validation-for="Photo" class="text-danger"></span>
    <input type="submit" value="Upload"/>
</form>

最后是 MyController

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhoto(UserViewModel userViewModel)
{
    if (ModelState.IsValid)
    {
        var formFile = userViewModel.Photo;
        if (formFile == null || formFile.Length == 0)
        {
            ModelState.AddModelError("", "Uploaded file is empty or null.");
            return View(viewName: "Index");
        }

        var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
        if (!Directory.Exists(uploadsRootFolder))
        {
            Directory.CreateDirectory(uploadsRootFolder);
        }

        var filePath = Path.Combine(uploadsRootFolder, formFile.FileName);
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await formFile.CopyToAsync(fileStream).ConfigureAwait(false);
        }

        RedirectToAction("Index");
    }
    return View(viewName: "Index");
}

如何使用 .jpeg 和 .png 等特定扩展名将上传的文件限制在 5MB 以下?我认为这两个验证都是在 ViewModel 中完成的。但我不知道该怎么做。

【问题讨论】:

标签: c# asp.net asp.net-core ef-core-2.1


【解决方案1】:

您可以自定义验证属性MaxFileSizeAttribute,如下所示

MaxFileSizeAttribute

public class MaxFileSizeAttribute : ValidationAttribute
{
    private readonly int _maxFileSize;
    public MaxFileSizeAttribute(int maxFileSize)
    {
        _maxFileSize = maxFileSize;
    }

    protected override ValidationResult IsValid(
    object value, ValidationContext validationContext)
    {
        var file = value as IFormFile;
        if (file != null)
        {
           if (file.Length > _maxFileSize)
            {
                return new ValidationResult(GetErrorMessage());
            }
        }

        return ValidationResult.Success;
    }

    public string GetErrorMessage()
    {
        return $"Maximum allowed file size is { _maxFileSize} bytes.";
    }
}

AllowedExtensionsAttribute

public class AllowedExtensionsAttribute : ValidationAttribute
{
    private readonly string[] _extensions;
    public AllowedExtensionsAttribute(string[] extensions)
    {
        _extensions = extensions;
    }
    
    protected override ValidationResult IsValid(
    object value, ValidationContext validationContext)
    {
        var file = value as IFormFile;
        if (file != null)
        {
            var extension = Path.GetExtension(file.FileName);
            if (!_extensions.Contains(extension.ToLower()))
            {
                return new ValidationResult(GetErrorMessage());
            }
        }
        
        return ValidationResult.Success;
    }

    public string GetErrorMessage()
    {
        return $"This photo extension is not allowed!";
    }
}

MaxFileSize 属性和AllowedExtensions 属性添加到Photo 属性

public class UserViewModel
{
        [Required(ErrorMessage = "Please select a file.")]
        [DataType(DataType.Upload)]
        [MaxFileSize(5* 1024 * 1024)]
        [AllowedExtensions(new string[] { ".jpg", ".png" })]
        public IFormFile Photo { get; set; }
 }

【讨论】:

  • 很好的解决方案!但是您能否编辑您的答案并制作两个单独的验证属性。一个用于 MaxFileSize,另一个用于 AllowedExtensions。基于 SOLID 原则的原因,每个属性都应该有一个单独的职责。
  • @ArianShahalami,查看我回复中的更新。如果您发现我的解决方法有效,您能否将我的回复标记为答案?
  • @XueliChen 我对您的答案进行了一些更改以使其更清楚。在您的MaxFileSizeAttributeAllowedExtensionsAttribute 中,您不应检查文件是否为空或存在。他们的职责是检查最大文件大小及其扩展名。接受我的评论,我会将其标记为最佳解决方案。您还可以更改我的评论以使其更好。
  • 非常酷的答案,不知道您可以制作自定义验证属性!
  • 这是基于扩展的。欺骗呢?我取exe,将扩展名更改为txt。 .
【解决方案2】:

您可以实现IValidatableObject 来验证您的模型。

public class UserViewModel : IValidatableObject
    {
        [Required(ErrorMessage = "Please select a file.")]
        [DataType(DataType.Upload)]
        public IFormFile Photo { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var photo = ((UserViewModel)validationContext.ObjectInstance).Photo;
            var extension = Path.GetExtension(photo.FileName);
            var size = photo.Length;

            if (!extension.ToLower().Equals(".jpg"))
                yield return new ValidationResult("File extension is not valid.");

           if(size > (5 * 1024 * 1024))
                yield return new ValidationResult("File size is bigger than 5MB.");
        }
    }

【讨论】:

    【解决方案3】:

    跟随previous comment,可以添加这个类:

    public class ValidateModelStateFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (context.ModelState.IsValid)
            {
                return;
            }
    
            var validationErrors = context.ModelState
                .Keys
                .SelectMany(k => context.ModelState[k].Errors)
                .Select(e => e.ErrorMessage)
                .ToArray();
    
            var json = new JsonErrorResponse
            {
                Messages = validationErrors
            };
    
            context.Result = new BadRequestObjectResult(json);
        }
    }
    

    最后,在 Startup 类的控制器中添加过滤器:

     services.AddControllers(options =>
                {
                    options.Filters.Add(typeof(ValidateModelStateFilter));
                })
    

    【讨论】:

      【解决方案4】:

      这就是我使用自定义验证属性的方式,与@xueli-chen 的答案几乎相同,但已准备好生产。

      FileExtensionsAttribute

      using System;
      using System.ComponentModel.DataAnnotations;
      using System.Globalization;
      using System.IO;
      using System.Linq;
      
      using Microsoft.AspNetCore.Http;
      
      using NewsPassWebApi.Properties;
      
      namespace NewsPassWebApi.Models.DataAnnotaions
      {
          /// <summary>
          /// Validation attribute to assert an <see cref="IFormFile">IFormFile</see> property, field or parameter has a specific extention.
          /// </summary>
          [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
          public sealed class FileExtensionsAttribute : ValidationAttribute
          {
              private string _extensions;
      
              /// <summary>
              /// Gets or sets the acceptable extensions of the file.
              /// </summary>
              public string Extensions
              {
                  get
                  {
                      // Default file extensions match those from jquery validate.
                      return string.IsNullOrEmpty(_extensions) ? "png,jpg,jpeg,gif" : _extensions;
                  }
                  set
                  {
                      _extensions = value;
                  }
              }
      
              private string ExtensionsNormalized
              {
                  get
                  {
                      return Extensions.Replace(" ", "", StringComparison.Ordinal).ToUpperInvariant();
                  }
              }
      
              /// <summary>
              /// Parameterless constructor.
              /// </summary>
              public FileExtensionsAttribute() : base(() => Resources.FileExtensionsAttribute_ValidationError)
              { }
      
              /// <summary>
              /// Override of <see cref="ValidationAttribute.IsValid(object)"/>
              /// </summary>
              /// <remarks>
              /// This method returns <c>true</c> if the <paramref name="value"/> is null.  
              /// It is assumed the <see cref="RequiredAttribute"/> is used if the value may not be null.
              /// </remarks>
              /// <param name="value">The value to test.</param>
              /// <returns><c>true</c> if the value is null or it's extension is not invluded in the set extensionss</returns>
              public override bool IsValid(object value)
              {
                  // Automatically pass if value is null. RequiredAttribute should be used to assert a value is not null.
                  if (value == null)
                  {
                      return true;
                  }
      
                  // We expect a cast exception if the passed value was not an IFormFile.
                  return ExtensionsNormalized.Split(",").Contains(Path.GetExtension(((IFormFile)value).FileName).ToUpperInvariant());
              }
      
              /// <summary>
              /// Override of <see cref="ValidationAttribute.FormatErrorMessage"/>
              /// </summary>
              /// <param name="name">The name to include in the formatted string</param>
              /// <returns>A localized string to describe the acceptable extensions</returns>
              public override string FormatErrorMessage(string name)
              {
                  return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, Extensions);
              }
          }
      }
      

      FileSizeAttribute

      using System;
      using System.ComponentModel.DataAnnotations;
      using System.Globalization;
      
      using Microsoft.AspNetCore.Http;
      
      using NewsPassWebApi.Properties;
      
      namespace NewsPassWebApi.Models.DataAnnotaions
      {
          /// <summary>
          /// Validation attribute to assert an <see cref="IFormFile">IFormFile</see> property, field or parameter does not exceed a maximum size.
          /// </summary>
          [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
          public sealed class FileSizeAttribute : ValidationAttribute
          {
              /// <summary>
              /// Gets the maximum acceptable size of the file.
              /// </summary>
              public long MaximumSize { get; private set; }
      
              /// <summary>
              /// Gets or sets the minimum acceptable size of the file.
              /// </summary>
              public int MinimumSize { get; set; }
      
              /// <summary>
              /// Constructor that accepts the maximum size of the file.
              /// </summary>
              /// <param name="maximumSize">The maximum size, inclusive.  It may not be negative.</param>
              public FileSizeAttribute(int maximumSize) : base(() => Resources.FileSizeAttribute_ValidationError)
              {
                  MaximumSize = maximumSize;
              }
      
              /// <summary>
              /// Override of <see cref="ValidationAttribute.IsValid(object)"/>
              /// </summary>
              /// <remarks>
              /// This method returns <c>true</c> if the <paramref name="value"/> is null.  
              /// It is assumed the <see cref="RequiredAttribute"/> is used if the value may not be null.
              /// </remarks>
              /// <param name="value">The value to test.</param>
              /// <returns><c>true</c> if the value is null or it's size is less than or equal to the set maximum size</returns>
              /// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
              public override bool IsValid(object value)
              {
                  // Check the lengths for legality
                  EnsureLegalSizes();
      
                  // Automatically pass if value is null. RequiredAttribute should be used to assert a value is not null.
                  // We expect a cast exception if the passed value was not an IFormFile.
                  var length = value == null ? 0 : ((IFormFile)value).Length;
      
                  return value == null || (length >= MinimumSize && length <= MaximumSize);
              }
      
              /// <summary>
              /// Override of <see cref="ValidationAttribute.FormatErrorMessage"/>
              /// </summary>
              /// <param name="name">The name to include in the formatted string</param>
              /// <returns>A localized string to describe the maximum acceptable size</returns>
              /// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
              public override string FormatErrorMessage(string name)
              {
                  EnsureLegalSizes();
      
                  string errorMessage = MinimumSize != 0 ? Resources.FileSizeAttribute_ValidationErrorIncludingMinimum : ErrorMessageString;
      
                  // it's ok to pass in the minLength even for the error message without a {2} param since String.Format will just ignore extra arguments
                  return string.Format(CultureInfo.CurrentCulture, errorMessage, name, MaximumSize, MinimumSize);
              }
      
              /// <summary>
              /// Checks that MinimumSize and MaximumSize have legal values.  Throws InvalidOperationException if not.
              /// </summary>
              private void EnsureLegalSizes()
              {
                  if (MaximumSize < 0)
                  {
                      throw new InvalidOperationException(Resources.FileSizeAttribute_InvalidMaxSize);
                  }
      
                  if (MaximumSize < MinimumSize)
                  {
                      throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.RangeAttribute_MinGreaterThanMax, MaximumSize, MinimumSize));
                  }
              }
          }
      }
      

      现在您可以使用任何内置验证属性,包括自定义/本地化错误消息、最大最小文件大小和文件扩展名。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-28
        • 1970-01-01
        • 2010-12-27
        • 2012-10-30
        • 2010-09-22
        • 1970-01-01
        相关资源
        最近更新 更多