Introduction
The Memento design pattern is a useful pattern in C# ASP .NET for saving the state of an object for retrieval at a later time. A common example of the Memento pattern would be the Undo command of various programs. With the Undo command, the current state information for the task at hand is saved (such as in a Memento). The user may continue modifying data. Upon invoking the Undo command, the state of the data is restored back to its previous state (as stored within the Memento). This article describes how to implement the Memento design pattern in a C# ASP .NET web application. Specifically, this version of the Memento pattern will allow saving multiple types of class states within a single memento object.
Why Not Just Code it the Basic Way?
If you’re familiar with implementing state-saving functionality, such as the Undo command, in C# ASP .NET web applications, you may be have previously used code similar to the sample shown below.
A basic way of saving state information
1 | int main() |
The above example is a straight-forward and basic method for saving state information through the use of a temporary class. While this method will certainly achieve saving and restoration of the object’s state, it lacks robustness and reusability. Specifically, if you wanted to save the state of various types of classes, you would be required to create a temporary class for each type of class you save. Another problem with the above example is that it requires directly reading and setting the internal data members of the classes. An additional problem is that you are limited to internal memory as a storage medium. What would happen if you needed to store state information on disk?
An improvement on the above example would be to utilize the C# .NET object class in place of the temporary MyClass object and upon restoration, casting the object back into MyClass. You would need to take into consideration the affect of passing by value vs. passing by reference in C# .NET.
A slightly better way of saving state information
1 | int main() |
Along Comes the Memento Pattern
Saving the state information for a single class or struct may not seem to be that complicated, as shown in the above examples. However, when you need to save the states of multiple classes or different classes, the above examples would become cumbersome. The Memento design pattern provides a much more robust method for saving and restoring the state information across different classes through the use of a well-defined and familiar interface.
A better way of saving state information
1 | static void Main() |
In the above example, we initially create a Memento object to hold our class’s state information. We then set the values for our class and save a memento of the current state. We then continue modifying the class’s data. Finally, we restore the memento for the class, thus restoring the class’s data.
The memento in this example happens to store data for a variety of classes so long as the class inherits from a common MementoBase class. By hiding away the details of the Memento’s state saving functionality, we can easily create state-aware classes which can carry any type of data.
Defining the MementoBase Class
We begin by defining a common base class for all classes which wish to have state-saving functionality. The classes will inherit from this base class and implement the required functions.
1 | abstract class MementoBase |
The two required functions for our Memento are the SaveMemento and RestoreMemento functions. Each function accepts a memento object. The memento object itself can store a variety of class data types. Note that the MementoBase class and its two functions are defined as abstract, indicating we must inherit from this class and implement the two functions. As you’ll see below, implementing the functions is quite easy.
Another important item to note is the inclusion of a mementoKey object. This key will be used by the actual class when it saves or restores its memento. The key identifies which data in the memento object is owned by the class. As you may have guessed, the memento itself contains a Dictionary collection of state data.
The Mighty Memento Class
Before we utilize the MementoBase class for implementing our concrete state-aware classes, we will first define the Memento class, which is the workhorse for saving and retrieving data.
1 | class Memento |
This version of the Memento class contains a Dictionary collection for storing state information in memory. The dictionary stores C# .NET objects and is accessed through the use of a key. The key identifies which class the data belongs to. Imagine the power of modifying the Memento class to store data to disk or a database simply by changing the storage method.
The Memento class contains two core functions for working with data. GetState and SetState simply work with data accessible by the key.
Our First Concrete State-Aware Class
We can now define a concrete implementation of a state-aware class, which utilizes the MementoBase class. For this example, our class will contain a combination of data.
1 | enum StateType |
In the above example, our concrete originator contains an InternalData variable, which consists of a string and a StateType enumeration. We wrap the two variables inside InternalData for easy usage of the Memento (the Memento class expects to store and retrieve a single object).
The important part of our concrete implementation is the SaveMemento and RestoreMemento functionality, inherited from MementoBase. Inside SaveMemento, we are simple telling the Memento to store our InternalData object. Within RestoreMemento, we are simply telling the Memento to retrieve the data associated with our key. Since the data is retrieved as a C# .NET object, we cast the result to our desired format, which in this case is InternalData.
The Real Power of the Memento Pattern
Up until now, it may seem like we’ve created a bit of code just to achieve the same effect of the very first basic example as shown above. However, the real power behind the Memento pattern is in working with a variety of classes and data types to instantly grant them a state-saving ability. You can see this in more detail by defining a second concrete implementation class which needs to contain state-saving functionality.
1 | class ConcreteOriginator2 : MementoBase |
This second class simply contains an integer as its data. It contains the same functions as the first implementation (only so that its familiar with the first), but its private variables are different. Instead of having to create a second temporary variable to store its state information, we simply inherit from MementoBase and share the state-saving functionality as our first concrete implementation.
Notice this time, our RestoreMemento function casts the result as an integer. Saving and retrieving the state is executed the same way as our first concrete implementation. As you can see, you could create any number of classes which inherit from the MementoBase and each would include the ability to save and restore their state information.
Putting it All Together
As a final piece to the puzzle, we can create a more powerful main program example by including both concrete implementations with a single Memento.
1 | static void Main(string[] args) |
The output for the above program would produce the following:
1 | Hello World! I'm in state ONE |
Notice the last 3 lines indicate that all 3 classes have been successfully restored to their original states, even though their internal variables may differ. You can see how powerful the Memento class can be when creating state-aware objects.
Conclusion
The Memento design pattern is be a powerful method for saving and retrieving state information in a class. By providing a common interface and abstracting the details of state management, the Memento design pattern makes it easy to store and retrieve states of an object. The actual method of storage can include memory, disk, a database, or even over the Internet, simply by modifying the internals of the Memento class itself. By using the Memento design pattern in C# ASP .NET web applications, you can obtain enhanced functionality that is both robust and reusable.
About the Author
This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.
Sponsor Me