.NET is a proprietary framework from Microsoft – it consists of both a large class library and a runtime environment called the Common Language Runtime.
SWiK: http://www.macquarieit.com
Web
e-commerce
solution,
developer
multimedia
asp
asp.net
.NET
PHP
Developer,
So as I wrote a few days ago I have been looking at BlogEngine.NET and considered how I would have designed its lower layers. So lets kick off with the provider that's currently used in BlogEngine.NET for accessing the various storages (XML and RDBMS). The provider is responsible for accessing all data, that is Pages, Categories, Posts etc. Furthermore for each of these types of data it is responsible for the structure of the storage location, transforming the data from storage to objects including loading of related data. In my book this isn't good for extensibility, and as BlogEngine.NET provides multiple point for extending its functionality I would argue that it should be possible to extend its storage mechanism to handle new types of data just as easily. Currently extending the data access would involve adding new members to the provider and its implementations.
So to solve this problem (and a few others I will address shortly) my suggestion would be to make data access classes responsible for a single type only, these can share some common functionality that deals with the structure of the storage when we talk about the XML provider. So basically we want to specify how these classes should look, the most generic approach is represented by this interface.
public interface IRepository<T>
{
//Retrieval
T Load(Guid identifier);
IList<T> LoadAll();
//Modification
void Save(T instance);
void Delete(T instance);
void Delete(Guid identifier);
}
I have made the assumption here that all data has a Guid identifier, of course this could be handled so that we are free to choose the type of our identifier. But for know im satisfied with the Guid-identifier
Instead of implementing our repositories right away we can abstract common details for storage types away in abstract classes. This way the implementation of our actual repositories are as slim as possible. I have (based on the way XML is stored in BlogEngine.NET) chosen two different storage types for XML and implemented the abstract classes SingleFileRepository<T> and SingleFolderRepository<T>. The first one handles storage of data types where all instances are in a single XML file, the second one handles storage of data types where each instance has its own XML file all contained in a folder. So the basic responsibility of these two classes is to handle details of the storage, that is the structure on disk and the structure in the files. Let me remind you that these abstract classes are only for simplifying the implementation of the actual repositories, if development later requires changing minor details we can override or even change the implementations on these, and if we want to start from scratch without the assumptions build into these abstract classes we can implement IRepository<T> any way we would like. This gives great flexibility in how different data is stored and the opportunity for code reusability is very much still there.
Lets take a look at the code for SingleFolderRepository:
public abstract class SingleFolderRepository<T> : IRepository<T> where T : class, IEntity
{
protected readonly IBlogSettings settings;
protected readonly IObjectXmlConverter<T> converter;
protected readonly string storagelocation;
public SingleFolderRepository(IBlogSettings settings, IObjectXmlConverter<T> converter)
{
this.settings = settings;
this.converter = converter;
this.storagelocation = settings.StorageLocationFor(typeof (T));
}
public T Load(Guid identifier)
{
string path = Path.Combine(storagelocation, identifier + ".xml");
FileInfo datafile = new FileInfo(path);
if (datafile.Exists)
{
return converter.Build(GetXml(datafile));
}
return null;
}
private string GetXml(FileInfo datafile)
{
string xml = string.Empty;
using (StreamReader sr = new StreamReader(datafile.Open(FileMode.Open, FileAccess.Read, FileShare.Read)))
{
xml = sr.ReadToEnd();
}
return xml;
}
public IList<T> LoadAll()
{
List<T> instances = new List<T>();
DirectoryInfo datadirectory = new DirectoryInfo(storagelocation);
if (datadirectory.Exists)
{
foreach (FileInfo datafile in datadirectory.GetFiles("*.xml"))
{
instances.Add(converter.Build(GetXml(datafile)));
}
}
return instances;
}
public void Save(T instance)
{
if (converter.IsNew(instance))
{
instance.Identifier = Guid.NewGuid();
}
string path = Path.Combine(storagelocation, instance.Identifier + ".xml");
using (FileStream fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (StreamWriter writer = new StreamWriter(fs))
{
writer.Write(converter.Flatten(instance));
writer.Flush();
}
}
}
public void Delete(T instance)
{
Delete(instance.Identifier);
}
public void Delete(Guid identifier)
{
string path = Path.Combine(storagelocation, identifier + ".xml");
File.Delete(path);
}
}
So notice the constructor. The dependencies that this class has is passed into the contructor so before we can use the repository we need settings for the blog and an IObjectXmlConverter for the type that the repository handles. The IObjectXmlConverter handles flattening of objects to XML and building objects from XML. Below is an example an implementation of this.
public class CategoryConverter : IObjectXmlConverter<Category>
{
public Category Build(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
Category c = new Category();
c.Identifier = new Guid(doc.SelectSingleNode("/category/").Attributes["id"].InnerText);
c.Name = doc.SelectSingleNode("/category/").InnerText;
return c;
}
public string Flatten(Category instance)
{
return
XmlOutput.Create().XmlDeclaration()
.Node("category")
.Attribute("id", instance.Identifier.ToString())
.InnerText(instance.Name).GetOuterXml();
}
}
So this is where the mapping between the datastore and the objects happen, of course error handling should be in place here if we expect missing values in the data we load.
With this change in structure presented above we have seperated concerns of the data access into smaller reusable components, furthermore in my book this makes the code easier to extend and understand but thats a subjective matter.
So now its time to specify the actual repositories, we could settle for the functionality present on our generic IRepository but often we want more specific functionality for each datatype for instance fetching all posts in a specific category, paging of posts and so forth. So for each data type we define an interface with its actions. Currently in BlogEngine.NET much of this functionality is done in various places for instance sorting, filtering and paging. I would like to have our repositories responsible for this because the most efficient way to do these things depends on the type of storage. That is paging would for instance be done different for XML storage and RDBMS storage.
So as an example we could make our repository for posts have an interface like this:
public interface IPostRepository : IRepository<Post>
{
PageableList<Post> GetPage(int pagesize, int pagenum);
PageableList<Post> GetPageInCategory(int category, int pagesize, int pagenum);
PageableList<Post> GetPageWithTag(int tag, int pagesize, int pagenum);
PageableList<Post> GetPageFromMonth(int month, int year, int pagesize, int pagenum);
}
Notice that we might need more methods, but this illustrates some examples on how we let the repository handle paging so it can optimize it based on the data storage.
The actual implementation in the XML storage can be optimized but for now we load all data and do it in memory like BlogEngine.NET currently does. (I will discuss caching in a later post)
The form and responsibility of our repositories is now pretty clear. You might have noticed that I have passed classes dependencies through their constructor. To avoid having to construct these dependencies myself I will use an Inversion of Control container that manages these dependencies for me. Furthermore configuration of the IoC container is what helps use interchange the repositiores for XML and RDBMS.
Inspired by some talk I "overheard" on twitter I thought it would be interesting to post a small challenge. Is it possible to implement the missing parts of the following code so it compiles and runs without exceptions, if yes how would you do it? You are not allowed to modify the body of the Main method but the rest is up to you.
static void Main(string[] args)
{
Foo f = null;
f.Bar();
}
And yes its a pretty useless exercise :-)
So as I wrote a few days ago I have been looking at BlogEngine.NET and considered how I would have designed its lower layers. So lets kick off with the provider that's currently used in BlogEngine.NET for accessing the various storages (XML and RDBMS). The provider is responsible for accessing all data, that is Pages, Categories, Posts etc. Furthermore for each of these types of data it is responsible for the structure of the storage location, transforming the data from storage to objects including loading of related data. In my book this isn't good for extensibility, and as BlogEngine.NET provides multiple point for extending its functionality I would argue that it should be possible to extend its storage mechanism to handle new types of data just as easily. Currently extending the data access would involve adding new members to the provider and its implementations.
So to solve this problem (and a few others I will address shortly) my suggestion would be to make data access classes responsible for a single type only, these can share some common functionality that deals with the structure of the storage when we talk about the XML provider. So basically we want to specify how these classes should look, the most generic approach is represented by this interface.
public interface IRepository<T>
{
//Retrieval
T Load(Guid identifier);
IList<T> LoadAll();
//Modification
void Save(T instance);
void Delete(T instance);
void Delete(Guid identifier);
}
I have made the assumption here that all data has a Guid identifier, of course this could be handled so that we are free to choose the type of our identifier. But for know im satisfied with the Guid-identifier
Instead of implementing our repositories right away we can abstract common details for storage types away in abstract classes. This way the implementation of our actual repositories are as slim as possible. I have (based on the way XML is stored in BlogEngine.NET) chosen two different storage types for XML and implemented the abstract classes SingleFileRepository<T> and SingleFolderRepository<T>. The first one handles storage of data types where all instances are in a single XML file, the second one handles storage of data types where each instance has its own XML file all contained in a folder. So the basic responsibility of these two classes is to handle details of the storage, that is the structure on disk and the structure in the files. Let me remind you that these abstract classes are only for simplifying the implementation of the actual repositories, if development later requires changing minor details we can override or even change the implementations on these, and if we want to start from scratch without the assumptions build into these abstract classes we can implement IRepository<T> any way we would like. This gives great flexibility in how different data is stored and the opportunity for code reusability is very much still there.
Lets take a look at the code for SingleFolderRepository:
public abstract class SingleFolderRepository<T> : IRepository<T> where T : class, IEntity
{
protected readonly IBlogSettings settings;
protected readonly IObjectXmlConverter<T> converter;
protected readonly string storagelocation;
public SingleFolderRepository(IBlogSettings settings, IObjectXmlConverter<T> converter)
{
this.settings = settings;
this.converter = converter;
this.storagelocation = settings.StorageLocationFor(typeof (T));
}
public T Load(Guid identifier)
{
string path = Path.Combine(storagelocation, identifier + ".xml");
FileInfo datafile = new FileInfo(path);
if (datafile.Exists)
{
return converter.Build(GetXml(datafile));
}
return null;
}
private string GetXml(FileInfo datafile)
{
string xml = string.Empty;
using (StreamReader sr = new StreamReader(datafile.Open(FileMode.Open, FileAccess.Read, FileShare.Read)))
{
xml = sr.ReadToEnd();
}
return xml;
}
public IList<T> LoadAll()
{
List<T> instances = new List<T>();
DirectoryInfo datadirectory = new DirectoryInfo(storagelocation);
if (datadirectory.Exists)
{
foreach (FileInfo datafile in datadirectory.GetFiles("*.xml"))
{
instances.Add(converter.Build(GetXml(datafile)));
}
}
return instances;
}
public void Save(T instance)
{
if (converter.IsNew(instance))
{
instance.Identifier = Guid.NewGuid();
}
string path = Path.Combine(storagelocation, instance.Identifier + ".xml");
using (FileStream fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (StreamWriter writer = new StreamWriter(fs))
{
writer.Write(converter.Flatten(instance));
writer.Flush();
}
}
}
public void Delete(T instance)
{
Delete(instance.Identifier);
}
public void Delete(Guid identifier)
{
string path = Path.Combine(storagelocation, identifier + ".xml");
File.Delete(path);
}
}
So notice the constructor. The dependencies that this class has is passed into the contructor so before we can use the repository we need settings for the blog and an IObjectXmlConverter for the type that the repository handles. The IObjectXmlConverter handles flattening of objects to XML and building objects from XML. Below is an example an implementation of this.
public class CategoryConverter : IObjectXmlConverter<Category>
{
public Category Build(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
Category c = new Category();
c.Identifier = new Guid(doc.SelectSingleNode("/category/").Attributes["id"].InnerText);
c.Name = doc.SelectSingleNode("/category/").InnerText;
return c;
}
public string Flatten(Category instance)
{
return
XmlOutput.Create().XmlDeclaration()
.Node("category")
.Attribute("id", instance.Identifier.ToString())
.InnerText(instance.Name).GetOuterXml();
}
}
So this is where the mapping between the datastore and the objects happen, of course error handling should be in place here if we expect missing values in the data we load.
With this change in structure presented above we have seperated concerns of the data access into smaller reusable components, furthermore in my book this makes the code easier to extend and understand but thats a subjective matter.
So now its time to specify the actual repositories, we could settle for the functionality present on our generic IRepository but often we want more specific functionality for each datatype for instance fetching all posts in a specific category, paging of posts and so forth. So for each data type we define an interface with its actions. Currently in BlogEngine.NET much of this functionality is done in various places for instance sorting, filtering and paging. I would like to have our repositories responsible for this because the most efficient way to do these things depends on the type of storage. That is paging would for instance be done different for XML storage and RDBMS storage.
So as an example we could make our repository for posts have an interface like this:
public interface IPostRepository : IRepository<Post>
{
PageableList<Post> GetPage(int pagesize, int pagenum);
PageableList<Post> GetPageInCategory(int category, int pagesize, int pagenum);
PageableList<Post> GetPageWithTag(int tag, int pagesize, int pagenum);
PageableList<Post> GetPageFromMonth(int month, int year, int pagesize, int pagenum);
}
Notice that we might need more methods, but this illustrates some examples on how we let the repository handle paging so it can optimize it based on the data storage.
The actual implementation in the XML storage can be optimized but for now we load all data and do it in memory like BlogEngine.NET currently does. (I will discuss caching in a later post)
The form and responsibility of our repositories is now pretty clear. You might have noticed that I have passed classes dependencies through their constructor. To avoid having to construct these dependencies myself I will use an Inversion of Control container that manages these dependencies for me. Furthermore configuration of the IoC container is what helps use interchange the repositiores for XML and RDBMS.