Using conventions with Passive View

December 19th, 2009

My new blog is at at http://cre8ivethought.com/blog/index come and follow me there.

-Mark Nijhof

I was reading Ayende’s blog post about building UI based on conventions and thought; hey I have something similar in my CQRS example. And since this is the least interesting part of the whole example I guess it will be missed by many, and I can’t let that happen.

Passive View

The example has a Win Forms application in there that is build accordingly to the Passive View pattern, so my actual forms are being dumbed down to simple views without any logic in them, well almost no logic. Then I have presenters that have the actual logic in them, or delegate the logic to other responsible entities (services or whatever).

The reason for doing this is because you want to be able to test the behavioral parts of your code as simple as possible, and nothing is more simple then being able to unit test your behavior, hey even better drive your design / behavior through Test-Driven Development Design (TDD). Imagine how hard that would be to do when not abstracting these different responsibilities from each other (hehe I am sure some of you don’t even need to imagine this ;)).

I realize the Win Forms is so not _in_ anymore and that nobody uses them, but perhaps what I show you here makes you think about other areas that this could be applied to.

The View

The view is responsible for displaying information to a user, capturing user requests, and … uhm no that’s it. Let’s take a look at a simple view.

    1 namespace Fohjin.DDD.BankApplication.Views
    2 {
    3     public partial class ClientSearchForm : Form, IClientSearchFormView
    4     {
    5         public ClientSearchForm()
    6         {
    7             InitializeComponent();
    8             RegisterClientEvents();
    9         }
   10 
   11         public event EventAction OnCreateNewClient;
   12         public event EventAction OnOpenSelectedClient;
   13 
   14         private void RegisterClientEvents()
   15         {
   16             addANewClientToolStripMenuItem.Click += (s, e) => OnCreateNewClient();
   17             _clients.Click += (s, e) => OnOpenSelectedClient();
   18         }
   19 
   20         public IEnumerable<ClientReport> Clients
   21         {
   22             get { return (IEnumerable<ClientReport>)_clients.DataSource; }
   23             set { _clients.DataSource = value; }
   24         }
   25 
   26         public ClientReport GetSelectedClient()
   27         {
   28             return (ClientReport)_clients.SelectedItem;
   29         }
   30     }
   31 }

So what is happening here? As you can see there are two public events declared and there are two properties that provide access to some form controls. The interesting thing here is that the two public events are wired to two events from two form controls. But hey nothing is happening; no behavior, no calls to services or anything. Below here is the declaration of the interface that the form implements.

    1 namespace Fohjin.DDD.BankApplication.Views
    2 {
    3     public interface IClientSearchFormView : IView
    4     {
    5         IEnumerable<ClientReport> Clients { get; set; }
    6         ClientReport GetSelectedClient();
    7         event EventAction OnCreateNewClient;
    8         event EventAction OnOpenSelectedClient;
    9     }
   10 }

Here are both the two public events and the two properties that provide access to two form controls, of course the implementation could be anything. This interface is used by the presenter to control the view.

The Presenter

So the presenter is responsible for controlling the view; which includes setting and retrieving data and executing behavior that is triggered by the user of the view. Below is the presenter that is responsible for managing the previous mentioned view.

    1 namespace Fohjin.DDD.BankApplication.Presenters
    2 {
    3     public class ClientSearchFormPresenter : Presenter<IClientSearchFormView>, IClientSearchFormPresenter
    4     {
    5         private readonly IClientSearchFormView _clientSearchFormView;
    6 
    7         public ClientSearchFormPresenter(IClientSearchFormView clientSearchFormView) : base(clientSearchFormView)
    8         {
    9             _clientSearchFormView = clientSearchFormView;
   10         }
   11 
   12         public void CreateNewClient()
   13         {
   14             // Do something
   15         }
   16 
   17         public void OpenSelectedClient()
   18         {
   19             // Do something
   20         }
   21 
   22         public void Display()
   23         {
   24             LoadData();
   25             try
   26             {
   27                 _clientSearchFormView.ShowDialog();
   28             }
   29             finally
   30             {
   31                 _clientSearchFormView.Dispose();
   32             }
   33         }
   34 
   35         private void LoadData()
   36         {
   37             // Do something
   38         }
   39     }
   40 }

