StateService In A CAB Application
Chris CAB comes with a default implementation for handling state called the State bag. However, it’s not a particularly good implementation. Part of the problem with the CAB state bag is that it’s not strongly typed; it’s basically a glorified Dictionary. When you retrieve an object from the State bag you do this:
Employee employee = (Employee)State["SomeKey"];
Since I started using CAB, I’ve avoided using the CAB State bag. I remember a hallway chat with Brad Wilson during last year’s Patterns & Practices Summit, where Brad basically said, “Yeah, we wish we would have had time to remove the State bag from CAB before it shipped, but the schedule didn’t allow it.” Brad’s advice was to roll our own State service if we wanted to handle state.
For a while, our shop functioned without a State service and without relying on the State bag. One solution we initially tried was to dump objects into the WorkItem’s Item collection. This is not much better than the State bag, but it does have the advantage of being typed. However, it makes unit testing a pain because now your Presenter class is dependent on having a WorkItem injected into it for unit testing purposes. I don’t like those sorts of dependencies when I unit test - I like to test classes in bare isolation with mocks.
A second solution we used was to simply hold on to objects via private member variables in Presenter classes:
public class SomeViewPresenter : Presenter<ISomeView>
{
private Employee _employee;
private IPayrollService _payrollService;
[InjectionConstructor]
public SomeViewPresenter([ServiceDependency] IPayrollService payrollService)
{
_payrollService = payrollService;
}
[EventSubscription(EventTopicNames.LoadEmployee, ThreadOption.UserInterface)]
public void LoadEmployee(object sender, EventArgs<int> e)
{
_employee = _payrollService.GetEmployee(e.Data);
}
}
This is simple, but creates its own pain during testing. When you use private member variables to hold on to references to objects, you have to ensure that whatever method in your Presenter is responsible for fetching the business object from its source (like LoadEmployee) must be called prior to calling the method you’re really interested during testing, because you have to be able to load the private variable with an actual object. This is awkward, to say the least. Not to mention that it precludes us from doing any sort of sophisticated state management, like undo/reset.
For testing purposes, wouldn’t it be better if we could just call the method we’re really interested in testing, and not have to initialize or load the Presenter? And isn’t maintaining model state outside the scope of the Presenter anyway? It’s job is coordination, not state management.
To solve these problems I gravitated toward the creation of an IStateService and an ApplicationClosingEventHandler delegate. The interface and delegate are listed below:
public delegate bool ApplicationClosingEventHandler(IList<string> unsavedChangesNotification);
public interface IStateService
{
void SetState(string key, object obj);
T GetState<T>(string key);
T Undo<T>(string key);
bool Remove(string key);
bool ContainsKey(string key);
bool HasUnsavedChanges();
IList<string> UnsavedChangesNotifications { get;}
event ApplicationClosingEventHandler ApplicationClosing;
}
The implementation is as follows:
public class StateService : IStateService
{
protected IDictionary<string, Memento> _state = new Dictionary<string, Memento>();
protected IList<string> _unsavedChangesNotifications;
public virtual void SetState(string key, object obj)
{
if (_state.ContainsKey(key))
{
throw new ArgumentException("An item with the same key has already been added to the StateService.");
}
else
{
using (MemoryStream buffer = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(buffer, obj);
Memento memento = new Memento(obj, buffer.ToArray());
_state.Add(key, memento);
}
}
}
public virtual T GetState<T>(string key)
{
return (T)_state[key].Item;
}
public virtual bool ContainsKey(string key)
{
return _state.ContainsKey(key);
}
public virtual bool Remove(string key)
{
return _state.Remove(key);
}
public virtual T Undo<T>(string key)
{
T obj;
using (MemoryStream buffer = new MemoryStream(_state[key].ByteArray))
{
buffer.Position = 0;
BinaryFormatter formatter = new BinaryFormatter();
obj = (T)formatter.Deserialize(buffer);
}
_state[key].Item = obj;
return obj;
}
public virtual event ApplicationClosingEventHandler ApplicationClosing;
public virtual bool HasUnsavedChanges()
{
_unsavedChangesNotifications = new List<string>();
bool hasUnsavedChanges = false;
if (ApplicationClosing != null)
{
foreach (ApplicationClosingEventHandler handler in ApplicationClosing.GetInvocationList())
{
bool result = handler.Invoke(_unsavedChangesNotifications);
if (result)
hasUnsavedChanges = true;
}
}
return hasUnsavedChanges;
}
public virtual IList<string> UnsavedChangesNotifications
{
get { return _unsavedChangesNotifications; }
}
}
The StateService has two main parts: state management and gathering unsaved changes notification. The first part, the state management, works similar to the Memento pattern you’d find in GoF. There’s a SetState, GetState and Undo (Reset), which will place your object back into the state it was in when you last called SetState. This is not n-level undo: it’s just 1-level undo because that’s all we need right now. But you could tweak it to support n-level undo pretty easy I think.
The StateService achieves the 1-level undo by taking a “snapshot” of the object when SetState is called. It then serializes the object to a binary stream and stores it, along with a reference to the actual object, in a “memento” class (not a true Memento pattern as you would see in GoF because we don’t ask objects to provide their own mementos, but the idea is similar). Of course, since it uses serialization it expects your objects to be Serializable. If you didn’t want that, you’d have to provide a different implementation. But since most objects that require state management are probably serializable already, this seems a logical fit.
The second part of the StateService is gathering unsaved change notification. Now, you may wonder: why do we need the delegate when we could just iterate through the list of objects we’re already holding in the StateService, and if they inherit from some base class (which ours do) then we could just cast them to the base class and check a “dirty” flag and know if there were objects in an unsaved state. But we added the delegate because we wanted interested classes (Presenters/Controllers) in our application to be able to return a message telling the user where those unsaved changes are located within the UI, so the user can quickly find them and make the necessary saves. You might think this is overkill or unnecessary, but you haven’t had to write code for our users
Thus, the StateService publishes an ApplicationClosing event which interested controllers/presenters in the UI layer can subscribe to. When the user goes to shut down the application we listen for that event (at the Shell level) and ask the StateService if it has any unsaved changes. The StateService queries all it’s listeners and if there are any unsaved changes then we can halt the application from closing and throw up a MessageBox with the list of messages from the event handler.
So why all the fuss with the service? The advantage here is that the StateService can easily be mocked via a tool like RhinoMocks. State management now becomes the responsibility of the StateService, and not the Presenter. We’re able to separate those concerns, which helps us focus on testing the real responsibilities of the Presenter, which is typically coordination between the model and the view.
It’s not the most awesome solution, I’m sure, but it serves our needs. And since this is a topic that continues to crop up in CAB circles, I thought I’d post our way of doing things.
December 6th, 2007 at 7:10 pm
Why go to all that extra trouble? All I do is make a public setter for the inner Model of each Presenter and don’t worry about it any further. That way I can start up a Presenter and test it at any point.
public class Presenter
{
// Only for testing
public Trade CurrentTrade
{
_trade = value;
}
}
December 6th, 2007 at 8:37 pm
I guess I just don’t like the idea of a property existing “only for testing”. Seems like a smell. Plus, we still needed the undo capability.
I’m sure I could be convinced to do it your way though. It is simple.