Virtual Happy Hour this month, Jun 28, we'll be getting a sneak preview at our soon to launch SaaS CMS!

Try our conversational search powered by Generative AI!

Introduction

In EPiServer 7 the "blocks" feature was introduced, allowing developers and content editors to divide pages into reusable components. This feature matches to some extent the one available in EPiServer Composer. In version 7 blocks are are part of the CMS core and are treated as a first class content type, which does not allow it to be fully backwards compatible with Composer.

This document describes how to migrate a site built based on Composer to a new solution based on EPiServer 7 CMS. The migration process is divided into two parts, migrating the solution and migrating the content. Each step is done through the use of migration tools made available by EPiServer.

The process described here is not an upgrade that is done in place over the current database, but a migration process that will bring content over to a new site. It requires manual steps and tasks that need to be completed by a developer. The migration is based on utilizing the import/export functionality in CMS, which means that it has a few limitations in what content it includes, for instance only published versions of content is included. See below for a list of limitations.

As the feature set in EPiServer 7 CMS does not completely match the one in Composer, there may be functionality that cannot be brought across to the new CMS site. Only features that have a direct equivalent in CMS are supported. See below for a list of differences and migration limitations.

Requirements

The migration tools are available as an open source project on GitHub. It is assumed that you have general knowledge of EPiServer 7 CMS. If not, it is recommended to download and study the Alloy sample site provided with EPiServer 7 CMS. Also refer to the Breaking Changes section in the CMS 7 SDK before starting the migration, since this guide does not cover an upgrade from EPiServer CMS 6 to EPiServer 7 CMS.

In addition to this, the following is recommended for a succesful migration:

  • EPiServer Composer 4 R2 - the migration tools and process have only been verified with Composer 4 R2. It is recommended to upgrade the solution to this version before starting the migration.
  • EPiServer 7 CMS/7.1 Patch 3 - the target solution should be set up with CMS version 7.1 with Patch 3. If you want to use a later version, you can upgrade your CMS after the migration has been completed.
  • .NET 4.0 - the computer where the code generation tool is run must have .NET 4.0 installed.

Migration task workflow

This is a brief overview of the migration steps:

  1. Export data from the Composer site
    a. Export page types
    b. Export settings (frames, tabs, categories and visitor groups)
    c. Export content (pages and dynamic properties)
  2. Create a new EPiServer 7 CMS solution
    a. Import settings package
    b. Configure site
    c. Install the EPiServer.ComposerMigration package
    d. Migrate users and groups (optional)
  3. Migrate the solution
    a. Copy the old solution files and configuration.
    b. Generate content type classes and add to solution
    c. Refactor Composer templates
  4. Migrate the content
    a. Import the Composer content package
  5. Remove the EPiServer.ComposerMigration package

Migrating your solution

The first step is to set up a new EPiServer 7 CMS solution. You can start with an empty solution and copy the files and configuration of your old solution that you want to keep. Alternatively, take your current solution and update it to EPiServer 7 CMS. In either case you should start with an empty database created through the EPiServer Deployment Center.

Once you have the new site running you need to configure it in the same way as your previous site. Some settings can be migrated using the export/import feature. Create an export package containing all settings that you want to bring across to the new site. This includes frames, tabs, categories and visitor groups.

It is recommended that you import this package into your empty solution before adding any content types. This way you will ensure that all tabs are added correctly and that you do not have to do any manual corrections afterwards.

Not all settings can be migrated this way, so you will have to do some configuration manually. If you have a globalized site, make sure that the website languages are set up in the same way. If your solution includes any custom property types, these may need some modification to work in EPiServer 7 CMS. Refer to the SDK for EPiServer 7/7.1 CMS for more information about changes to the interface regarding custom properties.

Content types

The recommended method to define page types in EPiServer 7 CMS is to create attributed model classes in your solution. For block types this is the only method as you cannot create blocks from the Admin view. It is therefore required to define all Composer Functions using model classes in your CMS solution. To alleviate this process, EPiServer has created a console application (cm.exe) that generates classes for your content types.

If you are using Page Type Builder or related module to enable strongly typed page types, it is recommended that you keep your existing page types and instead only update the syntax to match the one in EPiServer 7 CMS. In that case you might want to use the tool for some syntax hints. You can also limit the code generator to only generate block types.

First, create an export package that includes all page types from your Composer site, including the Composer Function page types. If you want strongly typed page types, include these in the package as well.

Figure 1 - Exporting all page types

Tip: double click the checkbox of the last page type to select all. The code generator will exclude the system page types automatically.

