将 DbContext 或存储库注入流畅的验证验证器中是一个好习惯吗

Ahm*_*mad 9 c# validation dependency-injection fluentvalidation

您如何看待这个想法:有些 DTO 需要涉及数据库交互的验证,因此我们在验证器类中注入 DbContext 或存储库类,并使用它进行验证。

    public class UserEditSelfResourceValidator : AbstractValidator<UserEditSelfResource>
    {
        private IUserRepository _userRepository;

        public UserEditSelfResourceValidator(IUserRepository userRepository, ILoggedInUserService loggedInUser)
        {
            _userRepository = userRepository;
            RuleFor(mem => mem.ProfileUrl).MustAsync(async (entity, value, c) => await UniqueProfileUrl(entity, value))
                .WithMessage("Profile Url must be unique.");

        }

        public async Task<bool> UniqueProfileUrl(UserEditSelfResource userEditSelf, string newProfileUrl)
        {
            var user = await _userRepository.FindByProfileUrlAsync(newProfileUrl);
            
            if(user.Id == _loggedInUser.GetId() || user == null)
            {
                return true;
            }
            return false;
        }
    }
Run Code Online (Sandbox Code Playgroud)

您认为这种想法是一种好的做法还是有问题?

Cyb*_*ogs 6

简短回答是的,这将确保验证位于与其他逻辑分开的一个地方,但要小心在类中插入的对象的生命周期Validator,如果Validator是单例,这将导致问题,如文档中所述

'使用自动注册的验证器RegisterValidatorsFromAssemblyContaining被注册为Scoped容器而不是Singleton. 这样做是为了避免生命周期范围问题,开发人员可能会无意中导致单例范围的验证器依赖于瞬态或请求范围的服务(例如,数据库上下文)。如果您意识到此类问题并了解如何避免它们,那么您可以选择将验证器注册为单例,这将通过传递第二个参数来提高性能:`


小智 3

我相信这是一个非常简单的答案:从 10,000 英尺的角度来看,你正在做的事情看起来不错。

我之所以这么说,是因为您将验证保存在一个地方。由于将注入您的 DbContext,因此每次需要执行此特定验证时,您都不会通过单独且重复地调用数据库来污染您的代码。

只要您的数据库调用仅使用处理任何给定的特定验证任务所需的最低限度(您不想仅为了选择一个属性而返回整个对象),您就可以将您的关注点分开。我假设您会将验证服务注入到控制器等地方。

这只是个人偏好,但我认为经验法则是,“如果下一个人需要在没有任何直接解释的情况下从我上次停下的地方继续,这是否容易理解?” 将您的关注点分开(例如您尝试实现的验证服务)意味着如果我需要对验证进行修复或更改,我确切地知道要去的地方,并且我不必了解太多其他事情,比如什么是消费服务。我需要的一切都在那个地方。