【问题标题】:How to move validation handling from a controller action to a decorator如何将验证处理从控制器操作移动到装饰器
【发布时间】:2014-07-23 12:22:32
【问题描述】:

维护编辑

在使用这种方法一段时间后,我发现自己只在每个控制器中添加完全相同的样板代码,所以我决定做一些反射魔法。与此同时,我放弃了使用 MVC 来查看视图——Razor 太乏味和丑陋——所以我基本上使用我的处理程序作为 JSON 后端。我目前使用的方法是用 Route 属性装饰我的查询/命令,该属性位于一些常见的程序集中,如下所示:

[Route("items/add", RouteMethod.Post)]
public class AddItemCommand { public Guid Id { get; set; } }

[Route("items", RouteMethod.Get)]
public class GetItemsQuery : IQuery<GetItemsResponse> { }

// The response inherits from a base type that handles
// validation messages and the like
public class GetItemsResponse : ServiceResponse { }

然后我实现了一个 MVC 主机,它提取带注释的命令/查询并在启动时为我生成控制器和处理程序。有了这个,我的应用程序逻辑终于摆脱了 MVC 的麻烦。查询响应也会自动填充验证消息。我的 MVC 应用程序现在都看起来像这样:

+ MvcApp +- 全球.asax +- Global.asax.cs - 启动主机并完成 +- 网络配置

在意识到我真的没有在主机之外使用 MVC 之后——并且经常遇到框架所具有的大量依赖项的问题——我实现了另一个基于 NServiceKit 的主机。在我的应用程序逻辑中无需更改任何内容,并且依赖关系降至 System.WebNServiceKitNServiceKit.Text,它们很好地处理了模型绑定。我知道这与NServiceKit/ServiceStack 的工作方式非常相似,但我现在完全脱离了正在使用的 Web 框架,所以如果出现更好的框架,我只需实现另一个主机即可。

情况

我目前正在开发一个 ASP.NET MVC 站点,该站点通过 IQueryHandlerICommandHandler 抽象实现业务逻辑视图分离(使用 the almighty SimpleInjector 进行依赖注入)。

问题

我必须通过装饰器将一些自定义验证逻辑附加到QueryHandler,这本身就工作得很好。问题是,如果出现验证错误,我希望能够显示与操作将返回的视图相同的视图,但当然还有关于验证错误的信息。这是我的案例的示例:

public class HomeController : Controller
{
    private readonly IQueryHandler<SomeQuery, SomeTransport> queryHandler;

    public ActionResult Index()
    {
        try
        {
            var dto = this.queryHandler.Handle(new SomeQuery { /* ... */ });
            // Doing something awesome with the data ...
            return this.View(new HomeViewModel());
        }
        catch (ValidationException exception)
        {
            this.ModelState.AddModelErrors(exception);
            return this.View(new HomeViewModel());
        }
    }
}

在这种情况下,我有一些由queryHandler 处理的业务逻辑,它用ValidationQueryHandlerDecorator 装饰,在适当的时候抛出ValidationExceptions。

我想要它做什么

我想要的是类似的东西:

public class HomeController : Controller
{
    private readonly IQueryHandler<SomeQuery, SomeTransport> queryHandler;

    public ActionResult Index()
    {
        var dto = this.queryHandler.Handle(new SomeQuery { /* ... */ });
        // Doing something awesome with the data ...
        // There is a catch-all in place for unexpected exceptions but
        // for ValidationExceptions I want to do essentially the same
        // view instantiation but with the model errors attached
        return this.View(new HomeViewModel());
    }
}

我一直在考虑一个特殊的ValidationErrorHandlerAttribute,但后来我失去了上下文,我无法真正返回正确的视图。我只是用装饰器包装IQueryHandler&lt;,&gt; 的方法也是如此......我看到一些奇怪的代码片段在路由上进行了一些字符串嗅探,然后通过Activator.CreateInstance 实例化一个新的控制器和视图模型 -这似乎不是一个好主意。

所以我想知道是否有一个很好的方法来做到这一点......也许我只是看不到树上的木头。谢谢!

【问题讨论】:

    标签: c# asp.net-mvc decorator simple-injector


    【解决方案1】:

    我认为没有办法让 action 方法忽略这一点,因为 action 方法控制着返回的视图模型,如果出现验证异常,您需要返回一个包含所有实际数据(以防止用户丢失他的更改)。然而,为了使这更方便,您可以做的是添加一个扩展方法来在操作中执行查询:

    public ActionResult Index()
    {
        var result = this.queryHandler.ValidatedHandle(this.ModelState, new SomeQuery { });
    
        if (result.IsValid) {
            return this.View(new HomeViewModel(result.Data));
        }
        else
        {
            return this.View(new HomeViewModel());
        }
    }
    

    ValidatedHandle 扩展方法可能如下所示:

    public static ValidatedResult<TResult> ValidatedHandle<TQuery, TResult>(
        this IQueryHandler<TQuery, TResult> handler,
        TQuery query, ModelStateDictionary modelState)
    {
        try
        {
            return new ValidatedResult<TResult>.CreateValid(handler.Handle(query));
        }
        catch (ValidationException ex)
        {
            modelState.AddModelErrors(ex);
            return ValidatedResult<TResult>.Invalid;
        }
    }
    

    请注意,如果验证是针对用户输入的数据,您应该只捕获此类验证异常。如果您发送带有以编程方式设置的参数的查询,则验证异常仅表示编程错误,您应该在博客上记录该异常并向用户显示友好的错误页面。

    【讨论】:

    • 这很好,我确实考虑过控制器上的扩展方法,但IQueryHandler 上没有,我会试一试。再次感谢!
    • @vanhelgen:在我最近做的一个 win forms 项目中,我们没有注入我们的ICommandHandler&lt;T&gt;,而是注入了一个IPromptableCommandHandler&lt;T&gt;(我谈到了here)。实现捕获了异常并将其显示给用户。使用扩展方法几乎相同。我们定义了一个新的抽象,因为实现需要一些额外的服务,否则这些服务需要由调用者传递。
    • 我一直在玩弄这个,我也找不到比你更好的方法,所以我现在将使用它。感谢您的建议和IPromptableCommandHandler&lt;T&gt; 链接。我已经有了通知用户验证错误的功能,但我会记住它以备将来使用。总是很高兴:-)
    猜你喜欢
    • 2015-03-06
    • 2023-03-25
    • 2020-08-27
    • 2021-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-03
    • 1970-01-01
    相关资源
    最近更新 更多