Have you ever needed to handle closing a dirty form which has unsaved data such as customer information? Have you ever ended up with something like this?
1 public partial class CustomerEditForm : Form
4 private void View_FormClosing(object sender, FormClosingEventArgs e)
6 if (bDirty)
8 DialogResult result = MessageBox.Show("Save the changes?",
"Save the changes?", MessageBoxButtons.YesNo);
9 if (result == DialogResult.Yes)
20 private void textBoxLastName_TextChanged(object sender, EventArgs e)
22 bDirty = true;
I have seen or done this several times, for my school projects, at work, etc. Recently, I did it again for a little fun project and got bitten by it. First, I sometimes forgot to set the flag somewhere. Second, the "dirty" code was so scattered that the flag got set/reset accidently and the YesNo dialog popped up at surprising times. Third, the dirty flag doesn't reflect the definition of dirty data. If the user undoes changes, the data is not dirty but the application still pops up the
YesNo dialog. Last, and worst, if I need the same capability in another Form, I have to write the same code again, resulting in duplicate code. In addition, different questions in the YesNo message box were used due to duplicate code, so that users were forced to read the question carefully to not select the wrong choice.
Wouldn't it be nice to consolidate the dirty handling code in one component and use it on any Form? With OOP, it is possible, but not easy.
The plan is to use Aspect Oriented Programming (AOP). This article uses the AOP module in Spring.Net. I'm going to assume you have basic understanding of Spring.Net and the AOP module in Spring.Net. The chapters 9 and 17 in the Spring.Net 1.0.2 manual do an excellent job of introducing AOP.
The basic idea is to abstract and move the dirty handling code (lines 6 to 17 and 22, etc.) to a dirty advisor so that the
Form class will be free of dirty handling code. We intercept the
Form closing event and execute the dirty handling code at runtime. Then, after the dirty handling code, the rest of
Form closing code resumes.
We also replace
bDirty flag with the Memento pattern to fix the problem when the flag doesn't really capture the definition of "dirty". A
Memento object stores the state of the application data, e.g. the
Customer object. We will test the equality of two
Mementos to determine the dirtiness of the data. Similarly, we intercept the
Form loading event to capture the application data for later comparison.
We move all dirty handling code to
DirtyHandlingAspect. In this aspect, two tasks need to be done:
- Create the baseline of the data after an event, e.g.
Form is visible.
- Run the dirty handling code before an event, e.g.
Form is closing.
In each task, we need to identify the event or the method call to intercept and the behavior to be executed around the event. In Spring.Net
AOP, each task is modeled as an advisor
. The following diagram shows the model of our
Before we talk about the content of
DirtyHandlingAspect, an important interface,
IOriginator, needs to be mentioned. For
DirtyHandlingAspect to work, it needs the internal state of the intercepted object and a method to save the data. The forms call the
IOriginator to get the state of the intercepted object, while
Save is called to save the data. Any object to be intercepted by
DirtyHandlingAspect has to implement this interface. In other words, as long as an object implements
DirtyHandlingAspect can add dirty handling capability to the object. The implementnation details of intercepted objects are hidden behind
IOriginator is borrowed from the Memento design pattern, where the internal state of an object is called
Memento and the object is called
Memento object is of type
DirtyHandlingAspect, the only requirement for the
Memento object is that object identity methods, e.g.
Equals, are implemented appropriately.
DirtyHandlingAspect has two Spring.Net AOP advisors. A Spring.Net AOP advisor has two components.
Pointcut identifies the join points, e.g. methods of the intercepted object, to intercept, while the intercepted object is called the
Advice defines the code to be inserted at the join points. Both advisors inherit
DefaultPointcutAdvisor as shown in the class diagram.
BaselineAdvisor implements the first task of
DirtyHandlingAspect. The code for
BaselineAdvisor is very simple:
sealed class BaselineAdvisor : DefaultPointcutAdvisor
public BaselineAdvisor(String methodNameRE)
Pointcut = new SdkRegularExpressionMethodPointcut(methodNameRE);
Advice = new BaselineAdvice();
The constructor is all this class has and it takes a regular expression as its argument. The regular expression is used to create
Pointcut of type
SdkRegularExpressionMethodPointcut. As its name suggests,
SdkRegularExpressionMethodPointcut identifies methods to intercept using regular expression. The first task of
BaselineAdvisor to intercept events like
Form loading. The
methodNameRE could be something like
OnLoad. It is up to whoever uses
DirtyHandlingAspect to pass in appropriate regular expressions for method names.
The code that captures the Memento of the application data is defined in
BaselineAdvice. When invoked,
BaselineAdvice calls the
CreateMemento on the target. Implementing
BaselineAdvice can put its code before and after a method invocation, as you can see in the following code snippet. Note that
IMethodInvocation.This points to the target, an object of type
1 sealed class BaselineAdvice : IMethodInterceptor
3 private object _baselineMemento;
4 public object BaselineMemento
8 return _baselineMemento;
11 public object Invoke(IMethodInvocation method)
13 IOriginator target = (IOriginator)method.This;
14 _baselineMemento = target.CreateMemento();
15 return method.Proceed();
BaselineAdvisor should be applied in the
Form OnLoad event so the Memento of the application data can be taken as soon as the form is shown.
HandleDirtyAdvisor implements the second task of the
DirtyHandlingAspect. Similar to
BaselineAdvisor, it uses
SdkRegularExpressionMethodPointcut. The dirty handling code in the old
CustomerEditForm is moved here:
1 sealed class HandleDirtyAdvice : IMethodInterceptor
3 private BaselineAdvice _baselineAdvice;
4 public HandleDirtyAdvice(BaselineAdvice baselineAdvice)
6 _baselineAdvice = baselineAdvice;
9 public object Invoke(IMethodInvocation method)
11 IOriginator target = (IOriginator)method.This;
12 object currentMemento = target.CreateMemento();
13 if (!currentMemento.Equals(_baselineAdvice.BaselineMemento))
15 DialogResult result = MessageBox.Show("Save the changes?", this.GetType().Name,
16 if (result == DialogResult.Yes)
21 return method.Proceed();
lines 13-20 are very similar to the old implementation in the
CustomerEditForm. When invoked, it gets the Memento of the data and compares it to the baseline Memento in
Equals. If the two Mementos are not equal (dirty), a YesNo dialog pops up. If user selects Yes, the advice calls the
Save on the target to save the data. Line 21 resumes the execution of the method/event.
HandleDirtyAdvisor should be called in Form closing event or the like.
CustomerEditView and CustomerEditPresenter
Spring.Net AOP only intercepts methods of an object defined in an interface. A little refactoring on the old
CustomerEditForm is required and we'll refactor it to the MVP pattern. You'll see later that by moving to MVP, the design allows Spring.Net AOP to automatically intercept methods of interest.
It's always a good practice to use MVP pattern in UI design, but I'll not elaborate on the MVP pattern here. You can find the link to a MVP article in the References section. The following class diagram shows the refactored design.
ICustomerEditPresenter has two interesting methods, namely
CustomerEditView delegates the
Form loading and closing events to
CustomerEditPresenter via these two methods and
DirtyHandlingAspect will intercept them. As the
CustomerEditPresenter implements the
CustomerEditPresenter is ready to be intercepted.
As discussed in the previous section, the intercepted object,
CustomerEditPresenter needs to implement
IOriginator. The following is the code for the two methods in the
public class CustomerEditPresenter: ICustomerEditPresenter, IOriginator
public void Save()
MessageBox.Show("Save() is called. Save the changes.");
public object CreateMemento()
Memento customer = new Memento();
customer.FirstName = _Customer.FirstName;
customer.LastName = _Customer.LastName;
customer.PhoneNumber = _Customer.PhoneNumber;
For demo purposes,
Save simply shows a
MessageBox and the
CreateMemento copies the data from
Customer object to a
Memento, in which
Equals is appropriately implemented.
The last thing required is to intercept the
CustomerEditPresenter with the
DirtyHandlingAspect. In Spring.Net AOP parlance, we need a proxy for
CustomerEditPresenter. Since creating a proxy object is complex, we create a factory class,
DirtyHandlingAspectProxyFactory to encapsulate the process. The following is the code from the
1 public sealed class DirtyHandlingProxyFactory
3 private string _baselineMethodRE;
4 private string _closingMethodsRE;
5 public DirtyHandlingProxyFactory(string baselineMethodsRE, string closingMethodsRE)
7 _baselineMethodRE = baselineMethodsRE;
8 _closingMethodsRE = closingMethodsRE;
10 public object GetProxy(object origin)
12 if (!(origin is IOriginator))
13 throw new Exception("origin must be of type IOriginator.");
14 ProxyFactory proxyFactory = new ProxyFactory(origin);
15 DirtyHandlingAspect dirtyHandlingAspect = new DirtyHandlingAspect(_baselineMethodRE,
16 foreach (DefaultPointcutAdvisor advisor in dirtyHandlingAspect.Advisors)
20 return proxyFactory.GetProxy();
The constructor takes two regular expression strings. The first identifies the method calls after which a Memento is created and stored as a baseline and is passed to the
BaselineAdvisor constructor. The second regular expression selects the method calls after which the dirty handling code is executed and is used to create a
HandleDirtyAdvice object. The
GetProxy(object) method of
DirtyHandlingProxyFactory creates a proxy of the
origin by applying the advisors in the
DirtyHandlingAspect. Line 12-13 checks if the
origin is of type
IOriginator since our advices only work with objects of type
IOriginator. Line 14 creates the
Spring.Aop.Framework.ProxyFactory. Lines 15-19 create and add the advisors in
DirtyHandlingAspect to the
ProxyFactory object. At line 21, the
ProxyFactory.GetProxy() creates the proxy using the advisors added at line 18. The proxy is then returned.
Using the DirtyHandlingAspect
Now, all we need to do is to create the
CustomerEditPresenter proxy using the
DirtyHandlingProxyFactory and then use the proxy. This is very easy to do:
1 DirtyHandlingProxyFactory proxyFactory =
new DirtyHandlingProxyFactory("OnLoad", "OnClosing");
2 ICustomerEditPresenter customerEditPresenter =
Line 1 tells the factory to bind
BaselineAdvice to the
OnLoad method of the target and
HandleDirtyAdvice to the
OnClosing method. Line 2 creates a
CustomerEditPresenter proxy which will be intercepted by the two advisors.
HandleDirtyAdvice in Action
Let's see how everything works together. The following sequence diagram describes what happens when
OnClosing of the
CustomerEditPresenter is invoked.
Note the stereotype advisor. An advisor accepts all incoming calls and redirects the requests according to the pointcut specification. In this case, since the method name
OnClosing (step 1)matches the regular expression in
SdkRegularExpressionMethodPointcut, it calls the
Invoke method of
HandleDirtyAdvice (step 1.1). The rest of the flow in the
HandleDirtyAdvice is straightforward.
Reusing the DirtyHandlingAspect
DirtyHandlingAspect were only useful for
CustomerEditPresenter, AOP wouldn't add any value here. In fact, the design carefully isolates dirty handling code in the
DirtyHandlingAspect so we can reuse
DirtyHandlingAspect. The sample project StateChooser illustrates just that.
To add dirty handling functionality to your project, refactor the class that will be intercepted to implement
IOriginator. The methods to be intercepted should be defined in an interface. Remember to check if the
Memento class implements
GetHashCode appropriately. Use the
DirtyHandlingProxyFactory to create the proxy and then use the proxy in the rest of the application.
Improving the DirtyHandlingAspect
The sample code is by no means optimized or bugless and it was simplified for pedagogical reasons. There is room for improvement. For example:
- Use better advisor such as
RegularExpressionMethodPointcutAdvisor instead of
- Support YesNoCancel dialog.
We have shown that with a little refactoring, dirty handling code that was once scattered in and among various classes can be extracted to a single aspect using AOP. Imagine how clean your code base can be without duplicate dirty handling code in various Form classes. The aspect can be used to add the same dirty handling functionality to other components and is not limited only to UI components. To reuse the
DirtyHandlingAspect, simply implement
IOriginator for the component to be extended and
Equals for the
Memento if needed. Use the
GetProxy method of
DirtyHandlingAspect to add an error handling aspect to the component.
The broader message of this article is to show that AOP can be used to implement crosscutting functional requirements. AOP has been around for a while and is touted for being able to solve crosscutting concerns. We use aspects to address non-functional requirements, which are usually crosscutting concerns, but we still resort to OOP when addressing crosscutting functional requirements. In fact, aspects should be treated as first-class citizens, like their fellow classes. Aspects should come up naturally in our routine software design to host crosscutting logic.
The idea behind this article is inspired by the book "Aspect-Oriented Software Development with Use Cases" by Ivar Jacobson and Pan-Wei Ng. The book also presents a systematic way to identify crosscutting use cases. It is amazing that there are many areas where AOP can be of great help to create simpler software.
- 2006-09-18: Added the sequence diagram "Adviced OnClosing"