【问题标题】:Can I refactor to Model View Query Handler?我可以重构为模型视图查询处理程序吗?
【发布时间】:2013-11-18 22:22:00
【问题描述】:

在我们的 MVC 应用程序中,我们所有的读取操作都作为参数获取一个查询,该查询实现:

public interface IQuery<out TResponse> { }

在操作中,查询被传递到总线,该总线定位处理程序并返回视图模型。所以控制器现在看起来像这样:

   public ActionResult Edit(DetailsQuery query)
    {
        var model = mediator.Request(query);
        return View(model);
    }

实际上只是将查询传递给我们的中介并返回结果。我们有数百个看起来像这样的动作。有一些奇怪的动作会做一些有条件的事情(我会保持原样),但其余的只是一次又一次的相同样板。我们有超过一百个不同的查询

如何将其重构为更明确的内容?我想转移到模型视图查询处理程序而不是样板控制器操作,它只是将查询交给总线并将模型返回给视图。

我应该在 MVC 中查看哪些扩展点?有效地不必编写动作处理程序 - 只需使用某种自动方式将强类型查询连接在一起并取回正确的 ViewModel。

如果可以的话?我是不是该?我只是不喜欢看到数百个看起来都一样的动作。

【问题讨论】:

  • 您有多少种不同的查询类型?以及您的请求如何映射到特定的查询类型
  • 我们有数百个查询。如果它不是单个 id 参数,它通常封装在查询中。基本上我们正在做同样的事情lostechies.com/jimmybogard/2013/10/29/…
  • 如果你确实只有一个参数,你是否仍然使用 mediator.Request 调用来获取视图模型?
  • 通常对于一个参数的直接查询,它可能会直接进入存储库。所以只需传递给它,然后自动映射到 ViewModel

标签: c# asp.net-mvc asp.net-mvc-4 architecture cqrs


【解决方案1】:

首先,感谢帖子"Put your controllers on a diet: GETs and queries" 的链接。我的代码示例使用其中的类型。

我的解决方案还涉及使用动作过滤器作为注入通用行为的点。

控制器很简单,看起来像@Kambiz Shahim 的:

[QueryFilter]
public class ConferenceController : Controller
{
    public ActionResult Index(IndexQuery query)
    {
        return View();
    }

    public ViewResult Show(ShowQuery query)
    {
        return View();
    }

    public ActionResult Edit(EditQuery query)
    {
        return View();
    }
}

处理QueryFilterAttribute 我意识到IMediator 及其实现可以省略。知道查询类型就足以通过 IoC 解析 IQueryHandler&lt;,&gt; 的实例。

在我的示例中使用了Castle Windsor'Service Locator' 模式的实现。

public class QueryFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        object query = filterContext.ActionParameters["query"];
        Type queryType = query.GetType();
        Type modelType = queryType.GetInterfaces()[0].GetGenericArguments()[0];

        var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, modelType);

        // Here you should resolve your IQueryHandler<,> using IoC
        // 'Service Locator' pattern is used as quick-and-dirty solution to show that code works.
        var handler = ComponentLocator.GetComponent(handlerType) as IQueryHandler;

        var model = handler.Handle(query);
        filterContext.Controller.ViewData.Model = model;
    }
}

添加IQueryHandler接口以避免使用反射

/// <summary>
/// All derived handlers can be refactored using generics. But in the real world handling logic can be completely different.
/// </summary>
/// <typeparam name="TQuery">The type of the query.</typeparam>
/// <typeparam name="TResponse">The type of the response.</typeparam>
public interface IQueryHandler<in TQuery, out TResponse> : IQueryHandler
    where TQuery : IQuery<TResponse>
{
    TResponse Handle(TQuery query);
}

/// <summary>
/// This interface is used in order to invoke 'Handle' for any query type.
/// </summary>
public interface IQueryHandler
{
    object Handle(object query);
}

/// <summary>
/// Implements 'Handle' of 'IQueryHandler' interface explicitly to restrict its invocation.
/// </summary>
/// <typeparam name="TQuery">The type of the query.</typeparam>
/// <typeparam name="TResponse">The type of the response.</typeparam>
public abstract class QueryHandlerBase<TQuery, TResponse> : IQueryHandler<TQuery, TResponse>
    where TQuery : IQuery<TResponse>
{
    public abstract TResponse Handle(TQuery query);

    object IQueryHandler.Handle(object query)
    {
        return Handle((TQuery)query);
    }
}

