Page Type Builder version 0.6

by: Joel Abrahamsson

Another week has passed and I have found some time to make some furtherimprovements to the Page Type Builder project which I’ve previously written about here and here. The project, which is now at version 0.6, can be downloaded here.

The TypedPageData class and inheritance validation

One major change in this version is the addition of the TypedPageData class. It is a descendant of PageData and supposed to be the base class for all classes with the PageType attribute. That is now also validated when the application starts up. If a class that does not inherit from TypedPageData or a descendant of it is found with the PageType attribute at startup an ApplicationException is thrown.

As all page type classes now must inherit from TypedPageData I’ve removed the PropertyValueUtility class and moved the GetPropertyValue() and SetPropertyValue() methods to the TypedPageData class.

Get typed PageData objects

I’ve added a new method to the PageTypeBuilder class,  GetTypeForPageType(int pageTypeID) which returns a Type. That is, feed it with a page type ID and you will get the corresponding Type in return. The type will either be one of the types with the PageType attribute that we have defined or PageData if it’s a page type defined in admin mode.

That new method in turn enables the static ToTyped(PageData pageData) method found in the TypedPageData class. Using this method we can convert a PageData object which underlying type is just PageData, even though it’s of a page type that we have defined in code, to it’s matching custom type.

Ehmn…come again?

Let me illustrate it in code. The below example assumes that we have defined a page type named StartPage in code.

Let’s begin by fetching a PageData object with the StartPage page type:

PageData startPage = DataFactory.Instance.GetPage(
    PageReference.StartPage);

We could have used DataFactory.Instance.GetPage<StartPage>() here, but this is just to illustrate a point :)

Now let’s check if the type of the startPage variable is StartPage:

bool isStartPage = startPage is StartPage;

The isStartPage variable will be false as the underlying type of the startPage variable is PageData even though it’s page type matches the StartPage type.

Let’s use the GetTypeForPageType method instead:

isStartPage = 
    PageTypeBuilder.PageTypeBuilder.GetTypeForPageType(
        startPage.PageTypeID) == typeof (StartPage);

Now the isStartPage variable will be true as the GetTypeForPageType() method knows that startPage.PageTypeID matches the StartPage type.

We could also use the ToTyped() method in the TypedPageData class to get a PageData object with the correct underlying type:

TypedPageData typedPageData = TypedPageData.ToTyped(startPage);
isStartPage = typedPageData is StartPage;
Again isStartPage will be true as the underlying type of typedPageData will be the StartPage type.

OK, so…?

The point of this is that we can now do things like this (assuming we have a page type named Article defined in code):

IEnumerable children = 
    DataFactory.Instance.GetChildren(startPage.PageLink);
List<PageData> typedChildren = new List<PageData>();
foreach (PageData child in children)
{
    typedChildren.Add(TypedPageData.ToTyped(child));
}
 
IEnumerable<PageData> articles = typedChildren.Where(
    page => page is Article);

Or things like this:

IEnumerable children = 
    DataFactory.Instance.GetChildren(startPage.PageLink);
List<Article> articles = new List<Article>();
foreach (PageData child in children)
{
    PageData typed = TypedPageData.ToTyped(child);
    if (typed is Article)
    {
        articles.Add((Article) typed);
    }
}
 
ShowArticleTeasers(articles);

As the article objects above are of the Article type the ShowArticleTeasers() method could in turn be implemented like this:

private void ShowArticleTeasers(List<Article> articles)
{
    foreach (Article article in articles)
    {
        //As we know that the article variable is an Article
        //we can access it's strongly type Introduction property
        Response.Write(article.Introduction);
    }
}

Settings for Long String and XHTML String properties

Another addition in this version is that it’s now possible to specify edit mode settings (whether the editor can set text as bold, edit the HTML source etc) for Long String and XHTML String properties. This is done using two new properties of the PageTypeProperty attribute, LongStringSettings and ClearAllLongStringSettings. If we don’t specify either of these all  options will be available in edit mode.

