As I mentioned in an earlier post we often use web parts in our EPiServer projects at Epinova. And by using the EPicode.WebParts.Core framework together with EPiServer, the solutions become highly flexible and adaptable.
But there have also been some drawbacks related to using web parts in EPiServer, as Steve Celius has commented. Fortunately, they are all eliminated with the new PageDataPersonalizationProvider at EPiCode.
In this article I’ll explain the drawbacks with web parts in EPiServer, and how they were resolved. I have seen some other interesting solutions addressing a few of these drawbacks. But I objectively mean that this is the most complete and versatile solution yet.
It’s worth mentioning that the most widespread usage of web parts in EPiServer is done by editors in shared scope and I’ll focus on this scenario, but user scope is still supported. I also apologize for being inconsistent when talking about the persisted web parts. I refer to it as: web part personalization state, data blob, image and binary data. They all mean the same.
Table of Contents
- Web part changes go live instantaneously
- Web parts lack versioning
- Web parts does not follow pages on export, import, copy, paste
- Web parts are shared for all languages
- Huge data blobs and migration issues
- Web part content is not searchable
- No warnings when deleting web part referenced content
- Web parts vanish when converting page types
- Catch 22 headache with corrupt data blobs
- New property type
- Backwards compatibility
- How to install
- What’s next?
Whenever an editor is editing web parts, these changes are visible for visitors in real time. And the version status of the page being edited has no impact what so ever.This is obviously not a desired feature as CMS editors would like to edit content before publishing it. This was resolved simply by storing the web parts in memory while editing. Visitors are presented with content stored in the database and editors are presented with content from memory.
The editors are reminded to save their work from memory when editing web parts.
This is highly related to the previous section. Web parts are stored in a database table, as a binary image type, and each page has one row. Whenever web parts on a page are changed, previous web parts for that specific page are overwritten. What we did was to store the web parts inside an EPiServer property directly on the page. By doing so EPiServer’s built in versioning takes care of the rest; compare versions, delete versions, publish earlier versions etc. The EPiServer property is automatically created (SYSTEM_aspnet_PersonalizationAllUsers), on the page type, if missing. For easy access; a version sub menu has been added to the context menu in view mode.
Notice the Save, Save and Publish and Cancel when editing web parts
Again this is related to the two previous sections. Editors got confused when copying pages and web parts were left out. I had several hard times trying to explaining this as not being a bug. Also administrators and especially developers were frustrated when losing web parts on export and import of pages. The fix was effortless as it magically started to have expected behavior. Once web parts were persisted as a property, EPiServer takes care of this.
I’ve come across a few multilingual sites that required unique web part setup for each language. As you might have guessed; all that is needed is to check the "Unique value per language" for the new web part property.
Notice that this never has been reported as a problem, as long as web parts were stored as an image data type in the database. And maybe it never would become a problem when storing web parts in EPiServer neither. But as EPiServer doesn’t support binary data types as property data, I was a bit concerned by storing massive data blobs as strings in EPiServer. A typical page with ten web parts could reach up to 100kb of binary data represented as a string. And I worried about that parsing these data blobs back and forth between byte arrays and strings could be time consuming.
Actually I found the solution when migrating web parts from EPiServer 4 to CMS 5. Web parts which expose PropertyData fields would simply not work after migration. The reason is that the entire PropertyData objects are serialized and de-serialized by the underlying web parts provider. The fact that the PropertyData and its inheritors have changed between the two versions of EPiServer caused de-serialization to fail. I realized that there was no actual reason to serialize and de-serialize entire PropertyData objects, as we really only need the Value property.
The CMS 4 persisted web parts were stripped for all PropertyData objects and only the Value were left untouched. In addition the provider was changed to be applicable with this new data blob format. Not only had I found a way to migrate web parts, but sizes of the data blobs were also dramatically reduced. Their sizes shrunk by 70-90%, and as a bonus they were no longer vulnerable to future changes in the PropertyData object.
This section does not apply to crawler based searches for obvious reasons.
Most index based search engines, including EPiServer search, aren’t capable of interpreting Microsoft’s proprietary binary web parts data blob personalization state format (gasp!). And neither can I; "0xFF01142B001A02020203192A3153797374656D2E5765622E5..."
Due to this I often advised editors to use web parts as content retrievers. By this I mean that they should use web parts to expose content that exists elsewhere, and not put content into web parts. But editors had a tendency of succumbing to the temptation of doing things the easiest way. And developers tend to arm them with web parts to do that. For instance would the text of a LongStringProperty in a web part not be searchable.
To cope with this I had to understand the content of the data blobs and store them in another format. I can assure you that this was not an easy task. Most of the objects involved are internal and/or sealed. And Microsoft’s code is optimized for performance, not readability. But thanks to Reflection, Lutz Roeder and Reflector it was achievable. The data blob is now stored in Xml format which makes it more readable to both humans and computers. By using EPiServer’s standard search, all you have to do is check the “Searchable property” in admin mode.
When using other search engines you could simply push the inner text of the xml to the indexer, in the page publish event. Using the open API, it would look something like this:
var persState = CurrentPage.GetSharedPersonalizationState();
if (persState != null)
var xmlDoc = persState.CreateXml(PersonalizationScope.Shared);
By deleting normal referenced content, as pages, images, etc., EPiServer warns editors about possible broken links. But since web parts were persisted as binary data there was no way EPiServer could pick up these soft links. However, as soon as the content is persisted as xml, EPiServer automatically starts to warn about broken links even in web parts.
Deleting a page that is referenced by web parts on page 92, 93 and 94
Deleting a file that is referenced by a web part on page 94
A highly appreciate new feature in EPiServer CMS 5 is the support for converting pages to new page types. Unfortunately EPiServer seems to have forgotten a minor detail. EPiServer’s own web part provider generates keys for mapping pages to data blobs
A typical key looks like:
When converting that page to another page type, the key remains the same even though it should have been updated to something like:
And the result is that web parts are orphaned after converting a page. This is of course not a problem anymore as the web part data lives within the EPiServer pages its self, and no key or mapping is necessary. But I would like to point out that the new provider continues to support the user scope of web parts. These user personalizations are continued in the original database and format, but without the page type path as a part of the key. (User personalization doesn’t need any of the new features, so it’s left untouched)
Due to insufficient error handling or other developer mistakes; pages with web parts sometimes throw unhandled exceptions. I briefly discussed input validation and how to write web parts less likely to throw exceptions in my previous post about web parts. Nevertheless, the problem arises when a web part causes a page to crash. The EPiServer context menu is not available in this situation. Thus you are not allowed to enter web part edit mode in order to correct the problem. In a production environment you’ll have to fix your code and deploy it, or manually delete all web parts on the page directly in the database. There is no need to tell that this is far from ideal. If it’s an important page like a corporate start page it makes it even worse.
Luckily it’s now possible to roll back to the previous page version in EPiServer from edit mode, and the fire is gone. The web part data are stored in Xml which makes it readable and easier to pin point the cause of error. I’ll come back with a 100% exception free and robust approach for writing web parts in a future blog post.
As mentioned the web parts are now stored inside pages in EPiServer. This uses a new property type capable of storing xml data. This is simply a LongString called: "PropertyWebPartPersonalizationState". For the time being it’s very simple and has only a little GUI in edit mode. It was created mainly for development purposes and does not really provide the editors whit any useful information nor functionality, so I recommend that you leave the "Display in Edit Mode" unchecked.
The new property type is optional, if you prefer; change it to the standard LongString property type.
Web parts stored with other providers are continued. Nothing is deleted from your database and your previously persisted web parts will appear as nothing happened. And it doesn’t matter which provider you used before. But the new features will not be effective until the first time you publish web part changes for each page, with the new provider. The provider has an open API which should make it simple to write a convert job for this. The user scope is continued out of the box.
The code was written with performance as a significant factor. I did not try to make the Xml format easy to understand. It was created as a reflection of the real personalization state format. Therefore transformations between xml and binary data are easily done. Any business layers in the API should be written on top of this concrete data layer, and not into it. I’ve done some simple performance tests with the new provider in comparison with the default provider. And the result is that there is no noticeable loss of performance. Visitor loading of massive page with 50 web parts executed 2 milliseconds slower with the new provider, on my lap top.
Installation is really simple and straight forward so I did not create an installer package for it.
- Download the Epinova.WebParts.Providers.dll and save it in your application’s bin folder.
- Add the new provider to the webParts section of your web.config so it looks something like this:
<add name="EPiServerPersonalizationProvider" type="EPiServer.WebParts.Core.EPiServerPersonalizationProvider" connectionStringName="EPiServerDB" />
<add name="EpinovaPageVersionedProvider" type="Epinova.WebParts.Providers.PageDataPersonalizationProvider" connectionStringName="EPiServerDB" fallbackProviderName="EPiServerPersonalizationProvider" />
In order to support backwards compatibility with your previous provider leave your old provider untouched in web.config and put its name in fallbackProviderName. Finally change the default provider. And you are done.
Grab the source code with TortoiseSVN, or download the compiled assembly.
This thigh interaction with EPiServer and the Xml format opens for new possibilities with web parts. I’m currently experimenting with web parts as true dynamic property in EPiServer. It would make it possible to create some web parts in e.g. the right column on the start page and inherit those web parts to all child pages with right column. Still you would be able to add more web parts in all zones on the child pages. The concept is tested out and the open API supports it, but the GUI part remains. It will act as a third scope for web parts in addition to user and shared.
The PersonalizationAdministration methods need to be rewritten in the new provider in order to use the administration methods.
I’ve been using web parts and EPicode.WebParts.Core framework in EPiServer for several years now. This provider is the last piece of the puzzle or missing link, which makes it all fit perfectly together. I hope you’ll find it just as flexible and useful as we do at Epinova. Please do not hesitate to contact me. I you have any questions.