You are here: Articles » DotNetNuke » Reverse engineering DotNetNuke skin
Reverse engineering DotNetNuke skin

Reverse engineering DotNetNuke skin

Abstract: This article shows how to extract skin from a DotNetNuke-based portal.



Transformation rules

1) Skin is filled with content

2) Content consists of modules wrapped in containers

3) How skin-specific ASP.NET tags are rendered

4) How container-specific ASP.NET tags are rendered

Reverse engineering

Gathering necessary files

Creating a skin control

Reverse engineering content panes

Creating container controls

Reverse engineering containers’ ASP.NET tags

Reverse engineering skin’s ASP.NET tags

Skin packaging




When designing a skin for a DotNetNuke portal it might be reasonable to see how skins are implemented at other DNN-based sites. However these skins may not be directly available. If this is the case the desired skin can be extracted from a DotNetNuke-based portal by following the technique described in this article.


First let us look into the lifecycle of a DotNetNuke page. Everything starts when DNN processes a skin control (*.ascx) inserting page content where needed. After the page is filled with content the control passes to ASP.NET engine which in its turn renders the page and substitutes ASP.NET tags with proper HTML code. The resulting HTML is passed to the browser. This is a simplified description but it shows the basic mechanism of the translation from a DNN skin to the client HTML code.




The main idea expressed in the article is that with the knowledge of translation rules we can reverse the transformation order and build the original DNN skin based on the client HTML code.




As an example we will go through reverse engineering the skin used for portal.



Transformation rules

As we have already mentioned the translation from a DotNetNuke skin to the HTML code returned to a browser is done in two steps. Both of them obey a number of rules.

1) Skin is filled with content

DotNetNuke engine inserts page content into content panes. In skin’s *.ascx file these panes are identified with <td> tag. However to distinguish content pane <td> tags from other <td> tags the former should be equipped with runat=”server” attribute value.


During the translation ‘runat’ attributes are removed from content <td> tags and “dnn_” prefix is added to the id. For internal needs <a name=”…”></a> tag is also inserted just before the content goes. Thus the following construct in a skin control:


<td id="ContentPane" valign="top" align="center" width="100%" runat="server"></td>


will result in the following HTML code:


<td id="dnn_ContentPane" valign="top" align="center" width="100%"><a name="…"></a>[content]</td>


Now let us understand what content is constituted by.

2) Content consists of modules wrapped in containers

Generally content is a number of modules encapsulated within module containers. Module container is described in a separate ASP.NET control file (*.ascx). During a page construction module contents are inserted into the module container. The insertion logic here is similar to one applied during skin population – a tag with id=”ContentPane” and runat=”server” attributes is found and filled with the module’s contents.


For example the following tag in a container control:


<td align="left" valign="top"><span id="ContentPane" runat="server"></span></td>


will be replaced with:


<td align="left" valign="top"><span id="dnn_ctr2016_ContentPane">

             <!-- Start_Module_2016 -->


             <!-- End_Module_2016 -->



3) How skin-specific ASP.NET tags are rendered

There is quite a limited number of ASP.NET tags commonly used in DNN skin controls. Here we list them and show what markup is generated for them.


<dnn:LOGO runat="server" id="dnnLOGO" /> will produce such markup:

<a id="dnn_dnnLOGO_hypLogo" …>…</a>


<dnn:SEARCH runat="server" … /> results in

<input name="dnn:dnnSEARCH:txtSearch"…/>&nbsp;<a id="dnn_dnnSEARCH_cmdSearch" …>…</a>


<dnn:MENU runat="server" id="dnnMENU" /> turns into:

            <span id="dnn_dnnMENU_ctldnnMENU"…>…</span>


<dnn:BREADCRUMB runat="server" …/> is replaced with

            <span id="dnn_dnnBREADCRUMB_lblBreadCrumb" …>…</span>


<dnn:USER runat="server" id="dnnUSER" CssClass="DNN_User"/> will give

            <a id="dnn_dnnUSER_hypRegister" class="DNN_User" … />…</a>


<dnn:LOGIN runat="server" id="dnnLOGIN" CssClass="DNN_Login" /> produces

            <a id="dnn_dnnLOGIN_hypLogin" class=" DNN_Login"…/>…</a>


<dnn:COPYRIGHT runat="server" CssClass="SkinObject" … /> results in

            <span id="dnn_dnnCOPYRIGHT_lblCopyright" class="SkinObject">…</span>


<dnn:TERMS runat="server" CssClass="SkinObject"… /> gives

            <a id="dnn_dnnTERMS_hypTerms" class="SkinObject" … >…</a>


