Blog posts by Maciej Grzyb2013-03-11T15:51:00.0000000Z/blogs/Maciej-Grzyb/Optimizely WorldImageProperty for EPiServer7http://mgrzyb.blogspot.com/2013/03/imageproperty-for-episerver7_11.html2013-03-11T15:51:00.0000000Z<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style><br /><br /><p>I’m happy to inform about a new addition to OpenWaves project. Grzegorz Wiechec – a colleague of mine at MakingWaves – contributed his first EPiServer7 property – ImageProperty. <br /><br />The property enhances Url property by adding improved edit mode experience and server side scaling of images in view mode.</p><h3>Getting started</h3><p>To use the property add the following UIHint to any Url property.</p><pre class="language-csharp"><code><span class="kwrd">using</span> OpenWaves.EPiServer.ImageProperty; <br /><br />...<br /><br />[UIHint(ImageProperty.UIHint)] <br /><span class="kwrd">public</span> <span class="kwrd">virtual</span> Url Image1 { get; set; }<br /></code></pre><h3>Form editing mode</h3><p>Clicking on the property opens a file browser.</p><br /><a href="http://lh6.ggpht.com/-yxQcRIMYa-Q/UT3ZpGFc02I/AAAAAAAAEIU/myQ-au2Ozj0/s1600-h/clip_image001%25255B2%25255D%25255B2%25255D.jpg"><img alt="clip_image001[2]" border="0" height="133" src="http://lh3.ggpht.com/-YbcmpB7pS7E/UT3ZpmKztZI/AAAAAAAAEIc/AnmMES6jGts/clip_image001%25255B2%25255D_thumb.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="clip_image001[2]" width="244" /></a><br /><br /><p>Images can also be drag-dropped from the file manager.</p><br /><a href="http://lh6.ggpht.com/-BzIwMjOiyj8/UT3Zqgn-NSI/AAAAAAAAEIk/kDTGtahOZCw/s1600-h/clip_image0033.jpg"><img alt="clip_image003" border="0" height="142" src="http://lh4.ggpht.com/-AzqCcemYhBw/UT3ZrucUyiI/AAAAAAAAEIs/0DnbMYO6HJg/clip_image003_thumb.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="clip_image003" width="244" /></a><br /><br /><p>After a picture is selected, the property shows a server-side scaled thumbnail.</p><br /><a href="http://lh5.ggpht.com/-2XzM3H3O2xs/UT3ZsVuy1FI/AAAAAAAAEI0/L8Ri0bw6CQc/s1600-h/clip_image0053.jpg"><img alt="clip_image005" border="0" height="141" src="http://lh4.ggpht.com/-bX8pHcnORzA/UT3Zs9M_n5I/AAAAAAAAEI8/kaox4lNue24/clip_image005_thumb.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; margin: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="clip_image005" width="244" /></a><br /><br /><h3>On-page editing mode</h3><p>In this mode the property is rendered as server-side transformed image and supports drag and drop of images from the file manager.</p><br /><a href="http://lh5.ggpht.com/-Zn8dBK8K85k/UT3Zt1zYiDI/AAAAAAAAEJE/bguy3STYxHo/s1600-h/clip_image0073.jpg"><img alt="clip_image007" border="0" height="171" src="http://lh3.ggpht.com/-gjfwHRmwLPQ/UT3ZurxvWWI/AAAAAAAAEJI/CYARzEN3Cig/clip_image007_thumb.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="clip_image007" width="244" /></a><br /><p>To enable enhanced on-page editing support, use <ow:ImageProperty> control in the page template. ImagePeoprty control is a wrapper around <EPiServer:Property> control that uses RenderSettings dictionary to pass relevant information to PropertyImageControl which in turn uses OpenWaves.ImageTransformations.Controls.Image control to enable server-side image transformations.</p><pre class="language-csharp"><code><span class="kwrd"><</span><span class="html">ow:ImageProperty</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">PropertyName</span><span class="kwrd">="Image1"</span> <br /> <span class="attr">Transformation</span><span class="kwrd">="ScaleToFill"</span> <br /> <span class="attr">ImageWidth</span><span class="kwrd">="200"</span> <span class="attr">ImageHeight</span><span class="kwrd">="200"</span> <span class="kwrd">/></span> <br /></code></pre><p>Transformation attribute can be any of the transformations supported by <ow:Image> property. See: <a href="https://openwaves.codeplex.com/wikipage?title=Image">https://openwaves.codeplex.com/wikipage?title=Image</a> for more details.</p><h3>Installation</h3><ol><li>Install OpenWaves.EPiServer.ImageProperty nugget package. </li><li>Add an initializable module that registers WebImageTransformationsModule used to perform server-side image transformations. <pre class="language-csharp"><code>[InitializableModule] <br />[ModuleDependency(<span class="kwrd">typeof</span>(EPiServer.Web.InitializationModule))] <br /><span class="kwrd">public</span> <span class="kwrd">class</span> Bootstrapper : IInitializableModule <br />{ <br /> <span class="kwrd">public</span> <span class="kwrd">void</span> Initialize(InitializationEngine context) <br /> { <br /> ServiceLocator.SetResolver(<span class="kwrd">new</span> BasicResolver() <br /> .RegisterWebImageTransformationModule(<br /> HostingEnvironment.VirtualPathProvider,<br /> <span class="str">"~/Content/Images/Transformed"</span>)); <br /> } <br /><br /> <span class="kwrd">public</span> <span class="kwrd">void</span> Uninitialize(InitializationEngine context) <br /> { } <br /><br /> <span class="kwrd">public</span> <span class="kwrd">void</span> Preload(<span class="kwrd">string</span>[] parameters) <br /> { }<br />}</code></pre></li></ol><h3>Source code</h3><p>Source code is available at: <a href="https://openwaves.codeplex.com">https://openwaves.codeplex.com</a></p>Scaling images for web–part 2http://mgrzyb.blogspot.com/2011/09/scaling-images-for-webpart-2.html2011-09-10T23:50:00.0000000Z<style type="text/css">.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } </style> After reading the <a href="http://mgrzyb.blogspot.com/2011/09/scaling-images-for-web.html" target="_blank">last part</a>, looking at <a href="http://openwaves.codeplex.com/wikipage?title=Image%20Transformations" target="_blank">Image Transformations</a> page in Open Waves documentation section, we know how to transform an image. All we need is an input stream and an output stream.<br />Where do we get an input stream from? If we have a virtual path of an image, we can try something along the following lines:<br /><br /><pre class="language-csharp"><code><span class="kwrd">public</span> Stream OpenImageStream(<span class="kwrd">string</span> virtualPath)<br />{<br /> <span class="kwrd">return</span> File.OpenRead(HostingEnvironment.MapPath(virtualPath));<br />}</code></pre><br />But this is not how ASP.NET does it. A very useful feature <a href="http://weblogs.asp.net/scottgu/archive/2005/11/27/431650.aspx" target="_blank">introduced in ASP.NET 2.0</a> is an abstraction layer on top of a file system – VirtualPathProvider. Ok, so it makes sense to use it to access files. Our code to open image stream can look like this:<br /><br /><pre class="language-csharp"><code><span class="kwrd">public</span> Stream OpenImageStream(<span class="kwrd">string</span> virtualPath)<br />{<br /> var file = HostingEnvironment.VirtualPathProvider.GetFile(virtualPath);<br /> <span class="kwrd">return</span> file.Open();<br />}</code></pre><br />What was wrong with the first approach? If you develop EPiServer sites you know that the file pointed to by a virtual path does not necessary is on the local disk. With projects like <a href="/link/bd96d836228b42e296bbbc8b4938005c.aspx" target="_blank">VPP for Amazon S3</a>, the file might not even be in our network. One problem with abstractions is that sometimes they hide just a little bit too much. In our case, if we had a file on a local disk, we could for example check its last modified date to see if we need to transform the image or can serve a cached version. Fortunately, ASP.NET developers have also noticed this need and provided a way for implementers of VPPs to notify clients of file changes. That’s why a VirtualPathProvider class has the following methods:<br /><br /><pre class="language-csharp"><code><span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">string</span> GetFileHash(<br /> <span class="kwrd">string</span> virtualPath, <br /> IEnumerable virtualPathDependencies)<br /><br /><br /><span class="kwrd">public</span> <span class="kwrd">virtual</span> CacheDependency GetCacheDependency(<br /> <span class="kwrd">string</span> virtualPath, <br /> IEnumerable virtualPathDependencies, <br /> DateTime utcStart)</code></pre><br />The first one should return a stable hash of a file – if the file hash changes, we can assume the file has changed.<br /><br />The second one should return a CacheDependency that will invalidate cache entry (if a client decides to cache results of the file processing) when the file changes.<br /><br />VirtualPathDependencies parameter is an important one. When calling the methods, it should contain a list of all dependencies of the given virtual path. If any of the dependencies change, the provider should consider the file indicated by the virtual path as modified, trigger cache dependency, and update file hash. When using the methods (or implementing a VPP) remember that the virtual path must be included in the list of dependencies. Example:<br /><br />Let’s say we have foo.aspx file that includes a reference to bar.ascx control. ASP.NET BuildManager will ask for file hash using the following method call:<br /><br /><pre class="language-csharp"><code>virtualPathProvider.GetFileHash(<br /> <span class="str">"~/foo.aspx"</span>,<br /> <span class="kwrd">new</span> [] {<span class="str">"~/foo.aspx"</span>, <span class="str">"~/bar.ascx"</span>})</code></pre><br />In image scaling scenario, where image files don’t have any dependencies, the dependencies list will only include a virtual path to the file itself.<br /><br />A word of warning. Not every implementation of a VirtualPathProvider will implement the above methods. It is fine not to implement one or both of them. In such case, the base class will return null for both file hash and cache dependency. EPiServer Unified File provider (and derived classes) is an example of the implementation where GetFileHash method is not present (GetCacheDependency is implemented by VirtualPathNativeProvider). For cases like this, if you know the details of the VPP implementation (DotPeek?) you can often find other ways to calculate the hash. In EPiServer case VPP.GetFile method returns instances derived from VirtualFileEx which has Changed property, giving us access to the last modified date of a file.<br /><br />For scenarios like this, and for better testability, image transformation related code in Open Waves does not depend on a VPP directly. Instead we have IVirtualFileProvider interface.<br /><br /><pre class="language-csharp"><code> <span class="kwrd">public</span> <span class="kwrd">interface</span> IVirtualFileProvider<br /> {<br /> IVirtualFile GetFile(Url fileUrl); <br /> }<br /><br /> <span class="kwrd">public</span> <span class="kwrd">interface</span> IVirtualFile<br /> {<br /> Url Url { get; }<br /> <span class="kwrd">string</span> Hash { get; }<br /> Stream Open();<br /> }</code></pre><br />The interface is implemented by VirtualPathFileProvider class which is a wrapper for a VirtualPathProvider. This gives us a chance to “fix” any issues we may find in the underlying VPP. Another difference is that we are not relying on virtual paths but rather on Urls (in most cases they will be virtual paths). This allows us to implement a virtual file provider that fetches images from external sites (flickr?) – just need to be smart about how to implement file hashes. For more details take a look at this page from Open Waves documentation section <a href="http://openwaves.codeplex.com/wikipage?title=Image%20Transformations%20for%20Web" target="_blank">Image Transformations for Web</a>.<br /><br />In the next part I’ll try to describe an approach we chosen to caching transformed images.Scaling images for webhttp://mgrzyb.blogspot.com/2011/09/scaling-images-for-web.html2011-09-06T00:23:00.0000000Z<p> </p> <p>Today I am going to divert a bit from the topic of my <a href="http://mgrzyb.blogspot.com/2011/08/lessons-learned-from-implementing.html" target="_blank">domain models series</a> and try to write about what I am currently working on. I hope to continue the series in the following posts.</p> <p>For the past 2 days I have been working on migrating a code responsible for image transformations (scaling to be precise) from the MakingWaves.Common library to <a href="http://openwaves.codeplex.com/SourceControl/list/changesets" target="_blank">Open Waves repository</a>. Whenever I move stuff to Open Waves, I try to improve what we have and implement ideas we’ve had for a given feature but never got time to code them. Before I talk about the code itself, let’s see what problems need to be solved.</p> <h3>Resizing</h3> <p>Ok, so we have an image file (or a stream) and want to generate a resized version of the image.</p> <h4>System.Drawing (GDI+)</h4> <p><a href="http://msdn.microsoft.com/en-us/library/system.drawing.aspx" target="_blank">System.Drawing namespace</a> has been around since the first version of the framework. It is a set of wrappers around GDI+ API.It has been used in many web application even thought, the documentation clearly says it is not supported for use in ASP.NET. The fact is, it works and works reasonably well. There are things to remember though. First, be very careful to dispose anything that should be disposed. Second, to achieve good results (image quality and performance) one needs to remember to set a couple of properties to just the right values. My favourite is <font face="Courier New">imageAttributes.SetWrapMode(WrapMode.TileFlipXY) </font>to avoid “ghosting” (1px frame) around the result image.</p> <p>See this <a href="http://dotnetslackers.com/articles/aspnet/Generating-Image-Thumbnails-in-ASP-NET.aspx#Caching_the_images" target="_blank">article</a> for more details and <a href="http://weblogs.asp.net/bleroy/archive/2007/12/05/what-interpolationmode-and-compositingquality-to-use-when-generating-thumbnails-via-system-drawing.aspx" target="_blank">this one</a> for comparison of quality vs. performance for different settings.</p> <h4>WPF</h4> <p>It may be suppressing, but it is possible to use WPF to resize images in the server environment. Again, <a href="http://weblogs.asp.net/bleroy/archive/2009/12/10/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi.aspx" target="_blank">an article</a> describing the details from Bertrand Le Roy. The performance is much better compared to GDI+, and the quality does not suffer. Two problems though: works only in full trust, and (according to Mr. Le Roy) it is again not supported in server scenarios (I could not find anything in MSDN to confirm this).</p> <p>By the way, this is the method that EPiServer.ImageLibrary uses to process images for image editor in edit mode. So, if you want to use this method, don’t want to code it yourself and are working on an EPiServer site, go and read <a href="/link/c9485d1cd18043e08c3db2fd637a9fda.aspx" target="_blank">this entry</a> from Mattias Lövström. The only problem is, that the API the component exposes makes it hard to maintain the aspect ratio of an image. Basically, they will let you scale the image to a given width and height, but first you will need to figure out the correct width/height ratio. I guess, when used in image editor that’s fine as the client-side code keeps a fixed aspect ratio when resizing an image, but when what you get is a random picture to resize, this becomes a problem. </p> <p>The bad news is, that if you try to use System.Drawing.Image to first load an image, inspect Width and Height properties to compute the ratio, you’ll end up decoding the file which is a pretty heavy operation. It is possible that whatever you can gain by using WPF transformations you will lost by unnecessarily decoding the image. The good news is, that if you use WPF API to do the same, it will only load image metadata and will not decode the image (it is lazy in that matter).</p> <h4>WIC (Windows Imaging Component)</h4> <p>It is not a surprise that WPF imaging API is a wrapper for a piece of native code. This native code is called WIC. here is <a href="http://msdn.microsoft.com/en-us/library/ff973956.aspx" target="_blank">a chapter from MSDN</a> showing the usage of the component. To use it from .NET you will need a managed wrapper. Once again I will send you to <a href="http://weblogs.asp.net/bleroy/archive/2010/05/03/the-fastest-way-to-resize-images-from-asp-net-and-it-s-more-supported-ish.aspx" target="_blank">Tales from the Evil Empire blog</a> for details on how to use the components. There you will also find a link to a ready to use managed wrapper.</p> <h4>Resizing summary</h4> <p>In theory, the only truly supported way is to use WIC directly (even though WPF does pretty much the same thing). In practice components from both System.Drawing (GDI) and System.Windows.Media (WPF) namespaces seem to work reliably on the server. System.Drawing has an advantage of working in a medium trust environment. At Making Waves, for quite a while, we have successfully used GDI for our resizing needs, but I figured, that since I am working on migrating this part of our frameworks I may as well implement the other two mechanisms and add support for plugging in EPiServer.ImageLibrary. Note: We use the same set of resizing components in non EPiServer projects, hence we need more then just EPiServer.ImageLibrary to cover our needs.</p> <h3>Transformations</h3> <p>In practice, in most cases, you will need transformations that maintain the aspect ratio of the original image. We use one of the following:</p> <p><strong>Scale to fit</strong> – resizes an image so it fits specified rectangle (width/height) without clipping. For example when fitting into a square, landscape images will be sized so their width matches the width of the square, while portrait images will be sized so their height matches the height of the square. This is the most often used transformation (galleries, thumbnails, user pictures, or whenever you want to avoid clipping the image).</p> <p><strong>Scale down to fit</strong> – same as the above but will only resize an image if it is larger then specified rectangle (width/height). This is for example useful if you want to scale images for display on mobile device, where any image wider then the width of the screen gets scaled down, but the ones that fit the display are not transformed.</p> <p><strong>Scale to fill</strong> – resizes an image so it fills the whole rectangle (width/height). Ultimately the image is scaled so it is large enough to cover the target area and centrally cropped to match the specified dimensions. Useful when a graphics design assumes that the picture covers the whole region of a page.</p> <p>Other transformations that are not very popular but may be useful once in a while are stretch and crop.</p> <h3>End of part 1</h3> <p>I have not planned this but it appears this one is going to be a first post in the series about scaling images for web. Things I want to cover in the next post include:</p> <ul> <li>Versioning of original images</li> <li>Methods for serving transformed images</li> <li>Caching strategies</li> <li>OpenWaves.Web.Image control</li> </ul> <p>A lot of the things I discussed here has already been implemented and is available in <a href="http://openwaves.codeplex.com/SourceControl/list/changesets" target="_blank">Open Waves repository</a>. Even though this is still work in progress, I will be happy to hear any comments you might have about the code.</p> EPiServer, OpenWaves, and PageDataAdaptershttp://mgrzyb.blogspot.com/2011/07/episerver-openwaves-and.html2011-07-13T00:44:00.0000000ZThis is a first post after a long break. Considering my lack of discipline and time I’m pretty sure this will not turn into regular blogging but anyways.<br />Some context first. It’s been 4 years since I started working for <a href="http://makingwaves.com/">Making Waves</a>. One of the thinks we do is implementing EPiServer CMS. If you have never heard of it, don’t read further – this may bore you.<br />Still with me? Good. From now on I’ll assume you know what EPiServer is and that you are a developer. <br /><h3>Open Waves</h3>Making Waves does a lot of projects. As you can imagine a lot of code written for one project very often can be reused in another. For some time, we have been trying to extract anything that seemed reusable into a library that we internally call MakingCommon. Recently, we begun to open source pieces of the library under the name <a href="http://openwaves.codeplex.com">Open Waves</a>. With time, we hope to move most of the internal frameworks to codeplex and share them with others.<br /><h3>PageData Adapters</h3>An interesting functionality (only if you implement EPiServer based solutions, and it still waits for its turn to be moved to Open Waves) is provided by PageDataAdapters. PageDataAdapters started as a mini project of mine 2 years ago but has been developed by couple of other Wave Makers since then. The inspiration for it came from <a href="http://www.castleproject.org/components/dictionaryadapter/index.html">Castle Dictionary Adapters</a>, but feature-set was influenced by patterns employed by model oriented methods, OR mappers, and AOP paradigm. The underlying assumption is that when implementing a CMS 95% of the code is just reading data entered by editors and this is where PageDataAdapters provide most of the functionality. As soon as you see the first example you will think to yourself: “This is the same thing as PageTypeBuilder…”, and in a way you will be right. But it will be a surprise to learn that initially we have not even planned to generate page types from the classes (but now we do). Now some of the usage examples (if you are familiar with what PageTypeBuilder does you will have no problem understanding them).<br /><pre class="language-csharp"><code>[PageTypeDefinition]<br /><span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">class</span> Article<br />{<br /> [Property(Required = true)]<br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">string</span> Title { get; }<br /><br /> [XhtmlProperty(DefaultValue = <span class="str">""</span>)]<br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">string</span> Body { get; }<br /><br /> [Parent]<br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> ArticleCategory Category { get; }<br /><br /> [PreviousSibling]<br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> Article PreviousArticle { get; }<br /><br /> [NextSibling]<br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> Article NextArticle { get; }<br /><br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> IEnumerable<Article> RelatedArticles { get; }<br />}<br /></code></pre>As you can see, to define a model for an article page we don’t have to use any of EPiServer related classes. It is pretty clean and self explanatory. We can use model classes to define properties of the class. We can use attributes to customize how values of the properties will be resolved in runtime and how they will be generated in the page type. Here is another example showing how article category can be modelled.<br /><pre class="language-csharp"><code>[PageTypeDefinition]<br />[AllowedChildrenPageTypes(typeof(Article))]<br /><span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">class</span> ArticleCategory<br />{<br /> [Property(BuiltInProperties.PageName)]<br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">string</span> Name { get; }<br /><br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> Person ContactPerson { get; }<br /><br /> [Children]<br /> <span class="kwrd">public</span> <span class="kwrd">abstract</span> IEnumerable<Article> Articles { get; }<br />}</code></pre>Now after the model is defined we can use it in the templates. Thanks to richness of the model templates can be much simpler and cleaner. All we have to do is derive the template from generic TemplatePage<TModel> to get access to Model property of type TModel.<br /><pre class="language-csharp"><code><span class="kwrd">public</span> <span class="kwrd">partial</span> <span class="kwrd">class</span> ArticleTemplate : TemplatePage<Article><br />{<br /> ...<br />}</code></pre><pre class="language-csharp"><code><span class="kwrd"><</span><span class="html">h1</span><span class="kwrd">></span><span class="asp"><%</span>: Model.Title <span class="asp">%></span><span class="kwrd"></</span><span class="html">h1</span><span class="kwrd">></span><br /><span class="kwrd"><</span><span class="html">div</span><span class="kwrd">></span><br /><span class="asp"><%</span>= Model.Body <span class="asp">%></span><br /><span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span><br />Contact person: <span class="asp"><%</span>: Model.Category.ContactPerson.Name <span class="asp">%></span><br /></code></pre>I hope this very simple example illustrates how easy it is to traverse page tree without a need to use any of EPiServer infrastructure in the template code or markup.<br /><br />An important aspect of the framework is that it is extensible in many ways and places. None of the attributes used in the examples is special or expected by the framework. All of them implement interfaces that extend the runtime behaviour or affect page type generation. For example this is a source code for the ParentAttribute class.<br /><pre class="language-csharp"><code>[AttributeUsage(AttributeTargets.Property)]<br /><span class="kwrd">public</span> <span class="kwrd">class</span> ParentAttribute : Attribute, IPropertyValueResolver<br />{<br /> <span class="kwrd">public</span> TypedValue ResolveValue(PageData pageData, <span class="kwrd">string</span> propertyName)<br /> {<br /> <span class="kwrd">return</span> TypedValue.From(pageData.ParentLink);<br /> }<br />}</code></pre>IPropertyValueResolver interface is responsible for resolving the value of a property. In most cases resolution means getting the value from PageData, but in this case we’re returning a PageReference to the parent. Now, since what we return is a PageReference, how come Article.Category can be of type ArticleCategory. This is possible thanks to automatic conversions that the framework tries to apply looking at the type of the value returned by a resolver and type of the property (PageReference –> ArticleCategory in the example).<br />Built in conversions include among others:<br /><ul><li>PageReference –> PageData</li><li>PageReference –> Url</li><li>LinkItem –> PageReference</li><li>LinkItem –> PageData</li><li>int –> Enum</li></ul>Creating custom property value resolvers and property value converters allows us in most cases to abstract away EPiServer internals and lets us focus on creating clean and easy to understand information models for the sites we develop.<br /><br /><h3>Future</h3><br />The future of PageDataAdapters is not quite clear. We have noticed how PageTypeBuilder grew to be a de facto standard library used in EPiServer projects and we know that parts of our framework duplicate functionality of PageTypeBuilder. At the same time we really like the abstraction on top of PageData provided by PageDataAdapters. Also, we have quite a few live projects using the library so we cannot just kill it even if we wanted :P<br /><br />For now we’ve decided that we want to open source it with the rest of the reusable code we maintain internally. I’ll be very happy to hear any comments you may have about the above and I do encourage you to check <a href="http://openwaves.codeplex.com/">Open Waves</a> (even though we have barley started the migration to codeplex).