Passing model objects between Presenters...what's a good pattern?

Topics: CAB & Smart Client Software Factory
Jul 11, 2007 at 3:43 PM
I'm trying to figure out a good pattern for the following scenario:

WorkItemController shows View1 in a workspace. Presenter1 fetches a data object (myData) then feeds the elements to View1 for display. The user then clicks 'OK' in View1 and Presenter1 notifies WorkItemController via an event passing along a myData object. WorkItemController then needs to show View2 and seed Presenter2 with myData.

What's a good way to accomplish this? I would prefer to pass this via Presenter2's constructor, but with the way the framework is structured it doesn't seem to fit. I've read that the preferred way of passing data between views is events but I'm assuming this is for when both views are active? I currently have it passed via View2's constructor and then passed to Presenter_2 on ViewReady(). This is obviously not what I want since now the view knows about the model layer.

Any thoughts? All feedback is appreciated!

Michael
Jul 11, 2007 at 6:55 PM
Edited Jul 11, 2007 at 7:01 PM
Maybe you can use a service as a context, which will hold your data object. This way the Presenter2 could inject that service and access the data. This is done in BankTeller v2007, which you can find in SCSF Contrib (http://www.codeplex.com/scsfcontrib)

(In BankTellerModule, the ContextService is used by the ModuleController and then injected by CustomerDetailViewPresenter and CustomerHeaderViewPresenter among others.)

Hope it helps!

Luciano G. Panaro
Jul 11, 2007 at 7:29 PM
Edited Jul 11, 2007 at 7:29 PM
Whenever we have a workitem that acts as a "controller" for several views that need to access the same model, we use EventBroker to handle object referencing.

The way we work it is like so:

We have the controlling workitem fetch the initial model/business object. If it is not a pre-existing model (like fetched from a database store) then we have the controller create a new one (because obviously the scenario calls for some new type of business object and the subsequent views are going to populate it).

The controller then creates all of the necessary views up front, during its OnRunStarted method. But it only shows the first view in the workspace.

Each time the controlling workitem shows a different one of its views, an eventbroker event is fired that passes the model to the view being shown, so it gets an updated reference to the object. Since objects are pass-by-reference in .NET, anytime one view makes a change to the model, every other view can get the update (so long as the controlling workitem fires the event).

When the process is complete (object needs to be saved, or the use case cancelled) the controlling workitem handles those responsibilities.

This creates a situation where each view/presenter can be very specific and self-contained, and the workitem just controls the order the views get shown, and when data gets pulled/pushed to services. The responsibilities are very clear, plus it saves us from having to write "Context Services" to accomplish these tasks.
Jul 11, 2007 at 8:08 PM
Chris,
Could you elaborate on why you create all the views up front? Why don't you have event subscribers on the controller that will instantiate a view if it has not been loaded and then display it or just display it if it is already loaded?
Jul 12, 2007 at 1:54 AM

rcsDev wrote:
Chris,
Could you elaborate on why you create all the views up front? Why don't you have event subscribers on the controller that will instantiate a view if it has not been loaded and then display it or just display it if it is already loaded?


There's probably a lot that will go into answering this question :-)

At the most basic level, it is because we don't let views live without a workitem managing them. So, for instance, our moduleController does not instantiate views by itself. It delegates to a specific workItem which creates those views and manages where they get shown, when, and for how long. We use workitems to manage 'use cases', basically.

Now, at the highest level of a module, in the moduleController, we may have conditional code in an event or commandHandler that looks for a workItem and if it can't find it, creates it (and in the process that workItem creates its views and shows them where necessary). And that is done because you are exposing functionality to the user that they may or may not elect to run when they have your app open, and it doesn't make a lot of sense to load everything at application startup if they aren't going to use all of it. So yes, I get that.

But from the point of my response, I was thinking lower down the object hierarchy, about the use case workItem (the one the workItem controller creates) Most of the time, in our application, when we're instantiating a "use case" it is an object (workItem) that is going to have a finite life cycle. It will be created, live for a while, and then be terminated (not always; sometimes we start a use case that never closes, but again, it's up to the workItem above it in the object hierarchy to manage its lifecycle). In these instances it doesn't make a lot of sense (from my perspective) to instantiate the views of that particular use case on a as-necessary basis. In these instances your use case either needs those views, or does not. And if there's ambiguity then that could be a code smell; maybe something in the design of the use case isn't right.

