到目前为止,我们大部分的验证都是通过在视图模型上使用验证属性来执行的。
我们需要执行的另一个验证检查是验证一个字符串在我们的数据库中是否已经存在。
最初我只是在控制器操作中处理这个检查,然后根据需要向ModelState添加错误。然而,我宁愿利用内置的验证基础结构。
我尝试的一种方法是在我的视图模型上实现IValidateableObject。这感觉有点不对,因为我正在调用DependencyResolver:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var viewModel = validationContext.ObjectInstance as EditPostViewModel;
if (viewModel != null)
{
var slug = (Slug)viewModel.Slug;
var repo = DependencyResolver.Current.GetService<IRepository<Post>>();
var existing = repo.Get(p => p.Slug == slug && p.Id != viewModel.Id);
if (existing != null)
yield return new ValidationResult("Duplicate slug.", new[] { "Slug" });
}
}
我想到的另一种方法是使用自定义ValidationAttribute。对我而言,这只有在我可以在多个视图模型上重复使用它时才有意义,并且为了重复使用它,我需要能够构建通用存储库接口(根据上面的代码),因为我可能需要IRepository或IRepository,具体取决于模型。
远程验证很好,但我仍然需要服务器端验证。
那么人们推荐或使用什么来实现类似的东西呢?
请注意,我在此列中具有唯一的数据库约束,但不希望退回到异常处理来执行此验证。
解决方案
我看了一下Darin建议的asp.net文章。这种方法绝对可行,但该文章有点缺陷,因为虽然它设法将验证服务与任何对ModelState的直接引用分离开来,但您最终会得到一个循环依赖项,其中控制器依赖于验证服务,而验证服务依赖于ModelState(通过包装器);直到创建控制器之前都不存在。太棒了!
相反,我将IValidationDictionary公开为验证服务上的公共属性,并在控制器的构造函数中设置它:
slugValidator.ValidationDictionary = new ModelStateWrapper(this.ModelState);
其余部分与应用程序相关,但基本上我为每种“可生成slug”的实体创建了一个验证器进行验证。然后由我的容器注入。
public interface ISlugValidator<TEntity> where TEntity : ISlugable {
IValidationDictionary ValidationDictionary { get; set; }
bool ValidateSlug(Guid? entityId, Guid featureId, Slug slug);
}
我在控制器操作中检查
ModelState.IsValid
之前调用ValidateSlug(...)
,这是当前我需要的验证级别的好解决方案,大部分可以使用数据注释来处理。如果我的验证/业务规则变得更加复杂,我可能会切换到FluentValidation(这也适用于依赖注入),因为它更适合将验证逻辑外部化。