What Optimizely CMS 12 upgrade advice do we have?

  • Page Owner: Not Set
  • Last Reviewed: 2022-12-09

Upgrading Episerver / Optimizely CMS 11 to CMS 12, what things should I know or prepare?


Additional Posts

First, don't bother with the upgrade assistant. The problem is the upgrade assistant will get you part way there, but you won't have a clear idea of how far it got and what remains to be addressed. It can also leave your project in a wonky state that is very hard to recover from.

You're better off starting a new, blank CMS 12 project, and then copying code over a bit at a time. Also create a copy of the database to be upgraded. Start copying over models, then more complicated things like controllers, jobs, services, etc.

It's a good idea to add GUID values to any content types, property types, scheduled jobs, property-lists, or any other CMS items that allow you to have a GUID set. Do this ahead of time, and make sure this is deployed to production before work on the upgrade begins.

The GUID values will allow you to change class names and namespaces if you want to during the upgrade, so classes can be moved around if necessary, without breaking database synchronization.

If the classes don't already have GUID and you change the name or namespace, the database synchronization will fail.

FEEDBACK FROM CHRIS (Updated Frequently probably lol)

Configuration Manager:

You can't easily get things using ConfigurationManager.AppSettings["chco:ev-personalOAuthToken"] anymore instead you have to get an instance of IConfiguration , I found it handy to just write an extension method. I ended up with:

 public static string GetConfigurationValue(string key)
        {
            var configuration = ServiceLocator.Current.GetInstance<IConfiguration>();

            var value = configuration.GetValue<string>(key);

            return value.HasValue() ? value : "";
        }

MvcString / TagBuilder:

System.Web.Mvc.MvcHtmlString => Microsoft.AspNetCore.Html.HtmlString

If you ever run into var metadata = ModelMetadata.FromLambdaExpression(expression, model.ViewData); which I think was something Josh used a lot. You can get to it via:

var modelExpressionProvider = ServiceLocator.Current.GetInstance<ModelExpressionProvider>();
var metadata = modelExpressionProvider.CreateModelExpression(model.ViewData, expression);

TagBuilder.SetInnerText(text) => TagBuilder.InnerHtml.SetContent(text);

HttpContext:

RequestContext.GetLanguage() => HttpContext.GetRequestedLanguage();

RequestContext.GetContentLink() => HttpContext.GetContentLink();

There is no more nice and easy NameValueCollection for query strings. It's a Dictionary<string, QueryString>() gross...

Accessed via: Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(HttpContext.Request.QueryString.ToString());

If you need to append this to a URL you can do so via:

QueryHelpers.AddQueryString(url, query);

If you need to just convert that dictionary back to a string you can do so via:

Microsoft.AspNetCore.Http.QueryString.Create(query).ToString()

Metadata:

public void OnMetadataCreated(ModelMetadata metadata => public void OnMetadataCreated(DisplayMetadata metadata)

PageEditing.PageIsInEditMode:

They took this nice easily callable very useful functionality and made it so you have to use dependency injection and an interface. Very annoying. I made a global extension that looked like this:

public static Injected<IContextModeResolver> contextModeResolver;
public static bool IsInEditMode()
{
   return contextModeResolver.Service.CurrentMode == ContextMode.Edit;
}

Razor Stuff

You can still do @helpers sort of. Something like this:

@helper Pill(ContentReference categoryLink, EPiServer.Url url)
{
    string pillText = string.Empty;
    if (categoryLink.HasValue())
    {
        pillText = categoryLink.Get<Geta.EpiCategories.CategoryData>().Description;
    }
    if (url.HasValue())
    {
        <a href="@Url.ContentUrl(url)" class="topic-pill">
            @pillText
        </a>
    }
    else if (pillText.HasValue())
    {
        <span class="topic-pill">
            @pillText
        </span>
    }
}

Becomes this:

@{
    void Pill(ContentReference categoryLink, EPiServer.Url url)
    {
        string pillText = string.Empty;
        if (categoryLink.HasValue())
        {
            pillText = categoryLink.Get<Geta.EpiCategories.CategoryData>().Description;
        }
        if (url.HasValue())
        {
            <a href="@Url.ContentUrl(url)" class="topic-pill">
                @pillText
            </a>
        }
        else if (pillText.HasValue())
        {
            <span class="topic-pill">
                @pillText
            </span>
        }
    }
}

Keep in mind this means you'll probably run into everobodies favorite You cannot convert void to object error. Which means you have to end your call in a semicolon Pill(Model.PillTextItem, Model.PillUrl);

Working with Git while working on the upgrade:

Per our Dev L10 discussion you should create a new project in Azure Devops for the Opti 12 upgrade.

Strategy:

I got about 70% through before I realized it was much quicker to just go copy files in the file system instead of creating them in Visual Studio. This made things MUCH quicker. Do this from the start. Don't be me.

Also Find and Replace is very useful for mass changing things. (Blend.Episerver => Blend.Optimizely) however take an extra second to think about what you're doing, cause it bit me at least twice.

External Resources:

Old but had helpful stuff in it: https://devblog.gosso.se/2021/11/upgrading-episerver-cms-11-to-optimizely-cms-12-porting-to-net-5/

Talking about Go Live with Clients:

Per: https://docs.developers.optimizely.com/digital-experience-platform/v1.3.0-DXP-for-CMS11-COM13/docs/migration-to-cms-12-commerce-14

Content gets copied to the new Instance when it is created and then right before the go live. Clients do not need to implement a content freeze.

WIP

Adding my 2 cents from ABS

  1. Follow Chris's advice.
    1. Make a repo from the latest project template
    2. COMMIT THE TEMPLATE TO A NEW REPO
    3. Copy basically all files into the new repo
    4. Right click the project in VS, and hit Sync Namespaces (this took ~15 minutes for ABS, roughly 900 files)
    5. Use Git to add new files
    6. Use Git Compare to implement template features (search base, page base etc).
  2. Clean up the project