Working with your own PageData types in CMS5 R2 SP2

by: Daniel Rodin

Updated 20090716.

Since I wrote my article about accessing properties in a strongly typed way EPiServer CMS5 R2 SP2 has been released wich offers way greater possibilities in this area. It is now possible to work with our own subtypes of PageData that can represent our EPiServer pagetypes, and eventually access their properties just like we´re doing with for example CurrentPage.PageName on PageData.

Create a PageData subtype to represent a EPiServer pagetype

Let´s say that in EPiServer I have a pagetype named "StartPage" with two properties, Heading and MainBody. I would then  create a PageData subtype that looks like this.

Example of a PageData subtype (StartPageData)

   1: public class StartPageData : PageData
   2: {
   3:     public string Heading
   4:     {
   5:         get { return this["Heading"] as string; }
   6:         set { this["Heading"] = value; }
   7:     }
   8:  
   9:     public string MainBody
  10:     {
  11:         get { return this["MainBody"] as string; }
  12:         set { this["MainBody"] = value; }
  13:     }
  14: }

Yes, there´s some “magic string” usage here wich I´m not a very big fan of but as it´s encapsulated I feel it´s no biggie. But if you wan´t to completely avoid using “magic strings” try these PageData extensionmethods for getting and setting property values instead.

   1: public static TProperty GetProperty<TPageData, TProperty>(this TPageData pageData, Expression<Func<TPageData, TProperty>> expression) where TPageData : PageData
   2: {
   3:     var methodExp = (MemberExpression)expression.Body;
   4:     return (TPageData)pageData[methodExp.Member.Name];
   5: }
   6:  
   7: public static void SetProperty<TPageData>(this TPageData pageData, Expression<Func<TPageData, object>> expression, object value) where TPageData : PageData
   8: {
   9:     var methodExp = (MemberExpression)expression.Body;
  10:     pageData[methodExp.Member.Name] = value;
  11: }

Example on using the GetProperty and SetProperty extensionmethods

   1: public class StartPageData : PageData
   2: {
   3:     public string Heading
   4:     {
   5:         get { return this.GetProperty(p => p.Heading); }
   6:         set { this.SetProperty(p => p.Heading, value); }
   7:     }
   8:  
   9:     public string MainBody
  10:     {
  11:         get { return this.GetProperty(p => p.MainBody); }
  12:         set { this.SetProperty(p => p.MainBody, value); }
  13:     }
  14: }


Regardless of the chosen approach above the “StartPageData” type should now be ready for use.

Example on using the PageData subtype

   1: var startPage = DataFactory.Instance.GetPage<StartPageData>(PageReference.StartPage);
   2: LiteralMainBody.Text = startPage.MainBody;

 

Have CurrentPage as your desired type

Now that we have a way of creating our own PageData subtypes the next natural step would be to have CurrentPage as the desired type that correctly represents the current EPiServer pagetype. In this case CurrentPage should be of type StartPageData.

So we need to create generic versions of TemplatePage, SimplePage and UserControlBase. I provide example for TemplatePage, the others should look the same.

Code for TemplatePage<TPageData>

   1: public class TemplatePage<TPageData> : TemplatePage where TPageData : PageData, new() 
   2: {
   3:     public new TPageData CurrentPage 
   4:     { 
   5:         get
   6:         {
   7:             return DataFactory.Instance.GetPage<TPageData>(base.CurrentPage.PageLink, new LanguageSelector(base.CurrentPage.LanguageBranch));
   8:         } 
   9:     }
  10: }

Example on using the generic TemplatePage class with the new CurrentPage property.

   1: public partial class StartPage : TemplatePage<StartPageData>
   2: {
   3:     protected override void OnLoad(System.EventArgs e)
   4:     {
   5:         LiteralMainBody.Text = CurrentPage.MainBody;
   6:     }        
   7: }


PageType building

This article is not about pagetype building but for those of you who had that in mind while reading might have thought of the property types in the StartPageData example. That none of them used the PropertyData types but rather the final output type, for example MainBody has string instead of PropertyXhtml. This is obviously done because of greater usability of the PageData subtypes, no casting, ToString() or the like needed for getting the values.

