Ha Bui
Jan 21, 2020
  3007
(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:

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
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
            };
            _propertyExporter.ExportProperties(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:

base.Import(importedContentData
                    , 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:

try
            {
                // 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(
                    defaultContentImporter
                    , 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)
            {
                Logger.Error(ex);
                throw;
            }
            finally
            {
                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!

.HaBui

Jan 21, 2020

Comments

Please login to comment.
Latest blogs
Optimizely CMS SaaS Migration Tool

Migrating and synchronizing environments in  Optimizely CMS SaaS can be challenging, particularly when managing content types, display templates, a...

Hieu Nguyen | Mar 4, 2026

OpenAI-Driven AI Assistant for TinyMCE in Optimizely CMS 12

The Tiny.AI add-on enhances Optimizely CMS 12 by seamlessly integrating OpenAI directly into the TinyMCE editor. It empowers editors to rewrite,...

Adnan Zameer | Mar 3, 2026 |

Your first SAAS Project -- Setup

Hey everyone, When I first started Remko's StarterKit, I honestly had no idea what I was doing. After spending a couple of months working through i...

PuneetGarg | Mar 3, 2026

Catalog Traversal with Hangfire. Part 3: Advanced Job Management

In Part 1, I showed how to build a memory-efficient catalog traversal service, and in Part 2, I demonstrated practical patterns using Optimizely’s...

Stanisław Szołkowski | Mar 3, 2026 |