validation:如何使用Ninject注入模型状态包装器?

我正在看这个教程http://asp-umb.neudesic.com/mvc/tutorials/validating-with-a-service-layer–cs关于如何包装我的validation数据。

我想使用dependency injection。 我正在使用ninject 2.0

namespace MvcApplication1.Models { public interface IValidationDictionary { void AddError(string key, string errorMessage); bool IsValid { get; } } } 

//包装

 using System.Web.Mvc; namespace MvcApplication1.Models { public class ModelStateWrapper : IValidationDictionary { private ModelStateDictionary _modelState; public ModelStateWrapper(ModelStateDictionary modelState) { _modelState = modelState; } #region IValidationDictionary Members public void AddError(string key, string errorMessage) { _modelState.AddModelError(key, errorMessage); } public bool IsValid { get { return _modelState.IsValid; } } #endregion } } 

//控制器

 private IProductService _service; public ProductController() { _service = new ProductService(new ModelStateWrapper(this.ModelState), new ProductRepository()); } 

//服务层

 private IValidationDictionary _validatonDictionary; private IProductRepository _repository; public ProductService(IValidationDictionary validationDictionary, IProductRepository repository) { _validatonDictionary = validationDictionary; _repository = repository; } public ProductController(IProductService service) { _service = service; } 

该文章给出的解决scheme将validation逻辑与服务逻辑混合在一起。 这是两个问题,应该分开。 当您的应用程序增长时,您将很快发现validation逻辑变得复杂,并在整个服务层重复。

因此,我想提出一个不同的方法。

首先,当发生validation错误时,让服务层抛出exception会更好。 这将使它更加明确,更难以忘记检查错误。 这留下了error handling到表示层的方式。 ProductController将如下所示:

 public class ProductController : Controller { public ActionResult Create( [Bind(Exclude = "Id")] Product productToCreate) { try { this.service.CreateProduct(productToCreate); } catch (ValidationException ex) { MvcValidationExtension.AddModelErrors(this.ModelState, ex); return View(); } return RedirectToAction("Index"); } } public static class MvcValidationExtension { public static void AddModelErrors(this ModelStateDictionary state, ValidationException exception) { foreach (var error in exception.ValidationErrors) state.AddModelError(error.Key, error.Message); } } 

ProductService类本身不应该有任何validation,但是应该委托给一个专门用于validation的类: IValidationProvider

 public interface IValidationProvider { void Validate(object entity); void ValidateAll(IEnumerable entities); } public class ProductService : IProductService { private readonly IValidationProvider validationProvider; private readonly IProductRespository repository; public ProductService(IProductRespository repository, IValidationProvider validationProvider) { this.repository = repository; this.validationProvider = validationProvider; } // Does not return an error code anymore. Just throws an exception public void CreateProduct(Product productToCreate) { // Do validation here or perhaps even in the repository... this.validationProvider.Validate(productToCreate); // This call should also throw on failure. this.repository.CreateProduct(productToCreate); } } 

IValidationProvider不应该自行validation,而是将validation委托给专门validation一个特定types的validation类。 当一个对象(或一组对象)无效时,validation提供者应该抛出一个ValidationException ,这个ValidationException可以被调用堆栈调用。 提供者的实现可能如下所示:

 sealed class ValidationProvider : IValidationProvider { private readonly Func<Type, IValidator> validatorFactory; public ValidationProvider(Func<Type, IValidator> validatorFactory) { this.validatorFactory = validatorFactory; } public void Validate(object entity) { var results = this.validatorFactory(entity.GetType()) .Validate(entity).ToArray(); if (results.Length > 0) throw new ValidationException(results); } public void ValidateAll(IEnumerable entities) { var results = ( from entity in entities.Cast<object>() let validator = this.validatorFactory(entity.GetType()) from result in validator.Validate(entity) select result).ToArray(); if (results.Length > 0) throw new ValidationException(results); } } 

ValidationProvider依赖于IValidator实例,它们进行实际validation。 提供程序本身并不知道如何创build这些实例,但使用注入的Func<Type, IValidator>委托。 此方法将具有容器特定的代码,例如对于Ninject:

 var provider = new ValidationProvider(type => { var valType = typeof(Validator<>).MakeGenericType(type); return (IValidator)kernel.Get(valType); }); 

这个片段显示了一个Validator<T>类。 我会在一秒钟之内显示出来。 首先, ValidationProvider依赖于以下类:

 public interface IValidator { IEnumerable<ValidationResult> Validate(object entity); } public class ValidationResult { public ValidationResult(string key, string message) { ... } public string Key { get; private set; } public string Message { get; private set; } } public class ValidationException : Exception { public ValidationException(IEnumerable<ValidationResult> r) : base(GetFirstErrorMessage(r)) { this.Errors = new ReadOnlyCollection<ValidationResults>(r.ToArray()); } public ReadOnlyCollection<ValidationResults> Errors { get; private set; } private static string GetFirstErrorMessage( IEnumerable<ValidationResult> errors) { return errors.First().Message; } } 

以上所有代码都是进行validation所需的工具。 现在我们可以为每个想要validation的实体定义一个validation类。 然而,为了帮助我们的Io​​C容器,我们应该为validation器定义一个通用的基类。 这将允许我们注册validationtypes:

 public abstract class Validator<T> : IValidator { IEnumerable<ValidationResult> IValidator.Validate(object entity) { if (entity == null) throw new ArgumentNullException("entity"); return this.Validate((T)entity); } protected abstract IEnumerable<ValidationResult> Validate(T entity); } 

如你所见,这个抽象类inheritance自IValidator 。 现在我们可以定义一个派生自Validator<Product>ProductValidator类:

 public sealed class ProductValidator : Validator<Product> { protected override IEnumerable<ValidationResult> Validate( Product entity) { if (entity.Name.Trim().Length == 0) yield return new ValidationResult("Name", "Name is required."); if (entity.Description.Trim().Length == 0) yield return new ValidationResult("Description", "Description is required."); if (entity.UnitsInStock < 0) yield return new ValidationResult("UnitsInStock", "Units in stock cnnot be less than zero."); } } 

正如您所看到的, ProductValidator类使用C#的yield return语句,这使得返回validation错误变得更容易。

我们应该做的最后一件事情就是设置Ninjectconfiguration:

 kernel.Bind<IProductService>().To<ProductService>(); kernel.Bind<IProductRepository>().To<L2SProductRepository>(); Func<Type, IValidator> validatorFactory = type => { var valType = typeof(Validator<>).MakeGenericType(type); return (IValidator)kernel.Get(valType); }; kernel.Bind<IValidationProvider>() .ToConstant(new ValidationProvider(validatorFactory)); kernel.Bind<Validator<Product>>().To<ProductValidator>(); 

我们真的完成了吗? 这取决于。 上面configuration的缺点是对于我们域中的每个实体,我们需要一个Validator<T>实现。 即使大多数实现可能是空的。

我们可以通过做两件事来解决这个问题:1.我们可以使用批量注册从给定的程序集dynamic地自动加载所有的实现。 2.当没有注册时,我们可以恢复到默认实现。

这样的默认实现可能如下所示:

 sealed class NullValidator<T> : Validator<T> { protected override IEnumerable<ValidationResult> Validate(T entity) { Enumerable.Empty<ValidationResult>(); } } 

我们可以如下configuration这个NullValidator<T>

 kernel.Bind(typeof(Validator<>)).To(typeof(NullValidator<>)); 

这样做后,Ninject会在请求Validator<Customer>时返回一个NullValidator<Customer> ,并且没有为其注册具体的实现。

现在缺less的最后一件事是自动注册(或批量注册)。 这将使您无需为每个Validator<T>实现添加注册,并让Ninject为您dynamicsearch您的程序集。 我找不到任何这样的例子,但我认为Ninject可以做到这一点。

更新:请参阅Kayess的答案 ,了解如何批量注册这些types。

最后一个注意事项:为了完成这个工作,你需要相当多的pipe道工作,所以如果你的项目(和保持)相当less,这种方法可能会给你太多的开销。 当你的项目不断增长的时候,如果你有这样一个灵活的devise,你将会非常高兴。 想想如果你想改变validation来说validation应用程序块或DataAnnotations你必须做什么。 你唯一需要做的就是编写一个NullValidator<T> (在这种情况下,我将它重命名为DefaultValidator<T> 。除此之外, NullValidator<T>你的自定义validation类添加额外的validation很难用VAB或DataAnnotations。

请注意,诸如IProductServiceICustomerService之类的抽象的使用违反了SOLID原则,您可能从这种模式转移到抽象用例模式中受益匪浅。

更新:也看看这个q / a ; 它讨论了关于同一篇文章的后续问题。

我想扩展史蒂文斯的梦幻般的答案,他写道:

现在缺less的最后一件事是自动注册(或批量注册)。 这将使您无需为每个Validator实现添加注册,并让Ninject为您dynamicsearch您的程序集。 我找不到任何这样的例子,但我认为Ninject可以做到这一点。

他指这个代码不能是automagic:

 kernel.Bind<Validator<Product>>().To<ProductValidator>(); 

现在想象一下,如果你有这样的几十个:

 ... kernel.Bind<Validator<Product>>().To<ProductValidator>(); kernel.Bind<Validator<Acme>>().To<AcmeValidator>(); kernel.Bind<Validator<JohnDoe>>().To<JohnDoeValidator>(); ... 

所以为了克服这一点,我发现自动化:

 kernel.Bind( x => x.FromAssembliesMatching("Fully.Qualified.AssemblyName*") .SelectAllClasses() .InheritedFrom(typeof(Validator<>)) .BindBase() ); 

在哪里可以将Fully.Qualified.AssemblyNamereplace为您的实际程序集名称完全限定,包括您的名称空间。