image

If we set the ClearAllLongStringSettings to true, like this:

[PageTypeProperty(ClearAllLongStringSettings = true)]
public string MainBody
{
    get
    {
        return GetPropertyValue<StartPage, string>(
            page => page.MainBody);
    } 
    set
    {
        SetPropertyValue<StartPage, string>(
            page => page.MainBody, value);
    }
}

No options will be available in edit mode.

image

Using the LongStringSettings property we can specify that only certain options should be available, Bold and Italics for instance:

[PageTypeProperty(LongStringSettings = 
    EditorToolOption.Bold | EditorToolOption.Italic)]
public string MainBody
{
    get
    {
        return GetPropertyValue<StartPage, string>(
            page => page.MainBody);
    } 
    set
    {
        SetPropertyValue<StartPage, string>(
            page => page.MainBody, value);
    }
}

image

Undefined properties are no longer removed

In the previous version I added functionality that automatically removed properties from page types when they no longer where defined in code. After some discussion regarding this with my Nansen colleague Christer Ottosson I decided to remove this functionality. The main reason, which Christer pointed out, is that it could do serious harm if someone where to get an older version of the code for a site from the source code repository, compile it and then browse the site to check something in the old version. While it’s very unlikely that this would happen on a production server and cause serious damage it’s quite possible that it would happen in a development environment and still be pretty serious. Just the fear of this happening is probably worse than having to manually remove properties in admin mode.

Not automatically removing properties also means that it’s easier to rename them. Renaming a property involves renaming it in admin mode, renaming it in code, compiling and finally releasing the new assembly. If properties are automatically removed and we rename a property in admin mode it would get permanently removed if the application restarts before we release the new assembly where the property has been renamed in code as well.

Alternative implementations of the GetPropertyValue and SetPropertyValue methods

Another thing me and Christer has been discussing is alternative implementations of the GetPropertyValue and SetPropertyValue methods. While using an expression as argument to get property access strongly typed is brilliant it’s not that intuitive and it would be interesting to find an alternative solution. So far we’ve found two, both with drawbacks though.

The first would be to use the System.Diagnostics.StackTrace class to find the name of the calling method. It could look something like this:

public T GetPropertyValue<T>()
{
    // Get call stack
    StackTrace stackTrace = new StackTrace();
 
    // Get calling method name
    string methodName = stackTrace.GetFrame(1).GetMethod().Name;
 
    //Get the property name
    string propertyName = methodName.Replace("get_", string.Empty);
 
    return (T) GetValue(propertyName);
}

Implementing a property using this method would be as simple as this:

[PageTypeProperty]
public string MainBody
{
    get
    {
        return GetPropertyValue<string>();
    }
}

Using this approach we loose quite a lot of compile time checks however. We also loose some performance. According to some quick tests I’ve done the lambda expression approach currently used is about six times faster.

Another alternative implementation would be to use a post-compiler such as PostSharp. Using that we could add attributes to our properties to show the post-compiler that it should implement the property access methods for us. Implementing a property this way could look something like this:

[AutoImplement]
[PageTypeProperty]
public string MainBody
{ get; set;}

This would give better performance than the stack trace approach but require a bit extra configuration (especially if we have the page type classes in the web project as PostSharp requires that MSBuild is used during the build process), mean slower builds and introduce a dependency on PostSharp (or whatever post-compiler we use).

Conclusion

As usual all feedback (both here as comments and in other forums) is appreciated! It would be especially interesting to hear opinions regarding the alternative implementations of the Get- and SetPropertyValue methods.

Source code

The source code for PageTypeBuilder version 0.6 as well as a compiled assembly can be downloaded here.

14 June 2009


Comments

  1. Awesome work Joel. Keep them coming, looking forward to 0.7
Post a comment    
User verification Image for user verification  
EPiTrace logger