我正在尝试将 Controller 的 ModelState 注入(inject)我的服务层以添加与业务逻辑相关的验证错误(例如条目已存在)。
为此,我创建了一个新的 IValidationDictionary,它通过 Initialize() 函数注入(inject)到我的服务中。一点也不新鲜,根据我的谷歌搜索判断,有些人在 MVC 中做了一些事情。
Controller 的构造函数如下所示:
public AccountController(IAccountService accountService)
{
_accountService = accountService;
_accountService.Initialize(new ValidationDictionary(ModelState));
}
一切正常,我可以在我的服务中添加错误。再次离开服务时会出现问题。那时我的错误都没有出现在 Controller ModelState 中。经过一些调试,我发现 Controller 的构造函数中的 ModelState 与 Action 中的不一样。在某些时候,它似乎创建了一个新的 ModelState。
另一种方法似乎是调用 Initialize() 在每个 Action 开始时注入(inject) ModelState。在我这样做之前,我想问问是否有人有更优雅的方式(如 less to type)来解决这个问题。
编辑: IValidationDictionary: 在业务层:
public interface IValidationDictionary
{
void AddError(string key, string message);
bool IsValid { get; }
}
在 Controller 中:
public class ValidationDictionary : IValidationDictionary
{
private ModelStateDictionary _modelState;
public ValidationDictionary(ModelStateDictionary modelState)
{
_modelState = modelState;
}
public bool IsValid
{
get
{
return _modelState.IsValid;
}
}
public void AddError(string key, string message)
{
_modelState.AddModelError(key, message);
}
}
最佳答案
首先,您不应该这样做,因为您将混合使用两件事并破坏关注点分离并使您的应用程序紧密耦合。
紧耦合
ModelState
属性类型为 ModelStateDictioanry
这是一个 ASP.NET Core 特定的类。如果您在业务层中使用它,则会创建对 ASP.NET Core 的依赖性,从而无法在 ASP.NET Core 之外的任何地方重用您的逻辑,即后台工作进程,它是一个纯控制台应用程序,因为您既不引用 ASP。 NET Core 也不会有 HttpContext 或其他任何东西。
关注点分离
业务验证和输入验证是两件不同的事情,应该区别对待。 ModelStateDictionary
用于输入验证,以验证传递给 Controller 的输入。它并不意味着验证业务逻辑或类似的东西!
另一方面,业务验证不仅仅是对字段及其模式的粗略验证。它包含逻辑和验证可能很复杂,并且取决于多个属性/值以及当前对象的状态。因此,例如,可能通过输入验证的值可能无法通过业务验证。
因此,通过同时使用两者,您将违反关注点分离并让一个类做不止一件事。从长远来看,这不利于维护代码。
如何解决?
转换IDicitionary<string, ModelStateEntry>
自定义验证模型/ValidationResult
ValidationResult
在 System.Components.DataAnnotations` 程序集中定义,它与 ASP.NET Core 无关,但是是 .NET Core/完整 .NET Framework 的端口,因此您不会依赖 ASP.NET Core 并且可以在控制台应用程序等,并使用工厂类在您的验证服务中传递它
public interface IAccoutServiceFactory
{
IAccountService Create(List<ValidationResult> validationResults);
}
// in controller
List<ValidationResult> validationResults = ConvertToValidationResults(ModelState);
IAccountService accountService = accountServiceFactory.Create(
这解决了依赖性问题,但您仍然违反了关注点分离,尤其是当您在业务层中使用与用作 Controller 参数相同的模型时。
自定义验证器(完全分离)
开始时需要做更多的工作,但您的验证将完全独立,您可以更改其中一个而不会影响另一个。
为此,您可以使用像 Fluent Validations 这样的框架,这可能会使验证更容易一些并且更易于管理
您编写一个自定义验证器,它将验证某个类/模型的逻辑。
自定义验证器可以像为每个模型编写自己的验证器一样简单,它可以实现这样的接口(interface)
public interface IValidator<T> where T : class
{
bool TryValidate(T model, out List<ValidationErrorModel> validationResults);
List<ValidationErrorModel> Validate(T model);
}
并将其包装在您的验证器类中
public class ModelValidator : IModelValidator
{
public List<ValidationErrorModel> Validate<T>(T model)
{
// provider is a IServiceProvider
var validator = provider.RequireService(typeof(IValidator<T>));
return validator.Validate(model);
}
}
自定义验证器(基于验证属性)
上述的替代,但使用验证属性作为基础和自定义逻辑。您可以使用 Validator.TryValidateObject
来自 System.ComponentModel.DataAnnotations
验证模型 ValidatorAttributes
.但请注意,它只会验证传递的模型属性,而不是子模型。
List<ValidationResult> results = new List<ValidationResult>();
var validationContext = new ValidationContext(model);
if(!Validator.TryValidateObject(model, validateContext, results))
{
// validation failed
}
然后另外执行自定义逻辑。看这个blog post关于如何实现子模型验证。
恕我直言,最干净的方法是自定义验证器,因为它既分离又分离,并且可以轻松地让您更改模型的逻辑而不会影响其他模型的验证。
如果您只验证消息(即 CQRS 中的命令/查询),您可以将第二种方法与验证属性一起使用。
关于c# - 将 ModelState 注入(inject)服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40426169/