You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:25:56