views:

193

answers:

2

I recently read Phil Haack's post where he gives an example of implementing Model View Presenter for ASP.NET. One of the code snippets shows how the code for the view class.

public partial class _Default : System.Web.UI.Page, IPostEditView
{    
    PostEditController controller;
    public _Default()
    {
         this.controller = new PostEditController(this, new BlogDataService());
    }
}

However, here the view constructs the instance of the BlogDataService and passes it along to the presenter. Ideally the view should not know about BlogDataService or any of the presenter's lower layer dependencies. But i also prefer to keep the BlogDataService as a constructor injected dependency of the presenter as it makes the dependencies of the presenter explicit.

This same question has been asked on stackoverflow here.

One of the answers suggests using a service locator to get the instance of the BlogDataService and passing it along to the presenter's constructor.This solution however does not solve the problem of the view knowing about the BlogDataService and needing to explicitly get a reference to it.

Is there a way to automatically construct the presenter object using an IoC or DI container tool such that the view does not have to deal with explicitly creating the BlogDataService object and also injecting the view and service instances into the presenter's constructor. I prefer to use the constructor injection pattern as far as possible.

Or is there a better design available to solve the problem?. Can there be a better way to implement this If i am building a WinForms application instead of a ASP.NET WebForms application?

Thanks for any feedback.

+2  A: 

Yes there is. For example using StructureMap in a webform constructor:

 public partial class AttributeDetails : EntityDetailView<AttributeDetailPresenter>, IAttributeDetailView
    {
 public AttributeDetails()
        {
            _presenter = ObjectFactory.With<IAttributeDetailView>(this).GetInstance<AttributeDetailPresenter>();
        }

....
}

and as you can see here presenter needs view and service injected

 public AttributeDetailPresenter(IAttributeDetailView view, IAttributeService attributeService)
        {
            MyForm = view;
            AppService = attributeService;
        }

You can also use StructureMap BuildUp feature for webforms so that you can avoid using ObjectFactory directly in your view.

epitka
@eptika - Thanks for the response. This is a better solution than the view constructing the service object and passing it into the presenter.
Scott
Would the design change for a WinForms application? In a WinForms application, is the view created first which then creates the presenter as in WebForms or is it the other way around?
Scott
A: 

I did exactly this. The solution is based on Autofac, but can be implemented on top of any container.

First, define an interface representing the authority for presenting views in a request to the MVP system:

public interface IMvpRequest
{
    void Present(object view);
}

Next, create a base page which has a property of that type:

public abstract class PageView : Page
{
    public IMvpRequest MvpRequest { get; set; }
}

At this point, set up dependency injection for pages. Most containers have ASP.NET integration, usually in the form of HTTP modules. Because we don't create the page instance, we can't use constructor injection, and have to use property injection here only.

After that is set up, create event arguments representing a view which is ready to be presented:

public class PresentableEventArgs : EventArgs
{}

Now, catch the events in PageView and pass them to the request (present the page as well):

protected override bool OnBubbleEvent(object source, EventArgs args)
{
    var cancel = false;

    if(args is PresentableEventArgs)
    {
        cancel = true;

        Present(source);
    }
    else
    {
        cancel = base.OnBubbleEvent(source, args);
    }

    return cancel;
}

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    Present(this);
}

private void Present(object view)
{
    if(this.MvpRequest != null && view != null)
    {
        this.MvpRequest.Present(view);
    }
}

Finally, create base classes for each type of control you'd like to serve as a view (master pages, composite controls, etc.):

public abstract class UserControlView : UserControl
{
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        EnsureChildControls();

        RaiseBubbleEvent(this, new PresentableEventArgs());
    }
}

This connects the control tree to the MVP system via IMvpRequest, which you'll now have to implement and register in the application-level container. The ASP.NET integration should take care of injecting the implementation into the page. This decouples the page entirely from presenter creation, relying on IMvpRequest to do the mapping.

The implementation of IMvpRequest will be container-specific. Presenters will be registered in the container like other types, meaning their constructors will automatically be resolved.

You will have some sort of map from view types to presenter types:

public interface IPresenterMap
{
    Type GetPresenterType(Type viewType);
}

These are the types you will resolve from the container.

(The one gotcha here is that the view already exists, meaning the container doesn't create the instance or ever know about it. You will have to pass it in as a resolution parameter, another concept supported by most containers.)

A decent default mapping might look like this:

[Presenter(typeof(LogOnPresenter))]
public class LogOnPage : PageView, ILogOnView
{
    // ...
}
Bryan Watts