Jan 26, 2011
visibility 7466
star star star star star
(0 votes)

EPiServer null property values for saved but unpublished pages


Introduction

Recently I have been working on an EPiServer 6 build.  One of the requirements for this build to manage time table data.

I am not going to go into the full requirements but we are using PageTypeBuilder for our entity objects and using the EPiServer page tree as a content modelling repository for our entities.  Please see Mark Rodseth’s blog post detailing this approach here.

One of the reasons for the approach is to cut development time as all of the entities can be managed through EPiServer using PageTypeBuilder and the edit mode editing interface.

The content repository we are developing is in a complex structure and will more than likely confuse the editors.

To solve this issue I have been developing an edit mode plugin that will provide a user friendly interface to populate all of the relevant relational page tree entities.

Does this make sense so far? Excellent Smile


The Problem

The plugin I have created allows the user to create pages in a saved state and then publish them in batches when they are happy.

The plugin also contains many drop downs lists and list views which are populated from various areas of the content repository.

Are you still with me? Now I will try and get to the point Disappointed smile

I am using DataFactory.Instance.FindPagesWithCriteria calls to get entities of different page types and criterias.

The issue I have is that I have pages in the page tree that are in a saved state and have also never been published.

The FindPagesWithCriteria call returns the saved pages in a PageDataCollection object which is exactly what I want to happen, Yay.

The issue is that all non meta data properties have null values even though the page has these properties set Sad smile.

After some digging around I figured out the problem is caused because the PageLink (PageReference) property of the PageData object has no WorkID specified (remember the page is in a saved state and has never been published)

I haven’t bothered to reflect this in great detail but I would assume that as no WorkID is specified the code is following a different logical route.

I am sure an EPiServer employee could provide detailed information on the reasoning for this Smile, but to be honest I can’t be bothered to look into the inner workings.

Solution

I am sure this issue has been found and solved many times before but I thought I would blog about it just in case any developers were not aware of this GOTCHA.

To resolve the issue I have created some extension methods for the DataFactory class which will force a PageData object to be inline with the latest version.

Extension methods are below and are referencing PageTypeBuilder types.  If you are not using PageTypeBuilder ask yourself WHY as you should be Winking smile

   1:  public static List<T> GetLatestPages<T>(this DataFactory dataFactory, List<T> pages) where T : TypedPageData
   2:  {
   3:      if (pages.Count == 0)
   4:          return pages;
   5:   
   6:      const double batchSize = 100;
   7:      List<T> pagesToProcess = pages.Where(current => current.PageLink.WorkID == 0).ToList();
   8:   
   9:      foreach (string languageId in pagesToProcess.Select(current => current.LanguageID).Distinct())
  10:      {
  11:          List<T> pagesToBatch = pagesToProcess.Where(current => string.Equals(current.LanguageID, languageId, StringComparison.OrdinalIgnoreCase)).ToList();
  12:          int numberOfBatches = (int)Math.Ceiling(pagesToBatch.Count / batchSize);
  13:   
  14:          for (int i = 0; i < numberOfBatches; i++)
  15:          {
  16:              int start = (int)(i * batchSize);
  17:              List<T> batch = pagesToBatch.Skip(start).Take((int)batchSize).ToList();
  18:              Dictionary<int, int> pagesAndWorkIds = dataFactory.GetLatestPageWorkIds(batch);
  19:   
  20:              foreach (var item in pagesAndWorkIds)
  21:              {
  22:                  var currentItem = item;
  23:                  int index = pages.FindIndex(current => current.PageLink.ID == currentItem.Key);
  24:                  pages[index] = DataFactory.Instance.GetPage(new PageReference(currentItem.Key, currentItem.Value)) as T;
  25:              }
  26:          }
  27:      }
  28:   
  29:      return pages;
  30:  }
  31:   
  32:  public static T GetLatestPage<T>(this DataFactory dataFactory, T page) where T : TypedPageData
  33:  {
  34:      List<T> pages = GetLatestPages(dataFactory, new List<T> { page });
  35:      return pages[0];
  36:  }
  37:   
  38:  private static Dictionary<int, int> GetLatestPageWorkIds<T>(this DataFactory dataFactory, List<T> pages) where T : TypedPageData
  39:  {
  40:      int languageBranchId = LanguageBranch.ListEnabled().Where(current => string.Equals(current.LanguageID, pages[0].LanguageID)).FirstOrDefault().ID;
  41:   
  42:      StringBuilder sqlBuilder = new StringBuilder();
  43:      sqlBuilder.AppendFormat("SELECT [fkPageID], MAX([pkID]) as WorkId FROM [tblWorkPage] WHERE [fkLanguageBranchID] = {0} AND [fkPageID] IN (", languageBranchId);
  44:   
  45:      for (int i = 0; i < pages.Count; i++)
  46:      {
  47:          string seperator = (i < (pages.Count - 1)) ? "," : string.Empty;
  48:          sqlBuilder.AppendFormat("{0}{1}", pages[i].PageLink.ID, seperator);
  49:      }
  50:   
  51:      sqlBuilder.Append(") GROUP BY [fkPageID]");
  52:   
  53:      Dictionary<int, int> pagesAndWorkIds = new Dictionary<int, int>();
  54:   
  55:      using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["EPiServerDB"].ConnectionString))
  56:      {
  57:          using (SqlCommand command = new SqlCommand(sqlBuilder.ToString(), connection))
  58:          {
  59:              connection.Open();
  60:   
  61:              using (SqlDataReader reader = command.ExecuteReader())
  62:              {
  63:                  while (reader.Read())
  64:                      pagesAndWorkIds.Add(reader.GetInt32(0), reader.GetInt32(1));
  65:              }
  66:          }
  67:      }
  68:   
  69:      return pagesAndWorkIds;
  70:  }


