Background
I'm using Winforms with a MVP pattern to create an application. I'm using SimpleInjector as my IoC container. My presenters inherit from:
public interface IPresenter<TView>
{
TView View { get; set; }
}
internal class HomePresenter : IPresenter<IHomeView>
{
public IHomeView View { get; set; }
...
}
In order to create my presenters, I have decided to use a presenter factory with the following method:
public static IPresenter<TView> CreateForView<TView>(TView view)
{
var presenter = _container.GetInstance<IPresenter<TView>>();
presenter.View = view;
return presenter;
}
And then in each view, the view creates its own presenter by calling the presenter factory:
_homeMainPresenter = (HomePresenter) presenterFactory.CreateForView<IHomeView>(this);
_homeMainPresenter.View = this;
In my Program.cs file, I have:
static void Main()
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
Bootstrap();
System.Windows.Forms.Application.Run((HomeView)container.GetInstance<IHomeView>());
}
private static void Bootstrap()
{
// Create the container
container = new Container();
// Register types
container.Register<IHomeView, HomeView>(Lifestyle.Singleton);
container.Register<IPresenter<IHomeView>, HomePresenter>();
...
// Verify the container
container.Verify();
}
Problem
When the presenter factory is called from the HomeView view, the type fed into the factory is a HomeView type, not IHomeView. So, the application throws an exception because the container does not have a HomeView registration (only IHomeView). My presenters all have interfaces for the views references they store as I feel this will be better for testing. How do I avoid this situation?
Having a interface-to-implementation binding for your forms is not useful, since they are root types for your presentation technology. Most presentation technologies can't deal with custom abstractions anyway and this is the reason that you are casting your IHomeView
back to HomeView
to allow it to be passed on to the Application.Run
method.
Instead of resolving the presenter from within the view, you can do the following instead:
public interface IHomeView { }
public interface IPresenter<TView> {
TView View { get; set; }
}
public class HomeView : Form, IHomeView
{
private readonly IPresenter<IHomeView> presenter;
public HomeView(IPresenter<IHomeView> presenter) {
this.presenter = presenter;
InitializeComponent();
}
}
Here the Form gets injected with an IPresenter<IHomeView>
and stores that incoming dependency. The factory is not needed anymore and can be removed from your code.
And in your program main:
static void Main()
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
Bootstrap();
System.Windows.Forms.Application.Run(GetForm<HomeView, IHomeView>(container));
}
private static void Bootstrap()
{
// Create the container
container = new Container();
// Register types
// NOTE: We register HomeView as concrete type; not by its interface.
container.Register<HomeView>(Lifestyle.Singleton);
// Here we batch-register all presenters with one line of code and
// since the forms depend on them, they need to be singletons as well.
container.Register(typeof(IPresenter<>), AppDomain.CurrentDomain.GetAssemblies(),
Lifestyle.Singleton);
...
// Verify the container
container.Verify();
}
private static TForm GetForm<TForm, TView>() where TForm : Form, TView
{
var form = container.GetInstance<TForm>();
container.GetInstance<IPresenter<TView>>().View = form;
return form;
}
The factory class is now replaced with the GetForm
method that is part of the composition root; Forms don't have access to it. The generic types allow us to resolve the proper presenter, while keeping the code type-safe.