Introduction

As a composable CMS, Orchard has the ability to load an arbitrary set of modules (also known as "extensions") at run-time. One of the goals of the 0.5 release was to make the process of installing and updating modules as easy as possible. The goal of this document is to describe at a technical level how Orchard loads modules since version 0.5.

Orchard, like any ASP.NET MVC application, supports loading modules compiled as assemblies using Visual Studio. Orchard also offers a customized module loading strategy which, for example, allows loading assemblies for modules without having to deploy them in the ~/bin folder.

In addition to that, Orchard supports the ability to dynamically compile modules deployed as source code only. This is also a very handy developer feature that you can read about in Compilation Configuration and Language Support and how dynamically compiled extensions are loaded is detailed below in the "Dynamic Module Loader" section.

High Level Overview

When an Orchard application starts, the Orchard Framework (the ExtensionLoaderCoordinator class to be precise) needs to figure out what are the modules installed in the Web Site and activate them (typically by loading their assembly).

At a high level, this process can be divided in 3 distinct phases:

  • Discovery: Figure out what extensions are present withing the application.
  • Activation: Figure out what strategy to use to "activate" (or load) each extension.
  • References Resolution: Figure out what are the assembly references needed to be activated for each extension. This phase is technically part of the "Activation" phase, but it is easier to think about the problem of reference resolution as a separate concern.

Once each extension is properly activated, they are further examined to detect and enable individual features, but this is a topic for another section.

Discovery

The list of available extensions in an Orchard installation is built by searching various folders of the file system for Module.txt and Theme.txt files. The folders looked at by default are listed in the following sections.

~/Core Folder

The ~/Core folder contains, by convention, modules defined in the Orchard.Core assembly. These modules are part of the "Core" Orchard system and are not intended to be modified as freely as modules in the ~/Modules folder.

~/Modules Folder

The ~/Modules folder is intended to contain the vast majority of Orchard modules. The convention is that each module is stored in a sub-folder named <ModuleName> containing a single Module.txt file. Packaging, distribution and sharing of modules is only supported for modules in the ~/Modules folder.

~/Themes Folder

The ~/Themes folder is intended to contain Orchard Themes. With respect to dynamic compilation, Themes are treated almost exactly the same as Modules, except that Themes don't have to have code (assembly in bin or .csproj file). For the rest of this page, when we refer to "Module", it should be understand that the concept applies to "Theme" the same way.

Custom Folders

Orchard 1.10 introduced a new feature that allows the loading of extensions from custom-defined folders outside of the ones listed above by adding the ExtensionLocations service that is utilised by each extension loader (see the Loaders in the Activation section below).

Additional extension folders can be configured by defining an AppSetting (e.g. by adding it to the root web.config file, which contains appropriate examples) with the key Modules and/or Themes with their respective value being e.g. ~/Modules.Custom and/or ~/Themes.Custom.

Example

Here is an example of an Orchard installation which contains the following extensions: Common and Localization (Core modules), Orchard.Azure and Orchard.Caching (built-in modules), SafeMode and TheAdmin (built-in themes), MyModule1 and MyModule2 (custom modules), MyBaseTheme and MyTheme (custom themes).

Root (Orchard.Web)
  Core
    Common
      Module.txt  <= "Common" module from "Core"
    Localization
      Module.txt  <= "Localization" module from "Core"
  Modules
    Orchard.Azure
      Module.txt  <= "Orchard.Azure" module
    Orchard.Caching
      Module.txt  <= "Orchard.Caching" module
  Modules.Custom
    MyModule1
      Module.txt  <= "MyModule1" module
    MyModule2
      Module.txt  <= "MyModule2" module
  Themes
    SafeMode
      Theme.txt  <= "SafeMode" theme
    TheAdmin
      Theme.txt  <= "TheAdmin" theme
  Themes.Custom
    MyBaseTheme
      Theme.txt  <= "MyBaseTheme" theme
    MyTheme
      Theme.txt  <= "MyTheme" theme

Activation

Once Orchard has collected all the Module.txt files from the discovery phase, Orchard uses distinct strategies (or "Module Loaders") to load these modules in memory. Internally, the act of "loading a module" is an activity that takes a Module.txt file as input and returns a list of System.Type as output. Note that this is slightly more generic than simply returning a System.Assembly, as it allows Orchard to support multiple modules per assembly. For example, the Orchard.Core.dll assembly currently contains about 10 modules.

The Orchard framework currently implements the following loaders:

"Referenced Module" Loader

This loader looks in ~/bin directory for a assembly name corresponding to the module name specified in Module.txt. If the assembly exists, it is loaded and all its types are returned. This loader is useful when someone wants to deploy an Orchard web site where all modules are pre-compiled and stored in ~/bin, in a typical "ASP.NET web application" way.

"Core Module" Loader

