The end of life for Umbraco 7 is getting near (September 2023), and with Umbraco 11 being released last week this is the perfect time to migrate your Umbraco solution! Today we will we taking a look at the popular Umbraco 7 multilingual property editor wrapper, Vorto, and the steps to keep in mind when migrating to a later version of Umbraco!
What is Vorto?
Perhaps you are only familiar with working with the later versions of Umbraco, so let's start at the beginning, what is Vorto? Vorto is a property editor wrapper (package) that allows you to turn any property editor into a multilingual property! Originally released for Umbraco 7.0 up to the latest version of 7.15, Vorto was THE property editor for doing 1:1 translations in Umbraco.
Now, time has passed, and ever since the release of Umbraco 8 and its' language & culture variants support, Vorto no longer serves a use. Sadly, migrating from Umbraco 7 Vorto properties to Umbraco 8 native properties isn't a "run and forget" kind of task, so here are some tips that I learned along the way that will hopefully help you out!
Before we get starting, I will be making a couple of assumptions and prerequisites to get going on your migrations. Make sure you can check off the following list before moving on:
- You've made a backup of your Umbraco 7 Database.
- You've run the official Umbraco Migration Process on a clean Umbraco 8+ instance, pointing towards your Umbraco 7 Database.
- You have resolved any solution startup issues, and can access your Umbraco 8+ back-office, albeit with broken content & property editors. (The following package and guide may help: ProWorks.Umbraco8.Migrations & https://www.proworks.com/blog/archive/how-to-upgrade-umbraco-version-7-to-version-8)
I won't be going into any specific code-highlights today, but hopefully the step-by-step instructions will be able to guide you! To get started with your Vorto Content Migrations, we will start by preparing our Content Types & Content Type Properties. For this, we will be making use of various custom Migrations. More info about creating & running migrations can be found here: How to Create & Run Migrations - Umbraco v10+.
The first step will be to toggle the "Allow vary by culture" on all ContentTypes that contain a Vorto Property. You can make use of the ContentTypeService to get all the different Content Types in your solution. For each Content Type, we can check if either:
- It contains a Property with PropertyEditorAlias "Our.Umbraco.Vorto", or
- It contains a Property from it's composition with PropertyEditorAlias "Our.Umbraco.Vorto"
If either of the two is true, make sure to set the Variations property of the ContentType to ContentVariation.Culture (or ContentVariation.CultureAndSegment).
Next up is to make the actual Vorto Properties variable by culture. We can do this in the same if-statement where we check if either of the two property groups contains the PropertyEditorAlias "Our.Umbraco.Vorto" by looping over all the various propertyTypes with said PropertyEditorAlias, and doing the same thing as we did on our ContentType, which is to set our PropertyType.Variations to ContentVariation.Culture (or ContentVariation.CultureAndSegment).
Save each ContentType using the ContentTypeService, and our first step is done!
As you may recall from the first chapter, Vorto is a wrapper around a property/dataType, so for our next step we will need to unwrap said property/datatype to it's original, unwrapped form. To get started, we'll be taking a similar approach as we did in our preparations, by getting all the different Content Types from the ContentTypeService, and filtering them on contentTypes that have a PropertyType with PropertyEditorAlias "Our.Umbraco.Vorto". You can skip over the Compositions here, because our list of "All Content Types" also contains all types used as Compositions!
For each content type, and for each Vorto Property within that content type, we'll be fetching the raw database information about this Vorto DataType using the following method in our Migration:
var ogDataTypeDto = Database.SingleByid<DataTypeDto>(property.DataTypeId)
From here on out, we have two options:
From that dataTypeDto object, we can access the Configuration property if not null, and deserialize that into a custom object to match our VortoDto (which you'll have to make yourself. A look like JSON to C# will make your life a little easier!). The nested value that we are looking for is the dataType.guid property, which references the original wrapped DataType. If said guid is not null, we can fetch the corresponding DataTypeDto matching this guid using the same method as shown above. From here we have two routes we can take:
- Add a new property based on our nested datatype, migrate the data from the old property to the new property, and then delete the old property, or
- Change the configuration from the existing property the datatype that it uses, and keep the data stored on that property.
To avoid having to migrate Data from one property to another and/or losing data, we will be going with option two in this case, by be changing the original Vorto property datatype to be our unwrapped datatype instead! To do so, we take our original ogDataTypeDto, and set the EditorAlias, DbType, and Configuration to be the same as the unwrapped one that we fetched with the deserialized guid.
After that, we can call a Database.Update(ogDataTypeDto) and have our property no longer reference our old Vorto Data Type, but reference the unwrapped editor, database type, and configuration instead!
Now comes the most difficult part, which is Content Migration. The following steps will only work for you if you're not using any Nested Vorto Properties, like Vorto Properties within Elements within Nested Content. These steps will require a custom approach depending on your situation. I'll be going through the various steps that helped me out, so hopefully they will help you get on your way too!
For our Data Migration, we have several options we can take; Two of those options are either:
- Create a Migration just like the previous two steps, that will automatically migrate when you run Umbraco instance.
- Create a Healthcheck to verify your data integrity, and add a button to perform the actual migration.
In my experience I would opt for option number two, as depending on the size of the amount of Content you're trying to migrate, having it be an automatic action can be very bothersome and something you may only want to do after having verified all of your other migrations have worked successfully. For a guide on how to create a custom HealthCheck, check 🥁 out the following entry on the Umbraco Docs.
In our Healthcheck Check & Fix function, our first step is to get all the various Content in our application. For this we can use the ContentService, and it's GetPagedDescendants. You can either make it a clean solution and loop over it with various pages, or be lazy and say "Get Int.Max content at pageIndex 0" and have a non-paginated list of all your content. Next up, we'll be looping over all our Content to perform various actions. For each IContent item, and for each property in said IContent item, we will check:
- If we can get the stored property value as a string, by accessing either the GetValue() or Values.First().EditedValue property
- If the parsing of said string went successful.
- If the result of said string is not an empty string.
- If the result of said string is a valid Json string.
- If the result of said string contains the " dtdGuid" property, which we will use as an indication that it is Vorto Content.
- If the result of said string can be parsed into a generic VortoValue object (once again, a custom object to match our JSON string to make our life easier further on!).
- If the contentType of our content has VariesByCulture() enabled.
If all of the above return true, we are in luck, and can continue migration our content! If any of them return true, either skip it as it's not a Vorto property, or find out what went wrong in the process.
What we end up with in a non-nested scenario, is a list of Values that's of type Dictionary<string, T>, where the key is the culture for said property, and the value is the...value of our new property! We can use the values in said Dictionary to set the various new values for our content using the content.SetValue() method, passing on the culture key and the value value. If all went right, we can add our Content item to a new list of items ready to be SavedAndPublished (the heaviest operation in this entire process)!
Once setting the data of all properties of all content is complete, we can use the ContentService to either Save the entire list in bulk, or SaveAndPublish each item individually, depending on your use-case!
Do note that if you plan on also Publishing the item, all other content issues on said item have to be resolved first before successful publishing!
And that should be it! While it's an oversimplified set of instructions that may not cover each and every scenario, especially when talking about nested Vorto properties, hopefully it can be a helpful starting point into your Umbraco Data Migration Journey!
Migrating Vorto based content can be tricky, but with some guidelines it will hopefully save you some of the troubles I had to go through myself! By preparing our Content- and PropertyTypes, unwrapping the underlying DataTypes for said PropertyTypes, and then migrating the content over from the single-language variant to the various different cultures, we will end up with a ready-to-go set of content matching the native Umbraco Language and Culture Variants! 🔥
If you have any further questions, feel free to contact me over at my socials available at the Contact page, and I'd love to hear about either your success stories, or further troubles to help improve this blog! 😄
Sources used in this article:
Proworks Umbraco 7 to Umbraco 8 Migration, https://www.proworks.com/blog/archive/how-to-upgrade-umbraco-version-7-to-version-8
Umbraco Healthcheck Documentation, https://docs.umbraco.com/umbraco-cms/extending/health-check
How to Create & Run Migrations - Umbraco v10+, https://cornehoskam.com/posts/how-to-create-run-migrations-umbraco-v10