<dnn:PRIVACY runat="server" CssClass="SkinObject"… /> is replaced with

            <a id="dnn_dnnPRIVACY_hypPrivacy" class="SkinObject"…>…</a>

4) How container-specific ASP.NET tags are rendered

Containers include some specific ASP.NET tags as well. Here they are:


<dnn:Title runat="server" id="dnnTitle" CSSClass="Head"/> is mapped into

            <span id="dnn_ctr384_dnnTitle_lblTitle" class="Head">…</span>

(here an arbitrary number may be instead of 384)


The following tag produces empty output if the user is not an administrator:


<dnn:SOLPARTACTIONS runat="server" id="dnnACTIONS"/>


Next three tags rarely appear in containers however the reader should be aware of them:


<dnn:ICON runat="server" id="dnnICON" />

<dnn:VISIBILITY runat="server" id="dnnVISIBILITY" />

<dnn:ACTIONBUTTON runat="server" id="dnnACTIONBUTTON" …/>

Reverse engineering

Now when we know how a DotNetNuke skin is transformed to the client HTML code we can perform the reverse translation for the website.


First of all let us gather all necessary material. These include start page HTML code, linked CSS files and referenced images.

Gathering necessary files

Open start page and choose the option in browser to show HTML code of the page (in IE6 View -> Source). Save shown HTML to a new file client.html.


In the top of the client.html find all references to *.css files and download these files. They usually include skin.css, container.css, default.css, portal.css. You will probably need some download manager since browsers often try to open files instead of downloading them.


Images are referenced in *.css files with “url(…)” syntax. Images in HTML code may be included with <img> tags and with “style=…” attributes. Locate all referenced images and download them.


A convenient way to download all needed files is using some offline download manager for example SurfOffline. Point it to the start page of a website and it will save locally all css and media files maintaining correct folder structure.

Creating a skin control

Since DNN skin is an *.ascx control it corresponds only to a part of the client HTML code. Usually it starts right after <SPAN ID="dnn_dnnMENU_ctldnnMENU_divOuterTables"></SPAN> tags and spreads down to the <input name="ScrollTop" type="hidden" id="ScrollTop" /> tag. Copy the specified section from client.html to a new skin.ascx file.




Supplement skin.ascx with several lines at the top of it. These lines are common for every DNN skin control:


<%@ Control language="vb" CodeBehind="~/admin/Skins/skin.vb" AutoEventWireup="false" Explicit="True" Inherits="DotNetNuke.UI.Skins.Skin" %>

<%@ Register TagPrefix="dnn" TagName="LOGIN" Src="/content/Portals/0/~/Admin/Skins/Login.ascx" %>

<%@ Register TagPrefix="dnn" TagName="USER" Src="/content/Portals/0/~/Admin/Skins/User.ascx" %>

<%@ Register TagPrefix="dnn" TagName="BANNER" Src="/content/Portals/0/~/Admin/Skins/Banner.ascx" %>

<%@ Register TagPrefix="dnn" TagName="CURRENTDATE" Src="/content/Portals/0/~/Admin/Skins/CurrentDate.ascx" %>

<%@ Register TagPrefix="dnn" TagName="LOGO" Src="/content/Portals/0/~/Admin/Skins/Logo.ascx" %>

<%@ Register TagPrefix="dnn" TagName="BREADCRUMB" Src="/content/Portals/0/~/Admin/Skins/BreadCrumb.ascx" %>

<%@ Register TagPrefix="dnn" TagName="MENU" Src="/content/Portals/0/~/Admin/Skins/SolPartMenu.ascx" %>

<%@ Register TagPrefix="dnn" TagName="COPYRIGHT" Src="/content/Portals/0/~/Admin/Skins/Copyright.ascx" %>

<%@ Register TagPrefix="dnn" TagName="HOSTNAME" Src="/content/Portals/0/~/Admin/Skins/HostName.ascx" %>

<%@ Register TagPrefix="dnn" TagName="TERMS" Src="/content/Portals/0/~/Admin/Skins/Terms.ascx" %>

<%@ Register TagPrefix="dnn" TagName="PRIVACY" Src="/content/Portals/0/~/Admin/Skins/Privacy.ascx" %>

<%@ Register TagPrefix="dnn" TagName="DOTNETNUKE" Src="/content/Portals/0/~/admin/Skins/DotNetNuke.ascx"%>

<%@ Register TagPrefix="dnn" TagName="Links" Src="/content/Portals/0/~/admin/Skins/Links.ascx"%>

<%@ Register TagPrefix="dnn" TagName="SEARCH" Src="/content/Portals/0/~/admin/Skins/Search.ascx"%>




Skin control we are having now is just a copy of the client HTML code rather than a real ASP.NET control. So in the following steps we will reverse engineer it to its original form.

