开发者

Recommended approach for model validation that needs to touch the Db

开发者 https://www.devze.com 2023-03-09 17:40 出处:网络
Up to now, most of our validation is performed using validation attributes on our view models. One additional validation check we need to perform is to validate that a string doesn\'t already exist i

Up to now, most of our validation is performed using validation attributes on our view models.

One additional validation check we need to perform is to validate that a string doesn't already exist in our database.

Originally I was just handling this check within the controller action and then adding an error into ModelState if required. However, I would rather make use of the built-in validation infrastructure.

One method I tried was implementing IValidateableObject on my viewmodel. This feels a bit wrong as I'm calling 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[] {开发者_JAVA百科 "Slug" });
    }
}

Another approach I thought about was using a custom ValidationAttribute. To me this only makes sense if I can re-use it on multiple viewmodels and to re-use it I need to be able to construct a generic repository interface (as per the above code) as I may need IRepository<Foo> or IRepository<Bar> depending on the Model.

Remote Validation is great but I still need to the validation server side.

So what would people recommend or have used themselves to achieve something similar.

Note that I do have unique database constraints on this column but don't want to fall back to exception handling to perform this validation.

Solution

I had a look as the asp.net article @Darin suggested. This approach definitely works, but the article is a bit flawed in that whilst it manages to decouple the validation service away from any direct references to ModelState, you instead end up with a circular dependency where controller depends on the validation service and the validation service depends on ModelState (via a wrapper); which doesn't exist until the controller is created. Nice!

Instead I exposed the IValidationDictionary as a public property on my validation service and set this in my controllers constructor:

slugValidator.ValidationDictionary = new ModelStateWrapper(this.ModelState);

The rest is kind of application specific but essentially I created a validator for each type of "slugable" entity I wanted to validate. These were then injected by my container.

public interface ISlugValidator<TEntity> where TEntity : ISlugable {
    IValidationDictionary ValidationDictionary { get; set; }
    bool ValidateSlug(Guid? entityId, Guid featureId, Slug slug);
}

I call ValidateSlug(...) just before I check ModelState.IsValid in my controller actions.

This is a good solution for the level of validation I need currently and the fact that most of it can be handled using Data annotations. If my validation/business rules get more complex I will probably switch to FluentValidation (this also works well with Depenency Injection) as it's better suited for externalizing validation logic.


I would recommend doing this type of validation at the service layer and not bother with data annotations.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号