我正在尝试将控制器的ModelState注入我的服务层,以添加与业务逻辑相关的验证错误(例如,条目已存在)。
为此我创建了一个新的IValidationDictionary,它通过Initialize()函数注入我的服务。没有什么新东西可以通过我的谷歌搜索来搜索一些人在MVC中做的事情。
Controller构造函数如下所示:
public AccountController(IAccountService accountService)
{
_accountService = accountService;
_accountService.Initialize(new ValidationDictionary(ModelState));
}
一切正常,我可以在我的服务中添加错误。再次离开服务时出现问题。此时,控制器ModelState中没有出现任何错误。经过一些调试后,我发现控制器构造函数中的ModelState与Action中的不同。在某些时候,它似乎创建了一个新的ModelState。
一种替代方案似乎是调用Initialize()在每个Action的开头注入ModelState。在我这样做之前,我想问一下是否有人有一种更优雅的方式(如在更少的类型中)解决这个问题。
编辑: IValidationDictionary: 在商务层:
public interface IValidationDictionary
{
void AddError(string key, string message);
bool IsValid { get; }
}
在控制器中:
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);
}
}
答案 0 :(得分:8)
首先,您不应该这样做,因为您将混合两件事并打破关注点分离并紧密结合您的应用程序。
ModelState
属性的类型为ModelStateDictioanry
,这是一个ASP.NET核心特定类。如果在业务层中使用它,则会创建对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(
这解决了依赖关系的问题,但您仍然违反了关注点的分离,特别是如果您在业务层中使用与用作控制器参数相同的模型。
一开始就有点工作,但你的验证将是完全独立的,你可以改变其中一个而不影响另一个。
对于它,你可以使用像Fluent Validations这样的框架,这可以使验证更容易和更有管理
您编写了一个自定义验证器,它将验证某个类/模型的逻辑。
自定义验证器可以像为每个模型编写自己的验证器一样简单,可以实现这样的接口
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。
Imho最干净的方法是自定义验证器,因为它分离和解耦,并且很容易让你改变模型的逻辑,而不影响其他模型的验证。如果您只验证消息(即CQRS中的命令/查询),则可以使用第二种方法和验证属性。
答案 1 :(得分:1)
为每个请求创建一个新控制器。根据您是将服务配置为瞬态还是单例,每次请求都会创建一个新的IAccountService
,或者重复使用一个实例。由于您管理每个请求的状态,我假设您有一个临时服务,即每个请求的新实例。请求消失后,这些实例将被取消引用。
我不知道离开服务的意思。我希望这能提供正确的输入来追踪你的问题。
答案 2 :(得分:1)
有一种更简单的方法来访问ModelState。
在启动时配置IActionContextAccessor:
Enum
并将其注入服务中
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
请注意,保持对public class MyService : IMyService
{
private readonly IActionContextAccessor _actionContextAccessor;
public MyService(IActionContextAccessor actionContextAccessor)
{
_actionContextAccessor = actionContextAccessor;
}
public bool IsValid
{
get
{
return _actionContextAccessor.ActionContext.ModelState.IsValid;
}
}
}
的引用非常重要(出于与IActionContextAccessor
相似的原因)。
答案 3 :(得分:0)
感谢用户 Ruard,这是我使用 .NET Core 5.0 的方法
在 Startup.cs 中
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
...
}
在控制器中
AccountService _service;
public AccountController(IActionContextAccessor actionContextAccessor)
{
//this.actionContextAccessor = actionContextAccessor;
_service = new AccountService(actionContextAccessor);
}
在服务层类中
public class AccountService
{
private readonly IActionContextAccessor _actionContextAccessor;
public AccountService(IActionContextAccessor actionContextAccessor)
{
_actionContextAccessor = actionContextAccessor;
}
public void Login(string emailAddress, string password)
{
_actionContextAccessor.ActionContext.ModelState.AddModelError("Email", "Your error message");
}
}
在行动中,你使用 like
_service.Login(model.Email, model.Password);
if(!ModelState.IsValid)
return View(model);