For simplicity I removed any other external dependencies and replaced the behavior with a comment “Do something”. Talking about external dependencies; the interface that is implemented by the view is injected into the presenter, this is interesting.

Look at the Display method, there you see that the view that is injected first gets its data loaded and then actually gets activated. This means that the view will not get its own data, but that the presenter will provide it to the view, for this the presenter will use the two properties that where declared in the interface. And it means that the presenter is responsible for activating the view. This is different from how Win Forms works out of the box.

For this to work I had to change the Program class, here take a look:

    1 namespace Fohjin.DDD.BankApplication
    2 {
    3     static class Program
    4     {
    5         /// <summary>
    6         /// The main entry point for the application.
    7         /// </summary>
    8         [STAThread]
    9         static void Main()
   10         {
   11             ApplicationBootStrapper.BootStrap();
   12 
   13             var clientSearchFormPresenter = ObjectFactory.GetInstance<IClientSearchFormPresenter>();
   14 
   15             Application.EnableVisualStyles();
   16 
   17             clientSearchFormPresenter.Display();
   18         }
   19     }
   20 }

Look at line 17 in there, the method Display is called on the presenter, which then prepares and activates the view.

But I can hear you think; what about these two events that where declared on that interface as well, you know “OnCreateNewClient” and “OnOpenSelectedClient”? I can see some obvious candidates “CreateNewClient” and “OpenSelectedClient” but they are not wired-up together. What gives?

The Magic

You must have noticed that the names are very similar and that they follow a certain pattern, this is the convention that I have chosen to use. Basically I call my event handlers the same as the events without the “On” prefix. Then I have a base presenter class without the word “Base” because that would be EVIL. And this class will wire-up the events with the event handlers for me.

    1 namespace Fohjin.DDD.BankApplication.Presenters
    2 {
    3     public abstract class Presenter<TView> where TView : class, IView
    4     {
    5         protected Presenter(TView view)
    6         {
    7             HookUpViewEvents(view);
    8         }
    9 
   10         private void HookUpViewEvents(TView view)
   11         {
   12             var viewDefinedEvents = GetViewDefinedEvents();
   13             var viewEvents = GetViewEvents(view, viewDefinedEvents);
   14             var presenterEventHandlers = GetPresenterEventHandlers(viewDefinedEvents, this);
   15 
   16             foreach (var viewDefinedEvent in viewDefinedEvents)
   17             {
   18                 var eventInfo = viewEvents[viewDefinedEvent];
   19                 var methodInfo = GetTheEventHandler(viewDefinedEvent, presenterEventHandlers, eventInfo);
   20 
   21                 WireUpTheEventAndEventHandler(view, eventInfo, methodInfo);
   22             }
   23         }
   24 
   25         private MethodInfo GetTheEventHandler(string viewDefinedEvent, IDictionary<string, MethodInfo> presenterEventHandlers, EventInfo eventInfo)
   26         {
   27             var substring = viewDefinedEvent.Substring(2);
   28             if (!presenterEventHandlers.ContainsKey(substring))
   29                 throw new Exception(string.Format("\n\nThere is no event handler for event '{0}' on presenter '{1}' expected '{2}'\n\n", eventInfo.Name, GetType().FullName, substring));
   30 
   31             return presenterEventHandlers[substring];
   32         }
   33 
   34         private void WireUpTheEventAndEventHandler(TView view, EventInfo eventInfo, MethodInfo methodInfo)
   35         {
   36             var newDelegate = Delegate.CreateDelegate(typeof(EventAction), this, methodInfo);
   37             eventInfo.AddEventHandler(view, newDelegate);
   38         }
   39 
   40         private static IDictionary<string, MethodInfo> GetPresenterEventHandlers<TPresenter>(ICollection<string> actionProperties, TPresenter presenter)
   41         {
   42             return presenter
   43                 .GetType()
   44                 .GetMethods(BindingFlags.Instance | BindingFlags.Public)
   45                 .Where(x => Contains(actionProperties, x))
   46                 .ToList()
   47                 .Select(x => new KeyValuePair<string, MethodInfo>(x.Name, x))
   48                 .ToDictionary(x => x.Key, x => x.Value);
   49         }
   50 
   51         private static List<string> GetViewDefinedEvents()
   52         {
   53             return typeof(TView).GetEvents().Select(x => x.Name).ToList();
   54         }
   55 
   56         private static IDictionary<string, EventInfo> GetViewEvents(TView view, ICollection<string> actionProperties)
   57         {
   58             return view
   59                 .GetType()
   60                 .GetEvents()
   61                 .Where(x => Contains(actionProperties, x))
   62                 .ToList()
   63                 .Select(x => new KeyValuePair<string, EventInfo>(x.Name, x))
   64                 .ToDictionary(x => x.Key, x => x.Value);
   65         }
   66 
   67         private static bool Contains(ICollection<string> actionProperties, EventInfo x)
   68         {
   69             return actionProperties.Contains(x.Name);
   70         }
   71 
   72         private static bool Contains(ICollection<string> actionProperties, MethodInfo x)
   73         {
   74             return actionProperties.Contains(string.Format("On{0}", x.Name));
   75         }
   76     }
   77 }

