Custom migration behavior

This article contains
NEW TO SITEFINITY?

Overview

You can extend the SiteSync module to implement custom migration behavior. This happens with the help of the ICustomMigrationBehavior interface, which provides extensibility point for the SiteSync migration operation.
Once you implement the interface, you need to register your instance in the ObjectFactory.
The same implementation should be used on the source and target servers.

Procedure

  1. Implement the ICustomMigrationBehavior interface.
    There are three main points where you can plug and intervene in the migration process using this interface:
    • Using ProcessItemExport method - where you can modify the WrapperObject that will be sent to the destination
    • Using ProcessItemImport method - where you validate the migration operation on the target and process the imported data
    • Using HandleMigrationCompleted method - Handles the logic on the target server after the migration process completes

    You can also use the SkipLoadingDependencies property - a value indicating whether implicitly syncing item dependencies should be skipped. You can set that to true, if you will migrate all items at once and the dependencies calculation will only slow things down.
    You can see the sample below for an example of implementation.

    using System;
    using Telerik.Sitefinity.Data.ContentLinks;
    using Telerik.Sitefinity.DynamicModules.Model;
    using Telerik.Sitefinity.Model;
    using Telerik.Sitefinity.Publishing;
    using Telerik.Sitefinity.SiteSync;
    using Telerik.Sitefinity.Utilities.TypeConverters;
    namespace SitefinityWebApp
    {
    public class CustomMigrationBehavior : ICustomMigrationBehavior
    {
    /// <summary>
    /// Gets a value indicating whether implicitly syncing item dependencies should be skipped
    /// </summary>
    public bool SkipLoadingDependencies
    {
    get { return false; }
    }
    /// <summary>
    /// Prepares an item for migration
    /// </summary>
    /// <param name="item">The exported item</param>
    /// <param name="obj">The wrapper object containing exported information</param>
    /// <param name="context">The export context</param>
    public void ProcessItemExport(object item, WrapperObject obj, ISiteSyncExportContext context)
    {
    if (item is DynamicContent dynamicContent)
    {
    // Example on how to tell the destination server to create new item with different ID.
    // All properties of the WrapperObject could be altered here.
    this.MapForceCreateItem(dynamicContent, obj, context);
    }
    }
    /// <summary>
    /// Validates the import operation
    /// </summary>
    /// <param name="wrapperObject">The wrapper object containing import information</param>
    /// <param name="importTransaction">The import transaction</param>
    /// <returns>A value indicating whether the import operation is valid</returns>
    public bool ProcessItemImport(WrapperObject wrapperObject, ISiteSyncImportTransaction importTransaction)
    {
    var itemType = wrapperObject.GetPropertyOrDefault<string>(ObjectTypeId);
    var type = TypeResolutionService.ResolveType(itemType, false);
    if (type != null && typeof(DynamicContent).IsAssignableFrom(type))
    {
    // Example on how to change the "OriginalContentId" property to match the new item ID
    this.Map(importTransaction.Response.Mappings, wrapperObject, "OriginalContentId");
    }
    return true;
    }
    /// <summary>
    /// Handles the logic on the target server after migration completes
    /// </summary>
    /// <param name="migrationMappings">The migration mappings</param>
    /// <param name="siteInfo">The target site info</param>
    public void HandleMigrationCompleted(SiteSyncMigrationMappings migrationMappings, SiteSyncSiteInfo siteInfo)
    {
    var linksManager = ContentLinksManager.GetManager();
    var links = linksManager.GetContentLinks();
    foreach (var link in links)
    {
    this.Map(migrationMappings, link, "ChildItemId", link.ChildItemType);
    this.Map(migrationMappings, link, "ParentItemId", link.ParentItemType);
    }
    linksManager.SaveChanges();
    }
    #region Helper methods
    private void Map(SiteSyncMigrationMappings migrationMappings, object obj, string propertyName, string mapType = null, string mappedPropertyName = ItemId)
    {
    if (obj is WrapperObject wrapperObject)
    {
    var itemType = wrapperObject.GetPropertyOrDefault<string>(ObjectTypeId);
    if (wrapperObject.HasProperty(propertyName))
    {
    var mapping = migrationMappings.GetMapping(mapType ?? itemType, wrapperObject.GetProperty(propertyName).ToString(), mappedPropertyName);
    if (mapping != null)
    {
    var currentProp = wrapperObject.GetPropertyOrNull(propertyName);
    if (currentProp != null && currentProp.GetType() == typeof(Guid))
    {
    wrapperObject.SetProperty(propertyName, Guid.Parse(mapping));
    }
    else
    {
    wrapperObject.SetProperty(propertyName, mapping);
    }
    }
    }
    }
    else
    {
    var itemType = obj.GetType();
    var property = itemType.GetProperty(propertyName);
    if (property != null)
    {
    var mapping = migrationMappings.GetMapping(mapType ?? itemType.FullName, property.GetValue(obj, null).ToString(), mappedPropertyName);
    if (mapping != null)
    {
    if (property.PropertyType == typeof(Guid))
    {
    property.SetValue(obj, Guid.Parse(mapping));
    }
    else
    {
    property.SetValue(obj, mapping);
    }
    }
    }
    }
    }
    private void MapForceCreateItem(IDataItem dataItem, WrapperObject obj, ISiteSyncExportContext context)
    {
    var mapping = context.GetMapping(dataItem.GetType().FullName, dataItem.Id);
    if (mapping == null)
    {
    // The item is not existing on the target server and it should be created even if the item with same id already exists.
    obj.AddProperty(ForceCreateNewItem, true);
    }
    else
    {
    var newId = (Guid)mapping;
    // The item is already created on the target server.
    obj.AddProperty(Id, newId); // The identity of the item can't be changed, so we add the Id as additional property.
    obj.SetOrAddProperty(ObjectId, newId);
    }
    }
    #endregion
    private const string Id = "Id";
    private const string ItemId = "ItemId";
    private const string ObjectId = "objectId";
    private const string ObjectTypeId = "objectTypeId";
    private const string ForceCreateNewItem = "ForceCreateNewItem";
    }
    }

  2. Register the custom items in the Global.asax class.

  3. You can see the sample below for an example on how to register your instance.

    using System;
    using Telerik.Microsoft.Practices.Unity;
    using Telerik.Sitefinity.Abstractions;
    using Telerik.Sitefinity.SiteSync;
    namespace SitefinityWebApp
    {
    public class Global : System.Web.HttpApplication
    {
    protected void Application_Start(object sender, EventArgs e)
    {
    // Subscribe to SiteSyncModule.Initialized
    SiteSyncModule.Initialized += SiteSyncModule_Initialized;
    }
    private void SiteSyncModule_Initialized(object sender, EventArgs e)
    {
    // Registering the custom ICustomMigrationBehavior implementation
    ObjectFactory.Container.RegisterInstance<ICustomMigrationBehavior>(new CustomMigrationBehavior());
    }
    }
    }

Want to learn more?

Increase your Sitefinity skills by signing up for our free trainings. Get Sitefinity-certified at Progress Education Community to boost your credentials.

Get started with Integration Hub | Sitefinity Cloud | Sitefinity SaaS

This free lesson teaches administrators, marketers, and other business professionals how to use the Integration hub service to create automated workflows between Sitefinity and other business systems.

Web Security for Sitefinity Administrators

This free lesson teaches administrators the basics about protecting yor Sitefinity instance and its sites from external threats. Configure HTTPS, SSL, allow lists for trusted sites, and cookie security, among others.

Foundations of Sitefinity ASP.NET Core Development

The free on-demand video course teaches developers how to use Sitefinity .NET Core and leverage its decoupled architecture and new way of coding against the platform.

Was this article helpful?