Sharing views between modules in DNN

How to share user controls between modules in DNN.

Gus Beare

A common principle in coding is the DRY(Don't Repeat Yourself) principle. Where the coder avoids repeating the same code.

I have been working recently on an application in DNN for an e-commerce site that requires identical forms to be available from different modules. But how could I share a common view between these modules in a DNN site?

Well, I found an answer. I am not sure it's a great idea but it works and it works reliably. It allows me to re-use a web user control that exists in one module in any other module on any other page on a DNN site.

Here's how it works. First I created a stored procedure which could get the tab Id and module Id for a given module definition:

CREATE PROC [dbo].q_GetModuleTabID
	@ModDefName VARCHAR(100),
	@PortalId int

SELECT tm.ModuleID, tm.TabID FROM dbo.Modules m 
INNER JOIN dbo.ModuleDefinitions md ON m.ModuleDefID=md.ModuleDefID
INNER JOIN tabmodules tm ON tm.ModuleID=m.ModuleID
WHERE md.DefinitionName= @ModDefName
AND PortalId=@PortalId


Once I had this I could easily call the procedure from my code, passing in the module definition name like this:

EXEC dbo.q_GetModuleTabID 'MyModule',0

This would tell me on which page I could find the module (the tab Id) and also the module Id. As you will see later, this is necessary to call the DNN API function Globals.NavigateUrl() to build a url to the shared control.

Clearly there is a potential issue with this method in that the module could be installed on dozens of pages. But that might not be a problem because the view being re-used would be the same view whichever location was chosen. But to avoid this I would consider joining to the tabs table and finding only modules for a given tab name, like this:

SELECT tm.ModuleID, tm.TabID FROM dbo.Modules m 
INNER JOIN dbo.ModuleDefinitions md ON m.ModuleDefID=md.ModuleDefID
INNER JOIN tabmodules tm ON tm.ModuleID=m.ModuleID
INNER JOIN tabs t on t.TabID=tm.TabID
WHERE md.DefinitionName= 'MyModule'
AND t.PortalId=0
AND t.TabName='test'

But in my case each module will only ever be installed on one page on the site. Therefore, I will always be able to find the correct page for a given portal without the tab name.

Having to include the tab name is an issue because this could change. It would be not practical to have an application like this that could be installed several times on the same portal using different page names. Thankfully this solution is not something that is going to be marketed and released for general consumption. It will only be installed in a controlled fashioned on a single site.

I have noticed that some commercial modules such as Document Exchange allow you to chose which module is the master module from a drop down in some settings. I have no idea how that works but I suspect it's similar to this kind of thing.

The rest of the code to load a control from another module is shown below.

Firstly I have a helper function that calls the stored procedure for me. I like to use a simple DB wrapper called Thunderstruck which makes working with the SQL Server back end so much easier. Hand coding ADO.Net is a pain so anything like PetaPOCO, Thunderstruck or Simple.Data is better.

public class Helpers
    // get the tabid where a module exists
    public static q_ModuleIDTabId GetTabIdForModule(string moduleDefinitionName,int portalId)
        using (var context = new DataContext("SiteSqlServer", Transaction.No))
            var r = context.First("EXEC dbo.q_GetModuleTabID @0, @1", moduleDefinitionName, portalId);
            return r;

In the above code we have a POCO called q_ModuleIDTabID which defines a row for tab Id and module Id. Then we use the DNN API to redirect to the control on the other module. Like this:

// send the user to the MyView form in the MyModule module
var row = Helpers.GetTabIdForModule("MyModule", PortalId);
var t = row.TabId;
var mid = row.ModuleId;
Response.Redirect(Globals.NavigateURL(t, "MyView", "mid=" + mid,"adId=" + adId));

And voila! The web control MyView.ascx in the module MyModule on another page is loaded from the current module. This saves me the maintenance headache and repetition of having identical views in different modules.