This content is retired. See latest version here.

Last updated: Jul 09 2014

The Add-ons Store shipped with the EPiServer, allows site administrators to view and install add-ons to EPiServer websites. It is also possible to install and manage the add-ons from Visual Studio but allow site administrators to get read-only view to add-ons, see Installing Add-ons for details. Add-on modules can be developed both by EPiServer as well as third-parties. This document describes how to develop your own add-ons as well as how add-ons are handled on a website.

An add-on can contain components that extend the functionality of the EPiServer website, like initializable modules, gadgets, visitor group criteria, virtual path providers, page and search providers.

Add-ons are packaged as ordinary NuGet packages and follows a defined set of guidelines. The folder structure and configuration system used by add-ons are defined by the module system. The module system, or Shell modules, is a built-in module system primarily to extend the user interface but can be used to build any module.

Guidelines

Below is a set of guidelines to consider when developing add-ons.

  • Add-on assemblies are being loaded and processed on site start-up just as any other assembly (so an add-on can contain components like InitializableModule and plug-ins that require assembly scanning).
  • The add-on must reside in a Shell module directory. For example, a Google Maps dynamic content add-on could reside in ~/modules/EPiServer.Samples.AddOns.GoogleMaps/.

  • Assembly names should be referenced in Web Forms pages, controls and strongly typed MVC views of your add-ons.
    User control example:
    <%@ Control Language="C#" AutoEventWireup="false" CodeBehind="Map.ascx.cs" Inherits="EPiServer.Research.DynamicContent.Map, GoogleMapsDynamicContent" %>
     
    Strongly typed MVC view example:
    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ColoredModel>" %>
    <%@ Assembly Name="EPiServer.Samples.Module" %>
    <%@ Import Namespace="EPiServer.Samples.Module.Models" %>
  • All paths to included resources should be relative to the add-on directory in the modules directory. The EPiServer.Shell.Paths class provides new methods that can be used to resolve URLs to resources located in the directory of the corresponding Shell module directory. The add-on module is identified by a specified assembly or by any type from the add-on assemblies.
     
    Resolving a path to an add-on resource by type
    // MapDynamicContent is type from add-on assembly:
    string pathToControl = Paths.ToResource(typeof(MapDynamicContent), "Map.ascx"); // load control using resolved path:
    page.LoadControl(pathToControl);
  • Resolving a path to a client resource:
    string pathToAddonScript = Paths.ToClientResource(typeof(MapDynamicContent), "ClientResources/MapContent.js");// register client script using resolved path:
    Page.ClientScript.RegisterClientScriptInclude("MapContent.js", pathToAddonScript);
  • Add-on GUI plug-ins should use relative paths to resources. Plug-in attributes provides new properties for that.

    – The GuiPlugInAttribute.UrlFromModuleFolder property can be used to define the URL to a control relative to the add-on module directory (found in the EPiServer.PlugIn namespace in EPiServer CMS).
    – The GuiPlugInAttribute.Url property returns a resolved URL to an add-on control (found in the EPiServer.PlugIn namespace in EPiServer CMS).
    – The DynamicContentPlugInAttribute.ViewUrlFromModuleFolder property can be used to define the URL to view a control for this dynamic content, relative to the add-on module directory (found in the EPiServer.DynamicContent namespace in EPiServer CMS).
    – The DynamicContentPlugInAttribute.ViewUrl returns a resolved URL to a view control (found in the EPiServer.DynamicContent namespace in EPiServer CMS).
     
    Recommended way to define paths to plug-in resources:
    [GuiPlugIn(UrlFromModuleFolder="Control.ascx")]
    [DynamicContentPlugIn(ViewUrlFromModuleFolder ="View.ascx")]
  • The Path property of the TemplateDescriptorAttribute on a page or a block template should contain the path to the template file relative to the module folder. This path should not be application relative and not an absolute virtual path. The system will resolve the virtual path to the content template file in the add-on directory.
    Example:
    [RenderDescriptor(Path = "Blocks/SampleBlockControl.ascx")]
    The resolved virtual path to the block template in a public add-on will be ~/modules/<package ID>/Blocks/SampleBlockControl.ascx
    The directory structure where template files reside, must follow the namespace convention if the Path property of TemplateDescriptorAttribute of a page or a block template is not set.
    Refer to Creating page templates and block controls.
  • The EPiServer platform LocalizationService model with embedded language files is primarily used for add-ons, and assemblies that belong to Shell modules are automatically scanned for XML localization data. You can also use standard resource files.
  • It is possible to add required client resources on the page without modifying the templates. You can use this approach to inject styles, scripts or HTML on the pages. See Managing Client Resources section for more information.
  • Client resources should be versioned to avoid caching problems when upgrading to a new version. Refer to the Client resources section in the EPiServer Framework SDK for more information.
  • Configuration file and source code transformation are not supported, and configuration changes should be kept to an absolute minimum.
  • PowerShell scripts and other tools are not supported.
  • Third-party add-ons are not allowed to use an EPiServer namespace.

