This content is archived. See latest version here.

Last updated: Mar 21 2013

Table of Contents

Introduction

This document describes how to develop your own add-ons as well as how add-ons are handled on the site. The add-ons feature is shipped with EPiServer Framework to be used from EPiServer Online Center by site administrators.

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, various plug-ins like dynamic content, GUI plug-ins, scheduled jobs and so on. Add-ons are deployed as Shell modules.

A lot of the power in the EPiServer platform lies in the pluggability – and therefore the many plug-ins and extensions to the platform. The add-ons system makes for an easy way to install ready-to-use extensions to the system, which makes it possible both for EPiServer and third parties to create add-on packages that extend and improve the functionality independently of full product releases.

The add-on feature works as follows:

  • Lists available packages for environment (products, version) to install and uninstall
  • Alerts the website administrator of available upgrades to installed packages

For instructions on how to work with packages and upgrades, see the administrator section of the EPiServer CMS User Documentation (web help).

Developing Add-on Packages

Consider the following when developing an add-on:

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

  3. 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" %>
  4. All paths to included resources should be relative to the add-on directory in modules directory.The EPiServer.Shell.Paths class provides new methods that can be used by add-on developers to resolve URLs to resources located in the directory of corresponding Shell module directory. The add-on module is identified by specified assembly or by any type from add-on assemblies.
     
    Resolving path to 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 path to client resource:
    string pathToAddonScript = Paths.ToClientResource(typeof(MapDynamicContent), "ClientResources/MapContent.js");
    // register client script using resolved path:
    Page.ClientScript.RegisterClientScriptInclude("MapContent.js", pathToAddonScript);
  5. Add-on GUI plug-ins should use relative paths to resources. Plug-in attributes provides new properties for that.

    – GuiPlugInAttribute.UrlFromModuleFolder property can be used to define the URL to control relative to add-on module directory (found in the EPiServer.PlugIn namespace in EPiServer CMS).
    – GuiPlugInAttribute.Url property returns resolved URL to add-on control (found in the EPiServer.PlugIn namespace in EPiServer CMS).
    – DynamicContentPlugInAttribute.ViewUrlFromModuleFolder property can be used to define the URL to view control for this dynamic content relative to add-on module directory (found in the EPiServer.DynamicContent namespace in EPiServer CMS).
    – DynamicContentPlugInAttribute.ViewUrl return resolved URL to 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")]
  6. The Path property of the RenderDescriptorAttribute 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 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 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 RenderDescriptorAttribute of a page or a block template is not set.
    Please refer to the topic Creating Page Templates and Block Controls in EPiServer CMS SDK.
  7. The new localization model with embedded language files is the primary localization model for add-on packages – assemblies that belong to Shell modules are automatically scanned for XML localization data. You can also use standard resource files.
  8. It is possible to add required client resources on the page without modifying the templates. Add-on developers can use this approach to inject styles, scripts or HTML on the pages. See Managing Client Resources section for more information.
  9. Client resources should be versioned to avoid caching problems when upgrading to a new version. 
  10. Configuration file and source code transformation are not supported in this CTP. For the future configuration changes should be kept to an absolute minimum.
  11. PowerShell scripts and other tools are not supported in this CTP.
  12. Third-party add-ons are not allowed to use an EPiServer namespace.

Creating Add-on Packages

An add-on package is a NuGet package containing all add-on resources and assemblies, see NuGet Docs.

Download the nuget.exe command line tool at NuGet Codeplex.

It is possible to create package from assembly, project or convention-based working directory. Please refer to NuGet Docs for more details.

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 have created and specify the information appropriate for your add-on package.

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

CopyXML
<?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" version="7.0" />
    </dependencies>
    </metadata>
</package>

Public and Protected Add-ons

The difference between public and protected add-ons is 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 if it provides content for the site’s visitors, for example: page templates, dynamic content rendering controls, public MVC views or client resources. 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 outside the website root folder, by default in C:\EPiServer\VPP\<SiteName>\Modules.
  • Protected add-ons have a default virtual path starting from ~/<EPiServerProtectedPath>/modules/<package ID>/.
Note that the add-on package must have a special tag. Protected add-ons should be marked with tag EPiServerModulePackage. Public add-ons should be marked with tag EPiServerPublicModulePackage.

Dependencies and Versioning

If your add-on requires other add-ons to be installed on the site, you can add these other add-ons 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 into the environment the installation will abort.

