Ha Bui
Jan 21, 2020
(2 votes)

Export content with versions

Hi guys,

As you knows, currently, EPiServer just supports latest published version in languages when you uses the default admin tool (Export / Import Data).

Of course in most case, this is enough but sometimes you need to export / import with versions as well. (at least in case editor created a common draft and doesn't want to rework)

How can we archive that? The solution (of-course by default EPiServer doesn't support it then you also should accepts some tips and tricks in code). No worry so much because of we still base on EPiServer default one quite a lot)

Below is our solution:

  1. Use structure map interceptor to apply our tips and tricks via InitializeModule
  2. Intercep on DefaultDataExporter to export contents with versions (instead of only latest published one)
  3. Intercep on DefaultContentImporter to import content version

Some small things should be considered like:

+ Keep saved date

+ Keep saved by (require user migrating)

Okay, lets start steps by steps:

1. Use structure map interceptor to apply our tips and tricks via InitializeModule

Your code should looks like this:

public class DataExporterInitializationModule : IConfigurableModule, IInitializableModule
    public void ConfigureContainer(ServiceConfigurationContext context)
         context.ConfigurationComplete += (o, e) =>
             // Your interceptor logic here
             e.Services.Intercept<IDataExporter>((locator, defaultDataExporter) =>

             e.Services.Intercept<IContentImporter>((locator, defaultDataImporter) =>

2. Intercep on DefaultDataExporter to export contents with versions (instead of only latest published one)

We will override ExportContent method, check our new option (ExportVersion) or just keep default one:

protected override void ExportContent(XmlTextWriter xml
    , ContentReference contentToExport
    , IContentTransferContext context
    , ITransferContentData transferContent) {
	if (_transferExportOptionsEx.ExportVersion) {
		ExportContentWithVerion(xml, contentToExport, context, transferContent, base.ExportContent);
	else {
		base.ExportContent(xml, contentToExport, context, transferContent);

Then get all versions  and build raw transfer content data via:

protected virtual TransferContentData BuildRawTransferContent(IContentTransferContext context
            , List<ContentLanguageSetting> contentLanguageSettings
            , IRawContentRetrieverEx rawContentRetieverEx, IContent version)
            var transferVersionContent = new TransferContentData()
                RawContentData = rawContentRetieverEx.CreateRawContent(version)
            if (contentLanguageSettings != null)
                transferVersionContent.ContentLanguageSettings = contentLanguageSettings;

            PropertyExportContext propertyExportContext = new PropertyExportContext
                TransferContext = context,
                TransferOptions = Options,
                Output = transferVersionContent.RawContentData,
                Source = version
                , transferVersionContent.RawContentData.Property
                , propertyExportContext);

            return transferVersionContent;

Viola! You have just done a big task! Export Content With Version task!

The last one is import the exported content versions (keep version status, keep saved date and saved by)! Take a coffee and relax before we go to the rest!

... (coffee break)

3. Intercep on DefaultContentImporter to import content version

This one is hard part and get much of your pains :( Remember do testings carefully. No pains no gains right :))

We should override IContent Import method (protected override IContent Import) with some tricks as below:

3.1 Tricks to keep language content version: (because of our exported versions are mixing with multiple languages and those versions are flatten)

CultureInfo originalSelectedLanguage = null;
            if (importedContentData.SelectedLanguage != null)
                originalSelectedLanguage = CultureInfo.GetCultureInfo(importedContentData.SelectedLanguage.Name);

            var selectedLanguageAction = importedContentData.GetType().GetProperty("SelectedLanguage");
            var contentLanguage = importedContentData.GetLanguageBranch();
            if (!string.IsNullOrEmpty(contentLanguage))
                selectedLanguageAction.SetValue(importedContentData, CultureInfo.GetCultureInfo(contentLanguage));

3.2 Tricks to keep content versions status (because of EPiServer just "accept" publish action OMG, other actions just for new one):

var status = importedContentData.GetStatus();
            var propSaveAction = context.GetType().GetProperty("SaveAction");
            SaveAction originalSaveActions = SaveAction.Publish | SaveAction.SkipValidation;
            if (!string.IsNullOrEmpty(status) && int.Parse(status) < (int)VersionStatus.Published)
                propSaveAction.SetValue(context, SaveAction.CheckOut | SaveAction.ForceNewVersion | SaveAction.SkipValidation);

3.3 Small things to keep saved date and saved by

ContextCache.Current["PageSaveDB:ChangedBy"] = string.Empty;
ContextCache.Current["PageSaveDB:PageSaved"] = string.Empty;

Okay, last one is: call to base method:

                    , requiredDestinationAccess
                    , context, options, out importedPageGuid);

That is all ? Not yet, something you should deal with:

+ In your interceptor class of DefaultContentImporter, please consider to save original data and then set it back right after content version is imported to avoid any impact.

+ EPiServer DefaultContentImporter only save your version if it's the new one :(

We can resolve those things like this:

                // check the current content version is new or not
                var importedContentGuid = new Guid(importedContentData.GetContentGuid());
                var handleContentGuidMethod = defaultContentImporter.GetType()
                    .GetMethod("HandleContentGuid", BindingFlags.NonPublic | BindingFlags.Instance);
                var guid = (Guid)handleContentGuidMethod.Invoke(
                    , new object[] { importedContentGuid, context });
                PermanentLinkMap permanentLinkMap = permanentLinkMapper.Find(guid);

                var baseContent = base.Import(importedContentData
                    , requiredDestinationAccess
                    , context, options, out importedPageGuid);

                if (permanentLinkMap != null && (context.SaveAction & SaveAction.Publish) != SaveAction.Publish)
                    contentRepository.Save(baseContent, context.SaveAction, requiredDestinationAccess);

                return baseContent;
            catch (Exception ex)
                selectedLanguageAction.SetValue(importedContentData, originalSelectedLanguage);
                propSaveAction.SetValue(context, originalSaveActions);
                ContextCache.Current["PageSaveDB:ChangedBy"] = orgPageSaveDBChangeBy;
                ContextCache.Current["PageSaveDB:PageSaved"] = orgPageSaveDBPageSaved;

Congratulations! Now please try it by your self with AlloyMvc template! Below is my result:

Full source code can be found under: https://github.com/NitecoOPS/ExportImportWithVersion

Hope that the article will help to reduce your headache! ;)


Happy Coding!


Jan 21, 2020


Please login to comment.
Latest blogs
IDX21323 - RequireNonce is true, Nonce was null

We have multiple clients configured with Azure Active Directory (Microsoft Entra) for requiring authentication when accessing their website. The...

David Drouin-Prince | Oct 1, 2023 | Syndicated blog

Minimum Detectable Effect in Optimizely Web Experimentation

Understanding Minimum Detectable Effect Minimum Detectable Effect (MDE) is a core statistical calculation in Optimizely Web Experimentation. In...

Matthew Dunn | Oct 1, 2023 | Syndicated blog

Configured Commerce - Introduction to Long-Term Support (LTS) Releases

First off, for those who have not had a chance to meet me yet, my name is John McCarroll, and I am the Technical Product Manager for the Optimizely...

John McCarroll | Sep 29, 2023

Auto-translate with OpenAI in Optimizely CMS

You can now auto-translate content using your favorite online AI service, inside the old trustworthy Episerver.Labs.LanguageManager!

Tomas Hensrud Gulla | Sep 29, 2023 | Syndicated blog