For PageType building however the correct PropertyData type need to be provided aswell so the builder knows if MainBody should be a PropertyString or a PropertyXhtml. One approach of solving this is to decorate the properties with attributes, as Joel Abrahamsson also shows in his Strongly typed property access and page type inheritance – again article.

Example of a PageData subtype with attributes.

   1: public class StartPageData : PageData
   2: {
   3:     [PropertyDataType((typeof(PropertyString)))]
   4:     public string Heading
   5:     {
   6:         get { return this["Heading"] as string; }
   7:         set { this["Heading"] = value; }
   8:     }
   9:  
  10:     [PropertyDataType((typeof(PropertyXhtmlString)))]
  11:     public string MainBody
  12:     {
  13:         get { return this["MainBody"] as string; }
  14:         set { this["MainBody"] = value; }
  15:     }  
  16: }

Code for PropertyDataType attribute

   1: public class PropertyDataTypeAttribute : Attribute
   2: {
   3:     public Type PropertyDataType { get; set; }
   4:  
   5:     public PropertyDataTypeAttribute(Type propertyDataType)
   6:     {
   7:         PropertyDataType = propertyDataType;
   8:     }
   9: }


Conclusion

I´m sure there are other approaches on working with these new features in SP2 but that´s what I´ve come up with, for now :) I hope you find it useful.

All feedback is much appreciated!

12 July 2009

Tags:


    Comments

    1. Great post! Also very well spotted that SP2 has the generic GetPage (I hadn't noticed before now). The synchronization does seem a bit overkill to me as opposed to: public class StartPageData : PageData{ public string MainBody{ get{ return (string) this["MainBody"]; } set{ this["MainBody"]=value;} } } Is there an important point I am missing?
    2. Thanks Allan! I added the synchronization mainly because I wanted to have the PageData subtypes as simple as possible. Without a wrapper (PageDataManager) it might be a bit overkill though. But if your already using a wrapper for other purpouses, as in Joel Abrahammson´s "wrapping and enabling mocking of DataFactory" scenario, it´s pretty smooth to just add the synchronization within the wrapper to take care of the getting and setting of property values.
    3. Allan, the article is now updated with your suggested approach. It made me realize that the synchronization part made the article unnecessary complex and took too much focus from the main topic. Thank´s for great feedback.
    4. Just a note, personally I don't like the this["MainBody"] part because of the "magic string" introduced. But that's easy to handle with reflection; get{ var propertyName = System.Reflection.MethodBase.GetCurrentMethod().Name.Replace("get_", ""); return this[propertyName] as string; } or similar.
    5. Mikael, true, but as the string usage is controlled/encapsulated and client access will be through the myPage.MainBody property I felt it was no biggie and went for a simple version. You have a good point though and it´s a great option if one want´s to completely remove "magic strings".
    6. Mikael, on the other hand. There´s a "magic string" in the MethodBase.GetCurrentMethod().Name.Replace("get_", "") too, what if "get_" is mistyped? :P
    7. Mikael, "magic string free" approach added!
    8. Interesting post as always! :) It might just be me missing something due to lack of vacation, but couldn't you use the magic string free solution (inspired by your original solution) that I use in Page Type Builder? There I have two methods in the base class for page types called GetPropertyValue and SetPropertyValue. They are in turned called like this: this.GetPropertyValue(page => page.Headline);
    9. So, uhmn, I really do need a vacation, missed large part of the post. Nevermind my previous comment please :)
    10. Oh and your new GetProperty and SetProperty methods are brilliant. *runs off to modify Page Type Builder*
    11. Joel, thank you, I´m glad you liked it :)
    12. This combined with Joels works is pretty awesome. Now if you could convert the whole epi framework to use mvc I'd really look forward to returning from my parental leave =)
    Post a comment    
    User verification Image for user verification  
    Daniel Rodin

    About me

    Works for IT service company Steria in Sundsvall, Sweden. Professional focus and interests are architecture and development of web client solutions.

    Syndications


    Archive


    Tag cloud

    EPiTrace logger