Add-on packages must follow the Semantic Versioning (SemVer) scheme for versioning for the package itself so that there is a common understanding of which versions will be compatible and which can introduce breaking changes.

In short, 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.

With these rules in place it is easy to predict which versions are going to be compatible.

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)".

For more information about version ranges in NuGet, see NuGet Docs.

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 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.

Package Directory Structure

Package directory structure should follow NuGet conventions.

All assemblies should be located in the package lib folder. During add-on installation assemblies are deployed to the site probing path folder and to the bin subfolder of corresponding module directory. For public add-ons it is typically ~/modules/<package ID>/bin/ folder.

All other package files like Web Forms pages and controls, MVC views, client scripts, CSS files and other resources should be located in content directory. These files are copied to package module directory during installation. For public add-ons it is typically ~/modules/<package ID>/ folder.

Supporting Multiple .NET Framework Versions

Add-ons can support several .NET Framework runtime versions. For more information about how to add assemblies compiled for different runtime version to folder, see NuGet Docs.

There is a known issue when installing add-on packages that are built for .NET Framework 4.5 and contain assemblies in subfolders specific to .NET 4.5, like net45 or 45. Assemblies in these subfolders are skipped as incompatible because current .NET version is detected as 4.0 for all websites that run both .NET Framework 4.0 and .NET Framework 4.5.

The reason is that the Add-on system uses assembly version to detect the current .NET Framework. The version of .NET Framework 4.5 assemblies is still 4.0.0.0 since .NET 4.5 is an in-place update for .NET 4.0.

There are 2 options to avoid this issue:

  1. Recommended: if you don’t use any new .NET 4.5 features in your add-on, you can build it for .NET 4.0. You can create the add-on package from the project and the package can contain add-on assemblies in framework-specific subfolder, like \lib\net40\AddOn.dll.
  2. If you need new .NET 4.5 features, you build the add-on for .NET 4.5. You should create the add-on package manually from the convention based working directory and place add-on assemblies directly in the lib folder inside your package, for example \lib\AddOn.dll. Don’t use framework-specific folders like net45 or 45 and ignore the NuGet.exe warning message about it. In this case your add-on will work only on websites running .NET Framework 4.5.

Including Resource Assemblies to the Package

Add-on resource assemblies should be located inside lib folder of your package. By default NuGet does not add resource assemblies to folder, you can include it explicitly for each supported runtime version in nuspec file:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <files>
    <file src="bin\**\*.resources.dll" target="lib\net40\" />
  </files>
</package>

Creating the Package

Once you have prepared NuGet manifest (nuspec) file, you can create the add-on package. If you are going to create package from Visual Studio project, run the following:

nuget pack AddOnProject.csproj

Please refer to NuGet Docs for information how to make sure that package is created from Release build configuration.

Alternatively if you have prepared a NuGet manifest (nuspec) file and convention based directory structure for the actual package contents, 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 shows an example of the Sample Google Maps add-on package opened in NuGet Package Explorer.

Executing Custom Code on Add-on Installation, Update or Delete

As an add-on developer you can execute a custom code at the following extension points, when certain actions are performed for your add-on:

  • After add-on installation
  • After add-on update
  • Before add-on uninstallation

If your add-on just requires to execute some custom code on every web application start up and does not require to be notified about installation, update or delete – please consider to use 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 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 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 as necessary or if the add-on is newly updated and calls AfterUpdate as necessary.

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

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

AfterInstall Method

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 add-on installation, as opposed to the Initialize method which is called each time the application starts.

To get custom code executed after add-on installation you need to do the following:

  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 Initialize method.
    • Call base.Initialize in the overridden method body.
    • Place your custom initialization code after the base.Initialize method call.

The process of add-on installation and point in a lifecycle 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 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. 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.

To execute a custom code after add-on update you need to do the following:

  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 add-on requires custom initialization:
    • Override Initialize method.
    • Call base.Initialize in overridden method body.
    • Place your custom initialization code after the base.Initialize method call.

The process of add-on update and point in a lifecycle 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 happens. If an exception is thrown in the BeforeUninstall method uninstallation will be aborted.

To execute a custom code before add-on unistallation you need to do the following:

  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 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.

Installing add-ons on an EPiServer Website

EPiServer add-ons are installed from the site’s web interface, which enables add-on management without having direct access to the server machine.

EPiServer Framework is using NuGet for handling add-ons and as a standard package format. To install add-on packages, the user needs to be a member in the PackagingAdmins user group and to have access to protected EPiServer UI path.

