DotNetNuke modules are generally a package of ASP.NET User Controls, plus some application code. The most popular way of
building and distributing modules is in a Private Assembly (PA) package, bundled into an installable Zip file. Most of the
third-party modules are distributed in this manner - the iFinity modules are all done like this.
This post is going to cover the why and how of designing and structuring your DNN modules. It doesn't cover the ways of writing
your module or packaging and distributing the module.
A Module is not a Module
The first problem at hand with understanding DNN module structure is the heavily overloaded term 'Module'. A 'DNN Module' in the
popular sense of the word means all of the following things:
- A Zip package that you install using the module installer.
- An entry in the list on the 'Module Definitions' page.
- An item in the list of installable modules in the DotNetNuke control panel.
- An instance on a DNN Page that is generally used to edit and display content.
For the end-user of a DNN site, it matters little that 'module' covers all of these definitions. We think of the 'Text/Html Module' as being
all of the above, and for end users, it's an accurate enough depiction to get through the process of installing and using the module.
However, for a developer of a module, it's not nearly accurate enough, because all these things are different, and when you're trying to design
and architect a module, you need to know the difference. It's a bit like database queries, stored procedures, views and tables. To an end user,
they all produce a list of data, but to the developer, there's a world of difference in how each is physically created and designed.
Note : In case you didn't already know, a 'Tab' is a DNN Page. So Page == Tab. The origins of the definition 'Tab' go back to the IBuySpy workshop,
the original code distributed by Microsoft which DNN grew from. The IBuySpyWorkshop used a 'tab control' interface, so Tabs == Pages.
More accurate descriptions of Module Parts
To eliminate confusion, I'm going to define some more accurate terms to cover the different parts. These are a little bit outside official DNN Terminology, but I'll stick as close as I can to the correct terms.
- A zip package that you install : A Module Install Package
- An entry in the list on the 'Module Definitions' page : A Desktop Module (it's confusing, I know)
- An item in the list of installable modules in the DotNetNuke control panel : A User Desktop Module (UDM)*
- An instance on a DNN Page that is generally used to edit and display content : A Tab Module
* Note that 'UMD' is my terminology. Not all Module Definitions are installable : you can't install, say, a File Manager Module Definition, even though it clearly exists as one in the DNN tables. This is
because it's an 'Admin' module that can't be installed. DNN actually needs a 'UAMD' or 'User Admin Module Definition', which would be a class of module where you can add it to a Tab, but it's not part of the DNN Admin modules.
And I'm going to add some more for the developer to understand:
- A logical group of controls which, when added to a page, create a Tab Module instance : A Module Definition
- An ASP.NET User Control (.ascx file) which is displayed when a UMD is added to a DNN Tab : A Module Control
- A Business Controller : A public class (.NET Type) in the module code which can be called by the DNN Framework.
- A Settings Control : An ASP.NET User Control, which can be loaded into the 'Module Settings' page, accessible on a Tab Module 'Settings' menu command.
With these terms defined, we can discuss choices on structuring a module correctly.
Designing a Module Structure
There's no one answer about how to build a module the correct way. I've seen a lot of core and third party modules, and they seem to all follow a different
path to achieve the end result. That's the beauty of the DNN platform in a way : you can really go off on some different tangents and it will all hang together.
If you've just got a simple module with one 'view' control, one 'edit' control and one 'settings' control, it's pretty simple : you just create the three separate user controls, package it up
and install. This is the design for most of the simple modules, like the 'Text/Html', 'Announcements' and 'Links' modules.
But there are really two main paths to DNN module structure, and I'll call these 'all in one' and 'separate parts'.
The 'all in one' structure is where all of the Module Controls for a module are combined into a single Module Definition. Remember, a Module Definition is what you see in the drop down list in the Control Panel. Most simple modules like the 'Text/Html' module are all-in-one, principally because there's not much to it.
More complex 'all in one' examples are the Blog module, which, as anyone who has ever installed it knows, drops about 6 actual Tab Modules onto your chosen Tab when you click 'add module'. At first this is confusing, but it's easy enough to configure by moving the Tab Modules around and deleting the ones you do not need.
The 'separate parts' model is where each individual control is in it's own module definition. When you install a module package for a design like this, you end up with a set of module definitions in your list. A couple of examples in the third-party market that I know of include the CataLOOK store and the Simple Gallery module from Ventrian.
There's no correct answer as to which to choose : each has benefits and drawbacks.
All In One
The 'all in one' design has simplicity on it's side: instead of being presented with a series of 20 character names for the module definitions, you just have one. End users will know what to click to install your module. However, when you end up with 4 or 5 different Tab Modules on the Tab, it's hard as an end-user to know what to do.
I would recommend this design when you have a module with only a couple of different Module Controls, and when those Module controls are reasonably related in terms of use. Taking the blog example, it makes sense : if you're installing a blog on page, the archive, entries and search functions are all related. If you're creating an e-commerce package, then
having the shop, cart, checkout and account controls on the one page doesn't make as much sense.
Separate Parts
You would normally use this when there are relatively unrelated parts to a module package, or perhaps when you would expect different Module Definitions to be added on different Tabs. An example might be a summary module of Blog Entries : it wouldn't normally live on the same Tab as the entries themselves, so you would probably create a separate Module Definition for the Summary and Main articles.
The advantage of this approach is that the user can install the different definitions to different tabs, integrating the functionality into their site they way they see fit. The disadvantage is that, unless you're careful with naming and documentation, it's not always obvious how to achieve a working setup.
The 'Blob'
The Blob is a subset of the 'All in One' approach. This is where there is only one module definition, and when added to a page, only one Tab Module. But the
module has a series of different functions - but all those functions are just different representations of the same page. Examples of this are the DNN Forums : There's only one module definition, and it just puts
a single Tab Module on the page. But that Tab Module has myriads of different functions - but you can't spread the module across separate Tabs in the site. WIth the forums module, you can't have a post add/edit on one page, and a post view on another. It works well with Forums, but this model needs to be chosen with care.
Choosing Between the Design Styles
My advice when choosing between the design styles is to consider what your finished page/site will look like. Do you want the user to be able to shift different parts of your module around their portals, or is it important that everything is restricted to a single DNN Tab? Are all the functions homogenous to the module, or does the module do various things.
In general terms, something like an e-commerce module has many functions, including search functions, product lists, product details, shopping carts, checkout pages and user-specific information like order details. In a design like this, it's probably better to break down the overall functionality into module definitions along the functional (or destination Tab, if it helps) lines:
- Product Listings
- Product Search
- Shopping Cart
- User Account
A design like this pretty much matches what you would expect to see in a list of Tabs on the site. But it would also allow flexibility for, say, having different DNN Tabs for different product categories. Allowing users to place different instances of essentially the same Tab Module gives them the flexibility to do things
like have different skins for each page, as well as place complementary DNN modules on separate pages. Consider an e-commerce site that sold baby products. With an e-commerce module that allows you to have different Tab Modules of the same Module Control, you could have a site structure like this:
mybabyproducts.com
-->products
-->-->boys
-->-->girls
-->checkout
-->my orders
This allows the end user to apply a pink skin to the girls page and a blue skin to the boys page, and save you, the module developer, from having to build complex module-based skinning code. But you must allow the different Tab Module instances to filter the displayed content by some type of Tab Module Setting.
However, if you're building a module that does something where there are a couple of modules, and they are all closely related, then it makes sense to go with the 'all-in-one' style. The Blog module is a good example of this, as already discussed. You don't have separate Tabs for 'entries', 'archive', 'search' on a blog - it all just goes on the one page.
Implementing your Design Style
After the design style is chosen, then you're at the point of actually writing some code. It helps if you actually start with the 'Manifest' file, otherwise known as the '.dnn' file. This is an Xml file which actually contains the structure of your module. It gets placed in the 'desktopModules' folder of your module as a .dnn.config file, so you can look at any particular example you like.
The way you structure this file controls the installation and architecture of your module.
Translating Design with the Manifest File
The below image depicts the manifest file for the Blog module. The various areas are highlighted and point to what effect the manifest file contents will have
on the Desktop Module once it is installed.
As you can see, the Blog module has a single folder defined for the entire install package. This is the 'all in one' approach I have already described. There is a 1:1 relationship between the Module Definition and the Folder in the Manifest file. Each <folder> entry will create two things: a new
folder in the /DesktopModules/ folder, and a new Desktop Module in the Module Definitions page. Remember, the 'Module Definitions' page should actually be called the 'Desktop Modules' page.
Within each <folder> there are one or more <module> entries. Each one of these relates to a Module Definition, which, from above, is a logical grouping of controls. Each Tab Module will be an instance of the contents of a <module> entry.
If you wish to create more than one DestkopModule (which, in turn, creates more than one entry on the 'Module Definitions' screen, then you need to define an extra <folder> entry, and provide all the same details.
It's worth studying this image and getting a solid understanding of how all the parts go together, because you will need to author your own Manifest files if you are to become a proficient DNN module developer. I realise it is confusing : if it wasn't, there would be no need for this blog entry.
Designing the Module Controls
There's three basic types of Module Controls:
- 'View' controls : these are the 'default' view of the module which all authorised users get to see
- 'Edit' controls : these are only visible when (a) the user is authorised to edit the module and (b) when the site is in 'Edit' mode.
- 'Settings' controls : these are loaded along with the standard DNN module settings, and also are only visible when the site is in 'Edit' mode and the user is authorised.
Most module definitions contain one module control with no <key> value. This is the default control loaded when the DNN Tab with the Tab Module on it is viewed. In the case of the 'Text/Html' module, that's just the Html of the page. In the case of the Blog Module, that's the 'MainView' control, which shows the list of latest entries.
The above image shows that there are no module-specific Url parameters for the DNN Tab. The blog Tab Module on the page is therefore showing the control which has no <key> specified : the 'View_Blog' control.
This image shows an Edit control for the Blog Module. Note that in the DNN Manifest file for the Blog Module, it doesn't use a <key> value of 'Edit', rather it shows a key value of 'Edit_Entry'. This is because, with the Blog module, there is more than one type of Edit control within the overall definitions. This is a good lesson to take away: if your module will have more than one type of edit screen, it's a good idea to
break down the different type of edit screen in the module keys. The highlighted Url in the image shows /ctl/Edit_Entry/mid/381/EntryId/47/, which will be rewritten into a querystring of ctl=Edit_Entry&mid=381&EntryId=47. This Url would have been generated by the 'EditUrl()' function call. The 'EditUrl' call returns the Url for a specific module control : it differs from the 'NavigateUrl' call in this way.
The difference is (and this is important) that the 'mid=381' value is added. In DNN, whenever you add a 'mid=xxx' specifier to the query string, it will still show the DNN Tab, it will only load the specified module. That is to say, will only load the module on the tab where the moduleId = 399 (or whatever your module Id is).
A popular design trend amongst modules is to put a 'control panel' into the module, and AJAX the living daylights out of it. Whilst this can produce a nice-looking control-panel style, in my experience it also produces bloated, slow pages. You're much better off defining a different control for each function in your control panel, and definining them all
with different <key> values.
For an example : let's imagine your module will have an 'edit' function, which will contain these three different groups of functionality:
Instead of creating a single module control with all three sections in some type of multi-panel AJAX control, you can define three separate 'Edit' controls, with specific module keys.
<controls>
<control>
<key>Edit_Widgets</key>
<title>Edit Widgets</title>
<src>EditWidgets.ascx</src>
<type>Edit</type>
</control>
<control>
<key>Edit_Wigwams</key>
<title>Edit Wigwams</title>
<src>Wigwams.ascx</src>
<type>Edit</type>
</control>
<control>
<key>Edit_Doodads</key>
<title>Edit Doodads</title>
<src>Doodads.ascx</src>
<type>Edit</type>
</control>
Note : You might think the edit control Urls aren't SEO-Friendly, and you'd be right. It doesn't matter because search engines should never be indexing edit content, and users should never bookmark it.
The last thing I want to cover is the 'Settings' control. The 'settings' control is a special type of DNN Module Control, and it inherits from a different base (PortalSettingsBase). The Settings control has a different <type> value - 'Settings'. This means it will be loaded
in the DNN Tab brought up when you click on the 'Settings' icon in the DNN module. This link will always load up the standard DNN settings, which has values for security, appearance etc. You can add the Tab Module specific settings to the bottom of this page, and add your own custom settings.
Again, this is controlled by the Url - as you can see in the above image. Only this time, the <key> value is set for you - it's always /ctl/Module.
The important thing to remember here is that the 'Settings' page allows you to save tab-module specific settings. This mightn't sound like much of a difference, but DNN provides you with a 'Settings' hashtable to store simple key/value settings for your module. DNN also provides the user the ability to copy any module onto one or more pages - and then change the settings individually for each of those Tab Module instances.
By storing custom values in the TabModuleSettings hashtable, you can have different options for each Tab Module instance, even when all those Tab Module instances were copied from somewhere in the site.
Summary of Module Design
DNN Module design can be as simple or as complicated as you need. But when you stray into more complex module designs, it's worth sketching out how your module is going to fit together.
Here's key questions you need to answer before you start coding:
- Will the module content be displayed on a single 'view', or are there multiple facets to what the module will display?
- If there are multiple facets, are they tightly coupled and likely to all be shown on the one Tab, or are they likely to be spread through the site? The answer to this will drive your choice between an 'all in one' design or a 'separate parts' style.
- How many edit screens will you need? Will you split them out into separate controls, or combine the edit functionality into one control? You must consider things like viewstate size when attempting to combine many controls onto a single page, especially when trying to AJAX everything up.
- What options and settings should be saved at the 'entire-module' level, and what should be saved at the 'Tab Module' level? Can you add settings like content-filters, that allow an end-user to spread the same module across different pages but filter for specific content? The example mentioned was an e-commerce product list that allowed for filtering between different categories, allowing the end user to build different Urls through
placement of Product listings on different pages, and to use different theming by applying DNN skins/containers to specific pages.
Of course, all of this discussion just scratches the surface of the DNN Module Design process, but hopefully it gives food for thought about building robust, flexible and speedy DNN modules.