Learn More





DNN Community Blog

The Community Blog is a personal opinion of community members and by no means the official standpoint of DNN Corp or DNN Platform. This is a place to express personal thoughts about DNNPlatform, the community and its ecosystem. Do you have useful information that you would like to share with the DNN Community in a featured article or blog? If so, please contact .

The use of the Community Blog is covered by our Community Blog Guidelines - please read before commenting or posting.

Abstracting your module’s settings into their own class

This is an old hobby horse of mine and when working on someone else’s module it’s one of the first things I do. As I’ve just reworked the Blog module source code I thought I’d blog it so others may learn from it and use it to their own benefit.

What is the issue?

DNN offers a property bag for a module to store its settings. The Settings are exposed as a hashtable through the PortalModuleBase class which is the default underlying layer for your module’s controls. This hashtable is retrieved from SQL through GetModuleSettings which gets all serialized (i.e. written as strings) settings from the ModuleSettings table in SQL. You save settings by writing them to this table using UpdateModuleSetting. A similar pattern is used for TabModuleSettings. This typically results in the following code blocks:

Reading a value:

Dim max as Integer = CType(Settings("RecentEntriesMax"), Integer)

First time loading a value:

If TabModuleSettings("RecentEntriesMax") IsNot Nothing Then
 txtMaxCount.Text = CType(TabModuleSettings("RecentEntriesMax"), String)
 txtMaxCount.Text = "10"
End If

Saving a value:

Dim objModules As New Entities.Modules.ModuleController
With objModules
 If Utility.IsInteger(txtMaxCount.Text) Then
  .UpdateTabModuleSetting(TabModuleId, "RecentEntriesMax", txtMaxCount.Text)
 End If
End With

The problems I’d like to address here are:

The use of a text literal throughout your code to reference a value. This is bad practice as it created an accident waiting to happen. In the example “RecentEntriesMax” is used across various controls. This means that not only do you need to have a mental note of all the strings you’ve been using but also that any error in the string (e.g. “RecentEntryMax”) would not result in an error thrown by the compiler.

The variable is initialized across your code. We need to cater for the case where RecentEntriesMax has not yet been added to the table. When a module is first instantiated the settings for it are empty. In the example the default value is 10. The first line in the example where the value is read will throw an error if there is no value for “RecentEntriesMax”. Again, you won’t notice the error until runtime. Thus this is bad practice.

The variables are not typed. In the example RecentEntriesMax is an Integer. But a value from a hashtable is an object. So conversion happens all over your code. Again this can lead to errors at runtime and should be avoided.


For a module’s settings I always use a separate class. This class encapsulates all the above into a single entity. Meaning:

  1. All string literals used to reference the variable are only used in this class
  2. All variables are initialized inside this class
  3. All variables only leave the class as typed variables

