cho*_*bo2 32 c# ninject ioc-container modelstate asp.net-mvc-2
我正在查看本教程http://asp-umb.neudesic.com/mvc/tutorials/validating-with-a-service-layer--cs,了解如何围绕包装器包装我的验证数据.
我想使用依赖注入.我正在使用ninject 2.0
namespace MvcApplication1.Models
{
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
}
Run Code Online (Sandbox Code Playgroud)
//包装
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
}
}
Run Code Online (Sandbox Code Playgroud)
//控制器
private IProductService _service;
public ProductController()
{
_service = new ProductService(new ModelStateWrapper(this.ModelState),
new ProductRepository());
}
Run Code Online (Sandbox Code Playgroud)
//服务层
private IValidationDictionary _validatonDictionary;
private IProductRepository _repository;
public ProductService(IValidationDictionary validationDictionary,
IProductRepository repository)
{
_validatonDictionary = validationDictionary;
_repository = repository;
}
public ProductController(IProductService service)
{
_service = service;
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*ven 63
该文章给出的解决方案将验证逻辑与服务逻辑混合在一起.这是两个问题,应该分开.当您的应用程序增长时,您将很快发现验证逻辑变得复杂并在整个服务层中重复.
因此,我想提出一个不同的方法.
首先,当发生验证错误时,让服务层抛出异常将是更好的IMO.这将使其更加明确,更难以忘记检查错误.这样就可以将错误处理到表示层.该ProductController会是这样的:
public class ProductController : Controller
{
private readonly IProductService service;
public ProductController(IProductService service) => this.service = service;
public ActionResult Create(
[Bind(Exclude = "Id")] Product productToCreate)
{
try
{
this.service.CreateProduct(productToCreate);
}
catch (ValidationException ex)
{
this.ModelState.AddModelErrors(ex);
return View();
}
return RedirectToAction("Index");
}
}
public static class MvcValidationExtension
{
public static void AddModelErrors(
this ModelStateDictionary state, ValidationException exception)
{
foreach (var error in exception.Errors)
{
state.AddModelError(error.Key, error.Message);
}
}
}
Run Code Online (Sandbox Code Playgroud)
本ProductService类不应该本身有它的任何确认,但应委派专门来验证类:在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);
}
}
Run Code Online (Sandbox Code Playgroud)
该IValidationProvider不该验证本身,而是委派验证,验证类是专门的验证一个特定的类型.当一个对象(或一组对象)无效时,验证提供程序应该抛出一个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)
{
IValidator validator = this.validatorFactory(entity.GetType());
var results = validator.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);
}
}
Run Code Online (Sandbox Code Playgroud)
这ValidationProvider取决于进行IValidator实际验证的实例.提供程序本身不知道如何创建这些实例,但使用注入的Func<Type, IValidator>委托.此方法将具有特定于容器的代码,例如,对于Ninject:
var provider = new ValidationProvider(type =>
{
var valType = typeof(Validator<>).MakeGenericType(type);
return (IValidator)kernel.Get(valType);
});
Run Code Online (Sandbox Code Playgroud)
这个片段显示了一个Validator<T>类.我将在一秒钟内展示这一点.首先,ValidationProvider取决于以下类:
public interface IValidator
{
IEnumerable<ValidationResult> Validate(object entity);
}
public class ValidationResult
{
public ValidationResult(string key, string message)
{
this.Key = key;
this.Message = message;
}
public string Key { get; }
public string Message { get; }
}
public class ValidationException : Exception
{
public ValidationException(ValidationResult[] r) : base(r[0].Message)
{
this.Errors = new ReadOnlyCollection<ValidationResult>(r);
}
public ReadOnlyCollection<ValidationResult> Errors { get; }
}
Run Code Online (Sandbox Code Playgroud)
以上所有代码都是进行验证所需的管道.我们现在可以为每个要验证的实体定义验证类.但是,为了帮助我们的IoC容器,我们应该为验证器定义一个通用的基类.这将允许我们注册验证类型:
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);
}
Run Code Online (Sandbox Code Playgroud)
如您所见,此抽象类继承自IValidator.现在我们可以定义一个ProductValidator派生自的类Validator<Product>:
public sealed class ProductValidator : Validator<Product>
{
protected override IEnumerable<ValidationResult> Validate(
Product entity)
{
if (entity.Name.Trim().Length == 0)
yield return new ValidationResult(
nameof(Product.Name), "Name is required.");
if (entity.Description.Trim().Length == 0)
yield return new ValidationResult(
nameof(Product.Description), "Description is required.");
if (entity.UnitsInStock < 0)
yield return new ValidationResult(
nameof(Product.UnitsInStock),
"Units in stock cnnot be less than zero.");
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,ProductValidator该类使用C#yield return语句,这使得返回验证错误更加容易.
我们应该做的最后一件事就是设置Ninject配置:
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>();
Run Code Online (Sandbox Code Playgroud)
我们真的完成了吗?这取决于.上述配置的缺点是,对于我们域中的每个实体,我们都需要Validator<T>实现.即使大多数实现可能都是空的.
我们可以通过以下两件事来解决这个问题:1.我们可以使用批量注册从给定的程序集动态加载所有实现.2.当没有注册时,我们可以恢复默认实现.
这样的默认实现可能如下所示:
sealed class NullValidator<T> : Validator<T>
{
protected override IEnumerable<ValidationResult> Validate(T entity)
{
return Enumerable.Empty<ValidationResult>();
}
}
Run Code Online (Sandbox Code Playgroud)
我们可以这样配置NullValidator<T>如下:
kernel.Bind(typeof(Validator<>)).To(typeof(NullValidator<>));
Run Code Online (Sandbox Code Playgroud)
执行此操作后,Ninject将NullValidator<Customer>在Validator<Customer>请求a 时返回,并且没有为其注册特定实现.
现在缺少的最后一件事是自动注册(或批量注册).这将使您不必为每个Validator<T>实现添加注册,并让Ninject为您动态搜索程序集.我找不到任何这方面的例子,但我认为Ninject可以做到这一点.
更新:请参阅Kayess的答案,了解如何批量注册这些类型.
最后一点:为了完成这项工作,你需要相当多的管道,所以如果你的项目(和停留)相当少,这种方法可能会给你带来太多的开销.然而,当您的项目增长时,如果您拥有如此灵活的设计,您将会非常高兴.如果要将验证更改为Validation Application Block或DataAnnotations,请考虑您必须执行的操作.你唯一需要做的就是编写一个实现NullValidator<T>(DefaultValidator<T>在这种情况下我会将它重命名.除此之外,仍然可以使用自定义验证类来进行VAB或DataAnnotations难以实现的额外验证.
请注意,使用抽象例如IProductService和ICustomerService违反SOLID原则,您可能会从这种模式转变为抽象用例的模式中受益.
更新:还看看这个q/a ; 它讨论了关于同一篇文章的后续问题.