While displaying read-only fake data is useful, what if you
want to simulate fake persistence as well? In a demo I am working on that
implements the Live Form Ajax pattern, I needed a way to at least create
illusion of persistence.
The repository class already includes methods for GetByID,
Insert, Update and Delete. The first iteration just had stubs that called
System.Diagnositcs.Debug.WriteLine so that you had a place to add a breakpoint
during testing. All that I needed to do was add some code that would “do
something” to persist the objects without a database.
The solution that seems to work well is to add a Hashtable
into a Cache entry. Objects IDs are used as the keys in the Hashtable and the
objects themselves are loaded into the value. To handle the grunt work of this
interaction I create a new class: CacheMockPersistence.
The CacheMockPersistence class is a simple class and also
features methods named Insert, Update, Delete and this time GetByKey. The cache
key is a GUID so if you use it in an existing application it shouldn’t stomp on
anything you are doing.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
[Serializable]
public class Book
{
public int ID { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Url { get; set; }
public double Price { get; set; }
public DateTime PublishDate { get; set; }
public string PriceFormatted
{
get { return string.Format("{0:C}", Price); }
}
public string PublishDateShort
{
get { return PublishDate.ToShortDateString(); }
}
public Book() { }
public static Book Create(int id)
{
Book bk = new Book();
Random random = new Random();
bk.ID = id;
bk.Title = string.Format("Title of Book {0}", id);
bk.Author = string.Format("Author {0}", id);
bk.Url = string.Format("http://site.com/book/{0}/", id);
bk.Price = Convert.ToDouble(string.Format("{0:##.##}", random.Next(5, 35) + random.NextDouble()));
bk.PublishDate = Convert.ToDateTime(string.Format("{0}/{1}/{2}", random.Next(1, 12), random.Next(1, 28), random.Next(1990, DateTime.Now.Year)));
return bk;
}
}
/// <summary>
/// Summary description for BookRepository
/// </summary>
public class BookRepository
{
private static BookRepository _instance;
private CacheMockPersistence _persistence = null;
private bool _hasPersistence = false;
public static BookRepository Instance
{
get { return _instance; }
}
public BookRepository()
{
if (HttpContext.Current != null)
{
this._persistence = new CacheMockPersistence(HttpContext.Current);
this._hasPersistence = true;
}
}
static BookRepository()
{
_instance = new BookRepository();
}
/// <summary>
/// Gets a Book object by the supplied ID. If the object does not exist it will create a new instance.
/// </summary>
public Book GetByID(int id)
{
object value;
Book item = null;
if (this._hasPersistence)
{
value = this._persistence.GetByKey(id);
if (value is Book)
{
item = (Book)value;
}
else
{
item = Book.Create(id);
this._persistence.Insert(id, item);
}
}
else
{
item = Book.Create(id);
}
return item;
}
/// <summary>
/// Returns a List of 10 Book objects
/// </summary>
[DataObjectMethod(DataObjectMethodType.Select, true)]
public List<Book> GetBooks()
{
return this.GetBooks(10);
}
/// <summary>
/// Mocks insertion into a persistence layer
/// </summary>
[DataObjectMethod(DataObjectMethodType.Insert, true)]
public void Insert(string title, string author, string url, double price, DateTime publishDate)
{
Debug.WriteLine("Insert object into database");
if (this._hasPersistence)
{
Random r = new Random();
int id = r.Next(10000,90000);
Book value = Book.Create(id);
this._persistence.Insert(id, value);
}
}
/// <summary>
/// Mocks update to a persistence layer
/// </summary>
public void Update(Book book)
{
Debug.WriteLine("Update database");
if (this._hasPersistence)
{
this._persistence.Update(book.ID, book);
}
}
/// <summary>
/// Mocks update to a persistence layer
/// </summary>
[DataObjectMethod(DataObjectMethodType.Update, true)]
public void Update(int ID, string title, string author, string url, double price, DateTime publishDate)
{
Book b = Book.Create(ID);
b.Title = title;
b.Author = author;
b.Url = url;
b.Price = price;
b.PublishDate = publishDate;
this.Update(b);
}
/// <summary>
/// Mocks deleting from a persistence layer
/// </summary>
[DataObjectMethod(DataObjectMethodType.Delete, true)]
public void Delete(int ID)
{
Debug.WriteLine("Delete from database");
if (this._hasPersistence)
{
this._persistence.Delete(ID);
}
}
/// <summary>
/// Returns a list of Book objects where you decide the quantity
/// </summary>
[DataObjectMethod(DataObjectMethodType.Select, false)]
public List<Book> GetBooks(int numberOfBooks)
{
List<Book> books = new List<Book>();
for (int i = 1; i < (numberOfBooks + 1); i++)
{
books.Add(Book.Create(i));
}
return books;
}
/// <summary>
/// Returns 10 book records in a DataSet
/// </summary>
[DataObjectMethod(DataObjectMethodType.Select, true)]
public DataSet GetBooksAsDataSet()
{
return this.GetBooksAsDataSet(10);
}
/// <summary>
/// Returns a series of books in a DataSet
/// </summary>
[DataObjectMethod(DataObjectMethodType.Select, false)]
public DataSet GetBooksAsDataSet(int numberOfBooks)
{
DataSet ds = new DataSet();
DataTable dt;
dt = new DataTable();
dt.Columns.Add(new DataColumn("ID"));
dt.Columns.Add(new DataColumn("Title"));
dt.Columns.Add(new DataColumn("Author"));
dt.Columns.Add(new DataColumn("Price"));
dt.Columns.Add(new DataColumn("Url"));
dt.Columns.Add(new DataColumn("PublishDate"));
for (int i = 1; i < (numberOfBooks + 1); i++)
{
this.AddNewRow(ref dt, i);
}
ds.Tables.Add(dt);
return ds;
}
private void AddNewRow(ref DataTable dt, int index)
{
DataRow dr = dt.NewRow();
Book bk = Book.Create(index);
dr["ID"] = index;
dr["Title"] = bk.Title;
dr["Author"] = bk.Author;
dr["Price"] = bk.Price;
dr["Url"] = bk.Url;
dr["PublishDate"] = bk.PublishDate;
dt.Rows.Add(dr);
}
}
public class CacheMockPersistence
{
private Hashtable _data = null;
private HttpContext _context = null;
private const string DATA_STORE_KEY = "{2E54BB16-3220-4B49-9932-1455C4014E5B}";
private Hashtable Data
{
get
{
if (this._context.Cache[DATA_STORE_KEY] == null)
{
this._context.Cache.Insert(DATA_STORE_KEY, new Hashtable());
}
return ((Hashtable)this._context.Cache[DATA_STORE_KEY]);
}
}
public CacheMockPersistence(HttpContext context)
{
this._context = context;
}
public void Insert(object key, object value)
{
this.Data.Add(key, value);
}
public void Update(object key, object value)
{
this.Data[key] = value;
}
public void Delete(object key)
{
this.Data.Remove(key);
}
public object GetByKey(object key)
{
object value = new object();
if (this.Data[key] != null)
{
value = this.Data[key];
}
return value;
}
}
I like to keep all this code in a file named
BookRepository.cs to make it easy to add to an application.