November Happy Hour will be moved to Thursday December 5th.

Lee Crowe
Jan 26, 2011
  6868
(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

Please login to comment.
Latest blogs
Optimizely SaaS CMS + Coveo Search Page

Short on time but need a listing feature with filters, pagination, and sorting? Create a fully functional Coveo-powered search page driven by data...

Damian Smutek | Nov 21, 2024 | Syndicated blog

Optimizely SaaS CMS DAM Picker (Interim)

Simplify your Optimizely SaaS CMS workflow with the Interim DAM Picker Chrome extension. Seamlessly integrate your DAM system, streamlining asset...

Andy Blyth | Nov 21, 2024 | Syndicated blog

Optimizely CMS Roadmap

Explore Optimizely CMS's latest roadmap, packed with developer-focused updates. From SaaS speed to Visual Builder enhancements, developer tooling...

Andy Blyth | Nov 21, 2024 | Syndicated blog

Set Default Culture in Optimizely CMS 12

Take control over culture-specific operations like date and time formatting.

Tomas Hensrud Gulla | Nov 15, 2024 | Syndicated blog