I'm using ASP.NET Core 2.2 and I'm using model binding for uploading file.
This is my UserViewModel
public class UserViewModel { [Required(ErrorMessage = "Please select a file.")] [DataType(DataType.Upload)] public IFormFile Photo { get; set; } }
This is MyView
@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>
And finally this is 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"); }
How can I limit uploaded files to lower than 5MB with specific extensions like .jpeg and .png ? I think both of these validations are done in the ViewModel. But I don't know how to do that.
You could custom validation attribute MaxFileSizeAttribute
like below
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!"; } }
Add MaxFileSize
attribute and AllowedExtensions
attribute to Photo
property
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; } }
This is how I use the custom validation attributes, almost the same as @xueli-chen 's answer but production-ready.
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)); } } } }
now you can use those like any built-in validation attribute including custom/localized error messages, max-min file sizes, and file extensions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With