Reverse engineering content panes

At this step we will locate all content panes in Skin.ascx. Due to the transformation rules described above content panes in the client HTML look as the following code blocks:


<td id="dnn_...Pane…" …>



There are 6 such blocks in Skin.ascx (“dnn_ControlPanel”, “dnn_LeftPane”… “dnn_BottomPane”). Let us supersede them with what they are generated from while copying their contents into separate files LeftPane_Contents.html … RightPane_Contents.html.


For example this image illustrates what should be done to the left pane:



Creating container controls

There are three types of containers used at website. First type has a visible border and a white background (for example “Quick Links” in the left pane). Second one is similar except its background is grey (“News” in the left pane). And the third container has simply no border (“Welcome to DotNetNuke” in the content pane).


First container’s generated HTML is located at the top of the LeftPane_Contents.html file. Let us copy it to a new file Container1.ascx.




Then put several lines at the top of new Container1.ascx file to make use of container-specific ASP.NET tags. These lines are common for all container controls:


<%@ Control language="vb" CodeBehind="~/admin/Containers/container.vb" AutoEventWireup="false" Explicit="True" Inherits="DotNetNuke.UI.Containers.Container" %>

<%@ Register TagPrefix="dnn" TagName="VISIBILITY" Src="/content/Portals/0/~/Admin/Containers/Visibility.ascx" %>

<%@ Register TagPrefix="dnn" TagName="SOLPARTACTIONS" Src="/content/Portals/0/~/Admin/Containers/SolPartActions.ascx" %>

<%@ Register TagPrefix="dnn" TagName="PRINTMODULE" Src="/content/Portals/0/~/Admin/Containers/PrintModule.ascx" %>

<%@ Register TagPrefix="dnn" TagName="TITLE" Src="/content/Portals/0/~/Admin/Containers/Title.ascx" %>

<%@ Register TagPrefix="dnn" TagName="ICON" Src="/content/Portals/0/~/Admin/Containers/Icon.ascx" %>

<%@ Register TagPrefix="dnn" TagName="ACTIONBUTTON" Src="/content/Portals/0/~/Admin/Containers/ActionButton.ascx" %>


According to “Transformation rules” we should replace the following fragment:


<span id="dnn_ctr1248_ContentPane" align="left"> <!-- Start_Module_1248 -->

  <div id="dnn_ctr1248_ModuleContent">

  <!-- End_Module_1248 -->




with what it was generated from, namely:


<span id="ContentPane" align="left" runat="server"></span>


In the same manner let us extract Container2.ascx from LeftPane_Contents.html and Container3.ascx from ContentPane_Contents.html. Repeat the actions described to both these files.

Reverse engineering containers’ ASP.NET tags

In “Transformation rules” paragraph we have already listed possible HTML code blocks generated from container-specific ASP.NET tags. Now we should investigate containers’ ascx files in search of such HTML blocks and replace them with corresponding ASP.NET tags.


Almost every container has a title. And so does Container1.ascx – it contains the following code:


<span id="dnn_ctr1248_dnnTITLE_lblTitle" class="Head">Quick Links</span>


Substitute it with what it originated from:


<dnn:Title runat="server" id="dnnTitle" CSSClass="Head"/>


For an administrator to adjust the container and inner modules we should place somewhere container actions menu. For example insert it as follows:



         <td align="left" valign="top" width="41" height="28" style="…">

           <dnn:SOLPARTACTIONS runat="server" id="dnnACTIONS"/> &nbsp;


         <td height="28" style="…" align="left" valign="top">

            <dnn:Title runat="server" id="dnnTitle" CssClass="Head" />&nbsp;




Now let us deal with referenced images. First take a look at the “style” attribute of <td> tags:


<td … style="background-image:url(/Portals/_default/Containers/DNN-Minimal/outline/topleft.gif);background-repeat:no-repeat">


The background image is referenced here as relative to website root. Since we do not want to be bound to any particular location we should make this path relative to the container’s control. For that let us create Container.css file and add there css classes declarations:














Then reference these classes from the container control:



         <td align="left" valign="top" width="41" height="28" class="Outline_Topleft">

           <dnn:SOLPARTACTIONS runat="server" id="dnnACTIONS"/>&nbsp;


         <td height="28" align="left" valign="top" class="Outline_Topright">

           <dnn:Title runat="server" id="dnnTitle" CssClass="Head" />&nbsp;




Another place in Container1.ascx where images are referenced as relative to the root are <img> tags:


<img src="/Portals/_default/Containers/DNN-Minimal/outline/bottomleft.gif">


To make paths relative to the Container1.ascx file we will replace these tags with <asp:Image>:


