.NET Core中如何通用化重复业务规则?特性/横切方案咨询
通用化重复唯一性校验的实现方案
针对你提到的重复校验业务规则(如用户/产品唯一性检查),可以通过自定义特性+Action过滤器的横切关注点方式实现通用化,下面是具体落地步骤:
1. 定义核心枚举
先通过枚举统一管理要校验的业务表和校验类型,避免硬编码:
// 业务表枚举 public enum BusinessTable { User, Product } // 校验类型枚举 public enum ValidationType { // 检查记录是否已存在 Exists, // 检查字段是否唯一(即不存在重复) Unique }
2. 自定义校验特性
创建特性类,用来标记需要自动校验的控制器方法,传递校验所需的参数:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class ValidateUniquenessAttribute : Attribute { public BusinessTable Table { get; } public string FieldName { get; } public ValidationType ValidationType { get; } public string DtoPropertyName { get; } // DTO中对应要校验的属性名 public ValidateUniquenessAttribute(BusinessTable table, string fieldName, ValidationType validationType, string dtoPropertyName) { Table = table; FieldName = fieldName; ValidationType = validationType; DtoPropertyName = dtoPropertyName; } }
3. 实现通用校验服务
编写一个通用服务,负责根据枚举参数执行数据库查询校验:
public interface IUniquenessValidationService { Task<bool> ValidateAsync(BusinessTable table, string fieldName, object value, ValidationType validationType); } public class UniquenessValidationService : IUniquenessValidationService { private readonly ApplicationDbContext _dbContext; public UniquenessValidationService(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task<bool> ValidateAsync(BusinessTable table, string fieldName, object value, ValidationType validationType) { // 根据表枚举获取对应的DbSet var query = table switch { BusinessTable.User => _dbContext.Users.AsQueryable(), BusinessTable.Product => _dbContext.Products.AsQueryable(), _ => throw new ArgumentOutOfRangeException(nameof(table), "不支持的业务表") }; // 动态构建查询条件:检查指定字段是否等于传入值 var parameter = Expression.Parameter(query.ElementType, "entity"); var property = Expression.Property(parameter, fieldName); var constant = Expression.Constant(value); var equalsExpr = Expression.Equal(property, constant); var lambda = Expression.Lambda(equalsExpr, parameter); // 执行Count查询判断是否存在重复 var count = await query.Where(lambda).CountAsync(); return validationType switch { ValidationType.Exists => count > 0, ValidationType.Unique => count == 0, _ => throw new ArgumentOutOfRangeException(nameof(validationType), "不支持的校验类型") }; } }
4. 实现Action过滤器
通过ASP.NET Core的IAsyncActionFilter,在控制器方法执行前自动读取特性参数并触发校验:
public class UniquenessValidationFilter : IAsyncActionFilter { private readonly IUniquenessValidationService _validationService; public UniquenessValidationFilter(IUniquenessValidationService validationService) { _validationService = validationService; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 获取当前方法上的所有校验特性 var validationAttrs = context.ActionDescriptor.EndpointMetadata.OfType<ValidateUniquenessAttribute>(); foreach (var attr in validationAttrs) { // 从请求参数中获取DTO对象(默认取第一个参数) if (!context.ActionArguments.TryGetValue(context.ActionDescriptor.Parameters.First().Name, out var dto)) { context.Result = new BadRequestObjectResult("请求参数异常"); return; } // 获取DTO中指定属性的值 var propertyInfo = dto.GetType().GetProperty(attr.DtoPropertyName); if (propertyInfo == null) { context.Result = new BadRequestObjectResult($"请求参数缺少{attr.DtoPropertyName}字段"); return; } var value = propertyInfo.GetValue(dto); // 执行校验 var isValid = await _validationService.ValidateAsync(attr.Table, attr.FieldName, value, attr.ValidationType); if (!isValid) { var errorMsg = attr.ValidationType switch { ValidationType.Exists => $"该{attr.Table}记录已存在", ValidationType.Unique => $"该{attr.Table}的{attr.FieldName}已被占用", _ => "校验失败" }; context.Result = new BadRequestObjectResult(errorMsg); return; } } // 校验通过,继续执行控制器方法 await next(); } }
5. 注册服务与过滤器
在Program.cs中注册相关服务和过滤器:
// 注册通用校验服务 builder.Services.AddScoped<IUniquenessValidationService, UniquenessValidationService>(); // 注册校验过滤器 builder.Services.AddScoped<UniquenessValidationFilter>(); // 方式1:全局注册过滤器(所有控制器方法都会触发校验) builder.Services.AddControllers(options => { options.Filters.Add<UniquenessValidationFilter>(); }); // 方式2:局部注册(仅在需要的控制器/方法上添加[ServiceFilter(typeof(UniquenessValidationFilter))])
6. 控制器中使用示例
现在可以直接在控制器方法上添加特性,无需再写重复的校验代码:
[ApiController] [Route("api/users")] public class UsersController : ControllerBase { private readonly IUserService _userService; public UsersController(IUserService userService) { _userService = userService; } [HttpPost] // 校验:用户表的UserName字段是否唯一,对应CreateUserDto的UserName属性 [ValidateUniqueness(BusinessTable.User, "UserName", ValidationType.Unique, nameof(CreateUserDto.UserName))] public async Task<IActionResult> CreateUser(CreateUserDto createUserDto) { await _userService.Add(createUserDto); return Ok("用户创建成功"); } } [ApiController] [Route("api/products")] public class ProductsController : ControllerBase { private readonly IProductService _productService; public ProductsController(IProductService productService) { _productService = productService; } [HttpPost] // 校验:产品表的ProductModelKey字段是否唯一,对应CreateProductDto的ProductModelKey属性 [ValidateUniqueness(BusinessTable.Product, "ProductModelKey", ValidationType.Unique, nameof(CreateProductDto.ProductModelKey))] public async Task<IActionResult> CreateProduct(CreateProductDto createProductDto) { await _productService.Add(createProductDto); return Ok("产品创建成功"); } }
可选优化点
- 把
FieldName也改成枚举,进一步避免硬编码字符串 - 添加缓存逻辑,对高频校验字段做缓存,减少数据库查询
- 支持多字段联合校验,修改特性和服务逻辑即可
- 完善异常处理,捕获数据库查询异常并返回友好提示
内容的提问来源于stack exchange,提问作者JacksonT
相关产品推荐
相关产品推荐

