Developing simple add-on for EPiServer 7 Preview

This blog post provides brief technical information about Add-On Store in EPiServer 7 Preview and describes the process of developing simple add-on step by step.

Meet the Add-On Store

Add-on Store is the new approach to create, distribute, share and manage modules for EPiServer based sites.
For site owners and administrators Add-On Store is the one place where they can find, install and manage required add-ons to bring new features and tools for site visitors and editors.

For EPiServer partners and developers Add-On Store means the standard way to create and distribute modules, plug-ins and extensions for EPiServer sites.

And we use this technology ourselves. It may be worth mentioning that new Edit UI and UI Platform in EPiServer 7 Preview are add-ons. Add-On system itself is implemented as add-ons. It proves that add-ons can be really powerful.

Go ahead, download EPiServer 7 Preview and see it in action.

Add-ons from developer point of view

Please look at the documentation for add-on developers in EPiServer Framework SDK Knowledge Base to get more information, technical details and things that should be kept in mind when you are developing add-on.

What can be an add-on

Technically add-on is a Shell module that is distributed as NuGet package and can be installed, updated and removed using Add-On Store interface on EPiServer 7 site. Each add-on resides in the one directory.

Add-on provides new functionality and extensions for EPiServer sites: various plug-ins, scheduled jobs, gadgets, Editing UI components, data stores, typed pages, blocks, templates and so on.

Configuration file changes are not supported in this version. I’m not sure if it is good idea to make it possible in the future.

Initialization and extension point

It is possible to implement specific initialization for add-on components and execute custom code when add-on is installed, updated or removed.

Public, protected and system add-ons

Public add-ons provide functionality for the public site.

Protected add-ons usually bring features for site administrators and editors and contain resources that should not be available publicly.

System add-ons are important system modules that cannot be removed, but can be updated. Usually it is EPiServer add-ons that are distributed with the platform and deployed on each new web site. For example: new Editing Interface in EPiServer 7 Preview.

Add-on prerequisites and dependencies

Dependencies are the list of other add-ons that should be installed on the site. The system makes sure that you will not break any installed add-ons by updating or removing related packages. Dependencies are installed and upgraded automatically if possible.

Add-on system allows installing only those add-ons that are compatible with your environment.

Prerequisites are dependencies to the products that are not installed as add-ons. For example, you probably would define EPiServer Community as prerequisite for the add-on that provides some new great widgets to manage Community entities.

Sample add-on: Sharing Widget

This sample add-on makes it easy for site visitors to share the content using ShareThis widget which allows to recommend things in tons of social networks and communities.

To try something new sharing widget is implemented as shared block that can be simply dropped on the site pages in new EPiServer 7 Edit UI.

Getting ShareThis widget code

Go to ShareThis site and generate default widget with small buttons for generic web site and pick 4 favorite social networks: Twitter, Facebook, Google+ and LinkedIn. All other communities will be available through ShareThis option.

Generating ShareThis widget code

You will be prompted to register in order to get Publisher ID. Using this account you can access a set of analytic tools to understand sharing behavior. It’s possible to skip the registration and just get a code, in this case you still get some Publisher ID but you don’t have access to analytics. Make sure that ShareThis terms of service and privacy policy are fine with you.

Save generated code on the last step for further use:

<span class='st_twitter' displayText='Tweet'></span>
<span class='st_facebook' displayText='Facebook'></span>
<span class='st_googleplus' displayText='Google +'></span>
<span class='st_linkedin' displayText='LinkedIn'></span>
<span class='st_sharethis' displayText='ShareThis'></span>

<script type="text/javascript">var switchTo5x=true;</script>
<script type="text/javascript" src="http://w.sharethis.com/button/buttons.js"></script>
<script type="text/javascript">stLight.options({publisher: "Your-Publisher-ID"}); </script>

Creating add-on project

Create new ASP.NET Empty Web Application project using Visual Studio. You can remove web.config file in the project root and the most of default references.

