Подтвердить что ты не робот

Могу ли я реорганизовать обработчик запросов вида модели?

В нашем приложении MVC все наши действия чтения в качестве параметра принимают запрос, который реализует:

public interface IQuery<out TResponse> { }

В рамках действия запрос передается на шину, которая находит обработчик и возвращает модель представления. Поэтому контроллеры теперь выглядят примерно так:

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

Эффективно просто передавать запросы нашему посреднику и возвращать результат. У нас есть сотни действий, которые выглядят так. Есть странное действие, которое делает что-то условное (которое я бы оставил, как есть), но все остальное - это один и тот же шаблон снова и снова. У нас более ста разных запросов

Как я могу реорганизовать это на что-то более явное? Я предполагаю, что вы перейдете к обработчику запросов вида Model View, а не к действию контроллера шаблона, который просто передает запрос на шину и возвращает модель View.

Какие точки расширения следует искать в MVC? Эффективно вместо того, чтобы писать обработчик действий - просто используйте некоторый автоматический способ подключения вместе строго типизированного запроса и возврата правильной ViewModel.

Если смогу? Нужно ли мне? Мне просто не нравится видеть сотни действий, которые выглядят одинаково.

4b9b3361

Ответ 1

Во-первых, спасибо за ссылку на сообщение Поставьте свои контроллеры на диету: GET и запросы. В моем примере кода используются типы из него.

Мое решение также включает использование фильтров действий в качестве точки для введения общего поведения.

Контроллер достаточно прост и похож на @Kambiz Shahim's:

[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<,> через IoC.

В моем примере Castle Windsor и используется "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 добавлен интерфейс, чтобы избежать работы с Reflection

/// <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 со всем кодом.

Ответ 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)
    {
    }
}

То, что вы в итоге получаете здесь, - это бестелесные действия, поэтому вы теряете повторяющийся код, но, на мой взгляд, вы жертвуете некоторой удобочитаемостью (сейчас в контроллере происходит много вуду, что не сразу становится очевидным).

Ответ 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;
    }
}

это основано на предположении, что метод Request Mediator является общим методом, подобным этому:

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

и

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