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. 编写筛选器 MultiFormatValidationFilter.cs
    using Microsoft.AspNetCore.Http.HttpResults;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using NightMarketPlatformWeb.Controllers;
    
    namespace NightMarketPlatformWeb.Filters
    {
    	/// <summary>
    	/// 支持JSON和View的参数验证过滤器
    	/// </summary>
    	public class MultiFormatValidationFilter : IAsyncActionFilter
    	{
    
    
    		private readonly ITempDataDictionaryFactory _tempDataDictionaryFactory;
    		private readonly IModelMetadataProvider _modelMetadataProvider;
    
    		// 日志记录器
    
    		/// <summary>
    		/// 构造函数注入依赖
    		/// </summary>
    		public MultiFormatValidationFilter(
    			ITempDataDictionaryFactory tempDataDictionaryFactory,
    			IModelMetadataProvider modelMetadataProvider)
    		{
    			_tempDataDictionaryFactory = tempDataDictionaryFactory;
    			_modelMetadataProvider = modelMetadataProvider;
    		}
    
    		/// <summary>
    		/// 异步执行过滤器
    		/// </summary>
    		public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    		{
    			// 检查模型状态是否有效
    			if (!context.ModelState.IsValid)
    			{
    				// TODO : 此处根据请求的Accept头决定返回格式
    				await HandleViewResponse(context);
    				return;
    			}
    			// 模型验证通过,继续执行
    			await next();
    		}
    
    		/// <summary>
    		/// 处理View响应
    		/// </summary>
    		private Task HandleViewResponse(ActionExecutingContext context)
    		{
    			// 获取当前Action的信息
    			var actionDescriptor = context.ActionDescriptor;
    			var controllerName = actionDescriptor.RouteValues["controller"];
    			var actionName = actionDescriptor.RouteValues["action"];
    
    			// 通常验证失败时返回提交表单的视图
    			// 可以根据需要修改为专用的错误视图
    			var viewName = actionName;
    
    			// 获取提交的模型
    			var model = context.ActionArguments.Values.FirstOrDefault();
    
    			// 创建ViewResult
    			var viewResult = new ViewResult
    			{
    				ViewName = viewName,
    				//
    				ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState)
    				{
    					Model = model
    				},
    				TempData = _tempDataDictionaryFactory.GetTempData(context.HttpContext)
    			};
    
    			// 添加错误消息
    			//viewResult.ViewData["ValidationMessage"] = "表单验证失败,请检查输入并重新提交。";
    
    			context.Result = viewResult;
    
    			return Task.CompletedTask;
    		}
    	}
    }
    
    
  2. Program.cs 内注册筛选器
    // 注册控制器和视图
    builder.Services.AddControllersWithViews(options =>
    {
    // 注册全局验证过滤器
    options.Filters.Add<MultiFormatValidationFilter>();
    // 其他过滤器...
    });
    
  3. 接收请求的 Dto 字段上添加相关注解,此处使用 AdminEditRequestDto.cs 举例。(相关注解可以参考微软文档)
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
    using Microsoft.EntityFrameworkCore;
    using System.ComponentModel.DataAnnotations;
    
    namespace NightMarketPlatformWeb.Dtos.Area
    {
    	/// <summary>
    	/// 请求DTO类
    	/// </summary>
    	public class AdminEditRequestDto
    	{
    
    		public int Id { get; set; }
    
    		/// <summary>
    		/// 管理员名称
    		/// </summary>
    		[Required]
    		// 显示名称
    		[Display(Name = "管理员名称")]
    		public string AdminName { get; set; } = string.Empty;
    
    		/// <summary>
    		/// 管理员账号/管理员联系方式
    		/// </summary>
    		[Required]
    		// 显示名称
    		[Display(Name = "管理员账号/联系方式")]
    		public string AdminPhoneNumber { get; set; } = string.Empty;
    
    		/// <summary>
    		/// 管理员密码(不填写则不修改,可为空,并且验证区间)
    		/// </summary>
    		// 显示名称
    		[Display(Name = "管理员密码")]
    		[StringLength(16, MinimumLength = 8, ErrorMessage = " {0} 区间为 {1} - {2}.")]
    		public string? AdminPassword { get; set; }
    
    
    		// 显示名称
    		[Display(Name = "是否启用")]
    		[Required(ErrorMessage = "{0}必填")]
    		public bool Enabled { get; set; } = true;
    	}
    }
    
  4. 将添加相关验证注解的类作为传入参数,则会自动验证。(下面代码无需指定if (!ModelState.IsValid)判断)
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authentication.Cookies;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using NightMarketPlatformWeb.Data;
    using NightMarketPlatformWeb.Dtos.Admin;
    using NightMarketPlatformWeb.Dtos.Area;
    using NightMarketPlatformWeb.Models;
    using NightMarketPlatformWeb.Utils;
    using NightMarketPlatformWeb.ViewModels;
    using System.Security.Claims;
    using X.PagedList;
    using X.PagedList.EF;
    
    namespace NightMarketPlatformWeb.Controllers
    {
    	// [Authorize(Roles = "Admin")]
    	public class AdminController : Controller
    	{
    		private readonly ApplicationDbContext _context;
    
    		public AdminController(ApplicationDbContext context)
    		{
    			_context = context;
    		}
    
    		[HttpPost]
    		[ValidateAntiForgeryToken]
    		public async Task<IActionResult> Edit(int id, AdminEditRequestDto requestDto)
    		{
    			var model = await _context.Admins.FirstAsync(m => m.Id == requestDto.Id);
    			// mapper 转换映射
    			model.AdminName         = requestDto.AdminName;
    			model.AdminPhoneNumber  = requestDto.AdminPhoneNumber;
    			if (!string.IsNullOrEmpty(requestDto.AdminPassword))
    			{
    				var hasher = new PasswordHasher<AdminModel>();
    				string hashedPassword = hasher.HashPassword(model, requestDto.AdminPassword);
    				model.AdminPassword = hashedPassword;
    			}
    			model.Enabled           = requestDto.Enabled;
    			// 保存
    			await _context.SaveChangesAsync();
    			return RedirectToAction(nameof(Index));
    		}
    	}
    }
    
  5. Edit.cshtml 视图如下:
    @model NightMarketPlatformWeb.Models.AdminModel
    
    @{
    	ViewData["Title"] = "修改";
    }
    
    <h1>修改</h1>
    
    <h4>管理员</h4>
    <hr />
    <div class="row">
    	<div class="col-md-4">
    		<form asp-action="Edit">
    			<div asp-validation-summary="ModelOnly" class="text-danger"></div>
    			<input type="hidden" asp-for="Id" />
    			<div class="form-group">
    				<label asp-for="AdminName" class="control-label">@Html.DisplayNameFor(model => model.AdminName)</label>
    				<input asp-for="AdminName" class="form-control" />
    				<span asp-validation-for="AdminName" class="text-danger"></span>
    			</div>
    			<div class="form-group">
    				<label asp-for="AdminPhoneNumber" class="control-label">@Html.DisplayNameFor(model => model.AdminPhoneNumber)</label>
    				<input asp-for="AdminPhoneNumber" class="form-control" />
    				<span asp-validation-for="AdminPhoneNumber" class="text-danger"></span>
    			</div>
    			<div class="form-group">
    				<label asp-for="AdminPassword" class="control-label">@Html.DisplayNameFor(model => model.AdminPassword)</label>
    				<!--留空输入框 value="@string.Empty" 忽略验证 data-val="@string.Empty"-->
    				<input asp-for="AdminPassword" class="form-control" value="@string.Empty" data-val="@string.Empty" />
    				<span asp-validation-for="AdminPassword" class="text-danger"></span>
    			</div>
    			<div class="form-group form-check">
    				<label class="form-check-label">
    					<input class="form-check-input" asp-for="Enabled" /> @Html.DisplayNameFor(model => model.Enabled)
    				</label>
    			</div>
    			<div class="form-group">
    				<input type="submit" value="保存" class="btn btn-primary" />
    			</div>
    		</form>
    	</div>
    </div>
    
    <div>
    	<a asp-action="Index">返回列表</a>
    </div>
    
    @section Scripts {
    	@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    }
    
posted @ 2025-07-27 18:49  夏秋初  阅读(22)  评论(0)    收藏  举报