Since I've started the Enhanced BlogEngine.NET project I've promised to write something about its technical part to show everybody, how the customized parts of the code work. Currently, Enhanced BlogEngine.NET represents the ability to write multilingual posts, pages, etc.
The most important part of the code stands on the extended data storage and ID. ID is not just a Guid type anymore, but a complex type of key and culture. But, thanks to generics used in the engine, it's easy to implement.
SQL Data storage
Let's first take a look at the SQL provider which is a layer between the application and SQL database engines. A completely new table called be_Cultures has been added and pre-filled with all available cultures. In this case, it's better to work with a known set of cultures. In the application, we work with the collection itself and nothing else. There's no need to do any compares with *.resx files. When saving we don't have to check whether the culture exists and we can even purge all records from the table and save them again. The table will always have only some records to notice any performance loss.
Table structure is following:
- CultureID - This number is used in relationship with other tables in the database only.
- Name - This string value is widely used in the whole application.
- Active - If true, the language is active in the administration area and is visible in front-end.
CultureID column has been added to be_Categories, be_Pages, be_PingService, be_PostCategory, be_PostComment, be_PostNotify, be_Posts, be_PostTag tables. There are also some changes in Primary Keys, because we need a unique record per ID and culture. Dual-column PKs are assigned to be_Categories, be_Pages, be_Posts tables.
I know there's possible to use additional table without need of multi-column PKs, something like be_CategoriesLanguage. Such additional table would contain localized data and the original table culture invariant data only.
I've chosen a way of less changes and impact. We preserve all original tables and do minor changes to them. As a result, we gain from backward compatibility.
XML Data storage
Changes in XML files are much less. The table be_Cultures is represented by cultures.xml in App_Data folder. Structure is as following:
<?xml version="1.0" encoding="utf-8"?>
<cultures>
<culture name="en" id="1" active="True" />
<culture name="af" id="2" active="False" />
...
</cultures>
Another change is in pingservices.xml and categories.xml files where the cultureid attribute has been added to all elements.
Slightly different change is made to posts and pages. I've decided to not add the culture information to xml structure of each file but right into the file's name. Each file was named in the format of <Guid>.xml, but I've changed it to <Guid>_<Culture name>.xml. This is much easier approach. The structure is the same as before, so it's backward compatible, and if we need to delete a specific culture, we just delete all files that have this culture name in its filename. Like this way:
foreach (string file in Directory.GetFiles(someStorageLocation, "*_" + cultureName + ".xml"))
File.Delete(file);
That's all.
Guid becomes KeyCulturePair<Guid>
All classes that work with some ID (Post, Page, Category…) inherit such ID from BlogEngine.Core.BusinessBase<TYPE, KEY> generic class. Type parameter KEY is a type of such ID. Originally, this ID was of Guid type, but I've taken advantage of this and changed it to KeyCulturePair<Guid> type. E.g. the BlogEngine.Core.Post class inherits from BusinessBase<Post, KeyCulturePair<Guid>>.
Generic class KeyCulturePair<T> is a newly created class and consists of two properties.
- Key property - It's the ID of post or page, etc.. and the type of this Key is specified by the type parameter T. Usually, it's Guid.
- Culture property - This property is of BlogEngine.Core.CultureCollectionItem type.
BlogEngine.Core.CultureCollectionItem is another newly created class and contains some information about the culture (specifically its ID, name and active flag).
KeyCulturePair class overrides ToString() method to preserve backward compatibility, because the original code used ID to get Guid right away. But now, there's no Guid, there is KeyCulturePair. That's why, ToString() is overridden to return Guid string even when you don't call property Key from KeyCulturePair.
public override string ToString()
{
return Key.ToString();
}
KeyCulturePair class overrides even equality and inequality operators, because some parts of code compare one ID with another ID and we need to compare Key and Culture together.
public static bool operator ==(KeyCulturePair<T> item1, KeyCulturePair<T> item2)
{
if ((object)item1 != null && (object)item2 != null && item1.Key.Equals(item2.Key) && item1.Culture.CultureID == item2.Culture.CultureID)
return true;
else if ((object)item1 == null && (object)item2 == null)
return true;
else
return false;
}
public static bool operator !=(KeyCulturePair<T> item1, KeyCulturePair<T> item2)
{
if ((object)item1 != null && (object)item2 != null && item1.Key.Equals(item2.Key) && item1.Culture.CultureID == item2.Culture.CultureID)
return false;
else if ((object)item1 == null && (object)item2 == null)
return false;
else
return true;
}
public override bool Equals(object obj)
{
if (obj != null && obj is KeyCulturePair<T>)
{
KeyCulturePair<T> objPair = (KeyCulturePair<T>)obj;
if (objPair.Key.Equals(Key) && objPair.Culture.CultureID == Culture.CultureID)
return true;
}
return false;
}
This is the end of part 1 but tomorrow I will post part 2.