Creating add-on packages

About NuGet

An EPiServer add-on is delivered as a NuGet package, containing all add-on resources and assemblies. A package can be created from an assembly, a project or a convention-based working directory. Refer to the tools and documentation available from Nuget, for information on how to create a NuGet package.

Creating the manifest

Run the following command to create a NuGet manifest (nuspec) file for your add-on package:

nuget spec

Edit the nuspec file you just created, and specify the information appropriate for your add-on package. If you are going to create your package from a project, you can use replacement tokens for the add-on package ID, version, author and description. The following nuspec file example is for the Google Maps add-on:

XML
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <title>Google Maps dynamic content</title>
    <authors>$author$</authors>
    <owners />
    <iconUrl>http://world.episerver.com/PageFiles/3/Icons/Nuget.png</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Allows to add Google Maps as dynamic content on pages.</description>
    <tags>EPiServerPublicModulePackage Google Maps Dynamic</tags>
    <dependencies>
        <dependency id="EPiServer.Samples.SampleFoundation" version="1.2" />
        <dependency id="EPiServer.CMS.UI" version="7.5" />
    </dependencies>
    </metadata>
</package>

Public and protected add-ons

The difference between public and protected add-ons lies in the user access rights required to access the add-on files/routes.

  • Protected add-on files/routes, can be accessed only by the authorized users within WebEditors or WebAdmins roles by default. Always consider to make your add-on protected, since it is a more secure approach.
  • Public add-on files/routes can be accessed by any site user including anonymous users. You should make your add-on public only if it provides content such as page templates, dynamic content rendering controls, public MVC views or client resources for the site visitor. If the size of the public content is relatively small, you can consider including it as an embedded resource in one of the add-ons binaries and make the add-on protected instead.

Public and protected add-ons have the following file locations and virtual paths:

  • Public add-on files, reside in the modules folder by default located under the site root (the same path where all the public shell modules are located).
  • Public add-ons have a default virtual path starting from ~/modules/<package ID>/.
  • Protected add-on files are located by default in ~/modules/_protected/<package ID>/.
  • Protected add-ons have a default virtual path starting from ~/<EPiServerProtectedPath>/modules/<package ID>/ (for example /EPiServer/Modules/<package ID>)
Note that protected add-ons should be marked with the tag EPiServerModulePackage, and public add-ons should be marked with the tag EPiServerPublicModulePackage.

Dependencies and versioning

If your add-on requires other add-ons to be installed on the site, you can add these to the list of dependencies in the nuspec file. Doing so will ensure that if these other add-ons are not already installed when a user chooses to install yours, the other add-ons will also be installed if possible. If the other add-ons are not possible to install to the environment, the installation will abort.

Add-on packages must follow the Semantic Versioning (SemVer) scheme for versioning for the package itself, in order to have a common understanding of which versions will be compatible and which can introduce breaking changes.

SemVer means having version numbers in the format Major.Minor.Patch, where the different segments correspond to:

  • Major: Breaking changes.
  • Minor: New features, but backwards compatible.
  • Patch: Backwards compatible bug fixes only.

If we for example require the feature set from version 1.3 of a dependency, we can simply set "[1.3,2)" as the version range. This will accept all versions that are known to be compatible. When version 2.0 is released it may or may not turn out to be compatible for our purposes. If it is compatible, the version range can be changed to "[1.3,3)" in the next update. Otherwise the version range, once our codebase has been changed to run with version 2.0 of this dependency, will be changed to something like "[2.0,3)".

Refer to NuGet Docs for more information about version ranges in NuGet.

Prerequisites

