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.
精彩评论