Calling all developers! We invite you to provide your input on Feature Experimentation by completing this brief survey.

 

Ha Bui
Jan 21, 2020
  2288
(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
Level Up with Optimizely's Newly Relaunched Certifications!

We're thrilled to announce the relaunch of our Optimizely Certifications—designed to help partners, customers, and developers redefine what it mean...

Satata Satez | Jan 14, 2025

Introducing AI Assistance for DBLocalizationProvider

The LocalizationProvider for Optimizely has long been a powerful tool for enhancing the localization capabilities of Optimizely CMS. Designed to ma...

Luc Gosso (MVP) | Jan 14, 2025 | Syndicated blog

Order tabs with drag and drop - Blazor

I have started to play around a little with Blazor and the best way to learn is to reimplement some old stuff for CMS12. So I took a look at my old...

Per Nergård | Jan 14, 2025

Product Recommendations - Common Pitfalls

With the added freedom and flexibility that the release of the self-service widgets feature for Product Recommendations provides you as...

Dylan Walker | Jan 14, 2025

My blog is now running using Optimizely CMS!

It's official! You are currently reading this post on my shiny new Optimizely CMS website.  In the past weeks, I have been quite busy crunching eve...

David Drouin-Prince | Jan 12, 2025 | Syndicated blog

Developer meetup - Manchester, 23rd January

Yes, it's that time of year again where tradition dictates that people reflect on the year gone by and brace themselves for the year ahead, and wha...

Paul Gruffydd | Jan 9, 2025