Views: 1491
Number of votes: 5
Average rating:

Force Property Type Reset

During the development phase of a project it is, at least for me, rather common to change property names and property types when refactoring or otherwise improving the code. This can create a clutter of properties on a content type that are not being used as well as property type mismatch since Episerver can ony handle some property type cconversions automatically. Episerver cannot for instance automatically convert a property that used to be a string to an int, which is very much understandable. And if you read up on refactoring content type classes Episerver states that: "If you need a change of [property] type and any data loss is acceptable, you can change the type of the property in admin mode, but you must temporarily remove the property from the code." 

Having to go into admin mode and temporarily remove property from code is too bothersome for me so instead of doing something that would take a minute or two I spent a couple of hours trying to write some code that will do this automatically, and here is what I came up with.

First a created an atribute (I really did this last but it sounds better like this) to be placed on the property that you have changed the type of and which cannot be converted automatically, for instance a string to an int.

[AttributeUsage(AttributeTargets.Property)]
public class ForceResetPropertyTypeAttribute : Attribute
{
}

And you place it on a property like this. And, please be aware that all previous data will be lost since we will delete the property and then add it again with the new type.

// I used to be a string but have been changed to an int
[ForceResetPropertyType]
[Display(GroupName = SystemTabNames.Content, Order = 100)]
public virtual int BackgroundColor { get; set; }

Next a made an initialization module that (1) deletes all properties that are missing in code and (2) scans each content type model for the attribute ForceResetPropertyTypeAttribute I create before, and simply deletes the property with all its data from Episerver and then re-adds it with the new property definition.

[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ResetContentTypeslInitialization : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        var contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>();
        var propertyDefinitionRepository = ServiceLocator.Current.GetInstance<IPropertyDefinitionRepository>();
        var contentTypeModelRepository = ServiceLocator.Current.GetInstance<ContentTypeModelRepository>();
        var propertyDefinitionSynchronizer =
            (PropertyDefinitionSynchronizer)ServiceLocator.Current.GetInstance<IPropertyDefinitionTypeResolver>();

        // foreach (var contentType in contentTypeRepository.List()) - takes all content types 
        // and not just yours and will actually remove to much
        foreach (
            var contentType in
                contentTypeRepository.List()
                    .Where(ct => !string.IsNullOrEmpty(ct.ModelTypeString)
                        && ct.ModelTypeString.StartsWith("Your.Assembly.Containing.The.Content.Types")))
        {
            // First we delete all properties that do not exist in code - probably due to renaming them
            var propertyDefinitionsThatDoNotExistInCode =
                contentType.PropertyDefinitions.Where(
                    propertyDefinition => IsMissingModelProperty(propertyDefinition, contentTypeModelRepository));

            foreach (var propertyDefinition in propertyDefinitionsThatDoNotExistInCode)
            {
                propertyDefinitionRepository.Delete(propertyDefinition);
            }

            var modelType = contentType.ModelType;

            if (modelType == null) continue;

            // Next we want to check if there are any content types that have a property with the 
            // ForceResetPropertyTypeAttribute placed on them, and if so delete and re-add the property
            // definition.
            foreach (var propertyInfo in modelType.GetProperties())
            {
                var forceResetPropertyTypeAttribute =
                    propertyInfo.GetCustomAttribute<ForceResetPropertyTypeAttribute>();

                if (forceResetPropertyTypeAttribute == null) continue;

                var propertyName = propertyInfo.Name;

                var contentTypeModel = contentTypeModelRepository.GetContentTypeModel(modelType);
                var propertyDefinition = contentType.PropertyDefinitions.Single(pd => pd.Name.Equals(propertyName));
                var propertyDefinitionModel =
                    contentTypeModel.PropertyDefinitionModels.Single(p => p.Name.Equals(propertyName));

                // We check the current property definition type to see if perhaps we have already made the conversion.
                // We do not want to remove data on an already converted property
                var propertyDefinitionType = propertyDefinitionSynchronizer.ResolveType(propertyDefinitionModel);
                   
                if (propertyDefinition.Type.DefinitionType == propertyDefinitionType.DefinitionType) continue;

                // And finally here we delete the property definition and then re-adds it according to the new information
                // Please be aware that this will delete all the data as well.
                propertyDefinitionRepository.Delete(propertyDefinition);
                propertyDefinitionSynchronizer.CreatePropertyDefinition(propertyDefinitionModel, contentType.ID);
            }
        }
    }

    private static bool IsMissingModelProperty(
        PropertyDefinition propertyDefinition,
        ContentTypeModelRepository contentTypeModelRepository)
    {
        if (propertyDefinition == null) return false;

        if (!propertyDefinition.ExistsOnModel) return true;

        return contentTypeModelRepository.GetPropertyModel(propertyDefinition.ContentTypeID, propertyDefinition)
                == null;
    }

    public void Uninitialize(InitializationEngine context)
    {
    }
}

[Pasting files is not allowed][Pasting files is not allowed]

Oct 22, 2016

d_ovaska
(By d_ovaska, 10/22/2016 12:30:00 PM)

Looks sweet! Nice work!

per.nergard
(By per.nergard, 10/22/2016 2:39:31 PM)

Did something similiar a while back but not with the automatically propertytype switch attribute which I really think is nice.

Peter Löfman
(By Peter Löfman, 11/1/2016 2:38:17 PM)

Well, this is embarrasing but my code actually "destroys" the Episerver Forms content type model FormContainerBlock, or at least it removes to vital actor properties from it because they are not part of the code of the model but rather, I guess, added during initialization. 

So, when using this you will need to make sure you are only doing it on your content types, and one way of doing this is to check the content type model type string and make sure it starts with your assembly name. 

Change:

foreach (var contentType in contentTypeRepository.List())

To:

foreach (var contentType in contentTypeRepository.List().Where(ct => !string.IsNullOrEmpty(ct.ModelTypeString) && ct.ModelTypeString.StartsWith("Your.Assembly.Containing.The.Content.Types")))

Please login to comment.