Peter Löfman
Oct 22, 2016
  3548
(5 votes)

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

Comments

Oct 22, 2016 10:30 AM

Looks sweet! Nice work!

Oct 22, 2016 12:39 PM

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

Nov 1, 2016 01:38 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.
Latest blogs
Integrating Optimizely DAM with Your Website

This article is the second in a series about integrating Optimizely DAM with websites. It discusses how to install the necessary package and code t...

Andrew Markham | Sep 28, 2024 | Syndicated blog

Opticon 2024 - highlights

I went to Opticon in Stockholm and here are my brief highlights based on the demos, presentations and roadmaps  Optimizely CMS SaaS will start to...

Daniel Ovaska | Sep 27, 2024

Required fields support in Optimizely Graph

It's been possible to have "required" properties (value must be entered) in the CMS for a long time. The required metadata haven't been reflected i...

Jonas Bergqvist | Sep 25, 2024

How to write a bespoke notification management system

Websites can be the perfect vehicle for notifying customers of important information quickly, whether it’s the latest offer, an operational message...

Nicole Drath | Sep 25, 2024

Optimizely DAM – An Introduction

I presented a talk about the Optimizely DAM at the OMVP summit during Opticon 2024 in Sweden. I have now converted that talk into two blog posts....

Andrew Markham | Sep 25, 2024 | Syndicated blog

Simple and Effective Personalization with Optimizely Data Platform (ODP)

As we dive into the amazing capabilities of Optimizely One, let’s shine a spotlight on the Optimizely Data Platform (ODP). This simple tool unifies...

Alex Harris - Perficient | Sep 24, 2024 | Syndicated blog