22

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

public interface IQuery<out TResponse> { }

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

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

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

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

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

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

4

3 回答 3

6

首先,感谢链接到帖子“让你的控制器节食: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它的实现可以省略。知道查询类型就足以解析IQueryHandler<,>via IoC 的实例。

在我的示例中,使用了Castle Windsor“服务定位器”模式的实现。

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());

在 github 上有一个gist 链接,其中包含所有代码。

于 2013-11-17T15:11:44.733 回答
4

听起来你想要一个自定义的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)
    {
    }
}

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

于 2013-11-07T11:52:43.833 回答
2

另一种解决方案是创建一个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;
    }
}

这是基于这样的假设,即 的Request方法Mediator是这样的通用方法:

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

public class ShowQuery  : IQuery<ShowModel>
{
    public string EventName { get; set; }
}
于 2013-11-17T02:10:14.137 回答