The challenges I faced during the migration from CMS version 11 to 12
This blog post covers the major steps I had to face when migrating from CMS 11 to CMS 12. And this also contains the issues I had to experience related to .NET because of upgrading from .NET Framework to .NET 8.
The intention of this blog post is to help you diagnose and fix similar issues to what I had faced when upgrading to Optimizely CMS 12. Below, you can find the major steps I have followed. Note that your solution will be different from mine; and ignore any of the steps below if they do not relate to your solution and views should be fixed separately, which is not covered in this blog post.
π¨π Before getting started
- Would be better if the CMS is updated to the latest CMS 11 version.
- Make sure the source code and the databases are backed up.
- Identify which features can be kept, removed, or updated. This might help when upgrading to CMS 12 as developers do not want to focus on fixing unneeded code on upgrade. (Despite, there could be situations where the developers may get to know in the middle of the migration when a specific feature needs to be refactored as the previous implementation is no longer compatible with .NET 8.)
- Remove the unnecessary projects in the solution. (In my case I had residue projects which had so many package references and no practical use to the main project etc.)
π Migration steps
- Install .NET Upgrade Assistant CLI tool.
dotnet tool install -g upgrade-assistant
- Or if you already have the upgrade assistant installed, update it to the latest.
dotnet tool update -g upgrade-assistant
- Run upgrade assistant using the below command.
upgrade-assistant upgrade "Path to solution file or project file"
Note: Here, I have not used Optimizely Upgrade Assistant Extensions as the latest version of .NET Upgrade Assistant (v0.5.568) still does not support having extensions. But if you need, you can use .Net Upgrade assistant v0.4.x (Legacy version) to use the Optimizely Upgrade Assistant Extensions (However, it does not support upgrading to.NET 8 as of January 2024.).
- After running the above command, It will ask which project to be upgraded if you have provided the path to the solution file in the above step and if you have many projects in the solution.
And once you have selected which project to be upgraded, it asks how the project to be upgraded. I have chosen “In-place project upgrade” as I want to replace the existing files with the updated files.
And then it asks to which .NET target framework version you would like to upgrade. I have chosen .NET 8.
- After the .NET upgrade is successfully completed, verify the TargetFramework in the project file is set to the relevant .NET version.
- Create Program.cs (and Startup.cs if you like to have separate startup file).β―
public class Program
{
public static void Main(string[] args)
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var isDevelopment = environment == Environments.Development;
if (isDevelopment)
{
// Logger configuration etc.
}
CreateHostBuilder(args, isDevelopment).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args, bool isDevelopment)
{
if (isDevelopment)
{
return Host.CreateDefaultBuilder(args)
.ConfigureCmsDefaults()
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
else
{
return Host.CreateDefaultBuilder(args)
.ConfigureCmsDefaults()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
}
Removed the below packages as they do not support CMS 12/.NET 8. (For more information, please check the CMS 12 breaking changes)
- TedGustaf.Episerver.GoogleMapsEditor 1.x (Use new GoogleMapsEditor. See this guide for more info.)
- EPiServer.ServiceLocation.StructureMap 2.x
- Semantix.AutoConnect related packages. (Only supports .NET Framework at the time this article was written)
- Swashbuckle (Note: If you have time, you could also try to install Swashbuckle.AspNetCore and refactor the old code which was on Swashbuckle package which did not support .NET Core. But I did not do that as Swashbuckle is not needed for our MVP)
- BVN.404Handler (Use https://github.com/Geta/geta-notfoundhandler instead)
- Geta.SEO.Sitemaps (Use https://github.com/Geta/geta-optimizely-sitemaps instead)
- EPiServer.ContentDeliveryApi (.NET Framework package)
- EPiCode.DynamicMultiSearch
- ImageProcessor
- Structuremap.web
- Owin
- Microsoft.AspNet.Identity.Core
- Microsoft.AspNet.Providers.Core
Check Episerver package versions
- If you check the Episerver package versions after the .NET Upgrade Assistant completed its tasks, you may see that the Episerver CMS packages might have been automatically upgraded to the latest major version. If yes, then you are lucky π€ π . In my case, The Episerver CMS packages are not upgraded. Thus, I had to upgrade them manually. Please refer to the below step.
(Note: Some of the Episerver packages like ContentDeliveryApi and Find are automatically upgraded to the latest in my solution. But not having the Episerver CMS 12 automatically upgraded means I must deal with NuGet package conflicts etc.)
Resolve package conflicts
(Ignore this step if you have the Optimizely 12 packages and no .NET Framework package warnings)
As you can see above, I had issues with EPiServer.Cms.AspNet as it conflicted with EPiServer.Cms.AspNetCore package, and this was resolved by addressing package conflicts.β― (I would recommend reviewing the package warnings that appear during the project build as they will indicate the packages resolved in .NET Framework)
- Try to upgrade the Episerver CMS package to v12. If it does not work, investigate the Package Manager logs, and fix them first.
- In my solution, I had to remove the below packages (In order) to reinstall Episerver CMS 12. (I had to reinstall all the below packages after the Episerver CMS 12 is installed)
- EPiServer.Labs.LanguageManager
- EPiServer.Personalization.MaxMindGeolocation
- EPiServer.AddOns.Helpers
- EPiServer.Forms.Core
- EPiServer.CMS.UI.Core
- EPiServer.CMS.UI
- EPiServer.CMS.TinyMce
- EPiServer.Forms.UI
- EPiServer.Forms
- DbLocalizationProvider.AdminUI.EPiServer
- DbLocalizationProvider.EPiServer
- Microsoft.Azure.Amqp
- EPiServer.ContentDeliveryApi.Core
- EPiServer.ContentDeliveryApi.Search
- EPiServer.ContentDeliveryApi.Cms
- EPiServer.ContentDeliveryApi.Forms
- EPiServer.Find.Cms
- EPiServer.Marketing.Testing
- EPiServer.Find.Framework
- EPiServer.Marketing.KPI
- DbLocalizationProvider.AdminUI
- EPiServer.Azure
- Microsoft.Identity.Client
- Microsoft.IdentityModel.Protocols
- Microsoft.IdentityModel.Protocols.OpenIdConnect
Other changes
- If you have GuiPlugins, refactor them with Menu Provider. See this blog post for more information.
- Remove the irrelevant <Content Include="..." /> elements from the ItemGroup in the .csproj file.
- Some classes (Such as the classes which are inherited from EnterprisePageSearchProvider, EnterpriseMediaSearchProvider, EnterpriseBlockSearchProvider etc.) had issues with constructor injection due to the parameter updates in the base class injection. Fixed these by passing the relevant arguments.
- If the solution has InternalServerError(object) in the controllers; replace them with StatusCode((int)HttpStatusCode.InternalServerError, object);.
- And I had to use Microsoft.AspNetCore.Mvc.IActionResult instead of System.Web.Http.IHttpActionResult as IHttpActionResult is not supported in .NET Core.
- If your solution used HttpContext.Context previously, use IHttpContextAccessor through DE instead. This way, you can get the HttpContext through _httpContextAccessor.HttpContext.
- The upgrade assistant has already replaced System.Web.Http.ApiController with Microsoft.AspNetCore.Mvc.ControllerBase. However, I had to replace the Json method calls with Microsoft.AspNetCore.Mvc.ContentResult with the ContentResult.Content set with System.Text.Json.JsonSerializer.Serialize(..) as below.
return new ContentResult()
{
Content = System.Text.Json.JsonSerializer.Serialize(value),
ContentType = "application/json",
};
- Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute now has no OnActionExecuting overridable method which only accepts HttpActionContext. Used ActionExecutingContext type instead.
- IPropertyModelConverter is missing in the new version of EPiServer.ContentDeliveryApi.Core package. Thus, created below interface in the solution to keep the Model Converters which has implemented the IPropertyModelConverter.
public interface IPropertyModelConverter
{
int SortOrder { get; }
IEnumerable<TypeModel> ModelTypes { get; }
bool HasPropertyModelAssociatedWith(PropertyData propertyData);
IPropertyModel ConvertToPropertyModel(PropertyData propertyData, CultureInfo language, bool excludePersonalizedContent, bool expand);
}
- ContentFilter, StandardSitemapXmlGenerator, MobileSitemapXmlGenerator and their interfaces which were belonged to Geta.SEO.Sitemaps.Utils are no longer available. Thus, used Geta.Optimizely.Sitemaps package. And added base constructor injections in the classes which have inherited from ContentFilter due to the changes in parameters in the new version.
- For the classes which are being inherited by ActionFilterAttribute, use Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext Instead of System.Web.Http.Filters.HttpActionExecutedContext in the overridden OnActionExecuted method. And use the Request and Response through ActionExecutedContext.HttpContext if needed.
- The places where we used DataFactory, I used IContentLoader to get the contents with only read-only access. If you need to have the whole CRUD access, use IContentRepository instead.
- Alternative approach: You could use EPiServer.CMS.AspNetCore.Migration package which contains some old APIs. With that, you could choose to keep the DataFactory implementations as they are without refactoring.
- Passed the IMemoryCache for the EPiServer.Find.Connection.RuntimeCacheAdapter constructor as it no longer has a default constructor.
- Try to build the solution to see the errors.β―β―
- Try to fix each error. (Could not describe each error as most of them were to find the correct namespace or the package to be referred. And there were situations where we must find the .NET Core/Optimizely 12 implementation; if not, remove the implementation as it could not be kept in .NET 8)β―
- I have got the below build error at last. π
CA0055 : Could not identify platform for “…\project.dll”.
CA0052 : No targets were selected.
Once I have set the <RunCodeAnalysis> to false in the .csproj file, this error is no longer shown.
- Create appSettings.json with EPiServer CMS MappedRoles, EpiserverDB and Find Index connection information.β―β―β―
- Remove Web.config files.β―
- Delete the packages folder in the solution folder to clear the package cache.
- Build again and try to fix all the build issues and repeat; if no build issues, try to run the project.β―
- Got Geta.Optimizely.Sitemaps module not found error on runtime when accessing the episerver/cms after logged in.β―It is fixed by adding "services.AddSitemaps();" in the Startup.cs
- Got the DbLocalizationProvider.AdminUI.EPiServer module not found issue and other issues when accessing the episerver/cms after logged in. Resolved by adding the below to Startup.cs
app.UseDbLocalizationProvider();
services
.AddDbLocalizationProvider(ctx =>
{
ctx.EnableLocalization();
ctx.UseSqlServer(Configuration.GetConnectionString("EPiServerDB"));
})
.AddOptimizely();
services
.AddDbLocalizationProviderAdminUI()
.AddOptimizelyAdminUI();
All done for MVP. Now, you should be able to log in with user credentials and access the CMS edit mode. And ContentDeliveryAPI should also work if you have enabled it.
Happy migrating!! π
Comments