EPiServer GUI-plugin without ascx or aspx file

When creating a GUI-plugin you are basically either creating a usercontrol (ascx) or a webform (aspx). The problem with this is that when you deploy your plugin you have to deploy both an assembly and a second file. The location of the second file is of utmost importance since it is defined in the GuiPlugInAttribute's Url property. Is this a problem? Not in most cases, but once in a while it is. Can it be solved? Of course...

This is not a new idea at all. Johan Olofsson has apparently already written a blog post about it, allthough I haven't read it. I came up with this solution after a short descussion with another developer who was in problem. So here is my solution. Create your plugin, incl. ascx or aspx, but set it's byuld action to embedded resource. On top of this you add a Virtual Path Provider that responds to the Url set in the GuiPlugInAttribute. Let the VPP return a Virtual FIle object that will open the embedded resource and return it. That way you don't have to deploy the markup-file. But to make the deployment even easier, I decided to use the PlugInAttribute inheritande "hack" to register the VPP.

The PlugInAttribute "hack" is pretty simple. All classes that inherit from PlugInAttribute and have a static void Start() method will be executed on the application's start up. It makes it possible to deploy functionality that is dependent on the application start up without having to make changes in the Global.asax or something similar.

So this is what I did. First off I created a new GuiPlugIn.
[code:html]
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DebugCtrl.ascx.cs" Inherits="EPiServer.Templates.DebugCtrl" %>
<asp:Repeater runat="server" id="rep">
    <ItemTemplate>
        <asp:Literal runat="server" ID="lit" />
    </ItemTemplate>
</asp:Repeater>
[/code]

With it's code-beside...
[code:c#]
namespace EPiServer.Templates
{
    [GuiPlugIn(DisplayName="Debug",Area=PlugInArea.EditPanel, Url = "~/Templates/DebugCtrl.ascx")]
    public partial class DebugCtrl : EPiServer.UserControlBase
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            rep.ItemDataBound += new RepeaterItemEventHandler(rep_ItemDataBound);
            rep.DataSource = CurrentPage.Property.Keys;
            rep.DataBind();
        }

        void rep_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
            {
                string key = (string)e.Item.DataItem;
                PropertyData pd = CurrentPage.Property[key];
                Literal lit = (Literal)e.Item.FindControl("lit");
                lit.Text = string.Format("{0}: {1}<br />",pd.Name,pd.Value);
            }
        }
    }
}
[/code]

Then I needed my Virtual Path Provider. Since it was using Resources, I named it Resource Path Provider.
[code:c#]
namespace EPiServer.Templates
{
    public class ResourcePathProvider : VirtualPathProvider
    {
        string _virtualPath;
        string _resourceName;

        public ResourcePathProvider(string virtualPath,string resourceName)
        {
            _virtualPath = virtualPath;
            _resourceName = resourceName;
        }
        public override bool FileExists(string virtualPath)
        {
            if (String.Compare(virtualPath, _virtualPath, true) == 0)
            {
                return true;
           }
            return Previous.FileExists(virtualPath);
       }
        public override VirtualFile GetFile(string virtualPath)
        {
            if (String.Compare(virtualPath, _virtualPath, true) == 0)
            {
                return new ResourceVirtualFile(_virtualPath, _resourceName);
            }
            return Previous.GetFile(virtualPath);
        }
    }
}
[/code]

The VPP isn't very complicated and does not implement any error handling or anything. This is only a DEMO and is not something I would put in production, but it shows the idea behind what I'm trying to do. It is acompanied by a ResourceVirtualFile which looks like the following.
[code:c#]
namespace EPiServer.Templates
{
    public class ResourceVirtualFile : VirtualFile
    {
        string _resourceName;

        public ResourceVirtualFile(string virtualPath, string resourceName) : base(virtualPath)
        {
            _resourceName = resourceName;
        }
        public override System.IO.Stream Open()
        {
            return Assembly.GetExecutingAssembly().GetManifestResourceStream(_resourceName);
        }
    }
}
[/code]

And then at the end I had to add my PlugInAttribute "hack" to register the VPP. Actually VPP's give you a lot of power and possibility and I think I will have to add another blog-post just about those... But for now I stick with this...
[code:c#]
namespace EPiServer.Templates
{
    public class DebugAttribute : PlugInAttribute
    {
        public static void Start()
        {
            HostingEnvironment.RegisterVirtualPathProvider(new ResourcePathProvider("/Templates/DebugCtrl.ascx", "EPiServer.Templates.DebugCtrl.ascx"));
        }
    }
}
[/code]

So that is basically my little demo. The first of goal with is to show you a simple way of making GUI plug-in deployment easier. The 2nd was to show you how flexible VPP's can make your applications file structure. This is only a demo as I have said, so if you feel like using something like this you should add some stabilizing code and test it well. I would also recommend giving it some thought. Don't get stuck on the fact that I added my markup as a resource, it could be stored anywhere and anyhow as long as you can get a stream to it. My goal was to not have to deploy both markup and assembly, which I did manage using this solution.

Comments (2) -

Nice post - this has been done before but always good to see new implementations:

blogs.interakting.co.uk/.../...ingle-assembly.aspx

Smile

Hi Dan!
I know it has been done before. As I mentioned, Johan has written about it before. And apparently so have you. I added it because it was something that came up during a discussion and I like the idea and wanted to try it out...

Comments are closed