Reverse
engineering DotNetNuke skin
Abstract: This article
shows how to extract skin from a DotNetNuke-based portal.
Introduction
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
www.dotnetnuke.com
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
Result
Conclusion
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 www.dotnetnuke.com
portal.
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.
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.
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 -->
</span>
</td>
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"…/> <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>
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" …/>
Now when we know how a DotNetNuke
skin is transformed to the client HTML code we can perform the reverse translation for the www.dotnetnuke.com website.
First of all let us gather all
necessary material. These include start page HTML code, linked CSS files and referenced images.
Open www.dotnetnuke.com 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.
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.
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…"
…>
…
</td>
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:
There are three types of containers
used at www.dotnetnuke.com 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 -->
</div>
</span>
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.
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:
<tr>
<td align="left" valign="top" width="41" height="28"
style="…">
<dnn:SOLPARTACTIONS runat="server" id="dnnACTIONS"/>
</td>
<td height="28" style="…" align="left"
valign="top">
<dnn:Title runat="server" id="dnnTitle"
CssClass="Head" />
</td>
</tr>
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">
FONT>
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:
.Outline_Topleft
{
background-image:url(outline/topleft.gif);
background-repeat:no-repeat
}
.Outline_Topright
{
background-image:url(outline/topright.gif);
background-repeat:repeat-x
}
Then reference these classes from
the container control:
<tr>
<td align="left" valign="top" width="41" height="28" class="Outline_Topleft">
<dnn:SOLPARTACTIONS runat="server"
id="dnnACTIONS"/>
</td>
<td height="28" align="left" valign="top" class="Outline_Topright">
<dnn:Title runat="server" id="dnnTitle" CssClass="Head"
/>
</td>
</tr>
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.
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" …/>
<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> |
<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" …>
…
</table>
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=" › "/>
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"
onmouseover="src=src.replace('icon_downloads','icon_downloads_over')"
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.
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 Skins.zip.
2) Container controls (ascx)
with accompanying css files and images (in folders) should be compressed into Containers.zip.
3) The resulting Skins.zip and
Containers.zip 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 articlesBack to homepage