Troubleshooting Optimizely Shortcuts: Why PageShortcutLink Threw an Error and the Solution
As developers working with Optimizely, we often encounter unique challenges that push us to explore the platform's depths. Recently, I tackled a fascinating task involving content migration and external linking, which led me down a rabbit hole of property management and ultimately, a much smoother solution. I wanted to share my journey, especially for anyone else who might encounter similar hurdles.
The Scenario: A Tale of Two Companies
Our company recently sold a subsidiary, and as part of the transition, we needed to facilitate a smooth redirection from their old content on our Optimizely site to their new home on another Optimizely instance. The core task was to convert existing pages on our site into external shortcuts, pointing to their corresponding URLs on the new company's website.
I was provided with an Excel file containing two columns: FirstUrl (the existing URL on our site) and SecondUrl (the target URL on the new company's site). My goal was to create a custom add-on that would automate this process, turning hundreds of internal pages into external links.
The Initial Approach: Properties and Puzzlement
My first instinct, and a common approach when manipulating page data in Optimizely, was to leverage the Property collection. I knew that Optimizely pages have properties like PageShortcutType and PageShortcutLink to manage shortcuts. So, I started with this:
writablePage.Property["PageShortcutType"].Value = PageShortcutType.External;writablePage.Property["PageShortcutLink"].Value = importResponse[index].SecondUrl;
The first line, setting PageShortcutType to PageShortcutType.External, worked perfectly. This correctly flagged the page as an external shortcut. However, the second line, where I attempted to set the PageShortcutLink with the SecondUrl from my Excel file, threw a rather cryptic exception:
{"ContentReference: Input string was not in a correct format."}
This was puzzling. The SecondUrl was a valid, absolute URL string. I double-checked the format, ensured there were no leading/trailing spaces, and even tried URL encoding – nothing seemed to work. A lengthy search on Google and various Optimizely forums yielded no clear solution to this specific error when assigning a direct URL string to PageShortcutLink via the Property collection.
The Technical Deep Dive: Why the Error?
This exception, "ContentReference: Input string was not in a correct format," strongly suggests that Optimizely's internal mechanisms for PageShortcutLink (when accessed via Property["PageShortcutLink"]) are expecting a ContentReference object, not a raw URL string, even if the PageShortcutType is set to External.
Here's the technical breakdown:
- PageShortcutLink's Internal Expectation: While logically one might expect to store any external URL, it appears that when you interact with PageShortcutLink through the generic Property collection, Optimizely's underlying type conversion or validation expects a ContentReference. This is likely a design choice to maintain consistency within its content management system, potentially allowing for future enhancements or stricter validation.
- ContentReference Nuance: A ContentReference is Optimizely's way of uniquely identifying a piece of content (a page, block, media file, etc.) within its system. It's not designed to hold arbitrary external URLs. The exception was a clear indicator that my string wasn't being parsed into a valid ContentReference.
This highlights a crucial aspect of Optimizely development: sometimes, the generic Property accessors might have underlying type expectations that aren't immediately obvious, especially for properties that can link to both internal content and external URLs.
The "Aha!" Moment: Direct Properties to the Rescue
Frustrated but undeterred, I started looking for alternative ways to achieve the same outcome. And then, it hit me. Optimizely's PageData object, which represents a page, has directly exposed properties for managing shortcuts!
writablePage.LinkType = PageShortcutType.External;
writablePage.LinkURL = importResponse[index].SecondUrl;
I swapped out my problematic Property assignments for these direct properties, and to my immense relief, it worked flawlessly! No exceptions, no wrestling with content references, just clean, straightforward assignment.
The Final Solution: Robust and Reliable
Here's the complete code snippet that successfully accomplished the task:
for (int index = 0; index < excelData.Count; index++){ try { // Resolve the existing page using the FirstUrl var content = _urlResolver.Route(new EPiServer.UrlBuilder(excelData[index].FirstUrl)) as PageData;
// Ensure we found a page if (content != null) { // Create a writable clone to modify the page PageData writablePage = content.CreateWritableClone();
// Set the LinkType to External writablePage.LinkType = PageShortcutType.External;
// Assign the external URL directly writablePage.LinkURL = excelData[index].SecondUrl;
// Save the modified page _contentRepository.Save(writablePage, SaveAction.Patch, AccessLevel.NoAccess); } else { // Handle cases where the URL doesn't resolve to a page viewModel.ImportResponse[index].Errors.Add(new ValidationResult($"Record for '{excelData[index].FirstUrl}' couldn't be found or resolved.")); } } catch (Exception ex) { // Log the exception for debugging // _logger.LogError(ex, "Error processing record for URL: {Url}", excelData[index].FirstUrl); viewModel.ImportResponse[index].Errors.Add(new ValidationResult("Record couldn't be updated. Please validate the data again. " + ex.Message)); }}
Technical Additions to the Final Code:
- Error Handling and Logging: I've added a more specific error message if the _urlResolver.Route method doesn't find a page, which is crucial for real-world scenarios. Also, I've commented out a placeholder for logging the exception (_logger.LogError), which is a best practice for debugging and monitoring in production environments.
- Null Check for content: It's vital to check if _urlResolver.Route actually returns a PageData object. If the FirstUrl doesn't correspond to an existing page, content would be null, leading to a NullReferenceException.
Lessons Learned
This experience reinforced a couple of key lessons for me:
- Prefer Direct Properties When Available: While the Property collection is powerful for dynamic or custom properties, always check if there are direct properties on the PageData (or ContentData) object for common functionalities. These direct properties often encapsulate the correct underlying logic and type handling.
- Understand Optimizely's Internal Expectations: The "ContentReference" error was a strong hint that Optimizely had a specific type in mind, even if my string looked like what was needed. Debugging these specific error messages often points to how Optimizely's APIs are designed internally.
- Community and Documentation are Key (but not always exhaustive): While I initially struggled to find an exact solution for my specific error, the journey of looking through documentation and forums eventually led me to consider alternative approaches.
This task, while seemingly simple on the surface, provided a valuable opportunity to deepen my understanding of Optimizely's content model and property management. It's these kinds of challenges that truly help us grow as Optimizely developers!
Comments