<asp:Image ID="Image1" runat="server" ImageUrl="outline/bottomleft.gif"/>


Repeat this to all occurrences of <img> tags in Container1.ascx.


In the same way modify Container2.ascx and Container3.ascx. After that we are ready with containers and the last step left is to bring Skin.ascx into its original form.

Reverse engineering skin’s ASP.NET tags

Now we should find all output HTML fragments generated from skin-specific ASP.NET controls. The search box comes first:


<input name="dnn:dnnSEARCH:txtSearch" type="text" …/>&nbsp;

<a id="dnn_dnnSEARCH_cmdSearch" class="DNN_Search"…>Search</a>


Replace it with


<dnn:SEARCH runat="server" id="dnnSEARCH" Submit="Search" />


Next is menu. It is described with the following HTML:


<span id="dnn_dnnMENU_ctldnnMENU" name="dnn:dnnMENU:ctldnnMENU" …>…</span>


We will rewrite it as follows:


                <dnn:MENU   runat="server" SubMenuCssClass="submenu"

                     &n bsp;      id="dnnMENU"

                     &n bsp;      RootMenuItemBreadCrumbCssClass="rootmenuitembreadcrumb"

                     &n bsp;      RootMenuItemCssClass="rootmenuitem"

                     &n bsp;      RootMenuItemSelectedCssClass="rootmenuitemselected"

                     &n bsp;      SubMenuItemSelectedCssClass="submenuitemselected"/>


Then we should reverse engineer “Register” and “Login” links. Thus we should substitute this:


<a id="dnn_dnnUSER_hypRegister" class="DNN_User"…>Register</a>&nbsp;&nbsp;|&nbsp;&nbsp;

<a id="dnn_dnnLOGIN_hypLogin" class="DNN_Login"…>Login</a>


with this:


<dnn:USER runat="server" ID="dnnUser" CssClass="DNN_User" />


<dnn:LOGIN runat="server" ID="dnnLogin" CssClass="DNN_Login" />


Let us proceed to the banners now. Actually banners are embedded to a skin with <dnn:Banner> tag but we will not use them. Instead just remove all banners’ HTML blocks from Skin.ascx. Such blocks look as follows:


<table id="dnn_dnnBANNER_lstBanners" …>



Breadcrumb line is not hard to reverse engineer too. Just replace the following HTML:


<span id="dnn_dnnBREADCRUMB_lblBreadCrumb"></span>


with such ASP.NET tag:


<dnn:BREADCRUMB runat="server"  id="dnnBREADCRUMB" RootLevel="1" Separator="&nbsp;›&nbsp;"/>


Accordingly to “Transformation rules” locate the rest (copyright, terms and privacy) generated HTML fragments and substitute them with appropriate ASP.NET tags.


One more thing left to do is to correct image references making them relative to the skin control rather than to the website root. The common technique here is the same as we used for container controls. That is we should replace <img> tags with <asp:Image> specifying a relative path in “ImageUrl” attribute. For example such image reference:


<img src="/Portals/_default/Skins/DNN-Minimal/controls/images/icon_ widthsmall.gif" onClick="sizeLayout(1)" width="16" height="11" alt="Medium width layout" border="0">


should be replaced with:


<asp:Image runat="server" ImageUrl="controls/images/icon_widthsmall.gif" onClick="sizeLayout(0)" Width="16" Height="11" AlternateText="Small width layout" BorderWidth="0"/>


The only difficulty arises when dealing with “onmouseover” and “onmouseout” client events. The following code solves the problem:


<asp:Image ID="Image1" runat="server" ImageUrl="images/icon_downloads.gif"


onmouseout="src=src.replace('icon_downloads_over', 'icon_downloads')"

Width="46" Height="44" AlternateText="Downloads" BorderWidth="0" />


When it is done with all <img> tags we are ready to package created skin and containers for deployment.

Skin packaging

The packaging procedure can be described with three steps:


1)     Skin control (ascx) along with its css files and image folders should be compressed into a single file

2)     Container controls (ascx) with accompanying css files and images (in folders) should be compressed into

3)     The resulting and should be compressed together into <SkinName>.zip


Packaging scheme for our example looks as follows:




As soon as we apply the resulting skin to a DNN portal we will get something like this:




We have shown how to extract skins from DotNetNuke-based websites. With this technique a skin designer is able to explore skins of any other DNN portals to make his work even better.


However be aware that you may not use the extracted skin at your website since it is intellectual property of the skin’s owner. You may only explore the reverse engineered skin and being inspired by it build something new.

More DotNetNuke articles
Back to homepage
Copyright 2007-2016 by Oleg Zhukov Terms Of UsePrivacy Statement