Although it's still in an early stage you may be interested in the book I'm writing about EPiServer 7. It aims to provide a structured way of learning EPiServer development, mixing theory with tutorials. Check out the book on Leanpub!
EPiServer 7 features a remade model for content with support for multiple types of content and an interesting new concept called blocks. In this post I'll visualize and walk through some of the most significant new concepts and changes from a developer's perspective.
EPiServer 7 CMS brings a lot of exciting new functionality both for editors and developers. The most conspicuous being the brand new editorial UI.
There has however also been a lot of changes to the underlying APIs and the way content is represented as classes and objects. In fact, the content/data model in EPiServer CMS 7 has undergone almost as big of a change as the editorial UI.
Understanding the changes made to the UI is fairly straight forward. All one has to do is log in to the edit mode of an EPiServer 7 site and look around.
Grasping the revamped content/data model isn’t as easy as classes and objects are abstract concepts that aren’t visible. I thought I’d make an attempt at visualizing them, hopefully making them slightly less abstract.
In EPiServer CMS 6 content is pages
Before we dig in to the content/data model of EPiServer 7, let’s look at what we’re coming from. In earlier versions of the CMS it could be illustrated like this:
In EPiServer 6 all content is represented by PageData objects, with the exception of uploaded files such as images, PDFs and the like.
A PageData object has a name, an URL and various properties defined by it’s page type. The page type in turn points to a template, an ASPX page, responsible for rendering pages of that type.
The identity of a page is represented by a PageReference object made up of an integer ID property, a string property named RemoteSite which specifies if the page is persisted by a custom page provider and, optionally a version identifier in the form of the integer property WorkID.
With the exception of container pages, a special type of pages whose page type doesn’t point to a template, a PageData object is clearly designed for representing a page that can be viewed by a visitor.
That is, a single PageData object is designed to corresponds to one URL on a site. However, for lack of other entity types for modeling content in the CMS and thanks to PageData objects being very flexible they are often used to represent other types of content.
A common example of such usage is teasers or widgets that are used not as stand-alone pages but as parts of other pages. Another example is to use PageData objects to model structure, such as menus where pages in a certain node in the page tree is only rendered as links to other pages.
EPiServer CMS 7 brings a content model based on actual usage
While the page based content model of earlier versions of EPiServer CMS has proven to be robust and flexible it’s also obvious that it’s been used in ways that it wasn’t designed for. After all, EPiServer CMS is a content management system and not a page management system but the only way to model content was with pages.
To better accommodate real world usage on modern websites and in order to make the new editorial UI possible the new version of the CMS features a more complex and flexible content model. In the new model content isn’t solely represented by pages.
An APIand entity model that maps closely to real world usage is of course great, but it does come at a price in the form of higher complexity. Therefore, an illustration of the content/data model in EPiServer 7 is significantly larger:
Wrapping one’s head around the above image isn’t easy at first glance. The image looks quite different compared to the CMS 6 version. Not to mention that it’s more complex. Let’s go through the main differences and concepts.
Content is more than pages, it’s IContent
In earlier versions of EPiServer pages was the only form of content and much revolved around their object representation, PageData objects. In EPiServer 7 the content concept has been widened and any type of object that implements an interface named IContent is considered content and thereby possible to persist and retrieve through EPiServer’s data access layer for content.
What this means in practice is that DataFactory’s methods, or rather IContentRepository’s methods, such as Get (by ID), GetDefault, Save, GetChildren etc no longer exclusively deals with PageData objects but instead works with any type that implements IContent.
EPiServer 7 utilize this by introducing two new types of content to complement pages, content folders and “shared blocks”.
Of course, this also means that we, as developers of sites or third party products that extend EPiServer CMS are able to create custom types of content that will work with EPiServer’s data access layer.
The IContent interface
The IContent interface declares a number of properties and also inherits the IContentData interface. Thus, any class that implements IContent must have a number of members from both IContent and IContentData:
Properties – a collection of (EPiServer) properties. Essentially the same as the Property property on PageData objects. Declared by IContentData.
IsNull – Declared by IContentData and can be used to check if such an object, while not being null it self, doesn’t have any properties and therefor could or should be treated as being empty.
Name – The name of the content item. Fills the same role as PageName did for pages as it’s used when displaying links to content, be it pages, blocks or other types of content in the CMS.
ContentLink – a unique identifier for the content in the form of a ContentReference. Performs the same roles as the PageLink property did for pages.
ParentLink – a unique identifier for the content’s parent content. All content, except for the root page, still has to have a parent in EPiServer 7. Unlike earlier versions though the parent doesn’t have to be a page but can be any type of content. Typically another page for pages and a content folder for shared blocks.
ContentGuid – also a unique identifier for the content.
ContentTypeID – The integer ID of the corresponding content type.
The combination of ID and RemoteSite is the unique identifier of a single page while the same combination also including WorkID is the unique identifier for a specific version of a single page.
From EPiServer 7’s perspective there was two problems with the PageReference class, it’s name and the name of its RemoteSite property.
As there are now other types of content than pages “PageReference” isn’t a very good name for their identifiers. The “RemoteSite” property was always used to identify which page provider the page belonged to meaning that the name was awkward.
EPiServer’s developers could have fixed these issues by renaming both the PageReference class and the RemoteSite property. That would however be a major breaking change considering how often the PageReference class is used as method parameters in sites built on the CMS.
Instead a new class, ContentReference has been introduced that PageReference now inherits from.
The ID and WorkID properties of PageReference has been moved up to ContentReference. RemoteSite however is left in PageReference and ContentReference instead has a property named ContentProvider that serves the same purpose but with a better name.
While EPiServer 7 now has native support for declaring page types in code it still supports the old way of managing page types from admin mode and the underlying model where a page always has a page type is intact.
However, the PageType class isn’t suitable for other content types as some of its properties only make sense for pages. Therefore EPiServer 7 has two new classes similar to the PageType class, ContentType and BlockType.
A content type contains the collections of property definitions (called page definitions in earlier versions), information about what Web Forms page or MVC controller to use when rendering content of that type as well as other properties previously found in the PageType class.
The PageType class is still around and has some properties relevant only for pages.
BlockType plays the same role for blocks as PageType does for pages. Unlike the PageType class BlockType doesn’t add any additional members and merely overrides a couple of members of ContentType which are used by the editorial UI to provide localized names and such.
IContentRepository, a much sought after abstraction for DataFactory
One criticism of the API in earlier versions of EPiServer CMS was its lack of abstractions, particularly for the DataFactory class.
As the relevant methods for working with pages in the DataFactory class weren’t virtual and not declared by an interface it made it very hard to isolate code that worked with pages from the EPiServer API and all the plumbing, such as the database, that it needed. Therefor it was difficult to create unit level tests for such code or to follow some of the SOLID principles.
EPiServer 7 addresses this with the introduction of the IContentRepository interface which declares all the significant methods for working with pages that DataFactory exposes. DataFactory of course implements this interface.
Similar changes has been made to other parts of the API as well, but IContentRepository is the most sought after.
EPiServer 7 also uses an IoC container, StructureMap, through which an instance of IContentRepository and many other interfaces in the EPiServer API can be obtained.
Backwards compatibility is maintained as the DataFactory class still has an Instance property following the Singleton pattern.
The page tree in edit mode is still there. It no longer shows by default as the new edit mode makes it easy to navigate the site without it in many situations. It has it’s very own button at the top left of the edit mode though and it will continue to be a key component for finding and arranging pages on the site.
The PageData class hasn't undergone any significant changes compared to previous versions and more or less maintain backward compatibility.
Quite a few of the characteristics of PageData objects, such as having an access control list and being versionable has been lifted to separate interfaces, but as PageData implements those interfaces that doesn't have any significant impact on how we work with PageData objects.
While it implements IContent and thereby has properties declared by it such as ContentLink and Name the corresponding old properties such as PageLink and PageName is still around and many of the new properties simply acts as aliases for the old ones.
One significant change from PageData's perspective is of course the introduction of native support for declaring page types in code. That doesn't really affect the content model however and many have used Page Type Builder for that with previous versions of the CMS.
Blocks, inheriting from BlockData, are a special, and at first confusing, type of entity in EPiServer 7. The BlockData class doesn't implement IContent but an instance of BlockData may.
Yep, that's right, a block object may, or may not, be an IContent. If it is, it's possible to persist it just like pages and other types implementing IContent. This is achieved by mixins, a concept commonly used in some other programming languages, but not very common in C#.
Such blocks don’t really have a formal name, they’re just “blocks”, but I’ve found myself talking about them as “local blocks”.
Local blocks are essentially just data containers. They don’t have a publication status, can't have access rights or be versioned themselves. Instead the versioning of their values and other such things are handled along side other properties on the page (or some other form of IContent) that they belong to.
Local blocks allows reuse of groups of properties. This reuse can be on a single content type as well as between different content types.
For instance, we could create a block type that defines two properties for an image, a URL and an alternative text. We can then add three code properties of this type to a single page type effectively creating six properties under the hood. We could also use the same block type on multiple page types.
A block may also be a stand alone entity that is saved individually in the database. Such blocks are referred to as shared blocks and can, after they have been saved, be added to pages, other shared blocks or other types of content.
Adding a shared block to a page doesn't actually save any of it's values to the page but instead involves adding a link to the block to the page in a special type of property called content area.
Shared blocks can have different versions and they maintain a publication status and access control list, just like pages.
Shared blocks are very similar to EPiServer Composer functions and a host of other solutions to the UI composition and content reuse problem created by various EPiServer partners.
Composer and many other solutions involved creating pages that weren't used as stand alone pages but instead added as links to other pages and had their properties rendered as components/widgets (or blocks) on the pages they were added as links to. Shared blocks serve the same purpose but are free of all of the infrastructure required for pages and aren't displayed in the page tree. In fact, one explanation of shared blocks that I've heard is that they are "like lightweight pages".
They are created and edited separately from pages. WYSIWYG editing is accomplished by, optionally, providing a template in which they are rendered while editing. Existing shared blocks are not listed in the page tree but in separate gadget. They can then from there be added to content areas on pages or other shared blocks using drag-n-drop.
Shared blocks are created by using the IContentRepository.GetDefault() method just like pages. During this creation, at runtime, a class will be generated that inherits from the block's original type. A proxy class. This class will implement IContent as well as a number of other interfaces, such as IVersionable.
While pages are naturally grouped in the page tree blocks and custom content isn’t showed there and while it would be possible to just list all blocks in a gadget that wouldn’t be very practical for editors of a site with many blocks.
Content folders are instances of the ContentFolder class and just like pages and shared blocks they implement the interfaces necessary for having different language versions and an access control list. However, unlike pages and shared blocks content folders cannot be versioned.
With earlier versions of EPiServer CMS it was very common to use pages as a means to persist other types of data that wasn’t pages or necessarily even content on the site.
While there were other means of storing non-page data, such as the DDS or custom database tables, storing it as pages allowed us to get an administrative interface (although not necessarily well suited) for it for free as well as access rights handling, versioning etc.
For those reasons doing so is something that I’ve used often myself and advocated for building integrations with external systems.
One downside of storing non-page data as pages is that we risk crowding the page tree for editors as well as risk exposing it to users who shouldn’t see it unless we explicitly filtered it out in page listings. EPiServer 7 provides an elegant solution to this by letting us save any object through the data access layer (DataFactory/IContentRepository) for content as long as it implements IContent.
If we create custom content classes that don’t inherit from PageData and save them through an IContentRepository they won’t show up in the page tree. Likewise, if they don’t inherit from BlockData they won’t show up in the blocks gadget either.
Of course this means that if we do want to look at such saved content we may need to build some custom functionality for listing it and linking to it in the edit UI as it won’t be listed in the page tree or shared blocks gadget. Having done so, or by figuring out the edit URL for the content we may however still utilize the CMS’ editing functionality for it.
To summarize, in EPiServer 7:
- Content is more than pages
- An IContentRepository, DataFactory, can persist all objects that implement IContent
- Pages are complemented with two new types of content, blocks and content folders
- Blocks can be used to reuse code and editorial content and shared blocks can be used to compose parts of pages
- A lot has changed, but backwards compatibility and familiarity is high
Many of the changes and new features of EPiServer 7 are made to align the CMS with how it’s used by us partners. Blocks solves the problem of dynamic UI composition and reuse of UI components in a far more elegant way than Composer did.
The new content/data model draws on the flexibility for modeling various kinds of data that PageData objects offered in previous versions without forcing all objects to be listed in the page tree and available for display on the site.
With a more powerful and flexible model for content comes complexity. EPiServer’s developers have however done a great job at maintaining the core strengths of the CMS. The PageData class is still there and it’s almost fully backwards compatible. So is the page hierarchy concept and data access layer.
The most confusing part of the new API is probably blocks and the concepts of shared and local blocks where one instance of a class may implement IContent while another may not. This solution does allow us not only to reuse definitions of groups of properties but also the components used for rendering though.
Learn more about EPiServer 7
Want to learn more about EPiServer 7 development? Check out the book that I'm working on!