Finally I add a static method to create the class and have it cached. The above example was refactored into the following code:

   1:   Public Class RecentEntriesSettings
   3:  #Region " Private Members "
   4:    Private _allSettings As Hashtable
   5:    Private _tabModuleId As Integer = -1
   6:    Private _RecentEntriesTemplate As String = ""
   7:    Private _RecentEntriesMax As Integer = 10
   8:  #End Region
  10:  #Region " Constructors "
  11:    Public Sub New(ByVal TabModuleId As Integer)
  13:     _tabModuleId = TabModuleId
  14:     _allSettings = (New DotNetNuke.Entities.Modules.ModuleController).GetTabModuleSettings(_tabModuleId)
  15:     Globals.ReadValue(_allSettings, "RecentEntriesTemplate", RecentEntriesTemplate)
  16:     Globals.ReadValue(_allSettings, "RecentEntriesMax", RecentEntriesMax)
  18:    End Sub
  20:    Public Shared Function GetRecentEntriesSettings(ByVal TabModuleId As Integer) As RecentEntriesSettings
  21:     Dim CacheKey As String = "RecentEntriesSettings" & TabModuleId.ToString
  22:     Dim bs As RecentEntriesSettings = CType(DotNetNuke.Common.Utilities.DataCache.GetCache(CacheKey), RecentEntriesSettings)
  23:     If bs Is Nothing Then
  24:      bs = New RecentEntriesSettings(TabModuleId)
  25:      DotNetNuke.Common.Utilities.DataCache.SetCache(CacheKey, bs)
  26:     End If
  27:     Return bs
  28:    End Function
  29:  #End Region
  31:  #Region " Public Members "
  32:    Public Overridable Sub UpdateSettings()
  34:     Dim objModules As New DotNetNuke.Entities.Modules.ModuleController
  35:     With objModules
  36:      .UpdateTabModuleSetting(_tabModuleId, "RecentEntriesTemplate", RecentEntriesTemplate)
  37:      .UpdateTabModuleSetting(_tabModuleId, "RecentEntriesMax", RecentEntriesMax.ToString)
  38:     End With
  39:     Dim CacheKey As String = "RecentEntriesSettings" & _tabModuleId.ToString
  40:     DotNetNuke.Common.Utilities.DataCache.RemoveCache(CacheKey)
  42:    End Sub
  43:  #End Region
  45:  #Region " Properties "
  46:    Public Property RecentEntriesTemplate() As String
  47:     Get
  48:      If _RecentEntriesTemplate = "" Then
  49:       _RecentEntriesTemplate = Localization.GetString("DefaultRecentEntriesTemplate", BlogModuleBase.BLOG_TEMPLATES_RESOURCE)
  50:      End If
  51:      Return _RecentEntriesTemplate
  52:     End Get
  53:     Set(ByVal value As String)
  54:      _RecentEntriesTemplate = value
  55:     End Set
  56:    End Property
  58:    Public Property RecentEntriesMax() As Integer
  59:     Get
  60:      Return _RecentEntriesMax
  61:     End Get
  62:     Set(ByVal value As Integer)
  63:      _RecentEntriesMax = value
  64:     End Set
  65:    End Property
  66:  #End Region
  68:   End Class

In the example I’d like you to follow the RecentEntriesMax variable. Note how it’s initialized on line 7, retrieved on line 16, and written on line 37. Outside this class no one needs to use the string literal any longer and all code can assume the variable has been initialized. What is still missing from the above are the reading methods. The reader needs to handle the case where the value is not yet in the hashtable. Using overloading we can handle all variable types with a single method signature. So these look like this:

   1:   Public Shared Sub ReadValue(ByRef ValueTable As Hashtable, ByVal ValueName As String, ByRef Variable As Integer)
   2:    If Not ValueTable.Item(ValueName) Is Nothing Then
   3:     Try
   4:      Variable = CType(ValueTable.Item(ValueName), Integer)
   5:     Catch ex As Exception
   6:     End Try
   7:    End If
   8:   End Sub
  10:   Public Shared Sub ReadValue(ByRef ValueTable As Hashtable, ByVal ValueName As String, ByRef Variable As Long)
  11:    If Not ValueTable.Item(ValueName) Is Nothing Then
  12:     Try
  13:      Variable = CType(ValueTable.Item(ValueName), Long)
  14:     Catch ex As Exception
  15:     End Try
  16:    End If
  17:   End Sub
  19:   Public Shared Sub ReadValue(ByRef ValueTable As Hashtable, ByVal ValueName As String, ByRef Variable As String)
  20:    If Not ValueTable.Item(ValueName) Is Nothing Then
  21:     Try
  22:      Variable = CType(ValueTable.Item(ValueName), String)
  23:     Catch ex As Exception
  24:     End Try
  25:    End If
  26:   End Sub
  28:   Public Shared Sub ReadValue(ByRef ValueTable As Hashtable, ByVal ValueName As String, ByRef Variable As Boolean)
  29:    If Not ValueTable.Item(ValueName) Is Nothing Then
  30:     Try
  31:      Variable = CType(ValueTable.Item(ValueName), Boolean)
  32:     Catch ex As Exception
  33:     End Try
  34:    End If
  35:   End Sub
  37:   Public Shared Sub ReadValue(ByRef ValueTable As Hashtable, ByVal ValueName As String, ByRef Variable As Date)
  38:    If Not ValueTable.Item(ValueName) Is Nothing Then
  39:     Try
  40:      Variable = CType(ValueTable.Item(ValueName), Date)
  41:     Catch ex As Exception
  42:     End Try
  43:    End If
  44:   End Sub