Prerequisites are dependencies to installed products that are not installed as actual add-on packages. These dependencies serve to limit the possibility to install packages that require specific products to ensure compatibility. For this purpose, assemblies installed in the application as well as system packages (e.g. EPiServer.CMS.Core and EPiServer.CMS.UI) are represented in the NuGet environment as virtual packages that one can add dependencies to. The names and version numbers of these packages are based on the assembly names and assembly versions. Note that for Visual Studio enabled add-ons the dependencies must be actual NuGet packages.

Package directory structure

The package directory structure should follow the conventions in NuGet Docs for more details on how to create packages.

If you are going to create the package from a Visual Studio project, run the following:

nuget pack AddOnProject.csproj

Alternatively, if you have prepared a NuGet manifest (nuspec) file and a convention based directory structure for the actual package content, you can create the package as follows:

nuget.exe pack addondirectory\addon.nuspec

Using NuGet Package Explorer

Another option is to use the NuGet Package Explorer GUI tool to view metadata and create new packages.

Below is an example of the Sample Google Maps add-on package opened in NuGet Package Explorer.

Developing Visual Studio enabled Add-ons

All dependencies should be to ordinary NuGet packages rather than just references to assemblies, in most scenarios the primary assembly of the NuGet package matches the ID of the package.

All content files must have a path in the nuspec as they appear on site. This implies that paths for content files should include "\modules\<packageid>" for public add-ons, for example:

<file src="Settings.aspx" target="Content\modules\<packageId>\Settings.aspx" />

and "modules\_protected\<packageid>" for protected add-ons, for example:

<file src="Views\Setting\Index.aspx" target="Content\modules\_protected\<packageId>\Views\Setting\Index.aspx" />

The NuGet package should contain a module.config file in the package root (e.g. <file src="module.config" target="Content\modules\<packageId>\module.config" />). The module config must contain an attribute 'tags' that contain either EPiServerModulePackage or EPiServerPublicModulePackage. This is required for the EPiServer add-on UI to be able to distinguish the add-on from other shell modules. The module config can also contain a 'description' attribute that will describe the module in the add-on UI. It should also list all assemblies the package contains of. A minimial module.config example is given below:

<?xml version="1.0" encoding="utf-8"?>
<module loadFromBin="false" description="Allows to run various support tools on the site." tags=" EPiServerModulePackage ">
  <assemblies>
    <add assembly="DeveloperTools" />
  </assemblies>
</module>

There is a tools package EPiServer.Packaging.Converter that can be used to convert old add-ons a format that allows the add-on to be installed from Visual Studio as well. In the conversion tool is a readme file included which describes the usage of the tool.

ZIP Compressed Content

It's recommended to deliver all content in your add-on as a compressed ZIP archive. One of the main benitifs of this approach is that developers that adds a reference to your package does not get a lot of files, for instance views and JavaScript files, included in their project. Simply compress the whole content structure below  the package folder to a zip file and give it the same name as your the package itself and place within the package directory, for example:

Content\modules\<packageId>\<packageId>.zip

EPiServer will scan all module directories during startup and add a virtual path provider for any archive that is found as long as it follows this convention.

Using this feature is NOT recommended if your module contains large files as the content will be kept in a memory cache. It can however be useful if you are developing a Visual Studio add-on that contains a vast amount of small files as it will prevent the module from adding all those files to the Visual Studio project of the developers installing your add-on. If you want to debug files in an add-on that uses this functionality, simply extract the archive into it's current directory and delete/rename the archive.

Executing custom code for add-ons

Custom code can be executed at the following extension points, when certain actions will be performed:

  • After the add-on installation
  • After an add-on update
  • Before an add-on uninstallation (does not trigger when uninstalling an add-on using Visual Studio)

If your add-on only requires to execute custom code on web application start-up and does not need to be notified about installations, updates or deletions, consider using IInitializableModule.

Base class for your custom code

In order to get custom code executed when the status of the add-on package changes, it should include a class inherited from the abstract class EPiServer.Packaging.PackageInitializer in the EPiServer.Packaging assembly:

public abstract class PackageInitializer : IInitializableModule, IPackageNotification    
{
        #region Implementation of IInitializableModule
 
        public virtual void ConfigureContainer(ServiceConfigurationContext context);
        public virtual void Initialize(InitializationEngine context);
        public virtual void Uninitialize(InitializationEngine context);
        public virtual void Preload(string[] parameters);

