First, thanks for the link to the post "Put your controllers on a diet: GETs and queries". My code example uses types from it.
My solution also involves usage of action filters as point to inject generic behaviour.
Controller is simple enough and looks like @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();
}
}
Working on QueryFilterAttribute
I realised that IMediator
and its implementation can be omitted. It is enough to know type of query to resolve an instance of IQueryHandler<,>
via IoC.
In my example Castle Windsor and implementation of 'Service Locator' pattern are used.
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
interface is added to avoid working with 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);
}
}
Types should be registered in Global.asax.cs
container.Register(Component.For<ISession>().ImplementedBy<FakeSession>());
container.Register(
Classes.FromThisAssembly()
.BasedOn(typeof(IQueryHandler<,>))
.WithService.Base()
.LifestylePerWebRequest());
There is a link to gist on github with all code.