Of modules and men
As you are probably aware, DNN’s extensibility model allows me to create a “module” which is delivered as a zip file, uploaded by the host user, and then instantiated on a DNN page. You’ll then see a rectangular box appear on the page with whatever I invented in that box (the “container”). What you may or may not realize is that actually three “things” were created when you installed the module and two more things when you dropped it onto a page. The three things that were created when you installed the module were a “package”, a “desktop module” and a “module definition”. The two things that were created when you instantiated the module were a “module” and a “tab module”. Say what? Yes. In a couple of clicks there are now 5 things at play whenever you look at the box with its awesome content on your site. So for the noobs and as a refresher for those not daily buried in DNN programming, here is a small glossary:
|Package ||A package is any zip file that can be installed into DNN. It includes, but is not restricted to modules. I.e. skins and authentication providers are also packages. You can even have a package with just a dll that you want installed. Packages are recorded in the “Packages” SQL table. This is where you’ll find fields like version and manifest. |
It’s also important to note that the package is the latest addition to this list. Before the entry point for installation was the desktop module.
|Desktop Module ||A desktop module (the “desktop” bit is heritage from the IBuySpy days where we might also want to target “mobile” modules) is a complete module. It has an ID (“Module Name”) which you must never change as DNN uses it to keep track of it during upgrades. It also has a version and a path to where it is installed. |
|Module Definition ||The module definition tells DNN which ascx to load and when. So typically you’d have a default ascx to render when you first come to the page and some edit ascx to load whenever you click the edit link. The module definition is specified in the module’s manifest and the data is stored in ModuleDefinitions and ModuleControls in SQL (the latter contains all the ascxs). |
|Module ||So when you instantiate the module (i.e. add it to a page) a record is created in the Modules table in SQL, telling DNN which module definition this is. Not more? Well, a few more details, but basically that’s it. The Module ID that is generated serves as a hook to the data associated with the module (e.g. ModuleSettings). |
|Tab Module ||“Tab” modules? Yes. “Tabs” are IBuySpy lingo for “pages”. And the Tab Module is actually a record of a specific module on a specific page on your site. You may have seen DNN’s ability to copy modules where you “reference” the original? That is actually creating a new Tab Module off the existing module. The data then remains the same in both instances as mostly data hooks into the Module ID. |
Tab modules have their own ID (TabModuleID) and their own settings (TabModuleSettings). This is also the place where the title is set and other properties you see in the module settings screen.
The messy bits
The above has grown organically over the years. And it is not hard to see where the architects have been jumping through hoops to get it to do what they wanted. So there is duplicate data between Desktop modules and Packages (version, path) for instance. There are legacy bits of data with their own history. E.g. originally there was no “FolderName” in desktop modules and the “ModuleName” was taken to be the folder name. Older modules (like my own DMX) still carry the module name as a path (“Bring2mind\DMX”) so the module would install to the desired place. There are more oddities that have crept into this system. But one major source of pain is that the technical implementation of this system (i.e. those 5 entities) are reduced to two entities on the front end. Users manipulate modules and are happily unaware of what is happening behind the scenes. And this is all fine when we’re dealing with a module like the HTML module. But it gets messy when we look at more complex scenarios.
With “complex”, here, I mean: modules that need more than one “box” on the site to work. A common example is a shop module. A shop module typically has some kind of browser (i.e. where the user looks up the product of desire), a cart (giving the user feedback on what he/she has clicked to buy) and some form of checkout (payment, yeah). It is undesirable to have all this in a single box. So DNN has for long supported this notion of “multiple module definitions per module”. In your manifest you’d list the various definitions and when a user would instantiate the module, DNN would add each definition to the page. And this is where things get quite messy. It can be quite overwhelming to a user to see all these modules appear on the page at the same time. Like the page just took a hit of buckshot. Boom. The page is suddenly filled with boxes. And the title of each box should give a hint as to what each box does.
The old Blog module took this approach. If you instantiated version 3 on a DNN page this is what you’d see:
As you can see, the module titles give some clues as to what each is for, but if you’re new to the module, it is all quite baffling. The idea is to then move these boxes around on the page so it will look a bit more presentable. With the main content in the main part of the screen and the auxiliary content on the side.
The problem with the “multiple definitions per module” model
The biggest problem is not that the plethora of modules on the page can be confusing, however. The biggest problem is that DNN has no way of managing them individually. That is: the DNN UI is all about modules, not module definitions. For instance: let’s assume that after instantiating the old blog module we have no desire for the categories box as we’re not using those. So we can click “delete” from the module dropdown and it’s gone. Now, let’s assume that a few years later you want to reverse this decision (and you’ve emptied the recycle bin). There’s no way to do that. You can’t say: add the categories definition back to this page. You can only add a completely new instance of the module to the page (eeeew).
The other scenario where this becomes poignant is when your definitions should typically go to different pages (e.g. shopping module with checkout on a different page). Although you can go to the module’s settings and make it hop to another page, it is unnatural. The “DNN way” of doing things is to go to a page and add the module you want to the pane you want. Again, this is where the DNN UI is lacking in its ability to control individual definitions.
In my opinion this has been a major oversight in the implementation of modules and module definitions. You may say “o, edge case”. And you’re not necessarily wrong. But as a module developer you want to cater for all scenarios, even the unintended ones. And this is one where we’re stuck. So in version 4 of the blog module (when I first contributed code) I decided to add at least a management panel to the main module definition so users could add the parts individually:
The other workaround is to create a package with multiple modules. Since packages were introduced in DNN 5, this is now a possibility. NBStore (the most successful open source store for DNN) uses this approach. Naturally this leads to bloating of the module list. You end up adding modules to the list of modules that you’re only likely to use once:
I find we’re left jumping hoops here. It’d be better if we all sat down (well, maybe not all of us) and had a long hard think about how this should work. I.e. how should the end user work with “complex” modules and how can we implement this in a way that is consistent across the UI and the technical domain?
Blog version 6
A final note on how I’ve approached this for the new blog module. In the new blog module I’ve done away with the multitude of definitions. As mentioned above I find them confusing to the end user and just not well implemented in the core. It also becomes quite a mess managing a whole bunch of ascx files that belong to different definitions. More code to manage = more vulnerabilities.
One of the main objectives in the rewrite was to allow the module to be “templateable”. That is: it should be easy for others to adapt the display of the module’s content to suit their needs. And realizing that the other module definitions were mostly rehashes of the same data, the approach has been to deprecate the existing definitions in favour of templates. So a category list becomes a template. The “archive” (actually a calendar where you can browse back to older posts) becomes a template. Etc, etc. And the main view becomes a template, of course. Here’s a screenshot of my dev installation:
Equally you could pump it out to a TimelineJS template:
The possibilities are endless (well, almost). So how does this wire up? Well, there is now just one definition that is pretty minimal. A few management ascxs and the main templating ascx. This has made it somewhat easier to keep an overview of all the controls. The module now keeps its data per module (and no longer “per portal” as before) which means that every “slave module” has a module setting pointing to the “master module”. I.e. the module where the data is:
You also get a dropdown which template to use and whether to restrict the data to a particular blog, author or category. These settings are stored as TabModuleSettings. Meaning that even if you copy the module, you can still set this individually. I’ve kept the button to add new definitions of the module on the same page as the main module:
So it should still be easy to add the calendar etc to the same page. But with the new module, once you’ve instantiated the module, you now only see the main definition on the screen and you begin working from there.
Now I have no illusions about the fact that this is not how everyone would like things. We have to jump through hoops and every module developer has their own opinions on this. So when the bar stools start flying late at night during one of our awesome conferences, it could well be because we’ll probably not be able to agree on a single approach. Which is why I hope we can open the debate before the conference , while we’re still at safe distance from each other (just kidding, we’re all very pacifist people, us developers, right?).