Copy property data on translate
Mattias Olsson
06.09.2020 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)
{
}
}