References to the following assemblies need to be added:

  • EPiServer 
  • EPiServer.BaseLibrary
  • EPiServer.Data
  • EPiServer.Shell
  • System.ComponentModel.DataAnnotations
  • System.Web.Mvc

Hopefully, platform binaries will be available in the EPiServer NuGet feed after EPiServer 7 release and it will be possible to reference corresponding NuGet package. Right now you can just create Dependencies directory in the solution folder and copy referenced EPiServer assemblies.

Adding block type

Create BlockTypes folder inside your project and add new class SharingWidgetBlock:

ContentType(DisplayName = "Sharing Widget Block", 
    Description = "Adds ShareThis widget on pages where block is placed.")]
public class SharingWidgetBlock : BlockData
{
    [Display(Name = "Publisher ID",
        Description = "Register on ShareThis.com to get your Publisher ID.")]
    [Required]
    public virtual string PublisherID { get; set; }
}

EPiServer.DataAnnotations.ContentTypeAttribute is applied to mark the content type and define the name and description that are displayed when this block type is listed in Admin mode.

EPiServer.Core.BlockData class provides the base implementation for all block types.

PublisherID property is the only one setting for sharing widget block and it is marked as required. DisplayAttribute is used to define property name and description that will appear in EPiServer Edit and Admin mode.

Implementing block rendering

EPiServer 7 Preview brings full MVC support and the great thing here is that you can have Web Forms and MVC working side by side.

Obviously your add-on is going to be so awesome that most of EPiServer site owners will dream about installing it on their EPiServer 7 sites, right? Of course it should support both Web Forms and MVC!

Web Forms control

Add Blocks sub folder in the project and create new user control SharingWidget.ascx.

Change control code-behind file and inherit BlockControlBase<SharingWidgetBlock> which is the base class for user controls that renders block data:

public partial class SharingWidget : BlockControlBase<SharingWidgetBlock>
{
}

Add ShareThis widget code to control markup.

Note that add-on assembly is referenced in the Control directive. This is required since add-on binaries are not placed to site bin folder and will be loaded from probing path.

<%@ Control Language="C#" AutoEventWireup="false" CodeBehind="SharingWidget.ascx.cs" 
    Inherits="EPiServer.Samples.SharingWidget.Blocks.SharingWidget, EPiServer.Samples.SharingWidget" %>

<script type="text/javascript">    var switchTo5x = true;</script>
<script type="text/javascript" src="http://w.sharethis.com/button/buttons.js"></script>
<script type="text/javascript">    stLight.options({ publisher: "<%= CurrentBlock.PublisherID %>" }); </script>

<span class='st_twitter' displayText='Tweet'></span>
<span class='st_facebook' displayText='Facebook'></span>
<span class='st_googleplus' displayText='Google +'></span>
<span class='st_linkedin' displayText='LinkedIn'></span>
<span class='st_sharethis' displayText='ShareThis'></span>

CurrentBlock property is used to access block instance that is being rendered by this user control and set publisher ID widget parameter.

MVC block rendering

Model

You already have it: SharingWidgetBlock is the model.

View

Add standard Views folder in the project. Create SharingWidget sub folder and add SharingWidget.cshtml partial view. It follows naming conventions for MVC views, however it is not really important in this particular case.

SharingWidget.cshtml view code looks like this:

@model EPiServer.Samples.SharingWidget.BlockTypes.SharingWidgetBlock
           
<script type="text/javascript">    var switchTo5x = true;</script>
<script type="text/javascript" src="http://w.sharethis.com/button/buttons.js"></script>
<script type="text/javascript">    stLight.options({ publisher: "@Model.PublisherID" }); </script>

<span class='st_twitter' displayText='Tweet'></span>
<span class='st_facebook' displayText='Facebook'></span>
<span class='st_googleplus' displayText='Google +'></span>
<span class='st_linkedin' displayText='LinkedIn'></span>
<span class='st_sharethis' displayText='ShareThis'></span>

