Niklas Melinder
Nov 14, 2010
(1 votes)

EPiSocial – Using PageObjects

After my previous post on the EPiSocial plugin, Paul Smith pointed out that I should consider using Page Objects (PO) rather than plain Dynamic Data Store (DDS) to store page related data. The upside of using PO is that it provides support for mirroring and import/export. The downside being that it handles things on a page by page basis which makes queries over multiple pages a bit tricky. This is necessary when doing things like getting pages with the highest rating or getting pages with most comments.

Luckily, the process of changing the plugin from using DDS to PO is as simple as creating new implementations of the ICommentRepository and IRatingRepository interfaces.

After some minor changes from the previous version they look like this:

   1: public interface ICommentRepository
   2:     {
   3:         void AddComment(PageComment pageComment);   
   4:         void RemoveComment(string commentId);
   5:         void RemoveAllCommentsForPage(int pageId);
   6:         IEnumerable<PageComment> GetCommentsForPage(int pageId, string pageLanguage);
   7:         IEnumerable<PageComment> GetLatestComments(int count, string pageLanguage);
   8:         IEnumerable<int> GetMostCommentedPageIds(int count, string pageLanguage);
   9:     }
   1: public interface IRatingRepository
   2:     {
   3:         void AddRating(PageRating pageRating);
   4:         void RemoveAllRatingsForPage(int pageId);
   5:         double GetAverageRatingForPage(int pageId, string pageLanguage);
   6:         int GetNumberOfRatingsForPage(int pageId, string pageLanguage);
   7:         IEnumerable<int> GetHighestRatedPageIds(int count, string pageLanguage);        
   8:     }

In this post I will focus on the PO implementation of ICommentRepository.

Page Objects

The first thing I noted was that the documentation for PO was rather slim. Nothing in the SDK, all I found was a single Tech Note that described the features briefly.

One thing that differs when using PO instead of DDS is that a PO is stored as a single object per page. To be able to store multiple comments for a single page I created a container object to hold the comments.

   1: public class PageObjectCommentContainer : IDynamicData
   2:     {
   3:         public Identity Id { get; set; }
   4:         public List<DynamicDataStoreComment> Comments { get; set; }
   6:         public PageObjectCommentContainer()
   7:         {
   8:             this.Comments = new List<DynamicDataStoreComment>();
   9:         }
  10:     }


The PageObjectCommentRepository takes two dependencies. The first being a CommentMapper that handles the mapping described below. The second one is a PageObjectManagerFactory that handles creation of PageObjectManager objects based on page id.

   1: private const string CommentKey = "PageComments";
   2: private readonly CommentMapper commentMapper;
   3: private readonly PageObjectManagerFactory pageObjectManagerFactory;
   4: private readonly DynamicDataStore commentStore;
   6: public PageObjectCommentRepository(CommentMapper commentMapper, PageObjectManagerFactory pageObjectManagerFactory)
   7:         {
   8:             this.commentMapper = commentMapper;
   9:             this.pageObjectManagerFactory = pageObjectManagerFactory;
  10:             this.commentStore = DynamicDataStoreFactory.Instance.GetStore(typeof(DynamicDataStoreComment));
  11:         }

About mapping

Before saving comments to the DDS the PageComment object is mapped to a DynamicDataStoreComment object that implements the necessary IDynamicData interface. The reverse thing happens when getting comments from the store. This mapping might seem unnecessary, why don´t use the DynamicDataStoreComment directly instead? There are good reasons.

One being that I want the plugin to be extendable. If I or someone else wanted to create a different implementation using a different storage, that implementation probably won´t care about EPiServer.Data.

Another reason is that testing code using the API should not have to know about EPiServer.Data either.


   1: public void AddComment(PageComment pageComment)
   2:         {
   3:             var pageObjectManager = this.pageObjectManagerFactory.GetPageObjectManagerForPage(pageComment.PageId, pageComment.PageLanguage);
   4:             var ddsComment = this.commentMapper.MapToDynamicDataStoreComment(pageComment);
   5:             var poCommentContainer = pageObjectManager.Load<PageObjectCommentContainer>(CommentKey);
   7:             if (poCommentContainer == null)
   8:             {
   9:                 poCommentContainer = new PageObjectCommentContainer();                
  10:             }
  12:             poCommentContainer.Comments.Add(ddsComment);
  13:             pageObjectManager.Save(CommentKey, poCommentContainer);               
  14:         }