The project I am working on has some helper classes to perform various different criteria based searching, some using FindPagesWithCriteria solely and others using a mix of FindPagesWithCriteria and LINQ.

These classes are being used in the live view of the site and in edit mode.  In the live view of the site everything works as expected as the user will only be able to view Published pages. 

Within my plugin I am having to determine whether the user is in edit mode and if so call the extension methods to make sure the PageData objects are populated correctly.

An example of calling one of the extension methods is below:

   1:  List<Timetable> timeTables = DataFactory.Instance.FindPagesWithCriteria(PageReference.StartPage, criterias).Cast<Timetable>().ToList();
   2:   
   3:  if (Functions.InEditMode)
   4:      DataFactory.Instance.GetLatestPages(timeTables);

 

Conclusion

Initially I wasn’t sure whether the access rights rules that are applied in all of the various PageData retrieval methods were causing the issues but that doesn’t seem to be the case.

I then started to think this may be a bug, but I am starting to think this is possibly by design and it is just the case that we are using EPiServer in a way that it have not been designed for.

Are we the only agency to suffer this problem or have any other agencies used a content repository entity approach with custom administration interfaces?  And if so have you suffered the same problems and created alternative solutions?

Thanks for reading this far, my blog style is not the greatest but I hope I have explained myself well enough and you have understood the point of my post. 

There was some serious hand banging the other day troubleshooting the issue.

Disclaimer

All code is provided as is and I accept no responsibility if it doesn’t work as you would expect.  You should always test your implementations yourself Winking smile

Jan 26, 2011

Comments

error Please login to comment.
Latest blogs
Finding Thomas Part 3 - The Moment of Recognition

Remember Thomas? In digital landscape, Thomas is the returning visitor who reads everything, opens every email, converts on nothing. In standard...

Ritu Madan | Jun 26, 2026

Add more scheduled job settings from the Optimizely CMS 12 admin UI -- with OptiScheduledJob.ExtraParameters

  Optimizely (EPiServer) CMS 12 ships a great scheduled-jobs framework, but it has one frustrating gap: a job has nowhere to store its own...

Binh Nguyen Thi | Jun 25, 2026

Automated Search & Navigation to Graph Migration with Claude Code

A Claude Code plugin that scans your S&N codebase, applies Graph SDK transformations, and validates the result. Install once, run one command. CMS ...

Connor Fortin | Jun 24, 2026

Migrating from Find to Graph: Lessons Learned from a Real CMS 13 Project

While migrating a search solution from Optimizely Search & Navigation (Find) to Optimizely Graph in CMS 13, I encountered several issues that were...

Binh Nguyen Thi | Jun 24, 2026

Optimizely: Upgrade Opti-ID and .NET 10 in CMS 12

Many Optimizely customers are planning their roadmap around a future migration to Optimizely CMS 13. As a result, upgrades such as Opti ID adoption...

Madhu | Jun 23, 2026 |

Understanding Optimizely Graph: Caching, Webhooks & Avoiding Stale Content (Optimizely SaaS CMS)

📌 Scope: This post covers Optimizely CMS (SaaS) only — using the official @optimizely/cms-sdk and @optimizely/cms-cli packages with Next.js 15. If...

Kiran Patil | Jun 23, 2026 |