As you can see, it uses Razor syntax to output publisher ID and contains the same HTML as user control.

To make it work add web.config file to the Views folder. It configures Razor engine and makes add-on assembly and namespace available in the views as follows:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="EPiServer.Samples.SharingWidget" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

  <system.web>
    <compilation>
      <assemblies>
        <add assembly="EPiServer.Samples.SharingWidget" />
      </assemblies>
    </compilation>
  </system.web>
</configuration>
Controller

Create controller responsible for block data rendering:

public class SharingWidgetController : BlockController<SharingWidgetBlock>
{
    public override ActionResult Index(SharingWidgetBlock blockData)
    {
        return PartialView(Paths.ToResource(this.GetType(), 
            "Views/SharingWidget/SharingWidget.cshtml"), blockData);
    }
}

There is a tiny trick to provide required view file.

BlockController<T> is the base class for block rendering controllers. Index method of base controller returns action result for partial view with the name equal to block type name. Then the system tries to find partial view in a folder structure according to the block namespace.

Search paths for EPiServer.Samples.SharingWidget.BlockTypes.SharingWidgetBlock type would be as follows:

~/Views/SharingWidget/SharingWidgetBlock.aspx
~/Views/SharingWidget/SharingWidgetBlock.ascx
~/Views/Shared/SharingWidgetBlock.aspx
~/Views/Shared/SharingWidgetBlock.ascx
~/Views/SharingWidget/SharingWidgetBlock.cshtml
~/Views/SharingWidget/SharingWidgetBlock.vbhtml
~/Views/Shared/SharingWidgetBlock.cshtml
~/Views/Shared/SharingWidgetBlock.vbhtml
/Util/Views/Shared/SharingWidgetBlock.ascx
/Util/Views/Shared/DisplayTemplates/SharingWidgetBlock.ascx
/Util/Views/Shared/EditorTemplates/SharingWidgetBlock.ascx
/EPiServer/CMS/Views/Shared/SharingWidgetBlock.ascx
/EPiServer/CMS/Views/Shared/DisplayTemplates/SharingWidgetBlock.ascx
/EPiServer/CMS/Views/Shared/EditorTemplates/SharingWidgetBlock.ascx

Most probably add-on view file will not be found in these folders, so the system needs some help to locate it.
The path to the directory where add-on is installed depends on packaging configuration and add-on developer should never hardcode absolute and concrete paths. Full paths to add-on resources like scripts, styles and views should be resolved from the path relative to the add-on directory. EPiServer.Shell.Paths class provides the bunch of methods to resolve path to resources located in the corresponding add-on folder. Add-on can be identified by name, by assembly or by type from add-on assembly.

Controller for sharing widget block overrides default Index method to resolve the view path using partial path in add-on directory.

Intermediate results

Solution structureYou have implemented block type, Web Forms and MVC rendering templates and this code can be deployed as Shell module on the site.

I agree, it’s nasty to have that script tags in both markups; you get several script includes if there are several controls/views rendered on the page. But don’t worry about it too much. Probably you won’t have several sharing widgets on page unless your site looks like this.

And of course it would be nice to make the style of the widget and the list of available buttons configurable. Again, keep it simple for the version one.

Creating add-on package

To turn the module into an add-on you need to create a NuGet package that contains add-on assemblies and resources.

Download NuGet command line or Package explorer from NuGet CodePlex.

There are several approaches to creating a NuGet package. Creating a package from a project looks like the easiest way for this simple add-on.

Creating package manifest file

Run following command in the directory where the .csproj file is:

nuget spec

It creates NuGet .nuspec manifest file for add-on package. Manifest file can be included to the project for convenience.

Package metadata

Edit .nuspec file, add basic metadata like add-on title, description, tags, icon URL etc.

Note that it contains replacement token $id$, $version$ and $author$ that are going to be replaced by assembly name, version and author when NuGet builds the package:

