This project is read-only.

Week 1. Setting up the prototype

1. Unit testing & dependency injection:

To make the code testable it should be written in loosely coupled manner from the very begging. Including:
  • minimal use of static classes;
  • static methods can be used only if the code inside such method doesn't rely on external functionality provided by other service or in special cases like factory method;
  • all services the class uses must be injected (preferably in the constructor).
Here is an example of layer service written in such way and unit test for it (NUnit & Moq): layer service, test for layer service.

During the testing the dependencies of the service are simulated by mocking.

During runtime they are injected by DI container. I chose to wrap particular DI container implementation in an interface IApplicationContainer and wrote a few implementations using popular tools: Castle Windsor, Ninject, Unity, LightInject. Each implementation is sitting in a separate project, so it's possible to switch them simply by changing reference of the main project.

2. MEF & loading of plugins:

No surprises here. MEF implementation worked for me from the very first attempt.

The thing that I explored however is the use of manually specified GUIDs on plugin interfaces (MapWindow 4 did that). I don't see any reason to do it with MEF. GUIDs for type are used only for COM interop. For .NET classes there are not used during type comparison.

The known issue during the use of plugins is dependencies with Copy Local flag, which are copied to plugins folder. These dependencies (like old MapWinInterfaces.dll) can be loaded twice to AppDomain (once for main app, once for plugin). After that the type of the class instantiated from different instances of assembly will be different, i.e.

typeof(IPlugin_from_first_instance).Equals(typeof(IPlugin_from_second_instance))

return false (description of the problem). I believe some MW4 interfaces set GUIDs manually to overcome this problem.

I haven't tested yet how MEF handles the situation. However it seems reasonable to check for this problem after loading plugins and not to wait for abnormal behaviour (possible solution).

3. MVP (model view presenter):

It's least explored yet. I started with this MVP template. Then I embedded in it command dispatcher mechanism I used in MapWindow Lite. Here is a brief description of it:
  • all commands within form / view are defined as enumerated constants;
  • the names of all menu items & toolbar items should be the same as enumerated constant + some prefix, i.e. mnuAddLayer or toolAddLayer (convention over configuration);
  • for all menu items & toolbar items a common event handler is attached;
  • when an item is clicked the code parses its name, translates it into enumerated constant and calls Presenter.RunCommand(EnumeratedConstant command).
Here the code for MVP abstract presenter and a presenter for the main view inherited from it: abstract presenter, main presenter.

It works, however I need to implement several more views / presenters to test how viable this approach is. In general MVP & dependency injection brings a lot of complexity, while in case of the Main form there is little benefit in terms of testability. Largely because of the MapWinGIS's Map control which is difficult to mock or to split into model & view. For other forms MVP benefits may be more tangible.

4. Plugin interfaces for menus:

Some general design principles:

a) Each menu or toolbar item added by plugins should hold reference to that plugin (in Tag). Then it will be possible:
  • automatically remove items when plugin is unloaded;
  • to send click event only to the plugin that owns the button (I don't like when each plugin receives all button events);
  • to forbid removing items added by other plugins.
b) All internal code should be using plugin interfaces to interact with menus & toolbars, so we can change Syncfusion implementation if needed (I'm not absolutely confident in Syncfusion controls yet).

c) The API should support free navigation through all toolbars / menus. MW4 didn't have that.

5. Passing application events to plugins:

The approaches can be different there:

a) IPlugin interface with events (initially used by MW4):
  • inflexible, not possible to add new event without breaking existing plugins;
b) Inheriting from PluginBase class (used by MW4 later):
  • more flexible, allows to add new events;
  • leads to a bad code structure since all the events are handled in the same plugin class, for complex plugins it leads to a mess;
c) Exposing application events as events of PluginBase class:
  • I like it better since it doesn't force to handle the event in the same class, i.e. better code structure;
  • also for VB.NET users I think it's easier handle event than to override the method;
d) Attaching handlers for application events directly:

AppContext.Map.ExtentsChanged += (s, e) =>
{
    // some code
};

It's probably the most intuitive way. However there is a problem how to remove the handlers when plugin is unloaded. I considered approaches:
  • manually unregister events on plugin termination - too troublesome for plugin writers especially when lambdas are used;
  • using techniques similar to weak events - handlers should be registered with special syntax, which negates all benefits;
  • automatically using reflection - according to this post it seems to be doable (I tested the code), but to complex for my liking.
So currently I plan to use the c) approach, i.e. exposing events via PluginBase class.

6. Syncfusion controls:

The use of Syncfusion's Essential Studio controls for WinForms is our plan A right now. The toolset is offered under community license, which basically means it's free for MapWindow 5 contributors & users.

The benefits are:
  • enhanced versions of some commonly used controls, like GridView with visualization support or separate set of menus & toolbars;
  • it provides styles & skins for controls, which gives modern look to the application;
The drawbacks are:
  • there is no NuGet package, so each developer has to download and install it manually;
  • it's quite large, installer is 200+MB and runtime dlls would most likely amount to 20-30 MB;
  • even community license requires registration on the site, which is additional encumbrance for users.
Bearing in mind these factors, here are the first impression from using the Suite.
a) Metro forms:
The designer for Metro forms isn't stable. I had a few crashes, some functionality is lagging. After switching to regular Form all works well. It seems there is no problems during runtime.
b) Docking windows:
Designer support with Metro forms is mostly broken. When setting up docking windows in code all works well. The API seems to be easier than Docking suite we were using in MapWindow 4.
c) Menus & toolbars:
I chose to use Syncfusion's XPMenus. They are recommend as are replacement for MenuStrip and ToolStrip.

Pros:
  • they support floating state;
  • it seems they can correctly restore state through built-in persistence mechanism;
  • it's possible to set docking / position of toolbars manually, though it's done in not very obvious way;
  • they support number of skins & styles (once again modern look);
Cons:
  • the designer support is horrible (items can be added using dedicated manager dialog; drag & drop not supported; takes time to get used to it);
  • the API is somewhat different from MenuStrip (for example there is a distinction between BarItem and ParentBarItem, i.e. the one having drop down list), so MapWindow 4 old interfaces can't be fully preserved;
  • the advanced functionality is poorly documented; couple of things I needed weren't present either in documentation or samples. I found most of them on forum but it takes times to read through number of irrelevant posts; I also asked my own question at forum but there is no answer for 3 days already.
The only thing I wasn't able to do with XPMenus so far is to increase the size of menu items in the main menu & drop down menus (I asked the question about it).

Last edited Mar 1, 2015 at 12:45 PM by sleschinski, version 15