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

mfe*_*eis 10 c# asp.net-mvc decorator simple-injector

维护编辑

在使用这种方法一段时间后,我发现自己只在每个控制器中添加完全相同的样板代码,所以我决定做一些反射魔法.与此同时,我放弃使用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 { }
Run Code Online (Sandbox Code Playgroud)

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

+ MvcApp
  +- Global.asax
  +- Global.asax.cs - Startup the host and done
  +- Web.config

在意识到我真的不在主机之外使用MVC - 并且经常遇到框架所具有的bazillion依赖性问题 - 我实现了另一个基于NServiceKit的主机.在我的应用程序逻辑中没有什么必须改变,并且依赖关系到了System.Web,NServiceKit并且NServiceKit.Text这很好地照顾了模型绑定.我知道这是一个非常类似于NServiceKit/ServiceStack他们的东西的方法,但我现在完全脱离使用的Web框架,所以如果更好的一个出现我只是实现另一个主机,就是这样.

情况

我目前正在开发一个ASP.NET MVC站点,该站点通过IQueryHandlerICommandHandler抽象实现businesslogic-view分离(使用全能的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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下我的影片在处理一些业务逻辑queryHandler装饰有一个ValidationQueryHandlerDecorator抛出ValidationException■当它是适当的.

我想要它做什么

我想要的是以下内容:

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

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

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

Ste*_*ven 2

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

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

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

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