(Part I)
Let's think about what Pico is good at in practical sense. I think Pico is great at wiring dependencies together. That means, it is good at wiring dependencies starting from your Presentation Model/Presenter layer and back, because these are all classes that you have complete control over. For your Views, at least if you develop in Visual Studio .NET, the restriction on their default constructors conflicts with the way Pico works. There are ways around it (setter injection as said in Part I), but they are ugly.
But what if I don't register my Views into my Pico container like other classes? Then the question becomes: how can my View get a reference to its dependent Presentation Model/Presenter?
Before I show you my solution, I have to say one more thing. The method InitializeComponent() synonymously refer controls as components for a reason. Every control in .NET is a component. What is a component? It is something you put in a container. Every container has many components. So yes, I am telling you that these controls/widgets are all being put in a container (not Pico) in the .NET control hierarchy framework. So what does each control being a component in this .NET container hierarchy enable me to do? It allows you to have access to the functionalities from other components in the same container. Let me rephrase: each View, being a component, can have access to other classes like a Presentation Model/Presenter, so long as they are all in the same .NET container hierarchy framework. Didn't I just say that this container framework is not Pico? Yes, I did, but it doesn't mean we have to use only one over the other. We can use them both together to each of their strengths to achieve the best of both worlds. Let me explain.
We have already said using Pico container to host everything starting from the Presentation Model layer is a good thing. So here is some example code on how to do this:
private static IMutablePicoContainer createMutablePicoContainer()
{
IMutablePicoContainer pico = new DefaultPicoContainer();
pico.RegisterComponentImplementation(typeof(IPageOnePM), typeof(PageOnePM));
pico.RegisterComponentImplementation(typeof(IPageTwoPM), typeof(PageTwoPM));
pico.RegisterComponentImplementation(typeof(IWebService), typeof(WebService));
return pico;
}
In order to allow the Views to have access to other components (Presentation Model/Presenter), we have to create a .NET container. It is just a class that subclass from System.ComponentModel.Container. I am going to call this an ApplicationContainer to avoid being confused with our Pico container.
internal class ApplicationContainer : System.ComponentModel.Container
{
// ...
}
To put our Views into this ApplicationContainer, you instantiate an instance of it, and put the Form object that you will start your application with into it like this:
[STAThread]
static void Main()
{
ApplicationContainer applicationContainer = new ApplicationContainer();
MainForm form = new MainForm();
applicationContainer.Add(form);
Application.Run(form);
}
From our Views, the method that they will use to get access to other components is called
GetService(Type serviceType). When this method is called from within our Views, if our Views are put in a System.ComponentModel.Container, by default the method will ask for its containing container to traverse all registered components and see who can provide this "service" to it. If it finds such component, the container will return it, and now the requesting component gets a reference to that object. How does the container traverse its registered components and decide to give the component requesting a service the service that it asks for? Well, interestingly a System.ComponentModel.Container also has its own
GetService() method to do just that. Now, since we have our own subclass ApplicationContainer, what if we override its
GetService(), and while our ApplicationContainer object receives a request for service from any of its components, we can instruct it also look to see if Pico has the stuff that the requesting component has. More concretely, when a View uses
GetService(typeof(MyPresentationModel) to get its Presentation Model/Presenter dependency, ApplicationContainer will ask for a Pico container that has already been fully registered to return an instance of that class if it is found, like such:
internal class ApplicationContainer : System.ComponentModel.Container
{
private IMutablePicoContainer _pico = createMutablePicoContainer();
protected override object GetService(Type service)
{
object instance = _pico.GetComponentInstanceOfType(service);
if (instance != null)
{
return instance;
}
return base.GetService (service);
}
}
To sum it all up, you need to do the following to get things to work:
1. Create an ApplicationContainer class subclassing System.ComponentModel.Container.
2. Add your starting Form into the ApplicationContainer instance, prior to starting it.
3. Set up a Pico container within the ApplicationContainer instance.
4. Register all dependencies that your Presentation Model/Presenter classes will need in your Pico container. You do not need to register your Views.
5. Override the ApplicationContainer's GetService() method, make it to look further into its already setup Pico container for anything that it should return.
Now from your Views, you can gain access to its dependency, in this case a Presentation Model/Presenter, by calling:
private void PageOneView_Load(object sender, System.EventArgs e)
{
// Add this View to ApplicationContainer. Otherwise we have to instantiate
// each and every view in public static void Main() and do the adding there.
base.FindForm().Container.Add(this);
// This GetService() call will now find what we need in ApplicationContainer.
IPageOnePM service = (IPageOnePM)base.GetService(typeof(IPageOnePM));
}
And modify your PresentationModel's constructor to include an additional parameter to take in the web service class. Then your class can start using the web service functionality, while in unit testing you can mock/stub it out!
public interface IWebService
{
}
public class WebService : IWebService
{
}
public class PageOnePM : PresentationModel, IPageOnePM
{
private IWebService webservice;
public PageOnePM(IWebService webservice)
{
this.webservice = webservice;
}
}
// ApplicationContainer class
private static IMutablePicoContainer createMutablePicoContainer()
{
IMutablePicoContainer pico = new DefaultPicoContainer();
// ...
pico.RegisterComponentImplementation(typeof(IWebService), typeof(WebService));
// ...
return pico;
}
So now, we have eliminated the child user control not knowing how to get a Presentation Model/Presenter problem, because a user control is also, well, a component in the same ApplicationContainer. We have also solved the ugly setter injecting child user controls' dependencies problem. You also did not modify a single View default constructor! We can now happily use our Visual Studio .NET IDE to do WYSIWYG design and manage good OO design, plus Inversion of Control-ing our dependencies.
After-thought: Though the title of these two posts say Presentation Patterns, in my mind they are more geared towards just for Presentation Model, due to in my opinion the difference in directional references between Presentation Model and Model-View-Presenter. I mentioned briefly about this in my earlier post
here.
By the way, note that now each page
can have access to multiple Presentation Model objects, instead of the what-it-used-to-be 1-to-1 relationship between a page and its Presentation Model, so one can do something similar to Ruby on Rails where each View can make calls to
multiple Controllers which operates on various Models to complete the desired action! This makes code-sharing between Presentation Models much easier, and also each Presentation Model can be named not after their page but by application functionality!