WinForms: Best approach for passing data between views?

Oct 9, 2007 at 2:21 AM
I'm really new to CAB so please excuse this if there is already some information on the topic. Also, I'm a web developer. I'm really not sure where to how to appraoch the issue of passing data around.

In the web world, I would just save off a collection of information to the http cache obj. In the WinForms CAB world, do I want to set populate this collection in the Shell? Then have the view subscribe to it? Any examples? Thanks!
Coordinator
Oct 9, 2007 at 1:20 PM
As you know, HTTP is stateless, which means any stuff you store server-side has to be correlated back to a client on each request (via a session ID). ASP.NET takes care of all this nastiness for you, but requires that you store state in certain places.

In the winforms world (or WPF), you essentially own the process (or AppDomain) in which your application is running. You can store stuff where it makes most sense for your application. You have any number of options, really: store in the work item, services, presenters, views or some other class.

Note that CAB includes special support for state via the WorkItem.State collection and the StateAttribute. Essentially, you can store state in a work item and have it automatically serialized / deserialized when suspending and resuming the work item. StateAttribute allows you to automatically inject this state from the work item into dependent child objects such as presenters. There's some samples that come with CAB that go into this more.

HTH,
Kent
Developer
Nov 6, 2007 at 8:51 PM
There's a lot hidden in this question :)

There's multiple answers to "passing data around".

When I mentor developers on CAB, I like to start by defining entry and exit points. Where is your data stored to begin with? Probably in a Database. So somewhere you're going to have a data access layer encapsulated, either as a Repository pattern, or behind a Service facade. That gives you an entry and exit point for your data to come into your application and travel out of your application.

When you're talking about a Repository or a ServiceFacade, I generally like to create those at the highest level of the WorkItem hierarchy, usually in the Shell so that all other modules can take advantage of it (this differs if there's security reasons not to do this, in which case you make the Module responsible for using that Repository/ServiceFacade create it).

So in the ShellApplication class I'll have this:

protected override void AddServices()
        {
            base.AddServices();
            
            // This is a WCF service, hence the endpoint
            IPayrollServiceFacade payrollService = new PayrollServiceFacadeProxy("PayrollServiceEndpoint");
            RootWorkItem.Services.Add<IPayrollServiceFacade>(payrollService);
        }

But that's only have the equation. Now you need to access it somewhere in your application. This is where MVP/MVC comes in.

Generally you want a Presenter, or a WorkItem (acting as a Controller for a group of Views), to fetch the data from the Repository or ServiceFacade. So you'll inject the service into the Presenter or WorkItem:

 
public class EmployeeViewPresenter : Presenter<IEmployeeView>
    {
        private IPayrollServiceFacade _payrollService;
        private Employee _employee;                                       // Here's one place you can store data: as a local variable!
 
        public EmployeeViewPresenter(
            [ServiceDependency] IPayrollServiceFacade payrollService)
        {
            _payrollService = payrollService;
        }

At some point in time a user might select an Employee, for instance, via search screen. The View sends the EmployeeID back to the Presenter (for example) and the Presenter uses the EmployeeID to query for an object graph:

 
public void GetEmployee(int employeeID)
{
   // Remember, we've declared the Employee object as a local variable, so we can just hang on to it here. 
   _employee = _payrollService.GetEmployee(employeeID);
 
   View.Employee = _employee; 
}

That's the simplest case. But suppose you want to be notified when the Employee object has changed (the user has updated a field via the View, for instance) and you want to update the UI to display this fact. You can do something like this (if your business object implements INotifyPropertyChanged)

 
public void GetEmployee(int employeeID)
{
   _employee = _payrollService.GetEmployee(employeeID);
   _employee.PropertyChanged += new PropertyChangedEventHandler(Employee_PropertyChanged);
   View.Employee = _employee; 
}

When you're done with the object, you can persist it back to the service:

public void SaveEmployee()
{
   _payrollService.Save(_employee);
 
   // Then refetch and rebind to the View
   _employee = _payrollService.GetEmployee(_employee.EmployeeID);
   View.Employee = _employee;
}

Other candidates for where you can store data: In the WorkItem's Item collection, or in the State bag (yuck). You can even go a third route, and write your own state service (we have one where I work - it serializes business objects to binary streams and saves them in a dictionary via a string key; if we need to "undo" what the user is editing we deserialize the stored business object and use it to restore the current object the user is editing).

The key thing to remember is this isn't the web. Everything is a real class, and class instances persist until you dispose of them. So a Presenter, WorkItem, View or other class will be "in memory" until you Terminate it (in the case of a WorkItem) or Dispose of it (in the case of everything else). As long as something is "in memory" you can use it to hold on to data. There's nothing wrong, IMO, with having a Presenter declare a local class variable for a business object and use that to reference it during operations.