Then run the console application, providing the location of the export file. This will generate content type class files for all block and page types to be included in your solution. An exported page type is assumed to be a block, and created as a block type if the template filename has an .ascx extension.

Figure 2 - Resulting folder structure example 

Pages and blocks are by default placed in separate namespaces and folders. Types with a name using the de facto group naming standard, for instance "[Group] Name", will be placed in a separate namespace/folder named after the group name.

You can provide arguments to the application to control the way that the classes are generated, including namespaces, type suffixes and imports. The table below describes all arguments that can be passed into the application. Note that all boolean arguments do not take a value and will be set to "true" if included.

Argument   Default value Description
-s, --source ExportedData.episerverdata Path to the source package that you want to generate classes from. The path can be either relative or absolute.
-o, --output output  Path to the folder where class files should be generated. Can be relative or absolute.
-n, --namespace MySolution.Models Base namespace that all generated model classes should be placed in.
--page-namespace Pages  Namespace where all page type classes will be placed. Can be set to empty to place in the base namespace.
--page-suffix Page  Suffix that all page type class names must end with. Can be set to empty if no suffix should be used.
-p, --page-base-class EPiServer.Core.PageData Base class that all page type classes will inherit from.
--block-namespace Blocks  Namespace where all block type classes will be placed. Can be set to empty to place in the base namespace.
--block-suffix Block  Suffix that all block type class names must end with. Can be set to empty if no suffix should be used.
-b, --block-base-class EPiServer.Core.BlockData Base class that all block type classes will inherit from.
-i, --imports (none) Namespaces that should be imported (using) in all classes. Separate each namespace with a space. Note that some namespaces are added automatically by the tool if they are needed.
-g --group-namespaces false  Indication if content types that has a group name prefix should include this name as a part of the namespace.
-w, --legacy-wrapper false  Indication if properties of unknown types should get a UIHint to force a legacy property wrapper to appear.
-u, --nullable false  Indication if properties of value types should be generated using their nullable equivalents.
--editable false  Indication if properties not visible in edit mode should be generated using the Editable attribute rather than with ScaffoldColumn.
-?, --help   Displays this table of all arguments that can be provided.

Table 1 - Command-line arguments for Content type generator

Example:

cm.exe -s "..\ContentTypes.episerverdata" -o "C:\Output\MySolution.Web\Models" -n "MySolution.Models" -i "MySolution.Utils" "MySolution.Core" -ugw

Names of content types and their properties are modified to be valid identifiers.

  • Spaces and symbols are removed and Pascal casing is applied, e.g. "My article!" becomes "MyArticle". Underscores are allowed.
  • Numbers at the start of the name are replaced by their textual name, e.g. "2 Column page" becomes "TwoColumnPage".
  • Non-English Latin characters are replaced by their simplified visual equivalent where possible, for instance "å" is replaced by "a". Some Latin characters and all non-Latin characters are not supported.

The name conversion takes a best effort approach and does not guarantee that valid identifiers are generated. There are edge cases where already valid identifiers will be modified and other where invalid identifier names will be created. If some of your type or property names are not converted properly the recommendation would be to change these on the Composer site before starting the migration.

While you can change most other settings on your content types after the code generation is completed, it is important that you do not change the Display Name of the content types or any property names before running the content import. The display name is used by the CMS import functionality to match content with types, and the property names to import the property data correctly.

All block types are set to be available in Edit view, as Composer Function page types are set to be unavailable to not be displayed in the Create page dialog.

Properties based on a value type are mapped to their equivalent type. If you prefer to use nullable types, you can provide the code generation tool with the -n argument, and all standard value types will be changed to their nullable equivalent. If you only want to use nullable types for some specific properties you can change the property type manually after generation.

Composer content areas will be changed to CMS content areas. They are also changed to be "Culture Specific" and "Visible in Edit" mode by default. This can be changed on the model classes if desirable.

Function Picker properties as used by dynamic functions in Composer, will be converted to Content Reference properties.

Limited support for Default values are added through the inclusion of a SetDefaultValues method. If you are using a default archive page, frame and/or publishing date offsets, you will have to include these manually.

The content type generation tool does not support the following settings in its current version:

  • Available page types
  • Access rights on block types

The tool only supports generation of C# classes.

Templates

The next step is to update your page and block templates to work with CMS controls rather than the Composer-specific ones. You will be able to continue to use your WebForms templates in the same format as they were set up with Composer. For most solution there are only a few steps required to get them working with EPiServer 7 CMS. You will need to update block and page templates that have Composer content areas, and remove any references to Composer namespaces. These will all begin with "Dropit.Extension". This includes any using and @Import statements.

