Passing the Presenter to NavigateTo is a good choice. If you have multiple presenters you may want to write a interface that NavigateTo can use and have each presenter implement that interface. But if you just using one presenter for this or they already share some common interface then this is not needed.
One idea behind MVP is that you are change the view without effecting the underlying UI Logic. Passing the presenter doesn't effect this goal of MVP as you can change the view the Presenter is using. Now if you are allowing direct access to the raw view through the presenter (by exposing a view property) then that not good. What you want are methods on the presenter to expose the needed information to NavigateTo. That way when you chance the view you re implement the code behind those methods.
Here is a simple example that demonstrates the concept of passive views using the MVP design pattern. Because we are using passive views the view has no knowledge of the presenter. The presenter will simply subscribe to events published by the view and act accordingly.
To start out we need to define a contract for our view. This is typically achieved using an interface, essentially, we want to have a very loose coupling with our view. We want the ability to switch to different views or event create mock views for unit testing.
Here is a contract that describes a simple view that will be used to display customer information
public interface ICustomerManagementView
{
void InitializeCustomers(ICustomer[] customers);
void DisplayCustomer(ICustomer customer);
event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged;
}
It exposes a single method InitializeCustomers that will be used to initialize our view with objects from our model.
We also have an event SelectedCustomerChanged that will be used by our presenter to receive notification that an action has occurred in the view.
Once we have our contract we can start to handle these interactions in our presenter.
public class CustomerManagementPresenter
{
private ICustomer _selectedCustomer;
private readonly ICustomerManagementView _managementView;
private readonly ICustomerRepository _customerRepository;
public CustomerManagementPresenter(ICustomerManagementView managementView, ICustomerRepository customerRepository)
{
_managementView = managementView;
_managementView.SelectedCustomerChanged += this.SelectedCustomerChanged;
_customerRepository = customerRepository;
_managementView.InitializeCustomers(_customerRepository.FetchCustomers());
}
private void SelectedCustomerChanged(object sender, EventArgs<ICustomer> args)
{
// Perform some logic here to update the view
if(_selectedCustomer != args.Value)
{
_selectedCustomer = args.Value;
_managementView.DisplayCustomer(_selectedCustomer);
}
}
}
In the presenter we can use another design pattern called dependency injection to provide access to our view and any model classes that we may need. In this example I have a CustomerRepository that is responsible for fetching customer details.
In the constructor we have two important lines of code, firstly we have subscribed to the SelectedCustomerChanged event in our view, it is here that we can perform associated actions. Secondly we have called InitilaizeCustomers with data from the repository.
At this point we haven't actually defined a concrete implementation for our view, all we need to do is create an object that implements ICustomerManagementView. For example in a Windows Forms application we can do the following
public partial class CustomerManagementView : Form, ICustomerManagementView
{
public CustomerManagementView()
{
this.InitializeComponents();
}
public void InitializeCustomers(ICustomer[] customers)
{
// Populate the tree view with customer details
}
public void DisplayCustomer(ICustomer customer)
{
// Display the customer...
}
// Event handler that responds to node selection
private void CustomerTreeViewAfterSelect(object sender, TreeViewEventArgs e)
{
var customer = e.Node.Tag as ICustomer;
if(customer != null)
{
this.OnSelectedCustomerChanged(new EventArgs<ICustomer>(customer));
}
}
// Protected method so that we can raise our event
protected virtual void OnSelectedCustomerChanged(EventArgs<ICustomer> args)
{
var eventHandler = this.SelectedCustomerChanged;
if(eventHandler != null)
{
eventHandler.Invoke(this, args);
}
}
// Our view will raise an event each time the selected customer changes
public event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged;
}
If we wanted to test our presentation logic we could mock our view and perform some assertions.
EDIT : Included custom event args
public class EventArgs<T> : EventArgs
{
private readonly T _value;
public EventArgs(T value)
{
_value = value;
}
public T Value
{
get { return _value; }
}
}
Best Answer
Abstraction is good, but it's important to remember that at some point something has to know a thing or two about a thing or two, or else we'll just have a pile of nicely abstracted legos sitting on the floor instead of them being assembled into a house.
An inversion-of-control/dependency injection/flippy-dippy-upside-down-whatever-we're-calling-it-this-week container like Autofac can really help in piecing this all together.
When I throw together a WinForms application, I usually end up with a repeating pattern.
I'll start with a
Program.cs
file that configures the Autofac container and then fetches an instance of theMainForm
from it, and shows theMainForm
. Some people call this the shell or the workspace or the desktop but at any rate it's "the form" that has the menu bar and displays either child windows or child user controls, and when it closes, the application exits.Next is the aforementioned
MainForm
. I do the basic stuff like drag-and-dropping someSplitContainers
andMenuBar
s and such in the Visual Studio visual designer, and then I start getting fancy in code: I'll have certain key interfaces "injected" into theMainForm
's constructor so that I can make use of them, so that my MainForm can orchestrate child controls without really having to know that much about them.For example, I might have an
IEventBroker
interface that lets various components publish or subscribe to "events" likeBarcodeScanned
orProductSaved
. This allows parts of the application to respond to events in a loosely coupled way, without having to rely on wiring up traditional .NET events. For example, theEditProductPresenter
that goes along with myEditProductUserControl
could saythis.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah))
and theIEventBroker
would check its list of subscribers for that event and call their callbacks. For example, theListProductsPresenter
could listen for that event and dynamically update theListProductsUserControl
that it is attached to. The net result is that if a user saves a product in one user control, another user control's presenter can react and update itself if it happens to be open, without either control having to be aware of each other's existence, and without theMainForm
having to orchestrate that event.If I'm designing an MDI application, I might have the
MainForm
implement anIWindowWorkspace
interface that hasOpen()
andClose()
methods. I could inject that interface into my various presenters to allow them to open and close additional windows without them being aware of theMainForm
directly. For example, theListProductsPresenter
might want to open anEditProductPresenter
and correspondingEditProductUserControl
when the user double-clicks a row in a data grid in aListProductsUserControl
. It can reference anIWindowWorkspace
--which is actually theMainForm
, but it doesn't need to know that--and callOpen(newInstanceOfAnEditControl)
and assume that the control was shown in the appropriate place of the application somehow. (TheMainForm
implementation would, presumably, swap the control into view on a panel somewhere.)But how the hell would the
ListProductsPresenter
create that instance of theEditProductUserControl
? Autofac's delegate factories are a true joy here, since you can just inject a delegate into the presenter and Autofac will automagically wire it up as if it were a factory (pseudocode follows):So the
ListProductsPresenter
knows about theEdit
feature set (i.e., the edit presenter and the edit user control)--and this is perfectly fine, they go hand-in-hand--but it doesn't need to know about all of the dependencies of theEdit
feature set, instead relying on a delegate provided by Autofac to resolve all of those dependencies for it.Generally, I find that I have a one-to-one correspondence between a "presenter/view model/supervising controller" (let's not too caught up on the differences as at the end of the day they are all quite similar) and a "
UserControl
/Form
". TheUserControl
accepts the presenter/view model/controller in its constructor and databinds itself as is appropriate, deferring to the presenter as much as possible. Some people hide theUserControl
from the presenter via an interface, likeIEditProductView
, which can be useful if the view is not completely passive. I tend to use databinding for everything so the communication is done viaINotifyPropertyChanged
and don't bother.But, you will make your life much easier if the presenter is shamelessly tied to the view. Does a property in your object model not mesh with databinding? Expose a new property so it does. You are never going to have an
EditProductPresenter
and anEditProductUserControl
with one layout and then want to write a new version of the user control that works with the same presenter. You will just edit them both, they are for all intents and purpose one unit, one feature, the presenter only existing because it is easily unit testable and the user control is not.If you want a feature to be replaceable, you need to abstract the entire feature as such. So you might have an
INavigationFeature
interface that yourMainForm
talks to. You can have aTreeBasedNavigationPresenter
that implementsINavigationFeature
and is consumed by aTreeBasedUserControl
. And you might have aCarouselBasedNavigationPresenter
that also implementsINavigationFeature
and is consumed by aCarouselBasedUserControl
. The user controls and the presenters still go hand-in-hand, but yourMainForm
would not have to care if it is interacting with a tree-based view or a carousel-based one, and you could swap them out without theMainForm
being the wiser.In closing, it is easy to confuse yourself. Everyone is pedantic and uses slightly different terminology to convey they subtle (and oftentimes unimportant) differences between what are similar architectural patterns. In my humble opinion, dependency injection does wonders for building composable, extensible applications, since coupling is kept down; separation of features into "presenters/view models/controllers" and "views/user controls/forms" does wonders for quality since most logic is pulled into the former, allowing it to be easily unit tested; and combining the two principles seems to really be what you're looking for, you're just getting confused on the terminology.
Or, I could be full of it. Good luck!