Ayende @ Rahien

It's a girl

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

Dave Newman
06/28/2007 02:29 AM by
Dave Newman

An inbetween solution is to have the presenters resolved in a base class. This is what my MVP setup looks like:

public class PresentedPage : Page

{

protected TPresenter _presenter;


protected override void OnInit(EventArgs e)

{

    base.OnInit(e);

    _presenter = IoC.ResolvePresenter<TPresenter>(this);

}


protected override void OnLoad(EventArgs e)

{

    _presenter.PageLoaded(IsPostBack, Request.QueryString);

}

}

and then the page would look something like this:

public partial class HomeIndex : PresentedPage, IHomeIndex_View

{

public IList<ResourceCategory> Categories

{

    set { DataControlExt.BindControl(_ddlCategories, value); }

}

}

If anyone's interested the IoC looks like this:

public class IoC

{

    public static TPresenter ResolvePresenter<TPresenter>(object view)

    {

        Hashtable deps = new Hashtable();

        deps.Add("view", view);

        return ((IContainerAccessor)HttpContext.Current.ApplicationInstance).Container

            .Resolve<TPresenter>(deps);

    }

}

because my presenters take a view interface as a constructor.

Fredrik Norm&#233;n
06/28/2007 09:04 AM by
Fredrik Normén

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.

Bernardo Heynemann
06/30/2007 06:56 PM by
Bernardo Heynemann

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"

           xmlns:xsd="http://www.w3.org/2001/XMLSchema" 

           presentersMustOverrideInit="true" 

           presentersMustOverrideLoad="true" 

           presentersMustOverrideSave="true" 

           onlyProcessCurrentCathegory="true">

<triad category="Presenter Category" 

          contract="full name"

          presenter="full name" />

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).

Bernardo Heynemann
06/30/2007 07:02 PM by
Bernardo Heynemann

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();

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.

Ayende Rahien
06/30/2007 07:04 PM by
Ayende Rahien

I'm currently in the process of adapting NMVP to Monorail as to give Monorail

This sounds interesting, let us take the discussion to castle-dev list, okay?

Gabor Ratky
07/14/2007 04:08 PM by
Gabor Ratky

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!

Comments have been closed on this topic.