Loading...
Area: Optimizely CMS
Applies to versions: 12 and higher
Other versions:

Initialization

Recommended reading 
Note: This documentation is for the preview version of the upcoming release of CMS 12/Commerce 14/Search & Navigation 14. Features included here might not be complete, and might be changed before becoming available in the public release. This documentation is provided for evaluation purposes only.

This topic describes the initialization system in the Optimizely platform, which is used by Optimizely CMS, Optimizely Commerce, and third-party and customized modules used with Optimizely products. You can develop your own initialization module.

How it works

An ASP.NET Core application controls the initialization of the application through the Startup class. CMS is typically added to the application by calling extension method AddCms on IServiceCollection (from NuGet package EPiServer.Cms.Web). That method will in its registration call extension method AddCmsHost on IServiceCollection (from NuGet package EPiServer.Hosting), this will create the InitializationEngine and initialize modules implementing IInitializableModule.

Custom initialization logic can preferably be done through standard ASP.NET Core components like for example IStartupFilter or  IHostedService. The IInitializableModule system (described below) within CMS that was built for ASP.NET - where there was no central Startup class - is also available in ASP.NET Core application and can be used also.

The Optimizely initialization system comprises the following:

  • A discovery mechanism to determine which modules should be part of the initialization process.
  • A dependency sorting algorithm that decides the order of execution.
  • An execution engine that executes the modules.
  • The  EPiServer.Framework.Initialization namespace, in the EPiServer.Framework assembly.

The discovery mechanism

The scanning of assemblies is primarily using an attribute-based discovery system. CMS uses the external-link.png AssemblyLoadContext and the DependencyContext to locate assemblies to locate which assemblies to scan for initialization modules. 

To have a discoverable initialization module, you need to add the [InitializableModule] or [ModuleDependency(...)] attribute to your class. The class also needs to implement the IInitializableModule or IConfigurableModule interfaces.

Dependency sorting

If your application has a dependency on one or more existing initialization modules, then explicitly state this dependency by using the [ModuleDependency(typeof(ModuleThatIDependOn))] attribute. This is used by the dependency sorting to ensure that all modules are executed in the correct order. You can define multiple modules on which the application is dependent. For example, in Optimizely CMS, you can take a dependency on EPiServer.Web.InitializationModule to make sure any CMS functionality was initialized before your module.

The modules are then simply sorted, ensuring that the modules are executed in the correct dependency order.

If the application defined circular dependencies or dependencies to non-existing modules, you receive an exception upon application startup.

Execution engine – InitializationEngine

InitializationEngine (resides in the namespace EPiServer.Framework.Initialization) is responsible for executing the list of modules created by the dependency sorting algorithm. As described in the IInitializableModule.Initialize method, it is guaranteed that the Initialize method gets called only once (unless the method error occurs).

The initialization engine is a simple state machine that logs information about all state changes and reports the initialization methods that were executed. See the Logging section in this document for information.

After all Initialization methods are executed successfully, an InitComplete event is raised, which lets you perform post-initialization actions. For information, see the InitComplete event section.

How the initialization system is started

The InitializationEngine is created when calling the AddCmsHost on IServiceCollection (from NuGet package EPiServer.Hosting). CMS will add an IHostedService that, during StartAsync,  calls the initialization engine, which then calls Initialize on all detected IInitializableModule implementations. This means that IInitializableModules are called after DI configuration of the application is done but before any HTTP requests are executed.

Assembly scanning and filtering

When the initialization system looks for modules, it relies on a common type scanning system based on the standard .NET reflection APIs for loading and composition. The basic idea is to scan all assemblies that are part of the application, with the exception of .NET assemblies. This assembly list is exposed through the EPiServer.Framework.Initialization.InitializationModule.Assemblies static property. You can attribute an assembly with [PreventAssemblyScan] to exclude it from the scanning process.

Optimizely products use the same scanning process as the initialization system when discovering plug-ins. This is important to remember when you decide which assemblies are scanned by the EPiServer Framework.

The types that were discovered during scanning are then available through an implementation of lookup ITypeScannerLookup component in EPiServer.Framework.TypeScanner namespace. Types that define plug-in systems, such as Optimizely CMS, uses an assembly attribute TypeScannerRegistration to register new plug-in types. Do not use the TypeScannerRegistration attribute to register plug-ins; only to register new plug-in types. By having a consolidated type scanning system, a single cached sweep over all assemblies is required for all products to initialize themselves.

The InitComplete event

There are cases where you might want your own initialization module to be called again after the initialization process is complete. A typical use case is to attach event handlers to an instance property that may be overridden by third-party code.

To attach to the InitComplete event, write your Initialize method as follows:

public void Initialize(InitializationEngine context)
  {
    context.InitComplete += InitCompleteHandler;
    StartThisModule();
  }

When all initialization modules have executed, the InitComplete event is raised. The InitComplete event is handled in a slightly non-standard way. When an event handler executes without error, the initialization system removes it from the InitComplete event. So do not detach from the InitComplete event in your Uninitialize method.

Although this procedure in the initialization system may seem peculiar, it is simply to verify that if an InitComplete event handler has an error, Optimizely CMS can re-execute the InitComplete event on the next request without reinvoking the already successfully executed event handlers.

Logging

To enable logging from initialization modules, configure logging for namespace EPiServer.Framework.Initialization.

Related topics

Do you find this information helpful? Please log in to provide feedback.

Last updated: Jul 02, 2021

Recommended reading