ASP.NET Core MVC 文件上传、文件扩展验证注解实现、文件扩展验证

参考

  • DeepSeek
  • 豆包
  • 其他(由于文章是后编写的,相关参考文章未存储连接)

环境

软件/系统 版本 说明
Windows windows 10 专业版 22H2 64 位操作系统, 基于 x64 的处理器
Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.14.9
Visual Studio Code 1.102.2
Docker Engine v28.3.2 Docker 桌面工具
Docker 28.3.2
pgAdmin4 9.0 PostgreSQL 数据库管理软件
PostgreSQL 15
.NET 8
ASP.NET Core MVC ASP.NET Core MVC in .NET 8.0
Microsoft.EntityFrameworkCore.Design 8.0.18 nuget 依赖
Microsoft.EntityFrameworkCore.Tools 8.0.18 nuget 依赖
Microsoft.VisualStudio.Web.CodeGeneration.Design 8.0.7 nuget 依赖 (Microsoft Visual Studio在通过模型生成控制器与视图时自动安装的依赖)
Npgsql.EntityFrameworkCore.PostgreSQL 8.0.11 nuget 依赖
EFCore.NamingConventions 8.0.3 nuget 依赖
X.PagedList.EF 10.5.7 nuget 依赖
X.PagedList.Mvc.Core 10.5.7 nuget 依赖(安装 X.PagedList.EF 时自动安装)

本项目入口文件为 Program.cs ,创建项目时为不使用顶级语句

