gpt4 book ai didi

c# - 验证: How to inject A Model State wrapper with Ninject?

转载 作者:IT王子 更新时间:2023-10-29 04:07:34 25 4
gpt4 key购买 nike

我在看本教程http://asp-umb.neudesic.com/mvc/tutorials/validating-with-a-service-layer--cs,了解如何在包装器上包装我的验证数据。

我想使用依赖注入(inject)。我正在使用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
}
}

// Controller
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;
}

最佳答案

该文章给出的解决方案将验证逻辑与服务逻辑混合在一起。这是两个问题,应该分开。当您的应用程序增长时,您将 swift 发现验证逻辑变得复杂,并且在整个服务层中重复出现。因此,我想提出一种不同的方法。

首先,当发生验证错误时,让服务层引发异常会更好,这是IMO所能做到的。这使得它更明确,也更难忘记检查错误。这就将错误处理的方式留给了表示层。以下 list 显示了使用此方法的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);
}
}
}
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);
}
}

但是,此 IValidationProvider不应自行验证,而应将验证委托(delegate)给专门用于验证一种特定类型的验证类。当一个对象(或一组对象)无效时,验证提供程序应抛出 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);
}
}
ValidationProvider取决于进行实际验证的 IValidator实例。提供程序本身不知道如何创建这些实例,但是为此使用了注入(inject)的 Func<Type, IValidator>委托(delegate)。此方法将具有容器特定的代码,例如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)
{
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; }
}

以上所有代码都是完成验证所需的管道。现在,您可以为每个要验证的实体定义一个验证类。但是,为了稍微帮助您的DI容器,您应该为验证器定义一个通用基类。这将允许您注册验证类型:
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);
}

如您所见,此抽象类继承自 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.");
}
}

如您所见, 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>();

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

您可以通过做两件事来解决此问题:
  • 您可以使用自动注册从给定的程序集中自动动态加载所有实现。
  • 如果不存在注册,则可以还原为默认实现。

  • 这样的默认实现如下所示:
    sealed class NullValidator<T> : Validator<T>
    {
    protected override IEnumerable<ValidationResult> Validate(T entity)
    {
    return Enumerable.Empty<ValidationResult>();
    }
    }

    您可以按以下方式配置此 NullValidator<T>:
    kernel.Bind(typeof(Validator<>)).To(typeof(NullValidator<>));

    完成此操作后,当请求 NullValidator<Customer>且未为其注册任何特定实现时,Ninject将返回 Validator<Customer>

    现在缺少的最后一件事是自动注册。这将使您不必为每个 Validator<T>实现添加注册,并使Ninject为您动态搜索程序集。我找不到任何示例,但是我认为Ninject可以做到这一点。

    更新:请参见 Kayess' answer以了解如何自动注册这些类型。

    最后一点:要做到这一点,您需要做大量的工作,因此,如果您的项目很少(并且保持不变),那么这种方法可能会给您带来过多的开销。但是,随着项目的增长,拥有如此灵活的设计将使您感到非常高兴。考虑一下如果您想更改验证(例如验证应用程序块或DataAnnotations),您必须做什么。唯一要做的就是编写 NullValidator<T>的实现(在这种情况下,我将其重命名为 DefaultValidator<T>。除此之外,仍然可以为其他验证难以实现的自定义验证类提供额外的验证技术。

    请注意,使用诸如IProductServiceICustomerService之类的抽象方法违反了SOLID原则,从此模式转换为a pattern that abstracts use cases可能会使您受益。

    更新:还可以看看 this q/a;它讨论了有关同一文章的后续问题。

    关于c# - 验证: How to inject A Model State wrapper with Ninject?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4776396/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com