API与ClassLibrary项目循环依赖的解决方案求助
方案1:通过ValidationContext获取抽象服务(基于IValidatableObject)
利用ASP.NET Core的ValidationContext可访问服务容器的特性,结合抽象接口彻底解耦循环依赖:
新建Contracts类库定义服务接口
把验证所需的服务抽象成独立接口,放在专门的类库中,让Common和API都能引用它,避免双向依赖:// Contracts/IDepartmentValidationService.cs public interface IDepartmentValidationService { Task<bool> IsDepartmentNameUniqueAsync(string name, int? excludeId = null); }在Common的Dto中实现IValidatableObject
Common项目仅引用Contracts类库,在Dto的Validate方法中通过ValidationContext.GetService获取服务实例:// Common/CreateDepartmentDto.cs public class CreateDepartmentDto : IValidatableObject { public string Name { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { var validationService = validationContext.GetService<IDepartmentValidationService>(); if (validationService == null) { yield return new ValidationResult("验证服务不可用"); yield break; } // 注意:IValidatableObject是同步方法,若需异步验证建议用方案2 bool isUnique = validationService.IsDepartmentNameUniqueAsync(Name).GetAwaiter().GetResult(); if (!isUnique) { yield return new ValidationResult("部门名称已存在", new[] { nameof(Name) }); } } }在API项目中实现服务并注册
API项目引用Contracts和Common类库,实现接口后注入到服务容器:// API/Services/DepartmentValidationService.cs public class DepartmentValidationService : IDepartmentValidationService { private readonly AppDbContext _dbContext; public DepartmentValidationService(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<bool> IsDepartmentNameUniqueAsync(string name, int? excludeId = null) { return !await _dbContext.Departments .AnyAsync(d => d.Name == name && d.Id != excludeId); } }在Program.cs注册服务:
builder.Services.AddScoped<IDepartmentValidationService, DepartmentValidationService>();
方案2:用FluentValidation实现异步验证(推荐)
如果需要异步验证(避免同步等待数据库操作阻塞线程),FluentValidation是更优选择,同样通过抽象接口解耦:
Contracts类库定义接口(同方案1)
Common项目中创建FluentValidation验证器
Common引用Contracts和FluentValidation包,编写验证规则并注入服务:// Common/Validators/CreateDepartmentDtoValidator.cs public class CreateDepartmentDtoValidator : AbstractValidator<CreateDepartmentDto> { public CreateDepartmentDtoValidator(IDepartmentValidationService validationService) { RuleFor(dto => dto.Name) .NotEmpty() .MustAsync(async (name, cancellation) => await validationService.IsDepartmentNameUniqueAsync(name)) .WithMessage("部门名称已存在"); } }API项目注册验证器和服务
API项目引用Contracts、Common和FluentValidation.AspNetCore包,完成注册:// Program.cs builder.Services.AddScoped<IDepartmentValidationService, DepartmentValidationService>(); // 注册Common项目中的所有验证器 builder.Services.AddValidatorsFromAssembly(typeof(CreateDepartmentDtoValidator).Assembly); // 启用自动验证 builder.Services.AddFluentValidationAutoValidation();
方案3:在API层用ActionFilter做验证
如果不想让Dto和验证逻辑有任何耦合,可以把验证逻辑放到API层的ActionFilter中:
Contracts类库定义接口(同方案1)
API项目中创建ActionFilter
// API/Filters/DepartmentNameUniqueFilter.cs public class DepartmentNameUniqueFilter : IAsyncActionFilter { private readonly IDepartmentValidationService _validationService; public DepartmentNameUniqueFilter(IDepartmentValidationService validationService) { _validationService = validationService; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 从Action参数中获取Dto if (context.ActionArguments.TryGetValue("dto", out var dtoObj) && dtoObj is CreateDepartmentDto dto) { bool isUnique = await _validationService.IsDepartmentNameUniqueAsync(dto.Name); if (!isUnique) { context.ModelState.AddModelError(nameof(dto.Name), "部门名称已存在"); context.Result = new BadRequestObjectResult(context.ModelState); return; } } await next(); } }注册Filter并应用到Action
在Program.cs注册Filter:builder.Services.AddScoped<DepartmentNameUniqueFilter>();在控制器Action上添加Filter:
[ServiceFilter(typeof(DepartmentNameUniqueFilter))] [HttpPost] public async Task<IActionResult> Create(CreateDepartmentDto dto) { if (!ModelState.IsValid) { return BadRequest(ModelState); } // 业务逻辑... return Ok(); }
你之前方案失效的原因
- 分部类方案:分部类的所有代码片段必须在同一个程序集(项目)中,API项目的分部类无法和Common项目的Dto合并,因此这个方法本身不可行。
- 继承Dto实现IValidatableObject:如果子类在API项目中,你需要在控制器Action中使用这个子类作为参数,而不是原Dto,否则验证逻辑不会被触发。比如应该用
DepartmentDto作为Action参数,而非CreateDepartmentDto。
内容的提问来源于stack exchange,提问作者Mukesh