正文

  1. 创建参数验证注解 AllowedFileExtensionsAttribute.cs
    using Humanizer.Localisation;
    using System.ComponentModel.DataAnnotations;
    
    namespace NightMarketPlatformWeb.Validations
    {
    	/// <summary>
    	/// 自定义验证特性,用于验证文件扩展名是否在允许的列表中。
    	/// https://learn.microsoft.com/zh-cn/aspnet/core/mvc/models/validation?view=aspnetcore-9.0&source=recommendations#custom-attributes
    	/// </summary>
    	public class AllowedFileExtensionsAttribute : ValidationAttribute
    	{
    		private readonly string[] _allowedExtensions;
    
    		public AllowedFileExtensionsAttribute(string[] allowedExtensions)
    		{
    			_allowedExtensions = allowedExtensions;
    		}
    
    		protected override ValidationResult? IsValid(
    	   object? value, ValidationContext validationContext)
    		{
    			var file = value as IFormFile;
    
    			if (file != null)
    			{
    				var extension = Path.GetExtension(file.FileName).ToLower();
    
    				if (!_allowedExtensions.Contains(extension))
    				{
    					// 获取属性的Display名称
    					var displayName = validationContext.DisplayName;
    					// 替换错误消息中的{0}占位符
    					var errorMessage = string.Format(ErrorMessageString, displayName);
    					//
    					return new ValidationResult(errorMessage);
    				}
    			}
    
    			return ValidationResult.Success;
    		}
    	}
    }
    
    
  2. 创建接受参数的Dto,在文件属性上面添加上自定义注解AllowedFileExtensions,并传入允许的格式[".png", ".jpg", ".jpeg", ".gif"] (当前示例为:SettingEditRequestDto.cs
    using NightMarketPlatformWeb.Validations;
    using System.ComponentModel.DataAnnotations;
    
    namespace NightMarketPlatformWeb.Dtos.Area
    {
    	/// <summary>
    	/// 请求DTO类
    	/// </summary>
    	public class SettingEditRequestDto 
    	{
    		[Display(Name = "系统名称")]
    		[Required(ErrorMessage = "系统名称不能为空")]
    		[StringLength(16, MinimumLength = 2, ErrorMessage = " {0} 区间为 {1} - {2}.")]
    		public string SystemName { get; set; } = string.Empty;
    
    		[Display(Name = "系统描述")]
    		[Required(ErrorMessage = "系统描述不能为空")]
    		[StringLength(120, MinimumLength = 2, ErrorMessage = " {0} 区间为 {1} - {2}.")]
    		public string SystemDesc { get; set; } = string.Empty;
    
    		// 留空则不修改
    		[Display(Name = "系统logo文件")]
    		// 验证文件后缀
    		//[FileExtensionsAttribute(Extensions = "png,jpg,jpeg,gif", ErrorMessage = " {0} 格式错误")]
    		// 验证文件后缀,自带 FileExtensionsAttribute 不满足
    		//[FileExtensionsAttribute(ErrorMessage = " {0} 格式错误")]
    		[AllowedFileExtensions([".png", ".jpg", ".jpeg", ".gif"], ErrorMessage = " {0} 格式错误")]
    		public IFormFile? SystemLogoFile { get; set; }
    
    		// 留空则不修改
    		[Display(Name = "系统logo路径")]
    		public string? SystemLogoPath { get; set; }
    
    	}
    }
    
  3. 视图文件 Index.cshtml
    @using NightMarketPlatformWeb.Dtos.Area
    @model SettingEditRequestDto
    
    @{
    	ViewData["Title"] = "设置";
    }
    
    <h1>列表页</h1>
    
    <h4>设置</h4>
    <hr />
    <div class="row">
    	<div class="col-md-4">
    		<form asp-action="Index" enctype="multipart/form-data">
    			<div asp-validation-summary="ModelOnly" class="text-danger"></div>
    			<div class="form-group">
    				<label asp-for="SystemName" class="control-label"></label>
    				<input asp-for="SystemName" class="form-control" />
    				<span asp-validation-for="SystemName" class="text-danger"></span>
    			</div>
    			<div class="form-group">
    				<label asp-for="SystemDesc" class="control-label"></label>
    				<input asp-for="SystemDesc" class="form-control" />
    				<span asp-validation-for="SystemDesc" class="text-danger"></span>
    			</div>
    			<div class="form-group">
    				<label asp-for="SystemLogoFile" class="control-label"></label>
    				<input asp-for="SystemLogoFile" class="form-control" />
    				<span asp-validation-for="SystemLogoFile" class="text-danger"></span>
    				<div>
    					<img src="@Model.SystemLogoPath" class="img-rounded img-thumbnail mt-2 mb-2" width="150" height="150" />
    				</div>
    			</div>
    			<div class="form-group">
    				<input type="submit" value="保存" class="btn btn-primary" />
    			</div>
    		</form>
    	</div>
    </div>
    
    @section Scripts {
    	@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    }
    
  4. 在控制器内验证、接收、移动文件
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using NightMarketPlatformWeb.Data;
    using NightMarketPlatformWeb.Dtos.Area;
    using NightMarketPlatformWeb.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace NightMarketPlatformWeb.Controllers
    {
        // [Authorize(Roles = "Admin")]
    
    	public class SettingController : Controller
    	{
    
    		private readonly ApplicationDbContext _context;
    
    		public readonly IWebHostEnvironment _hostEnvironment;
    
    		public SettingController(ApplicationDbContext context, IWebHostEnvironment hostEnvironment)
    		{
    			_context            = context;
    			_hostEnvironment    = hostEnvironment;
    		}
    
    		// GET: Setting
    		public async Task<IActionResult> Index()
    		{
    			// 获取所有设置记录
    			var settings = await _context.Settings.ToListAsync();
    			// 初始化DTO
    			var dto = new SettingEditRequestDto();
    			// 遍历记录并映射到DTO对应的属性
    			foreach (var setting in settings)
    			{
    				switch (setting.SettingKey)
    				{
    					case "SystemName":
    						dto.SystemName = setting.SettingValue;
    						break;
    					case "SystemDesc":
    						dto.SystemDesc = setting.SettingValue;
    						break;
    					case "SystemLogoPath":
    						dto.SystemLogoPath = setting.SettingValue;
    						break;
    				}
    			}
    
    			return View(dto);
    		}
    
    		[HttpPost]
    		[ValidateAntiForgeryToken]
    		public async Task<IActionResult> Index(SettingEditRequestDto editRequestDto)
    		{
    			// 建议将验证放入筛选器中进行统一处理
    			if (!ModelState.IsValid)
    			{
    				return View(editRequestDto);
    			}
    			// 添加事务处理
    			using var transaction = await _context.Database.BeginTransactionAsync();
    			try
    			{
    				// 获取所有设置记录
    				var settings = await _context.Settings.ToListAsync();
    				// 系统名称
    				var systemNameRow = settings.Where(r => r.SettingKey == "SystemName").First();
    				systemNameRow.SettingValue = editRequestDto.SystemName;
    				// 系统设置
    				var systemDescRow = settings.Where(r => r.SettingKey == "SystemDesc").First();
    				systemDescRow.SettingValue = editRequestDto.SystemDesc;
    				// 系统logo 不修改则不更新
    				var systemLogoPathRow = settings.Where(r => r.SettingKey == "SystemLogoPath").First();
    				if (editRequestDto.SystemLogoFile != null)
    				{
    					// 文件操作逻辑
    					var newFileName = Path.GetRandomFileName();
    					var fileExt = Path.GetExtension(editRequestDto.SystemLogoFile.FileName);
    					var uploadDir = Path.Combine("temps", DateTime.Now.ToString("yyyy_MM_dd"));
    					var fileDir = Path.Combine(_hostEnvironment.WebRootPath, uploadDir);
    					var filePath = Path.Combine(fileDir, newFileName + fileExt);
    					//
    					// 如果目录不存在则创建
    					if (!Directory.Exists(fileDir))
    					{
    						Directory.CreateDirectory(fileDir);
    					}
    					//
    					using var stream = System.IO.File.Create(filePath);
    					await editRequestDto.SystemLogoFile.CopyToAsync(stream);
    					systemLogoPathRow.SettingValue = Path.Combine(uploadDir, newFileName + fileExt); ;
    				}
    				//
    				await _context.SaveChangesAsync();
    				//
    				await transaction.CommitAsync();
    			}
    			catch (Exception)
    			{
    				await transaction.RollbackAsync();
    			}
    			return RedirectToAction(nameof(Index));
    		}
    	}
    }
    

预览

  • 界面预览
    image
posted @ 2025-07-27 18:36  夏秋初  阅读(25)  评论(0)    收藏  举报