Как проверить загруженный файл в ASP.Net Core

Я использую 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>

И, наконец, это МойКонтроллер

[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");
}

Как я могу ограничить загружаемые файлы размером менее 5 МБ с определенными расширениями, такими как .jpeg и .png? Я думаю, что обе эти проверки выполняются в ViewModel. Но я не знаю, как это сделать.

Вы читали docs.microsoft.com/en-us/aspnet/core/mvc/models/…? Вы работаете на IIS?

mjwills 13.06.2019 23:48

@mjwills да, я работаю на IIS. Я читал документы Microsoft, но не нашел ничего полезного в этой ситуации.

Arian Shahalami 14.06.2019 00:18

Что на этой странице говорилось о maxAllowedContentLength?

mjwills 14.06.2019 01:11

@mjwills ограничивает запросы на загрузку все размером более 50 МБ. Я просто хочу ограничить загрузку файлов размером менее 5 МБ на определенной странице. А на другой странице, например, 20 МБ, а на другой странице 30 МБ, ты понял?

Arian Shahalami 14.06.2019 10:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
29
5
52 045
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Вы можете настроить атрибут проверки 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.";
    }
}

Алловедекстенсионсаттрибуте

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; }
 }

Хорошее решение! Но не могли бы вы отредактировать свой ответ и сделать два отдельных атрибута проверки. Один для Максимальный РазмерФайла и другой для Разрешенные расширения. Причина, основанная на принципах ТВЕРДЫЙ, у каждого атрибута должна быть одна ответственность.

Arian Shahalami 14.06.2019 10:50

@ArianShahalami, проверьте обновления в моем ответе. Если вы обнаружите, что мой обходной путь работает, не могли бы вы отметить мой ответ как ответ?

Xueli Chen 14.06.2019 12:01

@XueliChen Я внес некоторые изменения в ваш ответ, чтобы сделать его более понятным. В ваших MaxFileSizeAttribute и AllowedExtensionsAttribute вы не должны проверять, является ли файл нулевым или существует. В их обязанности входит проверка максимального размера файла и его расширения. Примите мой отзыв, и я отмечу его как лучшее решение. Вы также можете изменить мой отзыв, чтобы сделать его лучше.

Arian Shahalami 14.06.2019 12:34

Действительно классный ответ, я понятия не имел, что вы можете создавать собственные атрибуты проверки!

perustaja 16.01.2020 05:46

Это расширение-база. Что насчет спуфинга? Беру exe, меняю расширение на txt . .

T.S. 30.01.2020 04:28

Как мне заставить это прерывать мой контроллер и отображать ошибку в пользовательском интерфейсе? Я использую метод GetErrorMessage(), но мой контроллер продолжает работать.

DMur 24.12.2021 17:25

Вы можете реализовать 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.");
        }
    }

Вот как я использую пользовательские атрибуты проверки, почти такие же, как ответ @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));
            }
        }
    }
}

теперь вы можете использовать их, как любой встроенный атрибут проверки, включая настраиваемые/локализованные сообщения об ошибках, максимальные и минимальные размеры файлов и расширения файлов.

После предыдущий комментарий вы можете добавить этот класс:

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));
            })

Другие вопросы по теме