        #endregion

        #region Implementation of IPackageNotification

        public abstract void AfterInstall();
        public abstract void AfterUpdate();
        public abstract void BeforeUninstall();

        #endregion
        }

The PackageInitializer class combines the IInitializableModule and IPackageNotification interfaces. Inheritors of this class are instantiated and executed by the EPiServer Framework initialization system in the same manner as for the regular IInitializableModule.

The Initialize method in PackageInitializer checks if the add-on (package ID) containing the assembly with the inheriting class is newly installed, and calls the AfterInstall method if necessary, or if the add-on is newly updated calls AfterUpdate if necessary.

When overriding the Initialize method, you should call the base implementation before proceeding with the initialization to ensure that the AfterInstall and AfterUpdate methods are executed before the initialization.

The BeforeUninstall method is called before the package contents are removed, when the user clicks the Uninstall button in the add-on system.

AfterInstall method

This contains the code to be executed after the add-on installation is complete. This method is called only the first time the application starts after the add-on installation, as opposed to the Initialize method which is called each time the application starts.

Do the following to get custom code executed after an add-on installation:

  1. Create a class that inherits from EPiServer.Packaging.PackageInitializer.
  2. Decorate the class with a [ModuleDependency(typeof(EPiServer.Packaging.PackagingInitialization))] attribute.
  3. Override the AfterInstall method and put you custom code inside this method.
  4. If the add-on requires custom initialization:
    • Override the Initialize method.
    • Call base.Initialize in the overridden method body.
    • Place your custom initialization code after the base.Initialize method call.

The add-on installation and the point where the AfterInstall method is called:

  1. The user clicks the Install button in the add-on management user interface.
  2. The add-on is deployed to the site.
  3. Site restart occurs automatically, or the user clicks the Restart button in the add-on management user interface.
  4. On system startup, the add-on assemblies are loaded into the AppDomain.
  5. The Initialize method is called for the initializable modules.
  6. The AfterInstall method is called from base.Initialize.

AfterUpdate method

Code executed after the add-on update is complete, this method is called only the first time the application starts after updating an add-on.

Do the following to execute custom code after an add-on update:

  1. Create a class that inherits from EPiServer.Packaging.PackageInitializer.
  2. Decorate the class with [ModuleDependency(typeof(EPiServer.Packaging.PackagingInitialization))] attribute.
  3. Override the AfterUpdate method and put you custom code inside this method.
  4. If the add-on requires custom initialization:
    • Override the Initialize method.
    • Call base.Initialize in the overridden method body.
    • Place your custom initialization code after the base.Initialize method call.

The add-on installation and the point where the AfterUpdate method is called:

  1. The user clicks the Update button in the add-on management user interface.
  2. The updated add-on is deployed to the site.
  3. Site restart occurs automatically or the user clicks Restart button in the add-on management user interface.
  4. On system startup the add-on assemblies are loaded into the AppDomain.
  5. The Initialize method is called for the initializable modules.
  6. AfterUpdate is called from base.Initialize.

BeforeUninstall method

This method is executed immediately after the user clicks the Uninstall button in the add-on management user interface, but before the actual uninstallation takes place. If an exception is thrown in the BeforeUninstall method, the uninstallation will be aborted. Does not trigger when uninstalling an add-on using Visual Studio.

Do the following to execute custom code before add-on unistallation:

  1. Create a class that inherits from EPiServer.Packaging.PackageInitializer.
  2. Override the BeforeUninstall method and put you custom code inside this method.

The process of add-on uninstallation:

  1. The user clicks the Uninstall button in the add-on management user interface.
  2. BeforeUninstall is called.
  3. Actual removal of the add-on is performed (but add-ons assemblies are still loaded in the AppDomain).
  4. Restart occurs automatically, or the user clicks the Restart button in the add-on management user interface.
  5. Assemblies from the deleted add-on are no longer loaded into the AppDomain.

Managing dependencies

If an add-on is dependent on other systems or add-ons, it needs to indicate these dependencies using ModuleDependency attributes. This way the initialization methods will be called after those of all of the listed dependencies.

See also

Comments

On the Guidelines section there is a point which describes the Path property of RenderDescriptorAttribute. This should be TemplateDescriptorAttribute.

Thanks for the feedback, I've updated this.