<?xml version="1.0"?>
<package >
  <metadata>
    <id>$id$</id>
    <title>Sharing Wigdet</title>
    <version>$version$</version>
    <authors>$author$</authors>
    <owners>$author$</owners>
    <projectUrl>https://github.com/dmytroduk/SharingWidgetAddOn</projectUrl>
    <iconUrl>http://w.sharethis.com/images/sharethis_32.png</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Enables site visitors to share the content in social networks and communities using ShareThis.com widget.</description>
    <tags>EPiServerPublicModulePackage Social Sharing ShareThis</tags>
    <dependencies>
      <dependency id="EPiServer" version="[7.0,8.0)" />
    </dependencies>
  </metadata>
</package>

Public or protected add-on

It’s required to add one of the special tags to indicate that this is add-on package for EPiServer based site:

  • EPiServerPublicModulePackage- this tag should be added for public add-ons
  • EPiServerModulePackage - should be added in the tag list of protected add-ons

EPiServerPublicModulePackage tag is more suitable for this add-on since sharing widget should be available for all site visitors.

Dependencies and prerequisites

Sharing Widget add-on does not depend on any other add-on, so you could skip dependencies list.
However, it can be used to set add-on prerequisites. Define that this add-on requires any version of EPiServer CMS 7. Add dependency with id equal to EPiServer core assembly name and version range from 7 (including) to 8 (excluding). Add-on system will check if corresponding assembly is loaded in site app domain.

Building the package

Build the project.

Run following command to create the package from the add-on project:

nuget pack EPiServer.Samples.SharingWidget.csproj

It creates add-on package EPiServer.Samples.SharingWidget.1.0.0.0.nupkg that is ready to be installed.

Installing Sharing Widget add-on

Login to EPiServer 7 Preview site as administrator and navigate to Add-Ons section.

Sharing Widget add-on is not published in the central EPiServer Store (yet!), you have to upload package file to install it on site. Click on “Manual Upload” button, select package file and press Install. You will be prompted to restart the site to finish the installation.

After successful installation Sharing Widget add-on should be displayed in installed section. You can remove it any time (why would you?) by clicking on Uninstall button in detailed view.

Add-on detailed view

Using add-on widget

Creating shared block

Navigate to new shiny Edit UI. Make sure that you have Shared Blocks widget in the right panel. Create new Sharing Widget block, specify your publisher ID and publish.

Creating Sharing Widget block

Placing widget on pages

Drag Shared Widget block and drop it on the appropriate content area. Publish the page.

Dropping widget on the page

Check out that sharing widget works both on Web Forms and MVC site:

Sharing widget on MVC site

Source code

Source code is available on GitHub, you are more than welcome to download or fork. Add-on package can be downloaded here.

Try it!

Hopefully the above example would encourage you to start developing your add-ons right now and make new great things.

Your feedback is highly appreciated and would help to make add-on system and EPiServer 7 better.

Updated

Add-on was rebuilt for EPiServer 7 RTM, you can find more information here.

Tags: EPiServer, Add-Ons, Samples

