In this tutorial, you will learn a little more about the new Optimizely CMS 12 project structure and how it differs from CMS 11 and below. The massive change in Optimizely CMS 12 is that it works with .NET 5 and .NET Core. Gone are the days of ASP.NET... long live the new world 🌎🌎🌎. As the CMS moves from the .NET framework to .NET Core, the underlining system also changes. The structure of a .NET Core application is very different. For example, how your website is bootstrapped is completely different. This is why the focus of this guide is on what changed. If you want to build a website using Optimizely CMS and you want to learn about some of the big differences between the two systems, this is the guide for you 💥.

I will start this discussion at application initialisation. If you want to configure how your application behaves on start-up, you will need to know both Program.cs and Startup.cs intimately ❤️. What do these files do and how are these files are used to bootstrap your website? 🤷🤷🤷


In Optimizely CMS 12, Program.cs is now the entry point for the application. A web solution is now a console project that gets started by executing the Main() method. The code in Program.cs is pretty simple to figure out:

From Progam.cs, Startup.cs is called. It is within Startup.cs that things start to get a little more interesting 🤔


All .NET Core applications require a StartUp.cs file. StartUp.cs can be compared to the Global.asax filein the .NET framework. StartUp.cs has two methods, ConfigureServices() and Configure():

  • ConfigureServices() is the place to add IoC configuration. It is now considered best practice to register your dependencies here. You can still register dependencies using the older CMS 11 technique of using initialization modules if that makes you really happy 😊, however, the best practice is to add stuff here.

TIP: If you are doing a CMS 11 to CMS 12 upgrade, keeping set-up code in an initialization module could save you a lot of time 😉

One thing to be mindful of when setting items within here is ordering. Let us say you are trying to read application settings from within ConfigureServices and you run into an ordering issue. Maybe your code runs to early and a value you were expecting has been overridden somewhere. You can use the code below to ensure the code you are trying to register is run towards the end of execution.

This approach is kind of similar to the ModuleDependency attribute within initialization modules:

  • Configure() is used to configure the request pipeline. Here you can add middleware that will add additional capabilities within your application request pipeline. Standard middleware will include routing rules and authentication. Make sure you do not change the ordering of these rules too much, as it can be a source of potential issues. Below show an example of Configure() from the blank website project:

There are a few useful things to note about this code:

Line 13: AddCms() is the main Episerver snippet you need to get the CMS up and running. Without this, you would need to add lots of additional code, structured in the right way in order to make the CMS function. There is also an AddCommerce() middleware for commerce!

Line 35: In ConfigureApplicationCookie() is where you define the editor log-in URL for the editor. You can also pass in additional parameters like the timeout period.

Line 56: defines MapContent. MapContent is the middleware that registers all the block or content controllers, so kind of important 👑


In .NET Core, the web.config has also gone. Instead, you now use appSettings.json. One useful thing to note is that settings are now a lot more configurable. In .NET 5 you have more control to specify where the settings come from. This is done via the IConfiguration interface. As the source for settings is defined against using an interface, this means that it is easy to create your own providers and read settings from wherever makes you happy in your project. Maybe a CMS settings page provider would be useful 🤔Out-of-the-box, you can also use environment variables to add settings. The precedence of settings order is important and the rules are:

  • Environment variables

  • Configuration settings

If you pass commands in the CLI these will override the values in application settings! The code below is an example of appSettings.json from the blank sample site:

One of the main Optimizely settings within 'AppSettings.json' is the find section. The find section is used to configure Episerver find. One cool thing is that all the Episerver sections map to specific C# option classes. You can read/write Optimizely Find settings in code using the FindOptions class. For example, you could use FindOptions to set the default Find index. Within ConfigureServices() in startup.cs, you could configure Find like this:

In terms of settings and start-up the above outlines the main differences. How you create pages is very similar to the old approach and I have created a video that walks through that process (see here). How you create blocks is slightly different and I will cover this in a later video. Asides from these big changes, most of the code and the APIs are very similar between the two platforms, Surprisingly, this short post covers the main big differences in CMS 12. Once you get started using CMS 12 in anger, you should notice very quickly that familiar workflow that you might have used for years. Happy Coding 🤘