A second reason for this: I'm an old C/C++ programmer and I'm used to objects having local references to their member variables. A workItem is just another class to me (albeit a special one with dependency injection capabilities and a lot of neat managedObjectCollections) I don't particularly like doing this in a workItem:

IMyView view = SmartParts.Get<IMyView>(SmartPartNames.MyView);
if(view != null)
Workspaces[WorkspaceNames.SomeDeckWorkspace].Show(view);

My preference is to have a reference to the view inside the WorkItem (just like any other member variable of the class) so I can just use it whenever:

private IMyView _view; 
 
// in OnRunStarted.... (ViewFactory is a class we have in our app to handle the creation of Views, so we can easily provide stubs via a testing framework)
 
_view = _viewFactory.Create<MyView, IMyView>(this, SmartPartNames.MyView);
 
// Then in some eventbroker event or command...
 
Workspaces[WorkspaceNames.SomeDeckWorkspace].Show(_view);

So, bottom line: When a use case is created - when that class is created - and Run() is called, that object (in my opinion) should create everything it needs to function properly during the OnRunStarted override. It's going to get all of its dependencies from constructor injection (we don't use property injection). To me, this is part of the "fully baked/ready to rock and roll" theme: when an object is created you better give it everything it needs to operate properly, and then you better have it created anything else it needs in order to run properly when it is initialized (which is essentially what OnRunStarted is; it's an initializer method).

Just keep in mind these are my opinions and there's many different ones out there. I don't claim to be right about any of this stuff :-)


Jul 12, 2007 at 7:29 AM
..."Just keep in mind these are my opinions and there's many different ones out there. I don't claim to be right about any of this stuff :-)"
I know a good one when I see one and I didn't ask for the right answer. I wanted your elaboration. Thanks a lot, much appreciated!
Jul 12, 2007 at 1:24 PM
Thanks for the great responses!

Event Based
Chris, do you perform anything in your presenters OnViewReady()? I assume it's mostly in you event handler?

Context Services
Luciano, interesting idea...strongly-typed state. I had initially went down the state route but didn't like throwing everything into a generic bucket but this approach may help me in other places.

Thanks again for your help.

Michael
Jul 12, 2007 at 2:54 PM

mbattey wrote:

Event Based
Chris, do you perform anything in your presenters OnViewReady()? I assume it's mostly in you event handler?

Michael


Short answer: Yes, We leave the Presenter's OnViewReady method alone and handle it in the EventHandler method.

In the beginning, when we first started using SCSF we were using the OnViewReady method, but we ran into problems
during unit testing. It may have been our fault the way we were coding things, but we ran into some timing problems with
data loading and causes exceptions. In an effort to make things easy and productive for our shop, we abandon the OnViewReady method.
Events seemed easier to us and we have more control over exactly when data gets loaded into a view, which we liked.

We have two basic cases for loading a view's data, and we use events in both cases, but they work differently.

(1) If the View is part of a family of views working in concert and being managed by a Controlling workitem (I like to think of the overseer and his grunts) then we have the controlling workitem
handle the interaction with the model (fetching it from storage and saving it to storage). The views then get populated via EventBroker event with the model as an event argument.

(2) If the View is a loner - not part of a team of views - then we'll inject the model-fetching service into it so it can handle the fetch/save of the data itself. We still tell it when to do that via
an EventBroker event. And we find this useful because sometimes you're telling a view to load a specific piece of data, so in the EventBroker event you can pass it something like an
EmployeeID that maybe you got from some other workItem or View, and then the view itself can fetch that particular Employee object from the service because it knows the ID.






Aug 24, 2007 at 2:15 PM
I am also in this familiar territory - to pass Model data across business modules.

I read all relevant posts and looked at the BankTeller quickstart.

I implemented a context service to share data.

I added a context service in Infrastructure.Module and added to the rootWorkItems's services collection.

The question is, is this an acceptable/standard approach?
I was contemplating on creating a separate project and having the context service there, but I was thinking, what is it buying me.

How are you guys sharing model data across modules?

Thanks.
Aug 24, 2007 at 7:08 PM
I have seen the context service thing done; we do not do it that way where I work. I am not sure it is necessary, and that is why we don't do that.