类型应该注册in Global.asax.cs

        container.Register(Component.For<ISession>().ImplementedBy<FakeSession>());
        container.Register(
            Classes.FromThisAssembly()
                .BasedOn(typeof(IQueryHandler<,>))
                .WithService.Base()
                .LifestylePerWebRequest());

所有代码都有一个link to gist on github

【讨论】:

  • 是的,删除中介更容易。
  • 不过,这会导致 Service Locator 反模式作为单元测试,您将不知道控制器及其方法实际依赖的内容。
  • 可能有一种方法可以将静态服务定位器替换为注册表并将其作为依赖项传递。这是一个很好的选择,但不适用于 C# 中的属性
【解决方案2】:

听起来你想要一个自定义的ControllerActionInvoker,例如

public class ReadControllerActionInvoker : ControllerActionInvoker
{
    private IMediator mediator;

    public ReadControllerActionInvoker(IMediator mediator)
    {
        this.mediator = mediator;
    }

    protected override ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
    {
        ViewDataDictionary model = null;

        // get our query parameter
        var query = GetParameterValue(controllerContext, actionDescriptor.GetParameters().Where(x => x.ParameterName == "query").FirstOrDefault());

        // pass the query to our mediator
        if (query is DetailsQuery)
            model = new ViewDataDictionary(this.mediator.Request((DetailsQuery)query));

        // return the view with read model returned from mediator
        return new ViewResult
        {
            ViewName = actionDescriptor.ActionName,
            ViewData = model
        };
    }
}

然后我们引入一个基本控制器,我们在其中注入我们的自定义ControllerActionInvoker

public class BaseReadController : Controller
{
    protected IMediator Mediator { get; set; }

    protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    {
        base.Initialize(requestContext);
        ActionInvoker = new ReadControllerActionInvoker(Mediator);
    }
}

最后在我们的控制器中,我们从我们的基础派生并从我们的操作中返回查询信息,例如

public class QueryController : BaseReadController
{
    // our actions now do nothing but define a route for our queries
    public void About(DetailsQuery query)
    {
    }
}

您实际上最终得到的是无实体的操作,因此您丢失了重复的代码,但在我看来,您牺牲了一些可读性(现在控制器中发生了很多伏都教,这并不是立即显而易见的)。

【讨论】:

  • 听起来很有趣,还有什么建议吗?
  • @GraemeMiller 这是一个interesting article,关于如何使用它来拦截/控制控制器操作的输出。在我看来,你最终会得到一个非常虚弱的控制器,除了动作定义之外什么都没有,例如public void Edit(DetailsQuery query) { } 带有位于自定义调用程序中的中介内容。如果我有时间,我会尝试将一个示例放在一起,但是如果您仔细阅读本文,您会发现它相对简单。
  • 我们将所有逻辑都移到了查询中,因此试图让控制器实际上只是查询和命令之上的一个层。让它尽可能的薄,真正只负责协调。
  • 看起来不错。今晚晚些时候会试一试。真的很感激
  • @GraemeMiller 听起来你只想要一个通用查询类,即Query&lt;T&gt; : IQuery&lt;T&gt;
【解决方案3】:

另一种解决方案是创建一个ActionFilter 来装饰控制器中的操作,如下所示:

    [GenericActionFilter(ModelType=typeof(ShowModel))]
    public ActionResult Edit(ShowQuery query)
    {
        return View();
    }

这是ActionFilter

public class GenericActionFilter : ActionFilterAttribute
{
    public Type ModelType { get; set; }
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        IMediator mediator = null;
        if(filterContext.Controller is BaseController)
        {
            mediator = ((BaseController)filterContext.Controller).GetMediator();
            object paramValue = filterContext.ActionParameters["query"];
            var method = mediator.GetType().GetMethod("Request").MakeGenericMethod(new Type[] { ModelType });
            var model = method.Invoke(mediator, new object[] { paramValue });
            filterContext.Controller.ViewData.Model = model;
        }
    }

}

还有BaseController

public class BaseController : Controller
{
    private readonly IMediator mediator;

    public BaseController():this(new Mediator())
    {

    }

    public BaseController(IMediator mediator)
    {
        this.mediator = mediator;
    }

    public IMediator GetMediator()
    {
        return mediator;
    }
}

这是基于MediatorRequest 方法是这样的通用方法的假设:

public interface IMediator
{
    TResponse Request<TResponse>(IQuery<TResponse> query);
} 

public class ShowQuery  : IQuery<ShowModel>
{
    public string EventName { get; set; }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多