EPiServer PlugIns in one single dll

by: Johan Olofsson

I have written numerous Edit/Admin-mode plugins for EPiServer CMS over the years, and it has somewhat intruiged me that plugin-framework requires an .ascx (or .aspx) to be available.

PlugIns thus have to consist of several files besides the assembly (dll) and often requires directories to be named (and positioned) correctly in order to work.

One approach to workaround this that I recently came up with is to have the plugin's assembly contain the needed .ascx files as embedded resources and then during initialization register a VirtualPathProvider with the web hosting framework that provide those "files" when the PlugIn framework wants to call LoadControl.

You can download the complete sample project from this link: http://labs.episerver.com/PageFiles/111555/SamplePlugin.zip
(unzip to your EPiServerCMS5-root folder)

First, in order to get the .ascx as an embedded resource, on needs to take the following steps:

  1. remove codebehind-attribute from the markup
  2. place it in parallell to the codebehind file
    parallell
  3. rightclick the ascx and select Properties and then select the Build Action "Embedded Resource

    EmbeddedResource

Note that you have to declare any web controls manually in the codebehind file, just like in Asp.Net 1.1, as the ascx is no longer processed by the compiler.

And, here is the complete code listings for the sampleplugin and the embedded sampleplugin.ascx file

   1: <%@ Control Language="C#" AutoEventWireup="false" Inherits="MyPlugin.SamplePlugin" %>
   2: <h1>This is an embedded resource</h1>
   3: <asp:Button ID="DontClickMeButton" runat="server" Text="Dont click me!" />

 

   1: using System;
   2: using System.Web;
   3: using System.Web.UI;
   4: using System.Web.Caching;
   5: using System.Web.Hosting;
   6: using System.Web.UI.WebControls;
   7:  
   8: using EPiServer.PlugIn;
   9:  
  10: namespace MyPlugin
  11: {
  12:     [
  13:       GuiPlugIn(DisplayName = "SamplePlugin",
  14:                 Area = PlugInArea.EditPanel,
  15:                 Description = "Sample Plugin",
  16:                 Url = "~/MyPlugin/SamplePlugin.ascx")
  17:     ]
  18:     public class SamplePlugin : UserControl, ICustomPlugInLoader
  19:     {
  20:         protected Button DontClickMeButton;
  21:  
  22:         protected const string EMBEDDED_RESOURCE_NAME = "MyPlugin.SamplePlugin.ascx";
  23:  
  24:         /// <summary>
  25:         /// ICustomPluginLoader.List is used as a "bootstrap" to get our virtualpathprovider registered
  26:         /// with the web hosting framework (if it hasnt been already in a previous call)
  27:         /// </summary>
  28:         public PlugInDescriptor[] List()
  29:         {
  30:             // the virtual path to our ascxfile is relative to siteUrl
  31:             string virtualPathToAscxFile = String.Format("{0}MyPlugin/SamplePlugin.ascx", EPiServer.Configuration.Settings.Instance.SiteUrl.AbsolutePath);
  32:  
  33:             // see if our vpp is already registered
  34:             VirtualFile testVF = HostingEnvironment.VirtualPathProvider.GetFile(virtualPathToAscxFile);
  35:  
  36:             if(null==testVF || !testVF.GetType().Equals(typeof(EmbeddedResourceVF)))
  37:             {
  38:                 HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedResourceVPP(virtualPathToAscxFile, EMBEDDED_RESOURCE_NAME));
  39:             }
  40:  
  41:             return new PlugInDescriptor[] { PlugInDescriptor.Load(typeof(SamplePlugin)) };
  42:         }
  43:  
  44:         protected override void OnInit(EventArgs e)
  45:         {
  46:             base.OnInit(e);
  47:  
  48:             DontClickMeButton.Click += new EventHandler(DontClickMeButton_Click);
  49:         }
  50:  
  51:         protected void DontClickMeButton_Click(object sender, EventArgs e)
  52:         {
  53:             DontClickMeButton.Text = "Dont click me again!";
  54:         }
  55:     }
  56:  
  57:     /// <summary>
  58:     /// Implements a VirtualFile for our embedded resource file
  59:     /// </summary>
  60:     public class EmbeddedResourceVF : VirtualFile
  61:     {
  62:         protected string ResourceName;
  63:  
  64:         public EmbeddedResourceVF(string virtualPath, string resourceName) : base(virtualPath)
  65:         {
  66:             ResourceName = resourceName;
  67:         }
  68:  
  69:         /// <summary>
  70:         /// Open simply returns the stream opened for the embedded resource
  71:         /// </summary>
  72:         /// <returns></returns>
  73:         public override System.IO.Stream Open()
  74:         {
  75:             return System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(ResourceName);
  76:         }
  77:     }
  78:  
  79:     /// <summary>
  80:     /// implements a virtualpathprovider that hands out EmbeddedResourceVF's if asked
  81:     /// for our embedded resource
  82:     /// </summary>
  83:     public class EmbeddedResourceVPP : VirtualPathProvider
  84:     {
  85:         protected string ResourceName;
  86:         protected string VirtualPath;
  87:  
  88:         public EmbeddedResourceVPP(string virtualPath, string resourceName) : base()
  89:         {
  90:             ResourceName = resourceName;
  91:             VirtualPath = virtualPath;
  92:         }
  93:  
  94:         public override bool FileExists(string virtualPath)
  95:         {
  96:             if(0==String.Compare(VirtualPath, virtualPath, true))
  97:                 return true;
  98:  
  99:             return base.FileExists(virtualPath);
 100:         }
 101:  
 102:         public override VirtualFile GetFile(string virtualPath)
 103:         {
 104:             if(0==String.Compare(VirtualPath, virtualPath, true))
 105:                 return new EmbeddedResourceVF(virtualPath, ResourceName);
 106:  
 107:             return base.GetFile(virtualPath);
 108:         }
 109:  
 110:         /// <summary>
 111:         /// Must override GetCacheDependency and return our own CacheDependency as the default implementation
 112:         /// fails as the "file" doesnt exist physically on disk when it tries to add a filemonitor to watch
 113:         /// it for changes.
 114:         /// 
 115:         /// We simple return a never expiring cachedependency (as the file is an embedded resource, it wont change
 116:         /// without a recompile in which case the appdomain is unloaded anyway)
 117:         /// </summary>
 118:         /// <param name="virtualPath"></param>
 119:         /// <param name="virtualPathDependencies"></param>
 120:         /// <param name="utcStart"></param>
 121:         /// <returns></returns>
 122:         public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
 123:         {
 124:             return new NeverExpiresCacheDep(virtualPath);
 125:         }
 126:     }
 127:  
 128:     public class NeverExpiresCacheDep : CacheDependency
 129:     {
 130:         public NeverExpiresCacheDep(string fileName) : base()
 131:         {
 132:         }
 133:     }
 134:  
 135: }

12 June 2008

Tags:


    Comments

    1. Old news, or you're not reading the other blogs here? Or both? :-) Nice touch registering the VPP in the List method. http://blogs.interakting.co.uk/danmatthews/archive/2008/02/28/EPiServer-plugin-in-a-single-assembly.aspx
    2. oh darn, no havent read that.
    Post a comment    
    User verification Image for user verification  
    EPiTrace logger