MementoContainer is an alternative approach to the Memento design pattern.
It is a lightweight utility that takes a snapshot of your objects' state so that you can easily rollback to a previous state when recovering from errors.
This approach, like the original one, promotes highly decoupled systems and preserves encapsulation boundaries while leveraging C#'s awesomeness (e.g., reflection, attributes, lambda expression analysis, dynamic typing) to avoid polluting your code base with Memento types.
Besides, taking a snapshot of an object's state isn't always a trivial task. If you have a big dependency graph, then you will probably have to perform tedious deep cloning of your objects. MementoContainer does all that for you - and more.
var memento = Memento.Create()
.Register(article);
try
{
ArticleFormatter.Format(article);
}
catch(FormattingException)
{
memento.Rollback(); //Et voilà!
}
The first step is to annotate your model's properties with the MementoProperty
and/or MementoCollection
attributes, so that the memento will know which properties it will need to watch.
public class Magazine
{
[MementoProperty]
public string Title { get; set; }
[MementoCollection]
public IList<Article> Articles { get; set; }
}
In order to preserve encapsulation, these properties can have any access modifiers (private, public, protected) and even be static.
And if the class Article
also happens to have annotated properties, those will be registered as well!
Alternatively, you may also use the MementoClass
attribute on your classes.
This way, all your object's properties/collections will be recorded.
[MementoClass]
public class Article
{
public string Title { get; set; }
public string Author { get; set; }
}
The Memento object exposes a fluent interface so that you can easily register all your objects with the container.
var magazine1 = new Magazine
{
Title = "Draft",
Articles = new List<Article>
{
new Article("Draft", "DCastro")
}
};
var memento = Memento.Create()
.Register(magazine1)
.Register(magazine2)
.Register(publisher);
Now you can freely act upon your objects and if anything goes wrong, just rollback!
try
{
magazine1.Name = "State of emergency declared";
magazine1.Articles.Clear();
db.Save(magazine1);
}
catch(DBException)
{
//The magazine will be renamed back to "Draft"
//and the article authored by "DCastro" will be re-inserted.
memento.Rollback();
}
You can also register single properties/collections, without the need to add attributes to your classes.
var memento = Memento.Create()
.RegisterProperty(publisher, p => p.Name) //simple property
.RegisterProperty(publisher, p => p.ProfilePhoto.Description) //chain of properties
.RegisterProperty(() => ArticleFactory.LastReleaseDate) //static property
.RegisterCollection(articlesList);
To install MementoContainer, run the following command in the Package Manager Console
PM> Install-Package MementoContainer
The IMemento interface is available so that you can easily mock it with tools like Moq in your unit tests and inject it as a depency using your favourite IoC container (e.g., Castle Windsor or Ninject).
For more information and advanced usage, head on the wiki.