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.
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:
This is a brief overview of the migration steps:
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.
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.
|-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
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.
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:
The tool only supports generation of C# classes.
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.
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.
<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.
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.
<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.
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.
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)
var contentRenderer = new ContentRenderer
CurrentData = Locate.ContentLoader().Get<BlockData>(CurrentBlock.FunctionPicker)
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.
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.
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:
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.
|ComposerMigration:Enabled||true||Set this to false to disable the migration module completely.|
|A list of properties that will be used to give imported blocks a friendly name.|
|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.|
|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
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.
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.