When removing a comment we first have to load the object, remove the comment from the list and save it back again. I also found that I had to delete the comment directly from the DDS for it to disappear completely, otherwise it will still be around when making queries like getting the latest comments below.

   1: public void RemoveComment(string commentId)
   2:         {
   3:             Identity identity;
   5:             if (!Identity.TryParse(commentId, out identity))
   6:             {
   7:                 throw new InvalidOperationException("Comment id is not an Identity.");
   8:             }
  10:             var ddsComment = commentStore.Load<DynamicDataStoreComment>(identity);
  11:             var pageObjectManager = this.pageObjectManagerFactory.GetPageObjectManagerForPage(ddsComment.PageId, ddsComment.PageLanguage);
  12:             var poCommentContainer = pageObjectManager.Load<PageObjectCommentContainer>(CommentKey);
  14:             if (poCommentContainer == null)
  15:             {
  16:                 return;
  17:             }
  19:             poCommentContainer.Comments
  20:                 .RemoveAll(c => c.Id == identity);
  22:             pageObjectManager.Save(CommentKey, poCommentContainer);
  23:             this.commentStore.Delete(identity);
  24:         }


   1: public void RemoveAllCommentsForPage(int pageId)
   2:        {
   3:            var pageObjectManager = pageObjectManagerFactory.GetPageObjectManagerForPage(pageId);
   4:            pageObjectManager.Delete(CommentKey);
   5:        }


   1: public IEnumerable<PageComment> GetCommentsForPage(int pageId, string pageLanguage)
   2:         {
   3:             var pageObjectManager = this.pageObjectManagerFactory.GetPageObjectManagerForPage(pageId, pageLanguage);
   4:             var poCommentContainer = pageObjectManager.Load<PageObjectCommentContainer>(CommentKey);
   6:             if (poCommentContainer == null)
   7:             {
   8:                 return new List<PageComment>();
   9:             }
  11:             return poCommentContainer.Comments
  12:                 .Select(c => this.commentMapper.MapToPageComment(c));
  13:         }


So far it´s been pretty straight forward. But since the PageObjectManager works on a per page basis, this kind of query cannot be done using it. But since PO uses DDS in the background, we can create a query using DDS directly to get the latest comments:

   1: public IEnumerable<PageComment> GetLatestComments(int count, string pageLanguage)
   2:         {
   3:             return this.commentStore.Items<DynamicDataStoreComment>()
   4:                 .Where(c => c.PageLanguage == pageLanguage)
   5:                 .OrderByDescending(c => c.Created)
   6:                 .Select(c => commentMapper.MapToPageComment(c))
   7:                 .Take(count);
   8:         }


The same thing applies here, we have to use DDS directly to get what we are looking for.

   1: public IEnumerable<int> GetMostCommentedPageIds(int count, string pageLanguage)
   2:         {
   3:             return this.commentStore.Items<DynamicDataStoreComment>()
   4:                 .Where(c => c.PageLanguage == pageLanguage)
   5:                 .GroupBy(c => c.PageId)
   6:                 .OrderByDescending(c => c.Count())
   7:                 .Select(c => c.Key)
   8:                 .ToList()
   9:                 .Take(count);
  10:         }

Unfortunately, due to a bug in the DDS implementation we cannot call Take() directly but have to call ToList() first. This is not optimal and I´m hoping this is fixed in the next release of EPiServer.

Wire it up

Now that we have our new PO repository ready we have to tell the application that we wish to use it instead of the default DDS implementation.

EPiSocial uses StructureMap to wire up it´s dependencies. To override the default setup we have to add a StructureMap registry of our own and the plugin will automatically pick it up during it´s initialization. So we add the following registry to the web application:
   1: public class PageObjectRegistry : Registry
   2:     {
   3:         public PageObjectRegistry()
   4:         {
   5:             For<ICommentRepository>()
   6:                 .Use<PageObjectCommentRepository>();
   8:             For<IRatingRepository>()
   9:                 .Use<PageObjectRatingRepository>();
  10:         }
  11:     }

For more on StructureMap check out this post by Stefan.

That´s all, we are now ready to use the PO version of the plugin.

An updated version of the plugin can be downloaded here. It now contains a EPiSocial.PageObjects assembly with the new PO implementation.

Nov 14, 2010


Please login to comment.
Latest blogs
Plug-in manager is back in CMS 12

Plug-in manager is back in the UI, what is it and how can i use it?

Luc Gosso (MVP) | Oct 6, 2022 | Syndicated blog

Display Child Pages in Content Delivery API Response

The below example will implement an instance of IContentConverterProvider to customise the serialisation of PageData and output child pages in the...

Minesh Shah (Netcel) | Oct 4, 2022

Bring the Report Center back in Optimizely CMS 12

The Report Center has been a part of Optimizely CMS since its first debut in version 5R2 in 2008, but in CMS 12, it's removed! Don't despair! Make...

Tomas Hensrud Gulla | Oct 4, 2022 | Syndicated blog

Customizing Property Lists in Optimizely CMS

Generic property lists is a cool editorial feature that has gained a lot of popularity - in spite of still being unsupported (officially). But if y...

Allan Thraen | Oct 2, 2022 | Syndicated blog