Pages

In the .aspx templates, replace all controls of type ExtensionContentArea with the standard EPiServer Property control. For the same control, add a PropertyName attribute with the same value as the ID attribute or simply change the attribute name to PropertyName if you are not referencing the controls using this ID. The Description attribute can be removed completely as it is no longer used.

Example:

EPiServer Composer

<Extension:ExtensionContentArea ID="SomeAreaName" Description="Some description" />

EPiServer 7 CMS

<EPiServer:Property PropertyName="SomeAreaName" />

If you are moving to strongly typed page types, change your template classes to inherit from TemplatePage<T>. If you are currently using your own base class for your page templates and for some reason cannot change the base class to inherit TemplatePage<T>, you can get around this by adding the IRenderTemplate<T> interface instead.

Blocks

Replace all usage of the Composer Property control with the standard EPiServer Property control. If you do not mind keeping the "Extension" prefix around, you can map it to the EPiServer.Web.WebControls namespace in the EPiServer.Web.WebControls assembly in web.config.

Example:

EPiServer Composer

<Extension:Property PropertyName="SomeProperty" />

EPiServer 7 CMS

<EPiServer:Property PropertyName="SomeProperty" />

If the block is a layout block containing content areas, you will need to do the same control replacement as you did for page templates.

Change your template classes to inherit from BlockControlBase<T> instead of BaseContentFunction. If you are currently using a base class for your block templates and for some reason cannot change the base class to inherit BlockControlBase<T>, you can get around this by adding IRenderTemplate<T> interface in the same way as you did for page templates.

If the namespace of your templates does not match your folder structure, you will have to attribute the template with a TemplateDescriptor and provide it with the Path argument pointing out the location of your template.

Where your current template code accesses block properties using ContentFunctionData, update it to use CurrentBlock instead. It is recommended to use the strongly typed accessors to work with property values, but you can still access PropertyData objects and values through the Property collection. Be aware that the indexer property of these two classes is implemented slightly differently. Where ContentFunctionData["PropertyName"] refers to the property object itself, CurrentBlock["PropertyName"] is a reference to the value of the property.

Example:

 EPiServer Composer

PropertyData property = ContentFunctionData["PropertyName"];
PropertyData property = ContentFunctionData.Property["PropertyName"];
object propertyValue = CurrentBlock.Property["PropertyName"].Value;

EPiServer 7 CMS

PropertyData property = CurrentBlock.Property["PropertyName"];
object propertyValue = CurrentBlock["PropertyName"];
object propertyValue = CurrentBlock.Property["PropertyName"].Value;

If you have used the IsEditMode property on BaseContentFunction to find out if the page is rendered in edit mode, you can get the same information from EPiServer.Editor.PageEditing.PageIsInEditMode.

Dynamic Function blocks

There is no feature in CMS that is a direct equivalent to Dynamic Functions in Composer. Therefore, all Composer Function Picker properties are converted into Content Reference properties. This will allow us to load the referenced block data and render it using its own template. The code snippet below shows an example on how the code behind of such a dynamic block template could look like. The corresponding .ascx file for this template is empty.

public partial class DynamicBlock : BlockControlBase<Models.Blocks.DynamicBlock>
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
var contentRenderer = new ContentRenderer
{
CurrentData = Locate.ContentLoader().Get<BlockData>(CurrentBlock.FunctionPicker)
};
Controls.Add(contentRenderer);
}
}

Migrating your content

The second part of the migration is about getting the Composer content migrated to the new EPiServer 7 CMS site. This will be done using the import/export functionality in CMS together with a small plugin provided by EPiServer. The plugin will transform the content data coming from a Composer site allowing it to be imported as CMS blocks.

Exporting the content

To get content from Composer we will need to create an export package containing the main content, including pages and dynamic properties. Include all pages by selecting the root page as the part of the structure you want to export. If you want your files migrated as well, keep the "Export files..." option selected.

Figure 3 - Main content export

If you want to break up your export into smaller packages, you will have to export your global blocks separately. This is done by using the dedicated Composer export tool. This tool will allow you to select the global blocks that you want to export.

Importing packages

Before importing your content package, make sure that your solution is fully deployed, including visitor groups, content types, custom properties and dynamic content definitions.

Start the import by selecting your package and then choosing the root page as the destination page where you want your page imported. If you have any dynamic or page properties that are set on the root page, these will not be imported and will have to be set manually.

If you have broken down your export into smaller packages, you should always start with your global blocks package and then move on to page packages.