If Module.txt indicates a module from the ~/Core folder, the CoreExtensionLoader returns the types from the Orchard.Core.<ModuleName> namespace of the Orchard.Core assembly. Orchard.Core is a special assembly containing modules that are "core" to the system, i.e. offering basic functionality on top of the Orchard Framework.

"Precompiled Module" Loader

If Module.txt indicates a module from the ~/Modules folder, the PrecompiledExtensionLoader looks for an assembly named <ModuleName> in the ~/Modules/<ModuleName>/bin folder. If the file exists, its is copied to the ~/App_Data/Dependencies folder, which is a special folder used by ASP.NET to look for additional assemblies outside of the traditional ~/bin folder.

"Dynamic Module" Loader

If Module.txt indicates a module from the ~/Modules folder, the "Dynamic Module" loader looks for a file named <ModuleName>.csproj in the ~/Modules/<ModuleName> folder. If the file exists, the loader will use the Orchard build manager for .csproj files to compile the file into an assembly and return all the types from that assembly.

Note: This loader is the only one in the system performing what is often referred to as dynamic compilation, and is indeed optional if modules have been pre-compiled.

Loader Disambiguation

Since there is potentially more than one loader able to load a given module, Orchard has to have a way to resolve the ambiguity, i.e. pick the "right" loader. Each loader has the ability to return a "date of last modification" for each module they can load. For a given module, if there are multiple candidate loaders, Orchard will pick the loader which returns the most "recent" date of last modification.

For example, a given module can be distributed with both full source code (including .csproj file) and compiled into an assembly in its bin directory. The first time the module is loaded, Orchard will pick the loader for the assembly in bin since it's very likely the assembly was compiled after the last source code change was made. However, if any change was made to the source code afterward, the "Dynamic Module" loader will return the date of the most recently modified file (either the source file or .csproj), and Orchard will pick that loader for the given module.

Note that the "Core Module" loader is never ambiguous, because there is only one way to load these modules. The ambiguity can only arise for modules in the ~/Modules directory.

Example

RootFolder
  Bin
    Orchard.Web.dll
    Orchard.Core.dll
    Foo.dll
  Core           
    Common        <= "Core Module" loader
      Module.txt
    Localization  <= "Core Module" loader
      Module.txt
  Modules
    Foo           <= "Reference Module" loader (because a "~/bin/Foo.dll" file exists)
      Module.txt
    Bar           <= "Precompiled Module" loader (because a "~/Modules/Bar/bin/Bar.dll" file exists)
      bin
        Bar.dll
      Module.txt
    Baz           <= "Dynamic Module" loader (because a "~/Modules/Baz/Baz.csproj" file exists)
      Controller
         BazControler.cs
      Baz.csproj
      Module.txt

Disabling the "Dynamic Module" loader

The dynamic module loader should be useless when deploying a website in production, as a production enviroment it should not be able to install and load module dynamically. But another important reason why it should be disabled is that it creates a lot of FileSystemWatcher instances to detect changes on the modules.

To disable the module, rename the file \Config\Sample.HostComponents.config to \Config\HostComponents.config, then check the content is:

<?xml version="1.0" encoding="utf-8" ?>
<HostComponents>
  <Components>
    <Component Type="Orchard.Environment.Extensions.ExtensionMonitoringCoordinator">
      <Properties>
        <Property Name="Disabled" Value="true"/>
      </Properties>
    </Component>
  </Components>
</HostComponents>

Deploy this file and restart the App Pool.

NB: You will have to ensure that the binaries for every modules are available in the /bin folder of each module, such that the Precompiled Module loader can use them directly. When using Visual Studio this should be the case. Otherwise use the command line tool to build the website, which will have the same effect.

References Resolution

(TODO: Explain how Orchard figures out references by looking at the "References" section of the csproj file as well as looking at additional assembly binaries dropped in each module bin directory)

Change of Configuration Detection

As explained above, modules are loaded at application startup. However, once the application is started up, changes can happen: a new module might be installed, the source code of a module might be manually updated, a module might be removed from the site, etc. To detect these changes, Orchard asks each module loader in the system to "monitor" potential changes, and notify when a change happens.

When a change is detected, the current module configuration is discarded and modules are re-examined, loaded and activated as if the application was starting up again. In some cases, these changes require an ASP.NET AppDomain restart (e.g. a new version of a module assembly needs to be loaded). Orchard detects these situations and forces an ASP.NET AppDomain restart.

Rendering Web Forms Views

(TODO: Explain that Orchard uses a custom virtual path provider to insert custom Assembly Src=xx and Assembly Name=xxx directive when reading .ascx and .aspx files)

Rendering Razor Views

(TODO: Explain that Orchard uses a Razor custom API to add Module dependencies to Views)

The ~/App_Data/Dependencies/Dependencies.xml file

This file contains the list of modules, their loader and their resolved references of the "last known good" configuration of module, i.e. the last time Orchard successfully loaded all modules of the application. Examining the content of this file can be useful for debugging purposes, e.g. if the latest version of a module doesn't seem to be loaded.