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)
Else
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.
Solution
For a module’s settings I always use a separate class. This class encapsulates all the above into a single entity. Meaning:
- All string literals used to reference the variable are only used in this class
- All variables are initialized inside this class
- 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
2:
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
9:
10: #Region " Constructors "
11: Public Sub New(ByVal TabModuleId As Integer)
12:
13: _tabModuleId = TabModuleId
14: _allSettings = (New DotNetNuke.Entities.Modules.ModuleController).GetTabModuleSettings(_tabModuleId)
15: Globals.ReadValue(_allSettings, "RecentEntriesTemplate", RecentEntriesTemplate)
16: Globals.ReadValue(_allSettings, "RecentEntriesMax", RecentEntriesMax)
17:
18: End Sub
19:
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
30:
31: #Region " Public Members "
32: Public Overridable Sub UpdateSettings()
33:
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)
41:
42: End Sub
43: #End Region
44:
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
57:
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
67:
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
9:
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
18:
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
27:
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
36:
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
3:
4: #Region " Private Members "
5: Private _settings As Settings.Settings
6: #End Region
7:
8: #Region " Properties "
9: Public Shadows Property Settings() As Settings.Settings
10: Get
11:
12: If _settings Is Nothing Then
13: _settings = New Settings.Settings(ModuleId)
14: End If
15:
16: Return _settings
17:
18: End Get
19: Set(ByVal Value As Settings.Settings)
20: _settings = Value
21: End Set
22: End Property
23: #End Region
24:
25: End Class
Now my controls inherit from that and all have hard typed and named settings.
Conclusion
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.