Peter Löfman
Oct 22, 2016
  4788
(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

Daniel Ovaska
Daniel Ovaska Oct 22, 2016 10:30 AM

Looks sweet! Nice work!

Per Nergård (MVP)
Per Nergård (MVP) 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
Commerce 15 and CMS 13: Optimizely’s Next Step Toward AI-Powered, Graph-First Commerce

Optimizely is preparing to release Commerce 15 in mid-May 2026 , positioning this as a foundational shift—not just an upgrade. The direction is...

Augusto Davalos | May 7, 2026

The future of Content: Introducing Optimizely CMS 13

Optimizely In the rapidly evolving landscape of digital experience, the "monolithic vs. headless" debate is being replaced by a more sophisticated...

Aniket | May 6, 2026

Hide built in scheduled job from the admin UI

Ok so this probably goes into the not so useful section but late last night I got a veery strong feeling that all projects I am  involved with have...

Per Nergård (MVP) | May 6, 2026

Optimizely CMS 11 Is Out of Support — and the Hard Part of the Upgrade Isn't the CMS

On 10 April 2026, Optimizely formally announced that CMS 11 was out of support — CMS 13 had reached GA on 31 March, and by policy only the two most...

Allan Thraen | May 6, 2026 |