The abstract base class Presenter needs the view interface as the generic parameter of the view that the presenter controls. A reference to the actual view will be injected into the presenter class. Then the first thing that happens is that I get all the events declared from the provided interface. Then I get the actual events from the provided view, but only those that have been defined on the provided interface. This makes it possible to define multiple interfaces on the same view that get controlled by different presenters. After this I get all the public methods from the presenter.

Once I have the events form the view and the methods from the presenter then I can start matching them together. As I mentioned before in my case I use a very simple convention where the event is prefixed with “On” so in order to get the event handler I only need to search my event handler collection for a name of the event minus “On”. Then finally the event gets the event handler added to its collection of event handlers.

When there is no event handler for a provided event then I throw an exception, because I consider this to be a bug. There might be event handlers that does not have an event associated with it, but I consider that less harmful since this logic would not be called anyway. This exception will be visible in the unit tests for the presenter.

Reflection

Yes this relies very heavily on reflection, but for this scenario I don’t mind. Indeed it is slower, but the question is; will you notice this when displaying a form, and I don’t think you will. You could improve this code by for example making the WireUpEventAndEventHandler an action and cache those for the combination of the presenter and view interface, but I don’t think that is worth the effort.

The Specifications

I am going to leave you with the specifications that I have for the presenter that I used for this post. The whole presenter is being tested by setting data on the view and triggering events. I am not calling the methods directly on the presenter itself. And if you want to see more, then get the code from GitHub.

    1 namespace Test.Fohjin.DDD.Scenarios.Opening_the_bank_application
    2 {
    3     public class When_in_the_GUI_openeing_the_bank_application : PresenterTestFixture<ClientSearchFormPresenter>
    4     {
    5         private List<ClientReport> _clientReports;
    6 
    7         protected override void SetupDependencies()
    8         {
    9             _clientReports = new List<ClientReport> { new ClientReport(Guid.NewGuid(), "Client Name") };
   10             OnDependency<IReportingRepository>()
   11                 .Setup(x => x.GetByExample<ClientReport>(null))
   12                 .Returns(_clientReports);
   13         }
   14 
   15         protected override void When()
   16         {
   17             Presenter.Display();
   18         }
   19 
   20         [Then]
   21         public void Then_show_dialog_will_be_called_on_the_view()
   22         {
   23             On<IClientSearchFormView>().VerifyThat.Method(x => x.ShowDialog()).WasCalled();
   24         }
   25 
   26         [Then]
   27         public void Then_client_report_data_from_the_reporting_repository_is_being_loaded_into_the_view()
   28         {
   29             On<IClientSearchFormView>().VerifyThat.ValueIsSetFor(x => x.Clients = _clientReports);
   30         }
   31     }
   32 }

    1 namespace Test.Fohjin.DDD.Scenarios.Adding_a_new_client
    2 {
    3     public class When_in_the_GUI_adding_a_new_client : PresenterTestFixture<ClientSearchFormPresenter>
    4     {
    5         protected override void When()
    6         {
    7             On<IClientSearchFormView>().FireEvent(x => x.OnCreateNewClient += delegate { });
    8         }
    9 
   10         [Then]
   11         public void Then_client_report_data_from_the_reporting_repository_is_being_loaded_into_the_view()
   12         {
   13             On<IClientDetailsPresenter>().VerifyThat.Method(x => x.SetClient(null)).WasCalled();
   14         }
   15 
   16         [Then]
   17         public void Then_display_will_be_called_on_the_view()
   18         {
   19             On<IClientDetailsPresenter>().VerifyThat.Method(x => x.Display()).WasCalled();
   20         }
   21     }
   22 }

    1 namespace Test.Fohjin.DDD.Scenarios.Displaying_client_details
    2 {
    3     public class When_in_the_GUI_opening_an_existing_client : PresenterTestFixture<ClientSearchFormPresenter>
    4     {
    5         private ClientReport _clientReport;
    6 
    7         protected override void SetupDependencies()
    8         {
    9             OnDependency<IPopupPresenter>()
   10                 .Setup(x => x.CatchPossibleException(It.IsAny<Action>()))
   11                 .Callback<Action>(x => x());
   12 
   13             _clientReport = new ClientReport(Guid.NewGuid(), "Client Name");
   14 
   15             OnDependency<IClientSearchFormView>()
   16                 .Setup(x => x.GetSelectedClient())
   17                 .Returns(_clientReport);
   18         }
   19 
   20         protected override void When()
   21         {
   22             On<IClientSearchFormView>().FireEvent(x => x.OnOpenSelectedClient += delegate { });
   23         }
   24 
   25         [Then]
   26         public void Then_get_selected_client_will_be_called_on_the_view()
   27         {
   28             On<IClientSearchFormView>().VerifyThat.Method(x => x.GetSelectedClient()).WasCalled();
   29         }
   30 
   31         [Then]
   32         public void Then_client_report_data_from_the_reporting_repository_is_being_loaded_into_the_view()
   33         {
   34             On<IClientDetailsPresenter>().VerifyThat.Method(x => x.SetClient(_clientReport)).WasCalled();
   35         }
   36 
   37         [Then]
   38         public void Then_display_will_be_called_on_the_view()
   39         {
   40             On<IClientDetailsPresenter>().VerifyThat.Method(x => x.Display()).WasCalled();
   41         }
   42     }
   43 }

  • http://dnperfors.blogspot.com David Perfors

    Actually, I still use WinForms… I don’t know why, but the project I’m working on was started with WinForms…

    What I found difficult with the standard way of WinForms and your example is Application.Run();
    I don’t know the details anymore, but I had troubles with don’t using the Application.Run() command… (I think it had something to do with the skin we use and the component framework that is providing that…)

  • http://elegantcode.com Jarod Ferguson

    Pretty cool autowiring here. This would work well in webforms MVP too (why anyone would choose webforms, ya ya it sucks I know, just sayin)

    Mark I like your spec framework, have you blogged it before? I would like to see the impl for OnDependency and On.

  • http://www.blogcoward.com jdn

    “Then I have a base presenter class without the word “Base” because that would be EVIL”

    LOL. 1

    Enjoying this series, very informative. Agree with Jarod.

  • http://www.blogcoward.com jdn

    1, that is. And agree with Jarod on liking to see the impl (WebForms are just fine).

  • http://elegantcode.com/about/mark.nijhof/ Mark Nijhof

    @Jarod Ferguson
    @jdn
    Thanks guys, I’ll blog about the little tiny test framework in the very near future.

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #503

  • http://blog.cozwecan.com Rob G

    Oops – I called my base controller class “BaseController”. It’s all good and well that “Presenter” wasn’t taken by WinForms, but “Controller” *is* taken by MVC, and without creating a namespacing nightmare – what would you call it… “PrimaryController”?

    Great post!

  • http://elegantcode.com/about/mark.nijhof/ Mark Nijhof

    @Rob G
    Hehe it was more a joke towards some of the things I have been reading. I think on technical concerns something can be called a BaseXxxx without doing something evil :)

    In my case perhaps Presenter is not a good name, perhaps something like AutoHookUpViewEventsPresenter would be a more descriptive name?