14 Comments

  • Gravatar image

    Paul Smith Link

    Nice!

  • Gravatar image

    Marcus Granström Link

    Nice writeup Dmytro.

  • Gravatar image

    CDK Link

    Thanks for a great article. I've setup your project, but I can't get the MVC-rendering to work. As of now, if I exclude SharingWidget.ascx from the project, there will be no rendering. Do you have any idea why the MVC-part won't work? I'm using episerver 7. Thanks in advance :-)

  • Gravatar image

    Dmytro Duk Link

    This code was written for EPiServer 7 Preview, so there can be slight differences and changes comparing to EPiServer 7 release version.
    Could you provide more details about errors that you get when previewing shared block or opening a page where this block is dropped in content area?

  • Gravatar image

    CDK Link

    Thanks for your reply. After I add the block and specify "Publisher Id" I get "There is no renderer for 'SharingWidgetBlock'" in the preview. Opening a page with the block in it gives me no error messages, nor any content.

  • Gravatar image

    Minh Bui Link

    Thanks for your great post. I've setup successfully your project. It works perfectly.

  • Gravatar image

    Dmytro Duk Link

    I have rebuilt this add-on for EPiServer 7 RTM, please see details here: http://dmytroduk.com/techblog/sharing-widget-for-episerver-7

  • Gravatar image

    Mark Link

    I have followed the above istructions but get an error meesage when I try and upload the AddOn, the error is "an error occure on Addon upload". is there any debug information on why this error is occuring, I have also downloaded and attempted to install you demonstraction add on with the same error

  • Gravatar image

    Dmytro Duk Link

    @Mark, let's try to find out what is the problem. First, you can enable debug logging for add-on system by adding something like this in your EPiServerLog.config:

    <?xml version="1.0" encoding="utf-8"?>
    <log4net>

    <appender name="debugPackagingFileLogAppender" type="log4net.Appender.RollingFileAppender" >
    <file value="App_Data\EPiServer.Packaging.Debug.log" />
    <encoding value="utf-8" />
    <staticLogFileName value="true"/>
    <datePattern value=".yyyyMMdd.'log'" />
    <rollingStyle value="Date" />
    <threshold value="Debug" />
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <appendToFile value="true" />
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %level %logger: %message%n" />
    </layout>
    </appender>

    <logger name="EPiServer.Packaging" additivity="false">
    <level value="Debug" />
    <appender-ref ref="debugPackagingFileLogAppender" />
    </logger>

    </log4net>

    Also you can use Chrome developer tools, Firebug, Fiddler or similar utility to inspect network calls and get more information about failed requests if any.

    And by the way, which versions of EPiServer platform and system add-ons (Edit UI, Add-on system) are deployed on your site?

    Please let me know what did you find.

  • Gravatar image

    Anderson Link

    Hello,

    I'm trying create my first package and your post is very helpful.

    At moment I just don't understand why your package copy your bins to C:\EPiServer\Sites\MyEPiServerSite2\modulesbin and my package not.

    You have some idea?

  • Gravatar image

    Dmytro Duk Link

    Hello,
    What is the target framework of your add-on project?
    Does your package contain add-on assembly?
    What folder structure you have in add-on package under lib folder?

  • Gravatar image

    Roy Gonzalez Link

    Hi, i have a question, i created a Add On, but i want to add a module.config for every addon, can i do this one?

  • Gravatar image

    Link

    @Roy: sure, you can add module.config file for your add-on.
    You can find an example of module.config in Disqus add-on here: https://github.com/dmytroduk/Duk.EPiServer.Disqus/blob/master/Duk.EPiServer.Disqus.UI/module.config

    module.config should be included in content sub-folder inside your add-on NuGet package.
    Just add module.config file in the root of your project in Visual Studio if you create add-on NuGet package from the .csproj project, as it is described in this blog post.

  • Gravatar image

    Henric Rosvall Link

    Great article.

    Got this error when rendering the view:
    The name 'model' does not exist in the current context

    and I couldn't for my life figure out what was wrong. Found pages indicating that it could be Razor views that didn't work because of a missing Web.Config, misspelled model path, and alot of other things - but no matter what I tried, nothing solved the problem.

    So in the end I sat there, changing one line at the time from your project to my project and reinstalling and testing every time a line had changed - and by doing that, I finally figured out what caused the problem:
    I was using EPiServerModulePackage instead of EPiServerPublicModulePackage in the nuspec-file

    I enterpreted the attribute as an indicator of wheter the plugin should be available to the public (through some nuget.org-package-publication-system or such) or private, like only available to those I sent it to. This enterpretation was of course wrong, but made me change to EPiServerModulePackage - and then there was in my mind no logical connection between that attribute and my razor view models failing, which made the error search that much longer.

    6 hours wasted. Hopefully this can help someone else not to make the same mistake I did :)

Add a Comment
  1. (used to show your gravatar; will not be published)