Dependency Injection in Web Forms MVC
David Hayden has a post about the issue that you face when you are trying to use dependency injection in Web Forms MVC. I talked about similar issues here.
He points out that this type of code is bad:
protected Page_PreInit(object sender, EventArgs e) { // Constructor Injection of Data Access Service and View ICustomerDAO dao = Container.Resolve<ICustomerDAO>(): _presenter = new AddCustomerPresenter(dao, this); // Property Injection of Logging ILoggingService logger = Container.Resolve<ILoggingService>(): _presenter.Logger = logger; }
This type of code a Worst Practice in my opinion. It means that the view is responsible for setting up the presenter, that is a big No! right there.
He gives the example of WCSF & Object Builder way of doing it, but I don't think that this is a good approach:
public partial class AddCustomer : Page, IAddCustomer { private AddCustomerPresenter _presenter; [CreateNew] public AddCustomerPresenter Presenter { set { this._presenter = value; this._presenter.View = this; } } // ... }
The problems that I have with this approach is that the view suddenly makes assumptions about the life cycle of the controller, which is not something that I want it to do. I may want a controller per conversation, for instance, and then where would I be? Another issue is that the view is responsible for injecting itself to presenter, which is not something that I would like to see there as well.
Here is how I do it with Rhino.Igloo:
public partial class AddCustomer : BasePage, IAddCustomer { private AddCustomerPresenter _presenter; public AddCustomerPresenter Presenter { set { this._presenter = value; } } // ... }
The BijectionFacility will notice that we have a settable property of type that inherit from BaseController, and will get it from the container and inject that in. I don't believe in explicit Controller->View communication, but assuming that I needed that, it would be very easy to inject that into the presenter. Very easy as in adding three lines of code to ComponentRepository's InjectControllers method:
PropertyInfo view = controller.GetType().GetProperty("View"); if(view!=null) view.SetValue(controller, instance);
Comments
An inbetween solution is to have the presenters resolved in a base class. This is what my MVP setup looks like:
public class PresentedPage<TPresenter> : Page
{
}
and then the page would look something like this:
public partial class Home_Index : PresentedPage<Home_Index_Presenter>, IHome_Index_View
{
}
If anyone's interested the IoC looks like this:
because my presenters take a view interface as a constructor.
I prefer to use Dave Newman solution, here is an post on my blog how I implement the MVP pattern in ASP.Net:
http://fredrik.nsquared2.com/ViewPost.aspx?PostID=416
The view is passed into the "presenter/controller".
I have also a post where I extend it to make sure the "presenter/controller" can handle the state and navigation for us:
http://fredrik.nsquared2.com/ViewPost.aspx?PostId=419
The solution I use is also prepared to inject the presenter to the View. I had a framework that I build where I could in an XML file specify a view and a presenter to the view. When I navigate to the View the framework injects the specified presenter to the view. By doing this I could easy change the presenter of a view by only modifying the XML file.
Hi Ayende,
I'm not sure if you're familiar with (probably not hehehe), but I maintain an open-source project called NMVP, which is a Model-View-Presenter framework to use with whatever view engine you'd like (since MVP is actually view independent). You can check it out at http://www.codeplex.com/nmvp.
Well I faced the same problem you describe in this post. How to hook every single presenter that composes my form without having to do it manually. So I choose to use Windsor indirectly. The user of NMVP creates a xml file that says:
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<triads>
</triad>
</configuration>
This way when you create the Host page (the one that will call the HostPresenter.Initialize method passing the path to this file, sort of) I configure a dynamic windsor container with the definitions of the xml file, inject every single user control (or whatever you'd like) as implementations of each contract and let Windsor resolve the rest for me.
I'd really like some insight from you in this subject. I'm currently in the process of adapting NMVP to Monorail as to give Monorail a MVP approach, allowing MonoRail users to use several views / presenters per action (composable views).
I'm sorry but the post was too long for the comments I think.
Here's how you'd initialize this host:
SecurityContextMock sec = new SecurityContextMock(); //Security for your App.
//Create mocks
IHost host = MockFactory.CreateHostMock();
host.CurrentCathegory = "ProjectForm";
IProjectDetails pdm = MockFactory.CreateViewMock<IProjectDetails>();
IProjectMembers pmm = MockFactory.CreateContainerMock<IProjectMembers, ProjectMemberPresenter>();
//Inject mocks into the Windsor container as instances of contracts
ContractViewCollection models = new ContractViewCollection();
models.Add(typeof(IProjectDetails), pdm);
models.Add(typeof(IProjectMembers), pmm);
//Resolve everything.
Resolver container = Resolver.Get(sec, host, models, confPath);
presenter = container.GetHostPresenter();
//Indicates that our views should be in editable mode.
presenter.SetEditableMode();
//Perform initialization and loading (this part calls all children
//presenters init and load methods, that act on their individual views).
presenter.Init(true);
presenter.Load(true);
Please notice that this code is the same no matter which platform you're in, with the only difference being the mocks part (in ASP.Net you'd provide user controls, same with Windows Forms, etc).
Hope to be hearing from you soon! :) BTW, Thanks for all the work.
This sounds interesting, let us take the discussion to castle-dev list, okay?
Ayende,
You point out that WSCF's (and for WinForms, SCSF's -> Composite UI App. Block) [CreateNew] pattern ties the lifecycle of the presenter to the lifecylce of ASP.NET pages and what if you want a single instance for multiple requests, including the postbacks. I think this is perfectly understandable but as soon as you are persisting state between multiple requests in your presenter/controller, you are now have two places where your model/state lives, your controllers and the ASP.NET session state. Of course, you could ignore the built-in session sate altogether but how would that then work with multiple pages?
You could also rely on a workflow engine to transition between pages and pass the state using that. That would be fairly clean and I like that, although suddenly you're doing the housekeeping of your controller per session per user pattern.
I would be interested how this is implemented in your BasePage and where do you exactly intercept.
Good post as always!
Comment preview