DNN Module Development Technique, Another Approach
We know that there are some technique when developing custom module in DotNetNuke. You can use some technique from the simplest to the complex one. For me, i'm usually using this technique. I will explain it like below.
First, in every module, i create a file which called Injector.ascx. This file will act as injector for other user control. It only contains one PlaceHolder object. This is the file that you have to registered inside DotNetNuke framework. The complete code for Injector.ascx is below :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using DotNetNuke.Entities.Modules;
namespace DemoDNNUserControlNavigator {
public partial class Injector : PortalModuleBa

se {
private string m_ControlToLoad =
string.Em

pty;
protected void Page_Init(
object sender, Ev

entArgs e) {
ReadQueryString();
LoadControlType();
}
private void ReadQueryString() {
string qs =
string.Empty;
if(Request.QueryString[
"ControlType"]

!=
null) {
qs = Request.QueryString[
"ControlT
ype"].ToString();
}
if(qs !=
string.Empty) {
switch(qs.ToLower()) {
case "p1":
m_ControlToLoad =
"page1.ascx"
;
break;
case "p2":
m_ControlToLoad =
"page2.ascx"
;
break;
default:
m_ControlToLoad =
"page1.ascx"
;
break;
}
}
else {
m_ControlToLoad =
"page1.ascx";
}
}
private void LoadControlType() {
PortalModuleBase objPMB = (PortalModul

eBase)
this.LoadControl(m_ControlToLoad);
if(objPMB !=
null) {
objPMB.ModuleConfiguration =
this.

ModuleConfiguration;
objPMB.ID = System.IO.Path.GetFile

NameWithoutExtension(m_ControlToLoad);
plhControl.Controls.Add(objPMB);
}
}
}
}
You see from the code above. I have two method and one private member. The first method is ReadQueryString(). This method is responsible to change the user control inside module based on querystring that we passed. The querystring will targetting to specific user control (.ascx) inside your module. The next method is LoadControlType(). This method is responsible to cast your desired user control into PortalModuleBase then put it inside PlaceHolder. To redirect to other user control, you can use Globals.NavigateUrl() method like below :
Response.Redirect(Globals.NavigateUrl(PortalSettings.ActiveTab.TabId, "", "ControlType=p2"));
Why i like this approach? Since i only have to register one control inside DNN. If i want to add new control, i just open my Injector.ascx.cs then add my new control. That's it. This solution is suitable in most of my cases.
But then i come into problem in a client. The problem is, i have to recompile my solution every time i have to add new user control. I think, it's time to make it dynamic. So, it's time to create a simple framework for navigation. My proof of concept is like this :
- All user controls are registered via configuration file inside DNN module (or maybe in root with a specific name)
- The DNN module (.ascx) should inherits from my custom class (not from PortalModuleBase again). At this time, I name it PageFlowLight.
- The page navigation mechanism should be simple like this : PageFlowLight.Next(), or PageFlowLight.Prev(), or to specific page like PageFlowLight.Go("StageTwo"). So i have create a adapter between Globals.NavigateUrl() and my new method.
Hey, sounds like Workflow Foundation Page Flow sample. :) Yep, i borrow some approach from the sample of ASPNETWFPageFlow from Microsoft.
1. All user controls are registered via configuration file inside DNN module (or maybe in root with a specific name)
First, my POC configuration file will be like this (i called it navigation.xml) :
<?xml version="1.0" encoding="utf-8" ?>
<modules>
<module name="PF" startStage="start">
<stages>
<add name="start" transition="stagetwo" />
<add name="stagetwo" transition="finish" />
</stages>
<pageflow stage="start" pageUrl="Page1.ascx" />
<pageflow stage="stagetwo" pageUrl="Page2.ascx" />
<pageflow stage="finish" pageUrl="Finish.ascx" />
</module>
<module name="Report" startStage="start">
<stages>
<add name="start" transition="stagetwo" />
<add name="stagetwo" transition="finish" />
</stages>
<pageflow stage="start" pageUrl="Page1.ascx" />
<pageflow stage="stagetwo" pageUrl="Page2.ascx" />
<pageflow stage="finish" pageUrl="Finish.ascx" />
</module>
<module name="Dashboard" startStage="start">
<stages>
<add name="start" transition="stagetwo" />
<add name="stagetwo" transition="finish" />
</stages>
<pageflow stage="start" pageUrl="Page1.ascx" />
<pageflow stage="stagetwo" pageUrl="Page2.ascx" />
<pageflow stage="finish" pageUrl="Finish.ascx" />
</module>
</modules>
As you can see above, we just register our module and also your user control. Stage attribute will give you direction to do Next(), Prev(), or Go() method inside PageFlowLight class. Then, the injection will be dynamically did inside PageFlowLight. To traverse inside navigation.xml file, i'm using System.XML.XMLDocument with XPath query syntax. The structure of XML should be validate before to make sure the integrity of the data.
For example, this code will traverse the xml file :
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("navigation.xml"));
XmlNodeList nodes = doc.SelectNodes("//module"); // get all modules tag
string result = "";
foreach(XmlNode node in nodes) {
result += "<br /><" + node.Name + " " + node.Attributes[0].Name + "=" + node.Attributes[0].InnerText;
if(node.HasChildNodes) {
foreach(XmlNode childnode in node.ChildNodes) {
result += "<br /><" + childnode.Name +
" " +
childnode.Attributes[0].Name + "=" +
childnode.Attributes[0].InnerText +
" " +
childnode.Attributes[1].Name + "=" +
childnode.Attributes[1].InnerText +
" />";
}
}
}
To get specific module name, you can use this XPath query syntax :
//modules[@name='PF']/pageflow
To get pageflow element with specific attribute stage, you can use this XPath query syntax :
//modules[@name='PF']/pageflow[@stage='start']
To get pageflow element with specific attribute pageUrl, you can use this XPath query syntax :
//modules[@name='PF']/pageflow[@pageUrl='Page1.ascx']
2 and 3. The DNN module (.ascx) should inherits from my custom class (not from PortalModuleBase again). At this time, I name it PageFlowLight. And provide simple mechanisme to call next user control.
The PageFlowLight framework should provide mechanism to get TabId from active Url then it can injects right user control inside the right module. I do it by creating Initialize() method with ModuleId as parameter. The ModuleId then will be translated into ModuleName to find correct tag inside navigation.xml file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using DotNetNuke.Common;
using DotNetNuke.Entities.Modules;
namespace DemoDNNUserControlNavigator {
public partial class page2 : PageFlowLight {
protected void Page_Init(
object sender, Ev

entArgs e) {
Intialize(
this.ModuleId); // this came from PageFlowLight framework
}
protected void btnNextPage_Click(
object se

nder, EventArgs e) {
PageFlowLight.Next();
}
protected void btnLastPage_Click(
object se

nder, EventArgs e) {
PageFlowLight.Last();
}
protected void btnNextPage_Click(
object se

nder, EventArgs e) {
PageFlowLight.Go(
"StageThree");
}
}
}
In the Page_Init, we have to call Initialize() method with ModuleId parameter. This will register our user control (page2.ascx) into PageFlowLight system so you can use Next(), Prev(), Last(), Go() navigation method.
This is my proof of concept that successfull to implement in my client. I will post complete source code for PageFlowLight framework in my next article.