Copy property data on translate

Mattias Olsson 2020-09-06 20:03:00

The property in question is [CultureSpecific] for full flexibility, but rarely differs between languages. So I started investigating if it would be possible to copy the data from the currently active language and came up with a solution that I thought I could share with the community.

My solution was to create an interceptor for the default IContentDataBuilder. Let me know your thoughts and feel free to suggest other approaches. Here's the kind of simple code:

Data annotation

First of all I added an attribute to annotate the properties. As you can see it's only adding metadata to the property.

[AttributeUsage(AttributeTargets.Property)]
public class CopyDataOnTranslateAttribute : Attribute, IMetadataAware
{
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues.Add(nameof(CopyDataOnTranslateAttribute), true);
    }
}


Example usage:

[CopyDataOnTranslate]
[CultureSpecific]
[Required]
[UIHint(UIHint.Image)]
public virtual ContentReference TeaserImage { get; set; }

 

Content data builder interceptor

Here's the code for the interceptor

public class ContentDataBuilderInterceptor : IContentDataBuilder
{
    private readonly IContentDataBuilder _inner;

    public ContentDataBuilderInterceptor(IContentDataBuilder inner)
    {
        _inner = inner;
    }

    public void AddProperties(
                    IContentData contentData,
                    BuildingContext buildingContext)
    {
        _inner.AddProperties(contentData, buildingContext);

        // Only run this code when SetPropertyValues is true
        // and the target is a new IContent.
        if (buildingContext.SetPropertyValues
            && contentData is IContent target
            && ContentReference.IsNullOrEmpty(target.ContentLink))
        {
            CopyPropertyValuesFromSource(
                target,
                buildingContext.Parent,
                buildingContext.ContentType);
        }
    }

    private void CopyPropertyValuesFromSource(
                     IContent target,
                     IContent source,
                     ContentType contentType)
    {
        if (!(target is ILocalizable localizableTarget)
            || source is ILocalizable localizableSource
               && Equals(localizableTarget.Language, localizableSource.Language))
        {
            return;
        }

        foreach (var propertyDefinition in contentType.PropertyDefinitions)
        {
            var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(
                null,
                target.GetOriginalType(),
                propertyDefinition.Name);

            if (!metadata
                    .AdditionalValues
                    .ContainsKey(nameof(CopyDataOnTranslateAttribute)))
            {
                continue;
            }

            var targetProperty = target.Property[propertyDefinition.Name];

            if (source.GetOriginalType() != target.GetOriginalType()
                || !source.ContentLink.CompareToIgnoreWorkID(target.ParentLink))
            {
                // Make sure to set IsRequired to false so we can skip
                // this property in the "Required properties" view
                targetProperty.IsRequired = false;
            }
            else
            {
                var sourceProperty = source
                                         .Property[propertyDefinition.Name]
                                         .CreateWritableClone();

                // Copy data from the target property
                targetProperty.IsRequired = false;
                targetProperty.LoadData(sourceProperty.SaveData(null));
                targetProperty.IsRequired = propertyDefinition.Required;
            }
        }
    }
}


Register the interceptor

To make it all work it's required to register the interceptor.

[ModuleDependency(typeof(ServiceContainerInitialization))]
[InitializableModule]
public class DependencyResolverInitialization : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        AddInterceptors(context);
    }

    private void AddInterceptors(ServiceConfigurationContext context)
    {
        context
            .Services
            .Intercept<IContentDataBuilder>((locator, inner) => new 
                ContentDataBuilderInterceptor(inner));
    }

    public void Initialize(InitializationEngine context)
    {
    }

    public void Uninitialize(InitializationEngine context)
    {
    }
}

copydataontranslate.gif