Installing Packages from EPiServer Central Repository

Go to EPiServer OnlineCenter > Go to EPiServer OnlineCenter > add-ons > Gallery tab. Here you can select any available add-on to install on the website.

The system will validate the package, try to resolve package dependents and dependencies and install it on the site if it is possible.

Installing Uploaded Packages

Go to EPiServer OnlineCenter > add-ons > Upload tab. Here you can select one or more package files to upload anThe installation process goes through the following steps:

  1. Package(s) are added to a temporary repository, where no validation of dependencies is performed.
  2. An attempt to resolve dependents, dependencies and install these packages to the application is done from the temporary repository combined with the other repositories.

Once a package has been installed to the repository it can be instantiated to any number of applications.

Validation of Packages

Uploaded files and selected add-ons are validated. That should be valid NuGet packages marked with proper module tag: EPiServerModulePackage or EPiServerPublicModulePackage.

Adding Uploaded Package to a Temporary Repository

The new uploaded packages are added to a temporary repository, by default located in the temporary directory designated by the operating system. If you want to use a different directory for this purpose, you can change the value of the packagesTemporaryFolder attribute in the episerver.packaging element:

CopyXML
<episerver.packaging packagesTemporaryFolder="...">

The temporary repository is used in conjunction with the virtual packages representing assemblies available on the site as well as the local repository to ensure that all dependencies can be resolved before installation. Provided that dependencies can be resolved from the package source repositories, the packages are installed to the current site.

 The package source repositories are the following:

  • The temporary repository
  • The site local repository
  • EPiServer central repository

You can also add additional repositories in the configuration, as shown in the following example:

CopyXML
<episerver.packaging>
  <packageRepositories>
     <add name="Gallery" url="C:\Your\Packages\Folder" />
  </packageRepositories>
</episerver.packaging>

All installed add-ons are registered in the packages.xml file in the site root. The system copies all supported files to the corresponding add-on folder in the ~/modules/ directory.

See information about the episerver.packaging element in this SDK documentation.

Adding Packages to the Local Repository

Successfully installed packages are added to the site local repository. The default repository is located in site VPP directory C:\EPiServer\VPP\<SiteName>\ModulesRepository. This directory is automatically set up with the correct access rights when EPiServer Framework is installed via EPiServer Deployment Center.

If you want you can configure a site to use a repository at another location by changing the value of the repositoryPath attribute on the episerver.packaging element:

<episerver.packaging repositoryPath="C:\EPiServer\Sites\MySite\App_Data\Packages">

Make sure that web application has write access right for that directory.

Copying add-on Assemblies to the Probing Path

Add-on assemblies compatible with the current framework version are copied to the probing path directory. The probing path default directory is the modulesbin directory in the site root.

The probing path directory must be under the site root and can be defined in the probingPath attribute in the configuration/episerver.framework/scanAssembly element. The same path should be configured in the privatePath attribute of probing element as well:

FileConfiguration
EPiServerFramework.config
CopyXML
<episerver.framework>
    <scanAssembly forceBinFolderScan="true" probingPath="modulesbin" />
            ...
Web.config
CopyXML
<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <probing privatePath="modulesbin" />
            ...

Completing the Installation

After installation of the selected add-on packages the user can see the results of the installation for each package and is informed that they need to restart the site if it was not restarted automatically by ASP.NET.

Site restart is required to load and scan new assemblies and initialize installed add-ons.

Updating Add-ons

Installed add-ons that have updates available are displayed in EPiServer OnlineCenter > Add-ons > Updates tab. Here you can update installed add-ons.

When upgrading an add-on the old version will be uninstalled and the new version installed. Any related add-ons (dependencies as well as dependents) that need to be upgraded will also be upgraded.

The process for updating installed add-ons works much the same as for the regular installation and uninstallation. For detailed information see the installation and uninstallation sections.

Uninstalling Add-ons

Installed add-ons are displayed in EPiServer OnlineCenter > Add-ons > Installed tab. Here you can uninstall add-ons.

An exception to the possibility to uninstall add-ons are system addons (for example, the add-on management system itself). These add-ons cannot be uninstalled, only upgraded.

During uninstallation add-on files and corresponding Shell module folder are removed and assemblies are deleted from the probing path directory.

Site restart is required to complete the uninstallation procedure and unload add-on assemblies from the application domain.

Comments