When running the import, make sure that you have checked "Update existing content items with matching ID".

Figure 4 - Content import

Once the import is started the migration module will scan the content and collect information about the items in the package. This includes block layout, personalization and language settings.

As the import processes each content item, the module will inspect and transform each Composer content item to match the new CMS 7 format. The following transformations will take place during this stage:

  • Global blocks in Composer are placed directly in the global block repository
  • Composer containers are replaced with block folders and placed in the global block repository.
  • Blocks are given a name based on their type plus the value of any heading property if found.
  • Block folders are given the same name as the page they are holding blocks for.
  • The language branch of local blocks is reset to mirror the page it was placed on.
  • Composer content areas are changed into CMS content areas.
  • The block layout is extracted from the parent page and recreated in CMS format in the right content area of the page or layout block placed on the page.
  • Personalization information is extracted from their Composer container and applied to the Content Area data where this information is stored in EPiServer 7 CMS. Only the standard Composer personalization blocks are supported.
  • Composer-specific properties and page types are completely excluded from the import.
  • Page type names are stripped of any Composer prefixes.
  • Property names are modified to match valid identifiers as created by the Code Generator. Page type properties can be excluded from this transformation by configuring a setting as described below.
  • Composer Function Picker properties are changed into Content Reference properties, and their values converted to match the new type.

Note that a Composer page that has not yet been published (regardless of publishing dates) will not include the block configuration and will therefore not be migrated properly. Blocks included on any unpublished pages will be included in the export package, but the migration tool will filter these out unless configured otherwise. If they are included, they are not guaranteed to be imported in the correct language, or placed in a block folder name, or associated with the page.

Migrated global blocks will always have the primary language of the Composer site, regardless of the language of the pages they are included on. If you want a global block to be available for another language, you can translate the block manually using the CMS interface.

As all local block folders will be placed directly under the Global block folder. This will result in a somewhat hard to manage structure, especially if there are lots of pages using the same name. However, once imported the folders can be restructured. 

The configuration options below can be used for the migration module by adding an entry to the AppSettings collection.

Key Default value Description
ComposerMigration:Enabled true  Set this to false to disable the migration module completely.
ComposerMigration:
BlockNameProperties
Heading, Header,
Headline, Title,
Name
A list of properties that will be used to give imported blocks a friendly name.
ComposerMigration:
TransformPageTypeIdentifiers
true  Set this to false if you don't want the module to modify the names of properties on your page types. This can be useful if you haven't generated model classes for your page types.
ComposerMigration:
IncludeUnusedBlocks
false  Set this to true if you want to import blocks in the package that is not placed on any page.

Table 2 - Import settings

Limitations

Export/import

  • Unpublished Composer pages will not include any content area information such as block layout, as Composer only saves layout information when the page is published.
  • Only the published version of a page is included, any version history will be lost.
  • Only files that are used on a page are included when exported unless manually selected.
  • Data stored in the Dynamic Data Store must be programmatically included in the package for it to be brought across. Code showing how this can be done can be found in the Code section of EPiServer World.
  • Form definitions are included, but any form data collected must be exported manually from their respective pages.
  • Any dynamic or page property values set on the root page will not be included in the import.
  • Users and groups will not be migrated. If a local membership database is used, migrating this can be done by following a separate procedure as described for instance in Migrating ASP.NET membership users from one database to another.
  • Many system settings will have to be re-configured. This includes but may not be limited to:
    - Scheduled job configuration
    - Website languages and access rights to these languages
    - Mirroring
    - Workflows
    - Content channels
    - Search configuration
  • Some default value on page types will not be exported properly:
    - Start and stop publish offsets
    - Archive page

Differences between Composer and CMS

There are a number of features in Composer that have no direct equivalent in EPiServer 7 CMS, these will not be supported by the migration tool either.

  • Any Composer dynamic properties will be imported to a plain text property containing the raw XML data.
  • Composer templates have no direct CMS equivalent and cannot be migrated.
  • Composer categories are not used by CMS and there is no reason to migrate these. If categorization of blocks are desired, use the group name setting of the Content Type attribute.
  • There is no equivalent configuration to the feature "Available blocks for areas" in Composer and it will therefore not be migrated.
  • Composer supports annotating Global Blocks with Description and Notes. CMS has no equivalent and these will not be migrated.

Feedback

Each implementation is different and there might be migration steps described here that do not work in your solution, or areas important to the migration that are not covered here. The source code for the migration tools is available as an open source project on GitHub, in order for you to be able to customize and adapt the procedure for your own solutions. Any feedback you may have to improve the migration process is much appreciated.

References