将ModelState注入服务

Mat*_*391 4 c# asp.net-core-mvc asp.net-core

我试图将控制器的ModelState注入我的服务层以添加与业务逻辑相关的验证错误(例如,条目已经存在).

为此,我创建了一个新的IValidationDictionary,它通过Initialize()函数注入我的服务.没有什么新东西可以通过我的谷歌搜索来判断一些人在MVC中所做的事情.

Controller构造函数如下所示:

public AccountController(IAccountService accountService)
{
   _accountService = accountService;
   _accountService.Initialize(new ValidationDictionary(ModelState));
}
Run Code Online (Sandbox Code Playgroud)

这一切都很好,我可以在我的服务中添加错误.再次离开服务时出现问题.此时,控制器ModelState中没有出现任何错误.经过一些调试后,我发现控制器构造函数中的ModelState与Action中的不同.在某些时候,它似乎创建了一个新的ModelState.

一种替代方案似乎是调用Initialize()在每个Action的开头注入ModelState.在我这样做之前,我想问一下是否有人有一种更优雅的方式(如在更少的类型中)解决这个问题.

编辑:IValidationDictionary:在商务层:

public interface IValidationDictionary
    {
        void AddError(string key, string message);
        bool IsValid { get; }
    }
Run Code Online (Sandbox Code Playgroud)

在控制器中:

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

Tse*_*eng 7

首先,你不应该这样做,因为你将混合两件事并打破关注点的分离并紧密结合你的应用程序.

紧密耦合

ModelStateproperty属于ModelStateDictioanryASP.NET Core特定类的类型.如果在业务层中使用它,则会创建对ASP.NET Core的依赖,从而无法在ASP.NET Core之外的任何位置重用逻辑,即后台工作进程,它是一个纯控制台应用程序,因为您既不引用ASP. NET Core也没有HttpContext或其他任何东西.

关注点分离

业务验证和输入验证是两个不同的事情,应该以不同的方式处理.ModelStateDictionary用于输入验证,以验证传递给控制器​​的输入.它并不意味着验证业务逻辑或类似的东西!

另一方面,业务验证不仅仅是对字段及其模式的粗略验证.它包含逻辑和验证可能很复杂,并且依赖于多个属性/值以及当前对象的状态.因此,例如,可能通过输入验证的值可能在业务验证中失败.

因此,通过同时使用两者,您将违反关注点的分离,并让一个班级做不止一件事.从长远来看,这对于维护代码是不利的.

如何解决它?

转换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(
Run Code Online (Sandbox Code Playgroud)

这解决了依赖关系的问题,但您仍然违反了关注点的分离,特别是如果您在业务层中使用与用作控制器参数相同的模型.

自定义验证(完全分离)

一开始它的工作量更多,但你的验证将是完全独立的,你可以改变其中一个而不影响另一个.

为此,您可以使用Fluent Validations之类的框架,这可以使验证更容易,更易于管理

您编写了一个自定义验证器,它将验证某个类/模型的逻辑.

自定义验证器可以像每个模型编写自己的验证器一样简单,可以实现这样的接口

public interface IValidator<T> where T : class
{
    bool TryValidate(T model, out List<ValidationErrorModel> validationResults);
    List<ValidationErrorModel> Validate(T model);
}
Run Code Online (Sandbox Code Playgroud)

并将它包装在验证器类周围

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

自定义验证(基于验证属性)

以上的替代,但使用验证属性作为基础和自定义逻辑.您可以使用Validator.TryValidateObjectfrom System.ComponentModel.DataAnnotations来验证模型ValidatorAttributes.但请注意,它只会验证传递的模型属性,而不验证子模型.

List<ValidationResult> results = new List<ValidationResult>();
var validationContext = new ValidationContext(model);

if(!Validator.TryValidateObject(model, validateContext, results))
{
    // validation failed
}
Run Code Online (Sandbox Code Playgroud)

然后另外执行自定义逻辑.请参阅此博客文章,了解如何实现子模型验证.

Imho最干净的方法是自定义验证器,因为它分离和解耦,并且很容易让您更改模型的逻辑,而不影响其他模型的验证.

如果您只验证消息(即CQRS中的命令/查询),则可以使用第二种方法和验证属性.


tho*_*ean 1

为每个请求创建一个新的控制器。根据您将服务配置为瞬态服务还是单例服务,IAccountService每个请求都会创建一个新服务,或者重用单个实例。由于您管理每个请求的状态,因此我假设您有一个瞬态服务,即每个请求都有一个新实例。一旦请求消失,这些实例就不再被引用。

我不知道你离开服务是什么意思。我希望这提供了正确的输入来追踪您的问题。