Friday, June 20, 2008
CAB/SCSF: View Parameters
This proven practice can be improved using generics to become type-safe, while still adhering to the MVP design rules. Add the following new method to the WorkItemController base class in the Infrastructure.Interface project:
public virtual TView ShowViewInWorkspace<TView, TParams>(string viewId, string workspaceName, TParams viewParams)
where TParams : class
{
string stateKey = StateItemNames.PresenterConstructorParams + typeof(TView).FullName + ":" + typeof(TParams).FullName;
_workItem.State[stateKey] = viewParams;
TView view = ShowViewInWorkspace<TView>(viewId, workspaceName);
return view;
}
Then add the following new method to the Presenter base class in the Infrastructure.Interface project:
protected TParams ViewParameters<TParams>() where TParams : class
{
//NOTE: must use typeof(view instance) to get correct run-time type of generic view type
string stateKey = StateItemNames.PresenterConstructorParams + _view.GetType().FullName + ":" + typeof(TParams).FullName;
return (TParams)_workItem.State[stateKey];
}
Now you can simply pass the parameters from the module controller to the view using ShowViewInWorkspace and then get the parameters in the presenter's OnViewReady event:
_viewParameters = ViewParameters<ViewParameters>();
if (_viewParameters == null)
{
throw new ArgumentException("Required view parameter error", "ViewParameters", null);
}
This little design improvement hides the gory details of using the untyped object State collection, e.g. generating shared keys and type casting.
Wednesday, May 07, 2008
Design Rules for Model-View-Presenter
Each view must implement an interface that defines all interactions that the presenter has with the view. This ensures a clear separation between the presenter and the view, and has the advantage of making the presenter easy to unit-test using mocking. The view never calls other modules and services directly; it must always use an event to request that the presenter provides the needed data or service.
This project uses CAB/SCSF to generate the view modules. The generated code is not quite as I would like it, I prefer that the view has no knowledge of the presenter and no direct access to it either as this gives a cleaner separation between views and presenters. I prefer that the view interface defines a set of events that is used to communicate view events to the presenter. This makes it harder for unknowledgable programmers and code monkeys to misuse the presenter reference; making shortcuts and ignoring the reasons for using patterns is very common and leads to unmaintainable code. Read Jeremy Miller's View to Presenter Communication for further opinions on this topic.
The following MVP design rules have a CAB flair, but are generally applicable to any MVP implementation:
1. All views should have a XxxView suffix: TaskView/ITaskView
2. All presenters should have a XxxPresenter suffix: TaskViewPresenter
3. Let the presenter do all use-case processing, but keep GUI control details in the view
4. All presenter methods called by the view must start with OnXxx() as they are events by design (MVP)
5. Calls from the view to the presenter should be kept at an absolute minimum, and used for "event" type calls only: _presenter.OnViewReady();
6. It is FORBIDDEN to use the presenter reference to access the model or services directly, no return values from presenter methods
7. Calls from the presenter to the view MUST go via the interface only
8. No methods in the view shall be public unless they are defined in the interface
9. It is FORBIDDEN to access the view from anywhere but the presenter, except for loading and showing the view from the CAB ModuleController
10. The interface methods shall have long meaningful names (not "SetDataSource") based on the domain language of the use-case
11. The interface should contain methods only, no properties - afterall the presenter drives the use-case by calling methods, not by setting data
12. All data in the MVP component should be kept in the model, no data should exist only as properties of UI controls
13. The methods of the view interface should not contain UI control name references (e.g. AddExplorerBarGroup) as this makes the presenter know too much about the implementation technology of the view
14. Stick with domain naming in the view methods (e.g. AddTaskGroupHeader), this makes the code easier to understand and the tests self-describing
Rule 11 and 12 might surprise you if your perception of the model is that it contains business entities only - traditional database-driven design thinking. The model is rather business process documents that are involved in the use-case, i.e. domain entity and value objects. Even views used for searching have a model, it is a specification or query object. Jeremy has some guidelines on assigning responsibilities in MVP.
Rule 6 helps adhering to the "tell, don't ask" principle - properties make it far too easy to ask, rather use methods on the view to tell it what to do. I've seen design-time view data-binding directly against presenter properties, not exactly separation of concerns. Neither is it very testable, as there is no interaction from the presenter through the view's interface when the view asks the presenter directly. When mocking the view, there will be no recorded / testable interaction.
Using long meaningful names in the view interface has the nice side-effect of making the presenter unit-tests a kind of mini documentation.
I wish there was a FxCop Contrib project on CodePlex like for EntLib, CAB/SCSF and ASP.NET MVC. Then maybe I would write and publish some of these MVP design rules as custom FxCop rules.
Friday, May 02, 2008
SCSF Dependency Injection Container, CAB WorkItem
The CAB workitem is simply a dependency injection container, even if "wrongly" promoted as an implementation of a use case. A use case contains no UI-details or service implementation references, but of course these artifacts are needed to realize a use case in an application. The workitem is a container of resources needed to implement a use case.
A DI-container is a collection of resources than can be located using a resource locator that is used to resolve resource injection requests at run-time. The resources can be loaded into the container either dynamically or through XML config. The workitem DI-container has several collections of resources, which can be quite confusing. A workitem has these collections:
- Items: all objects added to any of the child collections including this, except Workspace.SmartParts; independent of type of resource.
- WorkItems: child WorkItem objects, must inherit CAB.WorkItem.
- Services: any object added as a service. NOTE: the container can only contain one instance of each service type.
- Workspaces: encapsulate a particular visual layout of controls and SmartParts, such as within tabbed pages.
- SmartParts: any object marked with [SmartPart] in the class definition, do not have to be a visual component.
- Workspace.SmartParts: visual components shown in the workspace, must inherit System.Windows.Forms.Control. NOTE: these items are not added to the .Items or .SmartParts collections on the workitem.
When the DI-container tries to resolve a [ServiceDependency] injection or just Get<IService> for a service, it looks in the nested set of containers from the current workitem and up through the parent workitems, including the root workitem to find the service. The service locator does not look through the set of child workitems. Thus, shared services should be placed towards the root or in the root workitem for the service locator to find them.
One aspect of the workitem Services collection that most CAB developers seems to have missed, is that even if you can add only one instance of a service to a workitem, you still can add separate instances of the same service to child workitems. The service locator will find the nearest service in the tree towards the root. E.g. the ActionCatalog service can be added to several ModuleController first-level workitems, in addition to the root workitem. We have used this feature to add several scope-based services such as a SharedModelCacheSevice and a RibbonControllerService at different levels of the workitem structure. This way, the same service can have different scopes in a CAB solution, dependent on where it is injected. The scope controls how workitem resources are located by the resource locator, e.g. where the locator looks for Services, Commands and UIExtensionSites.
Note that the resource locator strategy only applies to these workitem collections: Services, Workspaces, Commands, EventTopics, UIExtensionSites.When the SmartClient application starts, it will create the root workitem and add a set of CAB services to it:
public ... class SmartClientApplication<TWorkItem, TShell> ...
{
protected override void AddServices()
{
RootWorkItem.Services.AddNew <ProfileCatalogModuleInfoStore, IModuleInfoStore>();
RootWorkItem.Services.AddNew <WorkspaceLocatorService, IWorkspaceLocatorService>();
RootWorkItem.Services.AddNew <XmlStreamDependentModuleEnumerator, IModuleEnumerator>();
RootWorkItem.Services.AddNew <DependentModuleLoaderService, IModuleLoaderService>();
RootWorkItem.Services.AddOnDemand <ActionCatalogService, IActionCatalogService>();
RootWorkItem.Services.AddOnDemand <EntityTranslatorService, IEntityTranslatorService>();
}
}
These standard CAB services are available from all over CAB as they are added to the root workitem that becomes the ultimate parent of all other workitems loaded into the SmartClient application.
In addition to the standard set of CAB services, the custom CAB modules that get loaded will typically add extra CAB services into the root workitem. SCSF will load CAB modules as defined in ProfileCatalog.XML and this will trigger the CAB module initialization mechanism filling the workitem collections and followed by the DI-container resolving.
What adds to the somewhat complicated workitem collections mechanism is that several of these CAB services themselves are collections of other shared objects. E.g. the EntityTranslatorService is a collection of translators that can be added to and located within that service. Note that the DI-container only performs the resource resolving and dependency injection on objects that are added directly to a workitem collection. Thus, objects not added directly to a workitem collection will not get any DI magic performed on them. E.g. if you create a service agent from within a CAB service to call a web-service, that agent will not be subject to any DI resolving. This can be very confusing when [ServiceDependency] objects are not injected as planned, thus be very careful when designing your solution.
NOTE: do not try to access resources that must be resolved by the DI-container, from within class constructors, as the resource may not have been injected yet. Use the [InjectionConstructor] attribute on your constructor if you need to force the resource locator to resolve and inject services that you need.
Using the DI-Container from CAB Modules
CAB Services are usually implemented using CAB foundation modules, but can also be implemented using CAB business modules. The former module type is applicable for shared CAB services and components, such as modules that provide access to web-services; while the latter module type is suitable for use-case specific CAB components. Note that both types of resources are "services" in CAB as they are added to the workitem Services collection. This overloading of the term "service" should not make you confuse CAB service modules with WCF service gateways, even if the CAB service used the WCF service.
Adding CAB services from CAB modules
A foundation module is a bare-bones module (the original CAB module) as it is just for implementing a container for shared components.
A business module is a module (new in SCSF, extending the original CAB module) enhanced with the application controller pattern as it for implementing a container for use-case specific components. The SCSF module adds the ControlledWorkItem to separate the use-case related code from the workitem container related code (in CAB you had to put the use-case controller code in the workitem class, mixing the DI-container logic with the use-case logic).
So even if you could implement an AddServices method in the ModuleController and call it from its Run method, I would recommended that you still add module services from Module.AddServices method. This keeps the container related code separated from the use-case related code:
public override void AddServices()
{
//NOTE: must add UserAccessClaimSet first, as NavigationService depends on this service
WorkItem.RootWorkItem.Services.AddNew <UserAccessClaimSet, IUserAccessClaimSet>();
WorkItem.RootWorkItem.Services.AddNew <NavigationService, INavigationService>();
}
Prefer adding services like this over the other mechanisms provided by SCSF (app.config / [Service] attribute).
Resolving and using CAB services
To be able to use the CAB services they must be gotten from the workitem DI-container. There are two ways of doing this, using the service collection Get<IService> method or using dependency injection with the [ServiceDependency] attribute. The DI way is preferred as the class using the service can then get the service reference once and use it multiple places. The Get<IService> way has the advantage that it can be called with a bool EnsureExists parameter that will throw an exception if the service is not (yet) registered in the workitem DI-container.
Use the [ServiceDependency] attribute either to inject a service into a class property or in the class c'tor:
[ServiceDependency]
public IUserAccessClaimSet Service
{set { _service = value; }}
[InjectionConstructor]
public NavigationService([ServiceDependency] IUserAccessClaimSet userAccessClaimSet)
{
_userAccessClaimSet = userAccessClaimSet;
}
Getting the service directly is more explicit and allows for using the EnsureExists parameter:
IUserAccessClaimSet costOfGasService = WorkItem.Services.Get<IUserAccessClaimSet>(true);
Without the EnsureExists parameter, the returned service can be null and your code will then not throw an exception when getting the service, but rather on the first service invokation later on, which can make it harder to diagnose the actual cause of the failure. Using the [ServiceDependency] attribute has the same weakness, and this is a common cause of CAB perplexity. Still, I would recommend using the [ServiceDependency] attribute.
Using the DI-container from CAB views
All CAB views exist within a CAB business module and must thus use one of the recommended methods for resolving service dependencies, preferring the [ServiceDependency] attribute. CAB views are designed using the 'supervising controller' MVP pattern, and the presenter object is added to the workitem Items collection like this:
_presenter = _workitem.Items.AddNew<TaskViewPresenter>();
Adding it to the Items collection of the workitem DI-container triggers the dependency injection resolver.
Tuesday, April 08, 2008
CAB/SCSF: Unit Testing Presenters, Views, Events
Use the TestableRootWorkItem like this:
private MockRepository _mocks;
private TestableRootWorkItem _workitem;
private MyViewPresenter _presenter;
private IMyView _view;
private IMyService _service;
[TestFixtureSetUp]
public void Initialize()
{
_mocks = new MockRepository();
_service = _mocks.CreateMock<IMyService>();
_view = _mocks.CreateMock<IMyView>();
_workitem = new TestableRootWorkItem();
_workitem.Services.Add(_service);
//perform the CAB DI-container magic
_presenter = _workitem.Items.AddNew<MyViewPresenter>();
_presenter.View = _view;
}
Note: do not try to access the workitem or any injected dependencies such as [ServiceDependency] inside constructors, things might not have been resolved by the DI container yet. This especially applies to stuff from base classes and things that are not resolved using an [InjectionConstructor].
When testing that event publishing actually triggers the subscribers, you can add fake event publishers and subscribers to help with the mocking. In addition, your unit-test should verify that the raised event causes presenters and views to be loaded by checking the content of the Items and SmartPart collections afterwards.
Implement your CAB/SCSF event-based unit-test like this:
[TestFixture]public class ModuleTestFixture
{
. . .
[TestFixtureSetUp]
public void Initialize()
{
_workitem = new TestableRootWorkItem();
_eventPublisher = _workitem.Items.AddNew<MockEventPublisher>(_eventPublisherId);
_eventSubscriber = _workitem.Items.AddNew<MockEventSubscriber>(_eventSubscriberId);
_moduleInitializer = new Module(_workitem);
_moduleInitializer.Load();
}
[Test]
public void ShouldLaunchPriceConfigViewOnEvent()
{
int itemPresentersCount = FindItemsByTypeRecursive <PriceConfigurationViewPresenter>(_workitem);
Assert.AreEqual(0, itemPresentersCount);
int smartPartViewsCount = FindSmarPartsByTypeRecursive <PriceConfigurationView>(_workitem);
Assert.AreEqual(0, smartPartViewsCount);
_eventPublisher.OnNewOrder(new NewOrderEventArgs(123, Constants.OperationNames.StartPriceConfigurationView));
Assert.AreEqual(true, _eventPublisher.HasRaisedNewOrder, "EventPublication <NewOrder> failed");
Assert.AreEqual(true, _eventSubscriber.HasHandledNewOrder, "EventSubscription <NewOrder> failed");
itemPresentersCount = FindItemsByTypeRecursive <PriceConfigurationViewPresenter>(_workitem);
Assert.AreEqual(1, itemPresentersCount);
smartPartViewsCount = FindSmarPartsByTypeRecursive <PriceConfigurationView>(_workitem);
Assert.AreEqual(1, smartPartViewsCount);
}
private int FindItemsByTypeRecursive<T>(WorkItem workItem)
{
ICollection<T> items = workItem.Items.FindByType<T>();
int typeCount = items.Count;
foreach (System.Collections.Generic.KeyValuePair<string, WorkItem> item in workItem.WorkItems)
{
typeCount += FindItemsByTypeRecursive<T>(item.Value);
}
return typeCount;
}
private int FindSmarPartsByTypeRecursive<T>(WorkItem workItem)
{
ICollection<T> items = workItem.SmartParts.FindByType<T>();
int typeCount = items.Count;
foreach (System.Collections.Generic.KeyValuePair<string, WorkItem> item in workItem.WorkItems)
{
typeCount += FindSmarPartsByTypeRecursive<T>(item.Value);
}
return typeCount;
}
}
public class MockEventPublisher
{
[EventPublication(Constants.EventTopicNames.NewOrder, PublicationScope.Global)]
public event System.EventHandler<NewOrderEventArgs> NewOrder;
public virtual void OnNewOrder(NewOrderEventArgs eventArgs)
{
if (NewOrder != null)
{
_hasRaisedNewOrder = true;
NewOrder(this, eventArgs);
}
}
public bool HasRaisedNewOrder
{
get { return _hasRaisedNewOrder; }
set { _hasRaisedNewOrder = value; }
}
private bool _hasRaisedNewOrder = false;
}
public class MockEventSubscriber
{
[EventSubscription(Constants.EventTopicNames.NewOrder)]
public void OnNewOrder(object sender, NewOrderEventArgs eventArgs)
{
if (eventArgs.OperationName == Constants.OperationNames.StartPriceConfigurationView)
{
_hasHandledNewOrder = true;
}
}
public bool HasHandledNewOrder
{
get { return _hasHandledNewOrder; }
set { _hasHandledNewOrder = value; }
}
private bool _hasHandledNewOrder = false;
}
If you have issues with understanding that a WorkItem is just a dependency injection container or what all the different collections such as Items, Services, SmartParts, WorkSpaces, etc are for; I recommend reading Rich Newman's Introduction to CAB/SCSF. Read part 18 first.
Tuesday, October 17, 2006
Enter Supervising Presenter, exit MVP
Just about the same time in june, Martin Fowler retired the MVP pattern for just about the same reasons, and the new data binding friendly pattern is the Supervising Controller. The MVP pattern has actually been split in two, the other pattern being the Passive View.
So if you're still using MVP, be sure to catch up on the new patterns.
Wednesday, June 14, 2006
Model Reference Data = Observer pattern + Reference Data + MVP (Part II)
All who have developed a tabbed wizard control know that using a common data object shared between all the tabs is a good design. The refactored design is analogous to this, in addition to employing the Observer pattern. The WinForm user control container module described in Part I is just an advanced, decoupled MVP implementation of a multi-tabbed user interface module.
The refactoring of the module comprises these steps:
1. Extract all common reference data into a new ‘subject’ class as private members.
2. Expose the reference data as public properties.
3. Add event definitions to the applicable properties to notify ‘observers’ of changes to the ‘subject’.
4. Add a property to the presenter to hold an instance of the new BPRD object type.
5. Add a property to the view interface, of the new BPRD object type.
6. Add code in the presenter to create an instance of the reference data (BPRD).
7. Add code in the presenter to set the BPRD property of each of the views.
8. Add event handlers in the presenter and views (observers) to subscribe to applicable notifications from the new BPRD object.
In the new design, all views hold a reference (pointer) to a common, shared instance of the BPRD object. They all change the common object and they all can subscribe to whatever event they need to get notified of. Thus, the presenter need not contain any code to handle BPRD events to then invoke methods on the views. This leads to a much simpler presenter. The views are also somewhat simpler, as the view interface now contains less to implement.
The refactored view interface looks like this:
public interface IEkspederingView
{
//note how all the get/set events from part I are gone
//property pointing to the current common, shared BPRD
EkspederingFellesData GjeldendeEkspederingFellesData{get; set;}
...
}
The new business process reference data class looks like this:
{
public EventHandler<EventArgs> KontonummerSatt;
public string Kontonummer
{
get { return _kontonummer; }
set
{
_kontonummer = value;
//subject: notify subscribers of change
if (KontonummerSatt != null) this.KontonummerSatt(this, new EventArgs());
}
}
private string _kontonummer;
...
}
The presenter looks like this:
public class EkspederingPresenter
{
public EkspederingPresenter()
{
//create and load the BPRD
_currentReferenceData = new EkspederingFellesData();
//add the current BPRD to all the views
_innbetalingView = new InnbetalingView();
_innbetalingView.GjeldendeEkspederingFellesData = _currentReferenceData;
}
public EkspederingFellesData GjeldendeEkspederingFellesData
{
get { return _currentReferenceData; }
}
private EkspederingFellesData _currentReferenceData;
private InnbetalingView _innbetalingView;
...
}
And finally, one of the views looks like this:
public class InnbetalingView : IEkspederingView
{
public InnbetalingView()
{
//observer: add event handlers to subscribe to notifications to BPRD
this.GjeldendeEkspederingFellesData.KontonummerSatt += new EventHandler<EventArgs>(OnKontonummerSatt);
}
public EkspederingFellesData GjeldendeEkspederingFellesData
{
get { ... }
set{...}
}
public void OnKontonummerSatt(object sender, EventArgs e)
{
//do function X13
}
...
}
The reference data value object is the ‘Subject’ of the Observer pattern, while the ‘Observer’ objects are the presenter and (possibly) the views. Thus, the reference data ‘Subject’ object (don’t get confused) is in fact a ‘Model’ object, although not a domain object such as a customer or an order.
If this combination of the observer pattern, the MVP pattern and a reference data object should have a name, I would call it the “Model Reference Data” pattern, the subject/model being the common, shared and observed reference data.
[UPDATE] Martin Fowler has since retired MVP and replaced it with the Supervising Presenter pattern. The new pattern is actually very like our adapted MVP usage, as it fits better with WinForms data binding.
Friday, June 09, 2006
Tell, don’t ask – MVP – Biz Process Reference Data (Part I)
Reference data is used in the model to annotate the business transaction work item data with extra information such as the customer number, account number, zip code, country, etc.
I have successfully used “tell, don’t ask” to refactor code that used events to get business process reference data (BPRD) values stored in a WinForms presenter control, to use a common, shared ‘current reference data’ object to contain and control the reference data. This also leads to a better design that follows the “single responsibility principle” and leads to better cohesion / separation of concerns in the presenter and the current-reference-data classes.
The WinForms module consists of a container user control that hosts a set of child user controls inside a tab control. In fact, one of the child controls is it self a tabbed container with further child controls. Before the refactoring, the reference data was stored in the presenter, much like the view-state data of the presentation-model. Note that we use an adapted version of MVP due to our extensive use of WinForms databinding. Note also that we by constraint cannot use CAB in this project.
See J Aron Farr’s post at JadeTower on ‘MVC, MVP, Presenter Model’ for more info about the different presentation logic patterns.
The old code contained several events for getting and setting the reference data defined in the view interface (one event pair per value). The child user controls (the views) would raise an event that the presenter would handle to provide the requested reference data value. Thus, a view would need ask for the data to get it and beg for the presenter to change it. After all, raising an event is not dictating (tell) something to happen, it is more like pleading (ask). This coding style started out small with just one value to get.
An example view interface (sorry about the Norwegian naming imposed on me):
Public Interface IKasseEkspedisjonView
#Region "Events"
Event KontonummerHent as EventHandler(Of GenericEventArgs(Of String))
Event KontonummerSett as EventHandler(Of GenericEventArgs(Of String))
Event BuntnummerHent as EventHandler(Of GenericEventArgs(Of Integer))
Event BuntnummerSett as EventHandler(Of GenericEventArgs(Of Integer))
Event EkspedisjonsnummerHent as EventHandler(Of GenericEventArgs(Of Integer))
Event EkspedisjonsnummerSett as EventHandler(Of GenericEventArgs(Of Integer))
#End Region
...
End Interface
The presenter would handle these events both to provide and to update the BPRD values stored in the presenter.
Note that I do not want the views to have a pointer/reference to the presenter. In addition, to keeps things simple, the container is also the presenter. The container naturally has pointers to all the views, added automatically by the Visual Studio designer when adding the child user controls to the tab control.
The events defined in the view interface had to be implemented by all child user controls using BPRD, and even using a user control base class to implement the events only once, it still would lead to a growing number of events and handlers as the number of reference data values increased. Likewise, the presenter would have to add handlers for each new event and for each new child user control added to the container.
It was time for a simpler solution to sharing the business process reference data. The refactoring involves creating a new value object to keep all the reference data and removing all of the get/set events from the view interface in favor of property changed events on the value object. The refactoring will also remove the need for the presenter to subscribe to BPRD events for each view instance.
Stay tuned for details about the refactored business-process-reference-data object in the upcoming part II.
[UPDATE] Martin Fowler has since retired MVP and replaced it with the Supervising Presenter pattern. The new pattern is actually very like our adapted MVP usage, as it fits better with WinForms data binding.