Introduction
The World of Module Development
Web application platforms like DotNetNuke (DNN), aka web portals, have grown in popularity over the years because they do a very good job of solving the problem of basic web application plumbing. Security, user creation and management, forgotten passwords, page layout, backups, and multi-site/single-footprint capabilities are all common among major portal systems.
Web portal systems do all that for you, but that’s where they stop. Or rather, that’s where you start.
Web application platform and infrastructure is obviously necessary, but businesses need custom solutions for the problems they face. Fortunately, platforms like DotNetNuke make it possible to extend basic functionality by running custom programming within the site pages. You can do this without getting into the core programming of the portal system. Called modules in DotNetNuke, these self-contained business application components are known by many names in other portal systems: SharePoint calls them WebParts, Java-based portal servers call them Portlets, and PHP-Nuke calls them blocks.
DotNetNuke allows you to get down to the business of implementing great new web site features without having to worry about the basics of user management, pages, and security. While the process of creating your custom DNN modules isn't difficult it can be hard to get your mind wrapped around how it all works.
What I hope you will learn from this article:
- How to create a the beginnings of a fully functional DotNetNuke module
- How to creating custom programming and logic for more engaging DotNetNuke web sites
- The architectural approach of DotNetNuke Module Development
Acknowledgements
I originally started down this path a year ago when I read the excellent article Creating a DotNetNuke® Module - For Absolute Beginners
The author also has a follow-up article that looks to be a good adjunct and touches on some of the same issues as this article Creating a Super-Fast and Super-Easy DotNetNuke® Module - for Absolute Beginners!
Background
While the process of adding a DNN Module is straightforward and has a consistent internal logic, once you understand how all the pieces fit together, it sure can seem confusing and daunting at first! In order to support as wide a range of business rules and design needs as possible the DNN architecture is very robust. That same robustness however means that it is not “simple”.
I can remember that my first attempt at Dot Net Nuke module development using the DNN development tools was confusing to say the least. I followed the very detailed and well written directions from a post here on the CodeProject (Creating a DotNetNuke® Module - For Absolute Beginners) and while at the end I had a working module, I didn’t really know how it worked! This is NOT a criticism of the article, I'm just slow sometimes, the fault lies totally with me
The process is very Menu intensive and has multiple steps that at first seem disconnected from each other. I had built a module following the directions that worked exactly how it was supposed to, but I had no idea if I could build one from scratch that operated differently from the tutorial. It took 2 days of reading, DNN core code review and study, multiple Hello World attempts that did things differently at different points in the process before I felt I understood what was going on beneath the covers. So in order to help those like me who don't pick up things as quickly, I would like to lead you thru an example of adding a module, but I want to make sure that when we are done, you also understand enough of how DNN works that you can make your own module with its own functionality.
I'll show you how to create a module from scratch. You will learn to benefit from the native Microsoft .NET framework and all its power, as well as gain insight into the organization of DotNetNuke modules and related APIs. Creating modules from scratch opens a world of possibilities for commercial distribution of your creations and tight integration with DotNetNuke.
Prerequisites
This walk thru requires
- Visual Studio, Express edition or better (you can download the express edition here)
- SQL Server, Express edition or better (you can download the express edition here)
- DotNetNuke Starter Kit (download here) [A walk thru on installing and using the starter kit by the Creator of DNN, Shaun Walker, can be found here]
Getting Started
To get started, let’s take a look at the finished product we are going to create. This is a fully-functional ToDo Task List that you can begin using today to track important action items.

The ToDo Task List we will create.
Now, let’s look at how to get there.
To Do Task Project Specification
So what exactly are we going to build? Before we get started, we should take a few moments and look at the map.
Our mission is to create a To Do Task list that allows our users to enter tasks to be completed, indicate when they are completed.
Project design goals
Our Functional Specification is as follows:
- The user should be able to view a list of tasks
- The user should be able to create a task
- The user should be able to mark a task as completed
- The user should be able to delete a task
- The task should have a Subject string field
- The task should have a Completed Boolean field
- Only site registered users should be able to edit tasks
- Anonymous users should only be able to view tasks
I strongly recommend that you first create and test your module in a separate DNN Website on a server other than your production DNN server machine. Once you have successfully downloaded and installed the DNN templates and followed Mr. Walker’s instructions on how to create a DNN Web Project, we can get started on our Module, so go ahead, follow those steps, I'll wait... Welcome back, All done? Good! In this article we will complete the following steps
- Add a module using the DotNetNuke Dynamic Module Template to a DNN website
- Register our module with the DNN module so we can add it to a page and use it
- Create a database table and stored procedures for persistent data storage
- Working from the bottom up of the DNN architecture; modify the template’s data layer classes to retrieve / persist data
- Modify the template’s business layer objects to allow UI manipulation of data.
- Modify the template’s UI layer user control to allow for end user interaction.
- Modify the UI's code behind so that only registered users who have logged in to DNN can edit tasks
That last requirement is a bit of a “cheat”. DNN has a full-featured security role system that would allow us to set up different permissions for the List based on the login but that is beyond the scope of this tutorial. For simplicity and brevity, we are just going to use the simplest case of security with this module. In a full production system or deployed module, this would not be recommended! Please see the CodeProject article Creating a Super-Fast and Super-Easy DotNetNuke® Module - for Absolute Beginners! for an example of how to implement security in a more robust way.
Create a new DNN module using the template

