如何将验证处理从控制器动作移动到装饰器

本文关键字:移动 控制器 验证 处理 | 更新日期: 2023-09-27 18:06:15

维护编辑

在使用这种方法一段时间后,我发现自己只在每个控制器中添加完全相同的样板代码,所以我决定做一些反射魔法。与此同时,我放弃了在视图中使用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+ - Global.asax+- Global.asax.cs -启动主机,完成+ - web . config之前

在意识到我真的没有在主机之外使用MVC之后-并且不断遇到框架所具有的无数依赖关系的问题-我实现了基于NServiceKit的另一个主机。我的应用程序逻辑中不需要更改任何内容,并且依赖关系下降到System.Web, NServiceKitNServiceKit.Text,它们可以很好地处理模型绑定。我知道这是一个非常类似的方法,如何NServiceKit/ServiceStack做他们的东西,但我现在完全从使用的web框架解耦,所以如果一个更好的出现,我只是实现另一个主机,就是这样。

<标题> 的情况

我目前正在做一个ASP。. NET MVC站点,通过IQueryHandler和ICommandHandler抽象实现业务逻辑-视图分离(使用全能的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装饰,在适当的时候抛出ValidationException s。

我想让它做什么

我想要的是这样的内容:

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<,>…我看到过一些奇怪的代码,在路由上做了一些字符串嗅探,然后通过Activator.CreateInstance实例化一个新的控制器和视图模型——这似乎不是一个好主意。

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

如何将验证处理从控制器动作移动到装饰器

我不认为有一种方法可以使操作方法忽略这一点,因为操作方法控制返回的视图模型,并且在验证异常的情况下,您需要返回具有所有实际数据的视图模型(以防止用户丢失他的更改)。不过,为了方便起见,您可以添加一个扩展方法,用于在操作中执行查询:

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;
    }
}

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