在控制器和 ActionFilter 之外获取 ModelState

Get ModelState outside of controller and ActionFilter

有没有办法从 DI(在 Controller 或 ActionFilter 之外)访问 ModelStateDictionary?我想我可以制作一个 ActionFilter,在某处存储对 ModelStateDictionary 的引用,这样我以后可以在其他地方访问它,但我想知道是否有 常规 访问方式它,就像 IHttpContextAccessor 对应 HttpContext.

我们有一个 Web 应用程序,它是我们 api 的客户端。我想这样做的原因是因为我想从我们的 API 客户端(类型化的 http 客户端)使用的 DelegatingHandler 自动向 Web 应用程序的 ModelState 添加错误。处理程序将监视来自 API 的每个响应,并且对于适用的响应(正文中带有自定义错误代码的 400 个响应,如 "Name already taken"),向 ModelState 添加一条消息。

到目前为止,我已经尝试请求一个 ControllerContext,但它似乎总是空的。

var controllerContext = _serviceProvider.GetService<ControllerContext>();
controllerContext?.ModelState.AddModelError("", result.ErrorMessage);

我也查看了所有使用 VS 调试器的注册服务,但我找不到任何有希望的东西。


旁注(相当大)关于 SRP 和关注点分离的评论:我不认为这违反了 SRP。 API 客户端是我们 API 的通用客户端实现,可以从任何地方使用(我们目前在 Xamarin 和 ASP.NET Core MVC Web 应用程序上使用它——本文中提到的那个)非常疑问)。但是,API 客户端在其构造函数中期望 HttpClient,这意味着其行为可以由其消费者修改。

例如,Web 应用程序使用 DI 来提供 HttpClient 和 API 客户端的需求。 HttpClient 设置为使用两个委托处理程序,其中之一就是我在这个问题中描述的那个。

至于 ModelState 是否应该在控制器之外进行操作:好吧,这正是像 FluentValidation(和 ASP.NET 默认验证)这样的库所做的。

至于 ModelState 是否应该在 DelegatingHandler 中被操纵:我认为这是一个更有效的讨论。然而,没有人真正提出任何关于为什么这不好的论据。

至于是否应该这样做 "automatically":我认为将代码放在一个地方比每次调用 api.

至于这些消息是否应该甚至放入ModelState:好吧,如果我在这里进入那个 side 注释会变得太大。另外,没有人真正争论过这个,所以...

在我看来,这样做会破坏单一职责原则,因为 http 客户端不应该知道有关您的控制器的任何信息。
但是,如果您真的想这样做,可以使用 lambda 表达式将 ModelStateDictionary 的引用传递给您的委托,请参阅:Passing delegate function with extra parameters


如果您不想破坏 SRP,一种选择是使 http 客户端 return 控制器添加到模型状态的错误列表(或如果没有错误则为 null)

您无法获得 ControllerContext itself but what you can get is the ActionContext (which is a more specialized version of the controller context). You can get it by using the IActionContextAccessor:

var actionContextAccessor = _serviceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
actionContext?.ModelState.AddModelError("", result.ErrorMessage);

您实际上不需要每次都解析操作上下文访问器,但可以保留它的实例并在需要时访问操作上下文。 ActionContext 将在您处理请求时处于操作范围内时设置。

另一种解决方案是分离关注点并将错误存储在其他地方,然后在操作过滤器期间从那里填充模型状态。这样,您的解决方案就不会局限于 MVC 方式,您还可以在其他地方访问这些错误。