We simply pass business objects. We do this because objects in .NET are pass-by-references. So I don't think wrapping an object in a context service really achieves anything. The most important thing, in my mind, is that you have some container to manage the lifetime of that object, specifically its entry point into the application and its exit point from the application. Is the object coming from a repository? From a web service? Is it being newly created? These are questions for the managing container. Once an entry point is established, an exit point has to be established as well, and then the lifetime of the object can be known. Then you can use it in a simple way, with something like EventBroker, which I find incredibly easy to deal with.

For us, the thing that typically manages our objects is a WorkItem, usually one controlling a Use Case of some kind.

Aug 27, 2007 at 1:32 PM
Chris,
Thanks for the response.
I was with you, until you said about EventBroker. My core object is populated by Module1. All other modules need this core object. The object is needed by the Presenter/View in the subscribing modules, but the ModuleController is the one who gets the event from the EventBroker system. To get around this, I am passing the core object wrapped in a context service(ServiceDependency).

Is there a better method you use, or suggest?

Thanks.


ChrisHolmes wrote:
I have seen the context service thing done; we do not do it that way where I work. I am not sure it is necessary, and that is why we don't do that.

We simply pass business objects. We do this because objects in .NET are pass-by-references. So I don't think wrapping an object in a context service really achieves anything. The most important thing, in my mind, is that you have some container to manage the lifetime of that object, specifically its entry point into the application and its exit point from the application. Is the object coming from a repository? From a web service? Is it being newly created? These are questions for the managing container. Once an entry point is established, an exit point has to be established as well, and then the lifetime of the object can be known. Then you can use it in a simple way, with something like EventBroker, which I find incredibly easy to deal with.

For us, the thing that typically manages our objects is a WorkItem, usually one controlling a Use Case of some kind.



Aug 28, 2007 at 5:27 AM

parimels wrote:

I was with you, until you said about EventBroker. My core object is populated by Module1. All other modules need this core object. The object is needed by the Presenter/View in the subscribing modules, but the ModuleController is the one who gets the event from the EventBroker system. To get around this, I am passing the core object wrapped in a context service(ServiceDependency).

Is there a better method you use, or suggest?


I don't understand this part: "ModuleController is the one who gets the event from the EventBroker system."

I'll tell you how I would do it.

ModuleController is the highest object in the object hierarchy (at least it normally is). So that makes WorkItem1 an object on the next level down. Let's assume you have a couple other work items, like WorkItem2 and WorkItem3 as well, and they both need the BusinessObject. Same goes for View/Presenter1.

I would have ModuleController create WorkItem1, and I would have WorkItem1 create the businessObject (normally by fetching it from a service layer, like data access from a database, but maybe you're just creating a new object to be filled out by the user).

I would have WorkItem1 also Publish an EventBroker event. Something like this:

[EventPublication("LoadData", PublicationScope.Global)]
public void LoadData(object sender, EventArgs<MyBusinessObject> e);

After the object is created/fetched from DB in WorkItem1, then I would fire this event from WorkItem1:

private void GetBusinessObjectAndLoadSubscribers()
{
   MyBusinessObject obj = _someService.GetBusinessObject();
   if(LoadData != null)
      LoadData(this, new EventArgs<MyBusinessObject>(obj));
}


In your other WorkItems & Presenters I would have an EventBroker Subscription that would listen for that event and fetch the MyBusinessObject off the event argument parameter. Since objects are pass by reference, everyone who subscribes to that event will have a reference to the same object. Then they can do whatever they need to with it.

-Chris
Sep 15, 2007 at 4:09 AM
Edited Sep 15, 2007 at 4:10 AM
I kind of like ComponentDependencies. That is, items that can be injected into dependent classes and whose lifecycle is either that of the containing work item or if you do a remove. Here's an example...

View1 lists, let's say account objects in a list box and now we want to use a new view (View2) to edit that item once it's selected.
Here's what i've done in the past.

First, before you show View2 in a modal dialog add the item to the workitem.
WorkItem.Items.Add("MyObject", object_instance).

Then, show the View using the typical WorkSpace.Show() method (which is where the injection will take place).
In the presenter of View2 I'd have a public property used to capture the injection.

[ComponentDependency("MyObject")]
public Account InjectedAccount {get; set;}

So now View2 will have an instance of the account selected in the previous view. The problem with this approach is you have to manage the items collection...Viz, remove that element once the view is closed.