There are a few good reasons for using the View Factory Pattern in ASP.NET MVC:
- It keeps the logic in your controller actions to a miniumum.
- It avoids constructor soup.
- Your views (or view models) can be easily tested without any dependency on ASP.NET MVC.
Implementing the View Factory Pattern
In most ASP.NET MVC applications you’ll have views that are built with or without input parameters. For example, a page of your latest products may have no parameters whilst the details view for a specific product will require the product id.
We can create an interface based on these specifications like so:
public interface IViewFactory
{
TView CreateView<TView>();
TView CreateView<TInput, TView>(TInput input);
}
We can then update our controllers to use this interface for creating the required view:
public class ProductsController
{
private readonly IViewFactory viewFactory;
public ProductsController(IViewFactory viewFactory)
{
this.viewFactory = viewFactory;
}
public ActionResult Featured()
{
return View(viewFactory.CreateView<FeaturedProductsView>());
}
public ActionResult Details(int id)
{
var view = viewFactory.CreateView<int, ProductDetailsView>(id);
if (view == null)
return HttpNotFound();
return View(view);
}
}
To build complex views we have two interfaces:
public interface IViewBuilder<TView>
{
TView Build();
}
public interface IViewBuilder<TInput, TView>
{
TView Build(TInput input);
}
The first is for creating views that have no input parameters and the second is for building views with input parameters. Heres an example for our product details view:
public class ProductDetailsViewBuilder : IViewBuilder<int, ProductDetailsView>
{
private readonly IDocumentSession session;
public ProductViewBuilder(IDocumentSession session)
{
this.session = session;
}
public ProductDetailsView Build(int id)
{
var product = session.Load(id);
if (product == null) // product doesn't exist
return null;
var view = new ProductDetailsView {
Name = product.Name,
Price = product.Price,
InStock = product.InStock
};
return view;
}
};
In this view builder we attempt to load the product from a database (using ravendb) and create a ProductDetailsView
from it. If the product doesn’t exist we simply return null (which is handled by the controller).
The idea for the view factory to locate the relevant builder and use it to build the view. Also, we need a way of resolving dependencies - as you can see, the above builder has a dependency on IDocumentSession
.
In addition to this, I want to benefit from the use of the view factory pattern for simple views, or rather those that don’t require a builder.
The DefaultViewFactory in Fabrik.Common.Web meets these requirements. You can download the source from GitHub:
public class DefaultViewFactory : IViewFactory
{
public TView CreateView<TView>()
{
var builder = DependencyResolver.Current.GetService<IViewBuilder<TView>>();
if (builder != null)
return builder.Build();
// otherwise create view using reflection
return Activator.CreateInstance<TView>();
}
public TView CreateView<TInput, TView>(TInput input)
{
var builder = DependencyResolver.Current.GetService<IViewBuilder<TInput, TView>>();
if (builder != null)
return builder.Build(input);
// otherwise create using reflection
return (TView)Activator.CreateInstance(typeof(TView), input);
}
}
In both methods we use the ASP.NET MVC DependencyResolver to try and locate a builder. If one exists we use it to build the view. If not, we just use reflection to create an instance.
You’ll need to configure DependencyResolver to use the IoC/DI tool of your choice. Mine is StructureMap and you’ll find an implementation of DependencyResolver in Fabrik.Common.Web.StructureMap.
Of course you are free to just implement IViewFactory directly if you want to do things differently.
Wiring everything up
You’ll need to register your IViewFactory and view builders with your IoC/DI tool. StructureMap provides a very easy way of doing this using assembly scanning and can automatically locate and register instances of generic types. Here’s my configuration:
protected static void BootStructureMap()
{
ObjectFactory.Configure(cfg =>
{
cfg.For<IViewFactory>().Use<DefaultViewFactory>();
cfg.Scan(scan =>
{
scan.TheCallingAssembly();
scan.ConnectImplementationsToTypesClosing(typeof(IViewBuilder<>));
// for TInput, TView builders
scan.ConnectImplementationsToTypesClosing(typeof(IViewBuilder<,>));
});
});
DependencyResolver.SetResolver(new StructureMapDependencyResolver(ObjectFactory.Container));
}
That’s all there is to it. In case you’re wondering how you can reap similar benefits for your POST actions I like to use the Command/Messaging Pattern. Here’s an example that illustrates both concepts:
public class JobApplicationsController
{
private readonly IViewFactory viewFactory;
private readonly ICommandBus bus;
public JobApplicationsController(IViewFactory viewFactory, ICommandBus bus)
{
this.viewFactory = viewFactory;
this.bus = bus;
}
[HttpGet]
[ImportModelStateFromTempData]
public ActionResult Details(int id)
{
var view = viewFactory.CreateView<int, JobApplicationDetailsView>(id);
if (view == null)
return HttpNotFound();
return View(view);
}
[HttpPost]
[ValidateModelState]
public ActionResult Details(UpdateApplicationDetailsCommand command)
{
bus.Dispatch(command);
return RedirectToAction("details", new { id = model.Id })
.AndAlert(AlertType.Success, "Application Updated");
}
}