Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVP dependency injection

Tags:

c#

mvp

winforms

using MVP, what is the normal order of construction and dependency injection.

normally you create a presenter for each view and pass the view into the presenter on constructor. But what if you have:

  1. A Service that multiple views need to listen to events on.
  2. Multiple views all pointing to the same data model cache.

can someone display a normal flow of info from a user click to data coming back in a service from a server.

like image 573
leora Avatar asked Oct 09 '08 20:10

leora


2 Answers

In WinForms, I prefer a simple approach. Usually you're dealing with a few UserControls on a design surface -- make these your view classes. .NET creates the control hierarchy for you (via InitializeComponent). If you use the Passive View pattern, each view then instantiates it's presenter. (You can do this either directly or by asking an IOC container.) Use constructor injection to pass a reference to the view's interface to the presenter's constructor. The presenter can then wire itself up to view events. Repeat the process for the model: the presenter instantiates a model and wires up to its events. (In this case you don't need the constructor injection since Passive View says the presenter keeps a reference to the model, not vice versa.)

The only nit I've found with this approach is properly managing lifetimes of the model and presenter. You want to keep the view as simple as possible, so you probably don't want it maintaining a reference to the presenter. However, that means you've got this presenter object hanging around with event handlers tied to your view. This setup prevents your view from being garbage collected. One solution is to have your view publish an event that indicates it's closing. The presenter would receive the event and remove both its model and view subscriptions. The objects in your web are now properly dereferenced and the garbage collector can go about its work.

You wind up with something like the following:

public interface IView
{
   ...
   event Action SomeEvent;
   event EventHandler Disposed;
   ...
}

// Note that the IView.Disposed event is implemented by the 
// UserControl.Disposed event. 
public class View : UserControl, IView
{
   public event Action SomeEvent;

   public View()
   {
      var presenter = new Presenter(this);
   }
}

public interface IModel
{
   ...
   event Action ModelChanged;
   ...
}

public class Model : IModel
{
   ...
   public event Action ModelChanged;
   ...
}

public class Presenter
{
   private IView MyView;
   private IModel MyModel;

   public Presenter(View view)
   {
      MyView = view;
      MyView.SomeEvent += RespondToSomeEvent;
      MyView.Disposed += ViewDisposed;

      MyModel = new Model();
      MyModel.ModelChanged += RespondToModelChanged;
   }

   // You could take this a step further by implementing IDisposable on the
   // presenter and having View.Dispose() trigger Presenter.Dispose().
   private void ViewDisposed(object sender, EventArgs e)
   {
      MyView.SomeEvent -= RespondToSomeEvent;
      MyView.Disposed -= ViewDisposed;
      MyView = null;

      MyModel.Modelchanged -= RespondToModelChanged;
      MyModel = null;
   }
}

You can decouple this example a step further by using IOC and asking your IOC container for implementations of IModel (in the Presenter class) and IPresenter (in the View class).

like image 89
Mike Post Avatar answered Sep 20 '22 10:09

Mike Post


Here is what I do:

First, I define theses interfaces:

public interface IView<TPresenter>
{
    TPresenter Presenter { get; set; }
}

public interface IPresenter<TView, TPresenter>
    where TView : IView<TPresenter>
    where TPresenter : IPresenter<TView, TPresenter>
{
    TView View { get; set; }
}

Then this abstract presenter class:

public abstract class AbstractPresenter<TView, TPresenter> : IPresenter<TView, TPresenter>
    where TView : IView<TPresenter>
    where TPresenter : class, IPresenter<TView, TPresenter>
{
    protected TView view;

    public TView View
    {
        get { return this.view; }
        set
        {
            this.view = value;
            this.view.Presenter = this as TPresenter;
        }
    }
}

The view is injected via a property, instead of the constructor, to allow the bi-directional affection in the setter. Notice that a safe cast is needed...

Then, my concrete presenter is something like :

public class MyPresenter : AbstractPresenter<IMyView, MyPresenter>
{
    //...
}

Where IMyView implements IView. A concrete view type must exists (e.g. MyView), but it's the container that resolves it:

  1. I register MyPresenter type as itself in the container, with a transient behavior.
  2. I register MyView as an IMyView in the container with a transient behavior.
  3. I then asks for a MyPresenter to the container.
  4. Container instanciate a MyView
  5. It instanciates a MyPresenter
  6. It inject the view into the presenter through the AbstractPresenter.View property.
  7. The setter code completes the bi-directional association
  8. The container returns the couple Presenter/View

It allows you to inject other dependencies (services, repos) into both your view and your presenter. But in the scenario you described, I recommend you to inject services and caches into the presenter, instead of the view.

like image 27
Romain Verdier Avatar answered Sep 20 '22 10:09

Romain Verdier