Etc etc. If not already in the DNN core API (couldn’t find them when I last looked for them), I’ll propose these methods to be added there.

Final touches

To put the final touches to this I shadow the Settings property of the PortalModuleBase in my own PortalModuleBase. This would look something like this (this is from a different example using ModuleSettings, not TabModuleSettings):

   1:  Public MustInherit Class PortalModuleBase
   2:   Inherits DotNetNuke.Entities.Modules.PortalModuleBase
   4:  #Region " Private Members "
   5:   Private _settings As Settings.Settings
   6:  #End Region
   8:  #Region " Properties "
   9:   Public Shadows Property Settings() As Settings.Settings
  10:    Get
  12:     If _settings Is Nothing Then
  13:      _settings = New Settings.Settings(ModuleId)
  14:     End If
  16:     Return _settings
  18:    End Get
  19:    Set(ByVal Value As Settings.Settings)
  20:     _settings = Value
  21:    End Set
  22:   End Property
  23:  #End Region
  25:  End Class

Now my controls inherit from that and all have hard typed and named settings.


Using the above pattern you solidify your code concerning the module’s settings. Adding a new variable is quite simple (just add a property and add relevant bits of code in the reading and writing methods). You have isolated all settings stuff into a single file which should be easy to maintain.


There are currently no comments, be the first to post one.

Comment Form

Only registered users may post comments.


2sic Daniel Mettler (125)
Aderson Oliveira (15)
Alec Whittington (11)
Alex Shirley (10)
Andrew Nurse (30)
Anthony Glenwright (5)
Antonio Chagoury (28)
Ash Prasad (22)
Ben Schmidt (1)
Benjamin Hermann (25)
Benoit Sarton (9)
Beth Firebaugh (12)
Bill Walker (36)
Bob Kruger (5)
Brian Dukes (2)
Brice Snow (1)
Bruce Chapman (20)
Bryan Andrews (1)
cathal connolly (55)
Charles Nurse (163)
Chris Hammond (203)
Chris Paterra (55)
Clinton Patterson (28)
Cuong Dang (21)
Daniel Bartholomew (2)
Dave Buckner (2)
David Poindexter (3)
David Rodriguez (2)
Doug Howell (11)
Erik van Ballegoij (30)
Ernst Peter Tamminga (74)
Geoff Barlow (6)
Gifford Watkins (3)
Gilles Le Pigocher (3)
Ian Robinson (7)
Israel Martinez (17)
Jan Blomquist (2)
Jan Jonas (3)
Jaspreet Bhatia (1)
Jenni Merrifield (6)
Joe Brinkman (270)
John Mitchell (1)
Jon Henning (14)
Jonathan Sheely (4)
Jordan Coopersmith (1)
Joseph Craig (2)
Kan Ma (1)
Keivan Beigi (3)
Ken Grierson (10)
Kevin Schreiner (6)
Leigh Pointer (31)
Lorraine Young (60)
Malik Khan (1)
Matthias Schlomann (15)
Mauricio Márquez (5)
Michael Doxsey (7)
Michael Tobisch (3)
Michael Washington (202)
Mike Horton (19)
Mitchel Sellers (28)
Nathan Rover (3)
Navin V Nagiah (14)
Néstor Sánchez (31)
Nik Kalyani (14)
Peter Donker (52)
Philip Beadle (135)
Philipp Becker (4)
Richard Dumas (22)
Robert J Collins (5)
Roger Selwyn (8)
Ruben Lopez (1)
Ryan Martinez (1)
Salar Golestanian (4)
Sanjay Mehrotra (9)
Scott McCulloch (1)
Scott S (11)
Scott Wilkinson (3)
Scott Willhite (97)
Sebastian Leupold (80)
Shaun Walker (237)
Shawn Mehaffie (17)
Stefan Cullmann (12)
Stefan Kamphuis (12)
Steve Fabian (31)
Timo Breumelhof (24)
Tony Henrich (3)
Torsten Weggen (2)
Vicenç Masanas (27)
Vincent Nguyen (3)
Vitaly Kozadayev (6)
Will Morgenweck (37)
Will Strohl (163)
William Severance (5)
Try Evoq
For Free
Start Free Trial
a Demo
See Evoq Live
Need More Information?