Sub-directories under the DesktopModules folder is where the individual modules that give DNN its functionality are located in conjunction with sub-directories in the App_Code folder, and where we will be working to add our custom modules functionality.
Add a new module to the project
So let’s get started by adding the new module. DNN depends on the physical placement of modules within the project tree in order to find and load modules correctly, so it is important that the new module be added in the correct place. To make sure that your new module is added correctly, click once on the root project node before proceeding (the root project node in the figure above would be the “C:\Src\DNNModuleDevelopment” node).
Once you have clicked and put focus on the web project’s root node click File on the menu bar, then New and then File…

and the Add New Item dialog is displayed

As you can see in the dialog graphic above, the DotNetNuke Starter Kit has added a new DotNetNuke Dynamic Module template to the Add New Item dialog under My Templates.
Select your language of choice (either Visual Basic or C#), select the DotNetNuke Dynamic Module item, give the module the name, ToDoTaskList, and click Add.
Mixed C# / VB Web Project VooDoo
If you look carefully at the figure above, you may have noticed that I’m added a C# module to a VB web project, and the natural question is “can you actually do this”? The answer is “yes”, if you do a little extra work.

If you added your module using VB as the language, you can skip this section but if you added your module as C# as I did, you will need to make a modification to the web.config file so the build process knows which directory under the App_Code folder contains your module. It will treat your module’s folder as “different” and at compile / build time will determine the correct compiler to use.
Open the web.config file and find the <compilation>
node. Inside of that node near the end, you will find a commented out section. Uncomment the section and modify it so it reads as:
<codeSubDirectories>
<add directoryName="ToDoTaskList"/></codeSubDirectories>
After you have added the module, the development environment will show you a helpful "To Do" list that was added to your project by the template telling you what you need to do next to make your To Do Task List work (which makes this a recursive exercise, wouldn’t you say?).
Renaming the Folders
The first thing we need to do, just as the instructions say, is rename the added folders that contain the skeleton of our module. Under both the App_Code and the DesktopModules folders, a ModuleName folder has been added; right mouse click on each of the ModuleName folders, select rename and rename them ToDoTaskList.

Rename "YourCompany" if desired
The module template added the files for your module, but it has inconveniently decided that the name of your company is … YourCompany. Actually, that is a placeholder for you to replace with the name of your choosing.
You are welcome to use the find and replace function in Visual Studio to replace YourCompany with the name of your choice, but I’m going to leave it as YourCompany. If you do want to change it, now is the time before we go any further. Save your work by clicking File on the menu bar then Save All and then build the project by clicking Build on the menu bar, then Build Web Site, just to make sure everything still compiles and the site builds correctly before we proceed.
Let DNN know about your Module
In previous versions of the starter kit a file used to be added that you could run in Sql Server Query Analyzer or in DNN to let the DNN framework know about your module, but with 4.4.x that has been removed and now you need to use the DNN interface to register your module. You can view the official blog entry here, or you can read my cited excerpt below
Register the module in DotNetNuke
The following section was excerpted and modified (to use ToDoTaskList instead of SuperSimple) from http://www.adefwebserver.com/DotNetNukeHELP/DNN_ShowMeThePages/Super_Simple2.htm originally prepared by Michael Washington.
While logged into your DotNetNuke site as "host" in the web browser, from the menu bar select "Host". Then select "Module Definitions".

Click the black arrow that is pointing down to make the fly-out menu to appear. On that menu select "Create New Module".

In the Edit Module Definitions menu:
- Enter "ToDoTaskList" for MODULE NAME
- Enter "ToDoTaskList" for FOLDER TITLE
- Enter "ToDoTaskList" for FRIENDLY TITLE
- Enter "ToDoTaskList" for DESCRIPTION
- Enter "01.00.00" for VERSION
Then click UPDATE

Enter "ToDoTaskList" for NEW DEFINITION
Then click "Add Definition"

Next, Click "Add Control"

In the Edit Module Control menu:
- Enter "ToDoTaskList" for TITLE
- Use the drop-down to select "DesktopModule/ToDoTaskList/ViewToDoTaskList.ascx" for SOURCE
- Use the drop-down to select "View" for TYPE
Then click UPDATE

End of Excerpted section, thank you to Mr. Washington
Now let's add one more control. Click "Add Control" again and this time in the Edit Module Control menu:
- Enter "Edit" for KEY
- Enter "Edit ToDoTaskList" for TITLE
- Use the drop-down to select "DesktopModule/ToDoTaskList/EditToDoTaskList.ascx" for SOURCE
- Use the drop-down to select "Edit" for TYPE
Then click UPDATE

While we won't be actually using this Edit Control in exactly the way the template anticipates in this tutorial, I wanted to hook it up so that later on when I give you some more information on DNN architecture / Module interaction, it will help make sense. Also I expect you will want to use this code as a starting point for your own work, and by taking the time to put it in here, you are better prepared to extend this example.
The extra string entry of "Edit" that we made here for the KEY is used by the plumbing of DNN to display the correct control depending on what "mode" the module is being displayed in. As I said this will all make more sense a little further down the line, so keep it in mind and trust me!
Prep The View Control
Back in Visual Studio open the user control ViewToDoTaskList.ascx file under the DesktopModules\ToDoTaskList folder, and switch to Design mode. This user control was added by the module template and has been registered as the main view into your module. This control will be displayed by DNN when our module is added to a web page. As created by the module template it displays a simple list of user text entries in a ASP.NET control, to complete the “Hello World”.
We won’t be using any of its current functionality. In order to support / display our task list we are going to tear this control down and rebuild it from the ground up. Select the asp:datalist on the control and delete it!

Next let’s add a Label to the control and set it’s text to “My ToDo Task List”, and the font size to Large

The next thing to do is remove the code that the Template inserted to fill the asp:datalist we just deleted / replaced. The code inserted by the template was put in a Code Behind file. You can access the code behind file by clicking the plus symbol next to the ViewToDoTaskList.ascx node in the solution explorer and then double clicking on the ViewToDoTaskList.ascx.cs file

Once you have opened up the ViewToDoTaskList.ascx.cs code behind file, open the Event Handlers region if necessary. Find the lstContent_ItemDataBound
procedure and delete it.
Finally, in the Page_Load
procedure, delete everything in the Try block. We have just removed code that was used to fill the asp:datalist with a list of simple text entries and is no longer required. Save your work and from the menu bar, select Debug ? Start Without Debugging… and login as the Host one more time.
Once you are logged in, at the top of the window, let’s add our module

Select the ToDoTaskList in the Module drop down, set the title to “Tasks”, and set the Pane drop down to TopPane, finally click the Add link. Once you have clicked the Add link our empty control module control is added to the page

Modifying the Database
Now that we have the UI ready for our modifications, it is time to build in the database support for our module. Let’s start by opening the DNN database. The first step is to find the Database.mdf in the Solution Explorer under the App_Data folder and double click it

Once you have double clicked it, a new data connection to the database will be added to the Server Explorer window, which will allow you to add and modify database structures

Add the ToDoTask Table
Looking at our specification, we will need to add a table to the DNN database to hold our ToDo Task List. Right mouse click on the Tables node under the Database.mdf in the Server Explorer and click Add New Table
- Add an integer column named ID. Make it an Identity / Primary key (make sure to set it as the Identity so that the IDs are generated for you as tasks are added).
- Add a bit column named Completed. Set the default value to 0, and don’t allow NULLS.
- Add a varchar(MAX) column named Subject . Set the default value to an empty string, and don’t allow NULLS.
- Name the table ToDoTask.

Create SUID Procedures
We need to add the stored procedures to support Select, Update, Insert, and Delete for our task list. You add stored procedures the same way you added the new table; right mouse click on the Stored Procedures node under the Database.mdf in the Server Explorer and click the Add New Stored Procedure.

The following five procedures allow us to retrieve the data for a particular task by ID, retrieve the data for all the tasks in the table, update a particular task’s values, insert a new task, and finally delete a particular task. Add these procedures to the database.
CREATE PROCEDURE ToDoTaskSelect
@ID int
AS
BEGIN
SELECT
ID AS ItemId,
Completed,
Subject
FROM [ToDoTask]
WHERE ID = @ID
END
CREATE PROCEDURE ToDoTaskListSelect
AS
BEGIN
SELECT
ID AS ItemId,
Completed,
Subject
FROM [ToDoTask]
END
CREATE PROCEDURE ToDoTaskUpdate
@ID int,
@Completed bit,
@Subject varchar(MAX)
AS
BEGIN
UPDATE [ToDoTask]
SET
[Completed] = @Completed,
[Subject] = @Subject
WHERE [ID] = @ID
END
CREATE PROCEDURE ToDoTaskInsert
@Completed bit,
@Subject varchar(MAX)
AS
BEGIN
INSERT INTO [ToDoTask]
([Completed], [Subject])
VALUES
(@Completed, @Subject)
END
CREATE PROCEDURE ToDoTaskDelete
@ID int
AS
BEGIN
DELETE FROM [ToDoTask]
WHERE [ID] = @ID
END
DNN Module N-Tiered Architecture
Now that the database is ready to work with the Task data, we need to modify the DNN data layer to work with the stored procedures we just created. Before we continue I think now would be a good time to give you a quick high-level overview of the DNN n-tiered architecture.

DNN was an outgrowth of Microsoft’s IBuySpy example web site, which was meant to highlight and recommend Microsoft’s “Best Practices” recommendations for the first release of ASP.Net. Consequently, DNN has a strong n-tiered architecture, with clear separation between the layers. The User Control that we modified earlier is one of the elements for the UI Layer. It communicates with the specific Controller and business objects for our Module that we will be modifying a little later on, and that Controller will in turn communicate with the Data Access Layer.
There are a couple of ways to proceed here, but for our example, I intend to start down in the Data Access Layer, making modifications down in the data provider to support the structure of our ToDo Task List table and stored procedures and then let those changes percolate to the top UI Layer.
While the core DNN engine depends on SQL Server to do its internal stuff, you as a module developer are not restricted to SQL Server as your backend. If you want to have your module run against an Access Database, or a MySql Database, or Oracle, or even simple text files you have that option.
Maybe you even want to have an advanced module that will support being run against either Access or MySql or SQL Server depending on what the consumer of the module wants to do.
So how exactly does the DNN architecture support this flexibility? in order to explain that let's take a look at some of the boilerplate code created by the module template. Let's take a look at the Business Layer class that was created by the template, which is located in the ToDoTaskListController.cs file. You can find that file in the App_Code\ToDoTaskList folder
.
Open the Controller class’s code and in the Public Methods region look at the first method.
public void AddToDoTaskList(ToDoTaskListInfo objToDoTaskList)
{
if (objToDoTaskList.Content.Trim() != "")
{
DataProvider.Instance().AddToDoTaskList(objToDoTaskList.ModuleId,
objToDoTaskList.Content, objToDoTaskList
.CreatedByUser);
}
}
I’d like you to look at the fifth line 5 and the interesting DateProvider.Instance()
. The DataProvider
is an abstract class that defines the methods the controller can use to interact with the actual data layer, and the Instance() is a method that will return an implementation of the DataProvider abstract class for a specific type of data access. The idea here is that the Controller runs against the DataProvider which maps thru to the specific data access that is wanted. In our case, we are only going to support SQL Server so we don’t need to make any changes to the Instance()
method. If we did want to support a data access layer other then Sql Server we would need to write an implementation of the DataProvider abstract class that goes against our specific data backend and override the Instance()
method to return it.
Modification of SQL Data Provider and Data Provider classes in project
Ok, enough with the theory! Let’s modify some of these data layer classes and that may help you to understand where theory becomes fact.
For the next few steps, we are going to be working in the App_Code\ToDoTaskList folder and the classes that are in there. Let’s start by modifying the DataProvider.cs file.

We won’t be making any changes to Shared/Static Methods region, but we are going to be making some changes in the Abstract Methods region. This region in the DataProvider class defines the data access functions that we will be using later on when we hook up our business layer objects.
The default methods created by the module template are:
AddToDoTaskList(int ModuleId, string Content, int UserId)
GetToDoTaskList(int ModuleId, int ItemId)
GetToDoTaskLists(int ModuleId)
UpdateToDoTaskList(int ModuleId, int ItemId, string Content, int UserId)
DeleteToDoTaskList(int ModuleId, int ItemId)
Let’s modify this list as follows:
AddToDoTask(bool Completed, string Subject)
GetToDoTask(int ItemId)
GetToDoTaskList()
UpdateToDoTask(int ItemId , bool Completed , string Subject)
DeleteToDoTask(int ItemId)
DNN Architecture Sidebar.
You may have noticed that we pulled a little sleight of hand here. In addition to modifying our procedure names to be more meaningful, we also eliminated the one parameter that was common to all of the original methods; int ModuleID
.
For most DNN modules, if you add a module to a page, its contents on that page are unique from all other instances of the same module on other pages.
If you drop a documents module on page 1, its contents are not the same as another documents module you put on the same page or on page 2, nor would you want them to be.
The exception to this general rule is that if you specify the “add to every page” option in the document’s module setting; that functionality is even more advanced, and beyond the scope of this tutorial.
Most modules accomplish this page specific content by using the ModuleID. When a module is added to a page, that module’s instantiation is assigned a unique ModuleID. When the page loads the module, that unique id is available so that you can make the particular content for that module on that page show up.
In our case however, we are not going to worry about that. For our task list, if there is a task that needs to be done, we want to make sure you see it! Anywhere you add our module, all the tasks will show up, in this case it is perfectly correct behavior for the user to click on a button on one instance of the module and return data from another instance of the module! I leave it as an exercise for you to make module specific task lists (hint: you will need to add ModuleID to the database table and stored procedures).
Save your work and try to compile now (be prepared for some errors!). Now that we have changed the abstract DataProvider class, we are going to need to modify the SqlDataProvider that inherits from the DataProvider to make the method signatures the same. Our modifications to the abstract methods mean that any class that inherits from the DataProvider class needs to be updated to support those changes.
Open the SqlDataProvider.cs file and look in the Public Methods region. You should notice two things here; first of all, if you tried to compile, all the methods in here are marked as an error, and second, the list of methods are the same as the list of abstract methods that we modified in the DataProvider class.
You need to go through each of the methods and modify them so their signatures match with the abstract methods we just modified in the DataProvider.
AddToDoTask(bool Completed , string Subject)
DeleteToDoTask(int ItemId)
GetToDoTask(int ItemId)
GetToDoTaskList()
UpdateToDoTask(int ItemId , bool Completed , string Subject)
As well as modifying the signature, you obviously also need to modify the body of these methods. The DNN development team has used the Application Blocks from Microsoft, and we get to leverage that work to simplify this step.
The first time you do this, however, it can be a little confusing, so we’ll step thru modifying the UpdateToDoTaskList
method, explaining as we go along.
Start by looking at the original unmodified method.
public override void UpdateToDoTaskList(int ModuleId, int ItemId,
string Content,int UserID)
{
SqlHelper.ExecuteNonQuery(ConnectionString, GetFullyQualifiedName(
"UpdateToDoTaskList"), ModuleId, ItemId,
Content,UserID);
}
Let’s do the easy part first and modify the signature
public override void UpdateToDoTask(int ItemId, bool Completed, string Subject)
The next line is the SqlHelper.ExecuteNonQuery. A little explanation is in order here.
Microsoft has provided a set of best practice open source application blocks that are available for you to use in your projects. You can find out more about these at “Microsoft patterns & practices: Application Blocks” located at http://msdn.microsoft.com/practices/guidetype/AppBlocks/
I won’t cover the application blocks in detail, but I highly recommend you investigate their applicability for inclusion in your projects. One of the application blocks is the Data Application Block that adds an extra abstraction layer on top of the .NET data access libraries. The Block simplifies and standardizes database calls regardless of the target data engine.
Please note that while DNN continues to use the first Microsoft Application Blocks v1.0 release, there was a major overhaul and update to these blocks in 2006 and they are now called Enterprise Application Blocks v2. At the time this article went to press Version 3.0 of the Enterprise blocks was in CTP. I heartily recommend for your projects use the version 2.0 or better. Better performance, scalability, reliability, etc.
The DNN team has used the Microsoft.ApplicationBlocks.Data.SqlHelper to allow easier access to the database. By using the SqlHelper class, we can save ourselves a lot of keyboard typing, but it is helpful, even with less typing to understand how it works.
We would like to break down the SqlHelper’s call.
SqlHelper.ExecuteNonQuery( ConnectionString, GetFullyQualifiedName(
"UpdateToDoTaskList"), ModuleId, ItemId,
Content, UserID);
The first argument required by the ExecuteNonQuery is a database connection string. This argument’s value is supplied by the ConnectionString property within the SqlDataProvider class, and was initialized in the constructor to the value stored in the web.config file; No need to change anything with this first argument.
The next argument required by the ExecuteNonQuery is the name of a stored procedure to run. This argument’s value is supplied by the GetFullyQualifiedName()
method also within in the SqlDataProvider class. This is provided in order to support a variety of naming methodologies to help you manage and separate your stored procedures from the many other ones added by DNN core and the many modules that are out there.
We are not going to use that functionality for this walk thru, so we will replace the argument with just the name of the Update stored procedure we created: ToDoTaskUpdate.
All of the remaining arguments for the ExecuteNonQuery are part of a parameter array. The ExecuteNonQuery takes the values in this parameter array and assigns them to the parameters of the stored procedure that was specified in the second argument. These arguments must be supplied in the correct order and of the correct type for the referenced stored procedure.
So taking a look at our ToDoTaskUpdate stored procedure, we can see it takes three parameters, @ID, @Completed, @Subject; and as luck would have it, they are exactly the parameters that were specified in the method signature. Putting in place all of our changes we end up with:
public override void UpdateToDoTask(int ItemId, bool Completed, string Subject) {
SqlHelper.ExecuteNonQuery( ConnectionString, “ToDoTaskUpdate”, ItemId,
Completed , Subject);
}
Following that as a guide, go ahead and modify the other methods.
Still need help?
If you still aren’t sure how to modify the other methods, you can see what they should look like in the Appendix at the end of this article or look in the zip download for the source.
Our data layer work is done, now is the time to save your work and try to compile… Once again, be prepared for errors! We now have some work to do on the Business Layer.
Modifying the BL Objects
We have completed our work on the data layer, but in the process, we seem to have broken our business layer. This is actually a good thing as our specs are beginning to percolate up thru the system.
Modification of the ToDoTaskListInfo class
Our compiler errors are now in the Controller, but before we start trying to “fix” that class, let’s modify the ToDoTaskListInfo class to be more applicable.
The ToDoTaskListInfo class is used to represent a single task. It is thru this class that we will modify our task information. First things first, let’s rename both the file and the class to be more reflective of what it is; a single task. Rename the ToDoTaskListInfo.cs file to ToDoTask.cs. Locate the file in the Solution Explorer, right mouse click on it and select Rename


Now open the newly renamed file by double clicking on it, rename the class ToDoTask, and modify the constructor appropriately

With a better name, let’s move on to the properties in this class. The Module Template created five properties and their private member variables.
- ModuleID
- ItemID
- Content
- CreatedByUser
- CreateDate
While we will need the ItemID, we don’t need the other ones, so go ahead and delete them. Add private bool
and string
member variables and name them _Completed
and _Subject
respectively. Finally, create public property gets and sets for the variables as shown in the code example below.
private bool _Completed;
private string _Subject;
public bool Completed {
get { return _ Completed; }
set { _ Completed = value; }
}
public string Subject {
get { return _Subject; }
set { _Subject = value; }
}
Our UI control will access and modify the values of the individual tasks using these properties.
When modifying the class, it is important that the names of the properties be the same as the names of the columns returned from the SQL select statements (which is why if you go back and look at the SQL select Statements I specified earlier, the ID column was returned named as ItemId). The reason why this is important is that by adhering to this standard, later in the Controller we can leverage a DNN built in utility that can build and populate our objects for us, saving us some coding time. We’ll cover how this “magic” works a little further on.
That’s all we need to do with this class, so save your work.
Modification of ToDoTaskListController class
Now we need to finish our modification of the Business layer by modifying the ToDoTaskListController.
Since we renamed our ToDoTaskListInfo class ToDoTask, the first thing we need to do in the controller is update it to reflect that change. Open the ToDoTaskListController.cs file and click Edit on the menu then click Find and Replace then click on Quick Replace and do the replacement as shown here

There is some other cleanup we need to do next; let’s start with the interfaces that the template added to our class. We will not be implementing those interfaces, so we can safely delete them. The class declaration for the controller,
public class ToDoTaskListController : ISearchable , IPortable {
should be modified to read as
public class ToDoTaskListController {
In addition, please delete the entire Optional Interfaces region from the Controller source file which would normally be used to support the interfaces we have removed from the class declaration
The ISearchable and IPortable Optional Interfaces
The ISearchable and IPortable interfaces can be implemented by your controller to let DNN know that your module supports these optional interfaces. So what do they do exactly?
• ISearchable
The ISearchable interface lets DNN know that your module should be searched when the built in DNN Search function is used. Every time the content in the module is added, deleted, or modified, DNN will use the ISearchable to walk thru your module’s individual items and add them to the search index so that users can search for content within your modules.
• IPortable
The IPortable interface is used by DNN to support Module Content copy. When the IPortable is supported, the user can elect to copy the module’s contents (the copy must be in an XML format, and you need to implement the serialization of the module’s contents), and then copy those contents to another module of the same type.
Ok, with that housekeeping out of the way, it is time to get to the meat of the modification of the Controller class. Open the Public Methods region and examine the methods contained in it.
public void AddToDoTaskList(ToDoTask objToDoTaskList)
public void DeleteToDoTaskList(int ModuleId , int ItemId)
public ToDoTask GetToDoTaskList(int ModuleId , int ItemId)
public List<ToDoTask> GetToDoTaskLists(int ModuleId)
public void UpdateToDoTaskList(ToDoTask objToDoTaskList)
Those should look familiar to you by now, they are the same calls we have modified in Data Layer, but the argument list for these methods looks a little different than the ones we have been working with so far. The earlier methods we modified in the Provider classes had parameters for the values that were being modified / added. These methods take a ToDoTask object and the values will be retrieved from the object. We obviously need to modify these signatures, and since we are working in C# we'll be good net developers and use Camel Casing instead of Hungarian
The Add, Delete, and Update functions are straightforward, so we will start by modifying them, all we need to do is remove the extraneous ModuleID parameter.
Let’s change the names and signatures so they match the following:
public void AddToDoTask(ToDoTask toDoTask)
public void DeleteToDoTask(ToDoTask toDoTask)
public void UpdateToDoTask(ToDoTask toDoTask)
Now we need to modify the body of the procedures. The DeleteToDoTask
method body will need to be modified to call the appropriate method in the SqlDataProvider
class. In the case of DeleteToDoTask
we want to call the SqlDataProvider’s DeleteToDoTask(int ItemId)
method. Of course you have noticed that the argument passed into this DeleteToDoTask
is a ToDoTask
object, not an int. We will retrieve the ItemID
from the ToDoTask
object that is passed in, thusly:
public void DeleteToDoTask(ToDoTask toDoTask) {
DataProvider.Instance().DeleteToDoTask(toDoTask.ItemId);
}
The Add and Update functions need to be modified to call the appropriate methods in the SqlDataProvider
class in the same way.
public void AddToDoTask(ToDoTask toDoTask){
DataProvider.Instance().AddToDoTask( toDoTask.Completed,toDoTask.Subject);
}
public void UpdateToDoTask(ToDoTask toDoTask) {
DataProvider.Instance().UpdateToDoTask(toDoTask.ItemId, toDoTask.Completed,
toDoTask.Subject);
}
Remember DataProvider.Instance()
points to our instantiated SqlDataProvider class during run time.
The Get functions also require just some simple modifications, but there is a little behind the scenes magic going on that is worth taking the time to look at. First up, here are the modifications required for the Get methods:
public ToDoTask GetToDoTask(int itemId){
return CBO.FillObject<ToDoTask>(
DataProvider.Instance().GetToDoTask(itemId));
}
public List<ToDoTask> GetToDoTaskList() {
return CBO.FillCollection<ToDoTask>(
DataProvider.Instance().GetToDoTaskList());
}
The name change, and modification to the called function on the Instance()
method should be straightforward to you by now, but you are probably wondering about the CBO.FillObject
and CBO.FillCollection
; what are these?
You may remember back when we modified the ToDoTask
class, we made mention of the need to make sure the property names for the class matched the column names returned from our Select stored procedures, the CBO functions are why:
The CBO (which I believe stands for Core
Business Object, but don't quote me!) is a DNN Core Utility support class that can be used to build and populate our ToDoTask object, or a collection of ToDoTask
objects in an automated fashion, provided we followed the correct naming convention. These functions use .NET Reflection to attempt to create and fill our objects.
The functions are Generic functions, where we specify the type of object we will be working with inside the < >
, in this case, they are marked as <ToDoTask>
. This tells the functions to create an object of ToDoTask
type and once the object has been created, fill it will data.
The GetToDoTask
and GetToDoTaskList
functions called on the DataProvider
each return an IDataReader. The CBO functions then use reflection to examine the properties of the class type specified in the < > (ToDoTask
in our case) and attempt to find a column in the returned IDataReader
with the same name. When a match is found, the functions set the object’s property values from the data in the current row for that column. In the case of the GetToDoTaskList
it will do this for each row in the Datareader. As long as you adhered to the naming standard, the CBO utility will create and fill your ToDoTask objects with data for you.
Shameless Plug Follows: Stay tuned, in the next few weeks I will be posting up an article here at the code project where I have extracted out the relevant code for this functionality from DNN's CBO, updated it to use the newest Enterprise Library, plus caching and other improvements, [better support for true nulls] and even "reversed" the process, so Updates and Inserts can take an object too instead of having to be coded out.
Ok, once again, save your work and compile (and yes… be prepared for some errors… again!).
Modifying the UI
Finally it’s time to modify the UI! We are back to working under the DesktopModules \ ToDoTaskList folder.
Edit the EditToDoTaskList.ascx
Let’s see if we can get rid of those compile errors. That will be easy to do, but again a little explanation will be in order so you can understand exactly what we are doing. We will start by getting rid of the errors, the theory behind our actions will follow afterwards.
Open the EditToDoTaskList.ascx file by double clicking it in the Solution explorer and switch to the Source view if necessary.
Delete the entire table construct and all of its content. Then delete the cmdUpdate and cmdDelete linkbuttons; leave the cmdCancel on the page.
Add the follow text before the cmdCancel “There is no separate editable content for the ToDo Task List”.
Your finished source should like something like this:
<%@ Control language="C#" Inherits=
"YourCompany.Modules.ToDoTaskList.EditToDoTaskList"
CodeFile="EditToDoTaskList.ascx.cs" AutoEventWireup="true"%>
<%@ Register TagPrefix="dnn" TagName=
"Label" Src="~/controls/LabelControl.ascx" %>
<%@ Register TagPrefix="dnn" TagName="TextEditor" Src=
"~/controls/TextEditor.ascx"%>
<%@ Register TagPrefix="dnn" TagName="Audit" Src=
"~/controls/ModuleAuditControl.ascx" %>
There is no separate editable content for the ToDo Task List.
<p>
<asp:linkbutton cssclass="CommandButton" id="cmdCancel"
OnClick="cmdCancel_Click" resourcekey="cmdCancel"
runat="server" borderstyle="none" text="Cancel"
causesvalidation="False"></asp:linkbutton>
</p>
<dnn:audit id="ctlAudit" runat="server" />
Open up the code behind file EditToDoTaskList.ascx.cs; again by clicking the plus symbol next to the EditToDoTaskList.ascx file and then double clicking on the .cs file.
In the Page_Load event, delete the entire contents of the Try block. Leave the cmdCancel_Click
event, but delete completely the cmdDelete_Click
and cmdUpdate_Click methods. Save your work and compile… and once again, we are back in a working state. To confirm this click Debug on the menu then click Start Without Debugging and make sure the web site comes up and our ToDo Task List is visible in the browser

Working once again (Finally!!)
So exactly what did we just delete and why? (You can skip this explanation if you want to get on with implementing the task list at Making the Task List Visible, the next major section).
DNN Module Security
DNN provides a built-in security system that allows for the definition of User Roles, Assignment of users to those roles, and Module Access permissions for those roles. A role can be denied access to a module, have just the ability to view the module content in read-only mode, or have the ability to edit the module and its contents.

This functionality is available to be used with your module without any coding on your part.
If a person is denied access when the page loads, the module won’t be loaded. If the person can view the module, but does not have edit permissions, the module will be displayed with its contents, but there will not be any way for the person to edit the module as is shown here:

If the person had Edit Module permissions, DNN will automatically provide the user with the links to Add / Edit content and modify the modules settings as shown here:

Notice the Add Content link and the Settings icon at the bottom right of the screen. When the user clicks that Add Content link, the Module is put into Edit mode and the control associated with the module that has its Key value set to "Edit", in our case the EditToDoTaskList User Control is displayed (This is the part where I finally explain to you that extra control we added in Module Definition all the way back at the beginning, remember?)

By adding the code to Add/Edit the contents of your module to the EditToDoTaskList.ascx user control, DNN will take care of security permissions for you. This seems like a good thing, and we will want people to be able to add and edit the content, so why did I have you make this control do nothing?
The problem is that when you give a person access to edit a module’s content using the built in DNN Security support, they also get the ability to edit the actual module. That means they could rename it, move it to a different pane, change its container, change the role permissions, or even delete the entire module all together! That would be bad in this case.
We want our users to be able to add tasks, and mark them as complete without giving them access to the module’s settings, so we are going to do all the editing in the ViewToDoTaskList.ascx control instead of the EditToDoTaskList.ascx. This way, as long as the user is able to view the task list, they will be able to interact with its contents.
Now I don't want you to think this is a "hole" in DNN architecture or it's security model (although I did have a developer who works with DNN a lot and should KNOW better try to tell me that my approach wasn't safe "70% of the time", but that using the built in system where someone who can edit content could also delete, move or rename the module "...is only un-secure 20% of the time", I guess that is like being a little bit pregnant). I actually think the DNN security model is very robust and advanced. I strongly urge you to study it and think carefully on how you can implement the best security for your problem domain. And in fact we wouldn't want an anonymous user to be able to edit our content, so we will be adding a little bit of security later on to restrict the ability to add / edit our task list items to registered users only.
Making the Task List Visible
It is time to modify the ViewToDoTaskList.ascx user control so we can work with our tasks (and about time too)!
Adding An ObjectDataSource
Open the ViewToDoTaskList.ascx user control and switch to Design view if necessary. In the Toolbox window, select the Data section and drag and drop an ObjectDataSource
control onto the surface of the designer

Now that we have added an ObjectDataSource
we need to tell it what business object it should use to retrieve / work with Data. If you remember from the DNN architecture diagram, our main source of data from the UI Layer is our Controller. We just need to tell the ObjectDataSource
to use it.
Switch to source view and inside the opening tag for the ObjectDataSource
, add a new attribute TypeName
and set its value to YourCompany.Modules.ToDoTaskList.ToDoTaskListController
Now switch back to Design view, right mouse click on the ObjectDataSource and click on Show Smart Tag (or you can click on the Common Tasks icon for the ObjectDataSource control ), and finally click on Configure Data Source…

On the first screen of the wizard, you can see that the value we just set in the source view is listed as the business object that we will bind to; Click Next.

In the next screen, we have four tabs; for each tab, we need to set the appropriate method from the Controller for the indicated function of the tab. Set them as follows.
Assigning Data Methods for SUID Tabs
Tab |
Method |
Select |
GetToDoTaskList(), returns List<ToDoTask> |
Update |
UpdateToDoTask(ToDoTask oToDoTask) |
Insert |
AddToDoTask(ToDoTask oToDoTask) |
Delete |
DeleteToDoTask(ToDoTask oToDoTask) |

Once you have set all the tabs, click Finish.
Add a GridView control and hook it up to the ObjectDataSource
In the Toolbox, select the Data section and drag a GridView
onto the surface of the designer

Right mouse click on the GridView control, click on Show Smart Tag, and set the Choose Data Source dropdown to ObjectDataSource1

With the GridView Tasks menu still visible, click on the ItemID
column header and then click Remove Column

Check the Enable Paging, Enable Editing, and Enable Deleting options in the GridView Tasks menu.

One last property to set on the ToDo Task list and we are done with it. Because we have removed the column that contains the ItemId
, if we left it like that, the row would not have access to that information which is needed for the Delete
and Update
functions. We need to make sure that the rows still have access to the ItemId
value. We accomplish this by setting the DataKeyNames
collection to include all columns that are not visible but are required for data operations.
Click on the GridView and in the Properties window find the DataKeyNames
property in the Data section and click on the ellipses button.

In the dialog that comes up, add the ItemId data field to the Selected list and click ok.

Save your work and compile. The task list could now be viewed and edited on your DNN web site, but we haven’t yet added a way to add new tasks; that’s next.
Adding new Tasks.
Drag a TextBox control from the Standard section of the Toolbox onto the designer.
Next drag a Button onto the designer and add it after the TextBox, name it AddTaskButton and set its text property to "Add Task".

Double click on the Add Task button so an Event Handler is added to the code behind. In the code behind, we will instantiate a new ToDoTask
object, set its Subject to the text in the TextBox, and tell the controller to add it to the list.
protected void AddTaskButton_Click(object sender , EventArgs e) {
ToDoTaskListController cntrlr = new ToDoTaskListController();
ToDoTask toDoTask = new ToDoTask();
toDoTask.Completed = false;
toDoTask.Subject = TextBox1.Text;
cntrlr.AddToDoTask(toDoTask);
TextBox1.Text = "";
this.GridView1.DataBind();
}
Finally we need to add some code to the Page_Load
event so that only registered users can edit our task list. In order to do that we will reference some functions in the DNN PortalModuleBase
class that our UserControl inherits from to get some information about the security permissions of the currently logged in user. If they are not registered we won't let them add or edit Tasks.
Open the code behind for the ViewToDoTaskList.ascx by clicking the plus symbol in the solution explorer and double clicking on the ViewToDoTaskList.ascx.cs.

Modify the Page_Load so it looks like the following.
protected void Page_Load(System.Object sender, System.EventArgs e) {
try {
if( !PortalSecurity.IsInRole("Registered Users") &&
!PortalSecurity.IsInRole(
"Administrators") ) {
this.AddTaskButton.Visible = false;
this.TextBox1.Visible = false;
this.GridView1.Columns[0].Visible = false;
}
}
catch (Exception exc)
{
Exceptions.ProcessModuleLoadException(this, exc);
}
}
PortalSecurity
is a Static class provided by DNN's core, using it's static members we can check to see what security role permissions the current user has. Based on those permissions we can then set up the visibility, level of functionality supported by our control.
That is it; our ToDo Task List is done; go ahead and run it and see it in action.

The ToDo Task List in action.
To recap, we added a module using the template from the DotNetNuke Team. We registered our module, and then created the database table and stored procedures to support our functionality. Next starting at the bottom of the N-Tiered architecture, we modified the data layer classes to support the Task List functionality and call the correct stored procedures. Those changes were percolated up to the business layer where we modified the Controller and modified the template supplied Business Object to represent a ToDo task. Finally we modified the View control to display / edit our ToDo task list and added it to a page.
Appendix
SqlDataProvider Public Methods
public override void AddToDoTask(bool Completed , string Subject) {
SqlHelper.ExecuteNonQuery( ConnectionString, "ToDoTaskInsert",
Completed,Subject);
}
public override void DeleteToDoTask(int ItemId) {
SqlHelper.ExecuteNonQuery(ConnectionString , "ToDoTaskDelete" ,
ItemId);
}
public override IDataReader GetToDoTask(int ItemId) {
return (IDataReader) SqlHelper.ExecuteReader( ConnectionString,
"ToDoTaskSelect",
ItemId);
}
public override IDataReader GetToDoTaskList() {
return (IDataReader)SqlHelper.ExecuteReader( ConnectionString,
"ToDoTaskListSelect");
}
public override void UpdateToDoTask(int ItemId, bool Completed,
string Subject) {
SqlHelper.ExecuteNonQuery( ConnectionString, "ToDoTaskUpdate", ItemId,
Completed, Subject);
}
ToDoTask SQL Creation Script
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[ToDoTask](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Completed] [bit] NOT NULL CONSTRAINT [DF_ToDoTask_Active] DEFAULT ((0)),
[Subject] [varchar](max) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
CONSTRAINT [DF_ToDoTask_Subject] DEFAULT (''),
CONSTRAINT [PK_ToDoTask] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
History
- 18 Jan 2007 - Updated download