VB.NET: My.Settings alternate for storing app settings
Introduction
Microsoft provides My.Settings Object which provides access to application settings and allows a developer to dynamically store and retrieve property settings and other information for your application. To create settings at design time is done using an interface under project settings as described in the following documentation.
There are fallacies when dealing with complex read and retrieval of setting like storing list or user defined data like storing information stored in a class instance. Several concepts are presented here which moves away from My.Settings using a section known as appSettings and sectionGroup which provide reading and retrieval of simple and complex data stored in an application’s configuration file.
Requires
A reference to System.Configuration for working with settings in an application configuration files.
Throughout the article this app.config file is used.
- Lines 4 to 7 point to lines 25 to 47 for storing multiple email addresses.
- Lines 13 to 23 store information to be used in the application. All values are stored as string while code presented here transforms strings to proper types
Sample configuration file
01.<?xml version="1.0" encoding="utf-8" ?>
02.<configuration>
03. <configSections>
04. <sectionGroup name="mailSettings">
05. <section name="smtp_Home" type="System.Net.Configuration.SmtpSection"/>
06. <section name="smtp_Work" type="System.Net.Configuration.SmtpSection"/>
07. </sectionGroup>
08. </configSections>
09.
10. <startup>
11. <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
12. </startup>
13. <appSettings>
14. <add key="MainWindowTitle" value = "Code sample for reading/setting app.config" />
15. <add key="IncomingFolder" value = "D:\UISides\oed_incoming" />
16. <add key="TestMode" value = "false" />
17. <add key="importMinutesToPause" value="2" />
18. <add key="LastCategoryIdentifier" value="-1" />
19. <add key="LastRan" value="10/31/2020 3:12:50 AM" />
20. <add key="DatabaseServer" value=".\SQLEXPRESS" />
21. <add key="Catalog" value="NorthWind2020" />
22. <add key="UserDetails" value="" />
23. </appSettings>
24.
25. <mailSettings>
26. <smtp_Home from="someone@gmail.com">
27. <network
28. host="smtp.gmail.com"
29. port="587"
30. enableSsl="true"
31. userName="MssGMail"
32. password=""
33. defaultCredentials="false" />
34. <specifiedPickupDirectory pickupDirectoryLocation="MailDrop"/>
35. </smtp_Home>
36.
37. <smtp_Work from="karenpayneoregon@gmail.com">
38. <network
39. host="smtp.gmail.com"
40. port="587"
41. enableSsl="true"
42. userName="oregon@gmail.com"
43. password=""
44. defaultCredentials="false" />
45. <specifiedPickupDirectory pickupDirectoryLocation="MailDrop"/>
46. </smtp_Work>
47. </mailSettings>
48.</configuration>
Application setting class
To properly interact with settings in a application file the following class provides read and write operations.
There are delegates/events which allow a developer to monitor changes along with runtime exceptions which a class or form may subscribe to as callbacks shown below. The reason for events is so developers need not wrap code which interacts with a configuration file need not be wrapped in a try/catch statement in calling code.
Namespace Classes
Public Class ApplicationSettings
Public Delegate Sub OnErrorSettingDelegate(args As ApplicationSettingError)
Public Delegate Sub OnErrorGetDelegate(key As String, ex As Exception)
Public Delegate Sub OnSettingChangedDelegate(key As String, value As String)
''' <summary>
''' Provides access to when an exception is thrown when a value can not be set
''' </summary>
Public Shared Event OnSettingsErrorEvent As OnErrorSettingDelegate
''' <summary>
''' provides access when a key in the configuration file is not found in the configuration file
''' </summary>
Public Shared Event OnGetKeyErrorEvent As OnErrorGetDelegate
''' <summary>
''' Provides access to a setting changed
''' </summary>
Public Shared Event OnSettingChangedEvent As OnSettingChangedDelegate
Basic read operation is done with the method below which resides in ApplicationSettings class. Note in the catch, Expections.wite(e), this is a method found here to write to a text log file.
''' <summary>
''' Get app setting as string from application file. If configKey is not located an exception is thrown without
''' anything to indicate this but will throw a runtime exception from the calling method.
''' </summary>
''' <param name="configKey">Key in app.config</param>
''' <returns>Key value or an empty string</returns>
Public Shared Function GetSettingAsString(configKey As String) As String
Dim value As String = Nothing
Try
value = ConfigurationManager.AppSettings(configKey)
If value Is Nothing Then
Throw New Exception($"Setting {configKey} not found")
End If
Catch e As Exception
RaiseEvent OnGetKeyErrorEvent(configKey, e)
Exceptions.Write(e)
End Try
Return value
End Function
To get the main window title on line 14 of app.config which is generic, pass in a property found under appSettings and a value is returned.
GetSettingAsString("IncomingFolder")
This works fine when there are only a few settings while like the configuration file above there are many settings. In these cases a wrapper method allows a developer to work with settings easily. Below is a wrapper for obtaining IncomingFolder value.
''' <summary>
''' Get incoming folder
''' </summary>
''' <returns></returns>
Public Shared Function GetIncomingFolder() As String
Return GetSettingAsString("IncomingFolder")
End Function
Usage
Me.Text = ApplicationSettings.GetIncomingFolder()
For long time VB.NET developers use to My.Settings a wrapper can be created under My.Settings as presented in the following class. Now the following is possible.
Me.Text = My.Settings.MainWindowTitle
Reading types other than strings
The following methods provide type conversion from string to specific types.
- GetSettingAsDateTime, pass in a setting to return a DateTime.
- GetSettingAsInteger, pass a setting to return an Integer.
There are many more types to convert from string to a specific type, by following one of the above methods other methods may be added e.g. GetSettingAsBoolean or perhaps a class instance or a setting with multiple values.
Suppose rather than reading one setting at a time a class is created to read all settings at once as shown below.
Namespace Classes
Public Class MyApplication
Public Property MainWindowTitle() As String
Public Property IncomingFolder() As String
Public Property ImportMinutesToPause() As Integer
Public Property TestMode() As Boolean
Public Property LastRan() As DateTime
Public Property DatabaseServer() As String
Public Property Catalog() As String
Public ReadOnly Property ConnectionString() As String
Get
Return $"Data Source= {DatabaseServer};Initial Catalog={Catalog};Integrated Security=True"
End Get
End Property
End Class
End Namespace
Back in ApplicationSettings class the following method reads property values to the proper types,
Public Shared Function Application() As MyApplication
Return New MyApplication() With {
.IncomingFolder = GetIncomingFolder(),
.MainWindowTitle = MainWindowTitle(),
.DatabaseServer = GetSettingAsString("DatabaseServer"),
.Catalog = GetSettingAsString("Catalog"),
.LastRan = GetSettingAsDateTime("LastRan")
}
End Function
This can be done dynamically also as per the following method. This method need not know property names.
''' <summary>
''' Populate <see cref="MyApplication"/> instance dynamically
''' </summary>
''' <returns></returns>
Public Shared Function CreateMyApplicationDynamically() As MyApplication
Dim propertyInfo = GetType(MyApplication).
GetProperties().
Where(Function(p) p.CanWrite).
Select(Function(p) New With {
.PropertyName = p.Name,
.Value = ConfigurationManager.AppSettings(p.Name)
}).
ToList()
Dim myApplication As New MyApplication()
For Each anonymous In propertyInfo
Dim propertyValue As Object = anonymous.Value
myApplication.SetPropertyValue(anonymous.PropertyName, propertyValue)
Next
Return myApplication
End Function
Mail section
In the application file shown above there is a mailSettings section which allows defining values for sending SMTP email messages. Note that passwords are plain text which is unwise, to handle this simply use a encrypt/decrypt methods so peeking eye's can not see passwords which would be done in the ApplicationSettings class with methods for encrypt and decryption in a helper class or code module.
For reading SMTP mail setting see MailConfiguration class into an instance the MailItem class. Now the following method reads in the (in this case) two settings for mail configurations.
Public Shared Function MailAddresses() As List(Of MailItem)
Dim emailList As New List(Of MailItem)
Dim config As Configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
Dim myCollect As ConfigurationSectionGroupCollection = config.SectionGroups
For Each configurationSectionGroup As ConfigurationSectionGroup In myCollect
For Each configurationSection As ConfigurationSection In configurationSectionGroup.Sections
Dim sectionName As String = configurationSection.SectionInformation.Name.ToString()
If sectionName.StartsWith("smtp") Then
Dim mc As MailConfiguration = New MailConfiguration($"mailSettings/{sectionName}")
Dim mailItem As New MailItem With {
.DisplayName = sectionName.Replace("smtp_", ""),
.ConfigurationName = sectionName, .MailConfiguration = mc
}
emailList.Add(mailItem)
End If
Next
Next
Return emailList
End Function
A property is used in the custom My.Settings class to return a List(Of MailItem).
''' <summary>
''' Get mail addresses for app.config
''' </summary>
''' <returns></returns>
Public ReadOnly Property MailAddresses() As List(Of MailItem)
Get
Return ApplicationSettings.MailAddresses()
End Get
End Property
Which can be used in a ComboBox or other control to display and allow a user to select for sending email messages.
MailItemsComboBox.DataSource = My.Settings.MailAddresses
Get a MailItem from the ComboBox.
''' <summary>
''' Get current selected email details from MailItemsComboBox
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Sub CurrentMailItemButton_Click(sender As Object, e As EventArgs) Handles CurrentMailItemButton.Click
Dim mailItem = CType(MailItemsComboBox.SelectedItem, MailItem)
MessageBox.Show($"From: [{mailItem.From}]{Environment.NewLine}User name: [{mailItem.UserName}]")
End Sub
Store/read a class instance
In the above examples all properties are stored in individual properties in the configuration file, an easy method to store a class inside of the application file is with JSON and Newtonsoft.Json NuGet package.
Example, store a person's first, last name and email address.
Namespace Classes
Public Class UserDetails
Public Property FirstName() As String
Public Property LastName() As String
Public Property EmailAddress() As String
End Class
Public Class Mocking
Public Shared Function CreateUser() As UserDetails
Return New UserDetails() With {.FirstName = "", .LastName = "", .EmailAddress = ""}
End Function
End Class
End Namespace
In ApplicationSettings class, read write opertions.
''' <summary>
''' Write UserDetails key to app.config
''' </summary>
''' <param name="userDetails"></param>
Public Shared Sub SerializeUserDetails(userDetails As UserDetails)
Dim details = JsonConvert.SerializeObject(userDetails)
SetValue("UserDetails", details)
End Sub
''' <summary>
''' Read UserDetails key from app.config
''' </summary>
''' <returns></returns>
Public Shared Function DeserializeUserDetails() As UserDetails
Dim json = GetSettingAsString("UserDetails")
Return JsonConvert.DeserializeObject(Of UserDetails)(json)
End Function
Assertion
To prevent runtime exceptions if key does not exists the following method accepts a key to see if the key exists. Use this method when for development and/or when a key may not exists e.g. a user edited a key name and renamed the key.
''' <summary>
''' Determine if a key exists
''' </summary>
Public Shared Function KeyExists(key As String) As Boolean
Dim result = ConfigurationManager.AppSettings.AllKeys.FirstOrDefault(Function(keyName) keyName = key)
Return result IsNot Nothing
End Function
Writing back to configuration file
To set a key value is a three step process, first set a key value, save the changes followed by refreshing the configuration file which is done in SetValue method. In the event of a runtime exception in the catch statement an event is raised to notifiy subscribers something went wrong and writes the exception to the application error log.
JSON configuration
Another option is to forego using app.config, instead use a json file for application settings. The following class represents the same properties mentioned above except for mail configuration items to keep things simple.
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Namespace Classes.Json
<Serializable()>
Public Class MyApplicationJson
Implements INotifyPropertyChanged
Private _mainWindowTitle As String
Private _incomingFolder As String
Private _importMinutesToPause As Integer
Private _testMode As Boolean
Private _lastRan As Date
Private _databaseServer As String
Private _catalog As String
Private _lastCategoryIdentifier As Integer
Public Property MainWindowTitle() As String
Get
Return _mainWindowTitle
End Get
Set
_mainWindowTitle = Value
OnPropertyChanged()
End Set
End Property
Public Property IncomingFolder() As String
Get
Return _incomingFolder
End Get
Set
_incomingFolder = Value
OnPropertyChanged()
End Set
End Property
Public Property ImportMinutesToPause() As Integer
Get
Return _importMinutesToPause
End Get
Set
_importMinutesToPause = Value
OnPropertyChanged()
End Set
End Property
Public Property TestMode() As Boolean
Get
Return _testMode
End Get
Set
_testMode = Value
OnPropertyChanged()
End Set
End Property
Public Property LastRan() As DateTime
Get
Return _lastRan
End Get
Set
_lastRan = Value
OnPropertyChanged()
End Set
End Property
Public Property LastCategoryIdentifier() As Integer
Get
Return _lastCategoryIdentifier
End Get
Set
_lastCategoryIdentifier = Value
OnPropertyChanged()
End Set
End Property
Public Property DatabaseServer() As String
Get
Return _databaseServer
End Get
Set
_databaseServer = Value
OnPropertyChanged()
End Set
End Property
Public Property Catalog() As String
Get
Return _catalog
End Get
Set
_catalog = Value
OnPropertyChanged()
End Set
End Property
Public ReadOnly Property ConnectionString() As String
Get
Return $"Data Source= {DatabaseServer};Initial Catalog={Catalog};Integrated Security=True"
End Get
End Property
Public Overrides Function ToString() As String
Return $"Last ran {LastRan}"
End Function
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(<CallerMemberName> Optional memberName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(memberName))
End Sub
''' <summary>
''' Prevent serializing ConnectionString property
''' </summary>
''' <returns></returns>
Public Function ShouldSerializeConnectionString() As Boolean
Return False
End Function
End Class
End Namespace
File operations are performed in the following class. Note MockedUp method used for this article to show how to initialize with values rather then null values.
Imports System.IO
Imports Newtonsoft.Json
Namespace Classes.Json
Public Class JsonFileOperations
Private Property mFileName() As String
Public Sub New()
End Sub
Public Sub New(fileName As String)
mFileName = fileName
End Sub
Public Function LoadApplicationData(fileName As String) As MyApplicationJson
Using streamReader = New StreamReader(fileName)
Dim json = streamReader.ReadToEnd()
Return JsonConvert.DeserializeObject(Of MyApplicationJson)(json)
End Using
End Function
Public Sub SaveApplicationData(searchItems As MyApplicationJson, fileName As String)
Using streamWriter = File.CreateText(fileName)
Dim serializer = New JsonSerializer With {.Formatting = Formatting.Indented}
serializer.Serialize(streamWriter, searchItems)
End Using
End Sub
Public Shared Sub MockUp()
If Not File.Exists(My.Settings.JsonFileName) Then
Dim appSetting = New MyApplicationJson With {
.MainWindowTitle = "Code sample for reading/setting app.config",
.IncomingFolder = "D:\UISides\oed_incoming",
.TestMode = False,
.ImportMinutesToPause = 2,
.LastCategoryIdentifier = 3,
.LastRan = #10/31/2020 3:12:50 AM#,
.DatabaseServer = ".\SQLEXPRESS", .Catalog = "NorthWind2020"}
My.Settings.SaveJsonSettings(appSetting)
End If
End Sub
End Class
End Namespace
Summary
Alternatives to using My.Setting for persisting and changing values in an application configuration file and using JSON for persisting and changing values in a .JSON file have been presented for fine tuning application settings. Although not complete e.g. there are not wrappers for all types there is enough here to allow a developer to add more wrappers in the ApplicationSettings class and the JSON file classes.
These alternates may not suit everyone but consider moving past conventional windows forms projects where web and Core projects use JSON for configuration settings which gives the reader an opportunity to move forward.
See also
- VB.NET My.Settings deep dive
- Database selective connection strings (VB.NET)
- DataGridView column configuration VB.NET
Source code
Clone the following GitHub repository which current contains 59 code sample projects. These 59 projects are all various code samples for future articles. To look at code specific to this article review this project online in the same repository. Also in the same repository check out MyApplicationNamespace project which demostrates other operations regarding settings using a Singleton class.