Share via


Configuration files for Windows Forms (VB.NET)

Introduction

Developers building conventional .NET desktop solutions typically store information needed while running an application using My.Settings, which provides read/write capabilities. Here, learn how to use a Json file to store read-only information while discarding write back to the Json file. If there is a need to modify the Json file consider using Microsoft’s System.Text.Json or Newton.Json libraries to save modifications.

Note

Several of the projects in the GitHub repository are .NET 5 along with several projects using Entity Framework Core. When building the projects and one or more fail because the developer machine does not have .NET 5 and/or Entity Framework Core consider unloading those projects.

Working with My.Setting

Creating and working with settings in Visual Studio, open project properties to the Settings tab. From here see Microsoft documentation.


For the above, Visual Studio adds these properties to app.config which after building a project names the file project name.exe.config.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings"
                      type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="AppSettingsMySettings.My.MySettings"
                     type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                     allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
    </startup>
    <userSettings>
        <AppSettingsMySettings.My.MySettings>
            <setting name="ConnectionString" serializeAs="String">
                <value>Data Source=DevServer;Initial Catalog=NorthWindAzure;Integrated Security=True</value>
            </setting>
            <setting name="WindowsAuthentication" serializeAs="String">
                <value>True</value>
            </setting>
        </AppSettingsMySettings.My.MySettings>
    </userSettings>
</configuration>

Access these settings as follows

Rolling your own loader

As developer become seasoned, they learn rolling their own method to store and read setting is more reliable and provides more control while if the developer does not code in specific functionality it's not there.

Using My.Setting properties from the first example.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <section name="SettingsConfig" type="BasicAppConfig.AppSettngsLoader, BasicAppConfig" />
    </configSections>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
    </startup>
    <SettingsConfig>
        <ConnectionString>Data Source=DevServer;Initial Catalog=NorthWindAzure;Integrated Security=True</ConnectionString>
        <WindowsAuthentication>true</WindowsAuthentication>
    </SettingsConfig>
</configuration>

AppSettingsLoader is a class in a sample project (included in the source code) which requires a reference to System.Configuration.

01.Imports System.Configuration
02.Imports System.Xml
03.Imports System.Xml.Serialization
04. 
05.Public Class  AppSettngsLoader
06.    Implements IConfigurationSectionHandler
07. 
08.    Public Function  Create(parent As  Object, configContext As Object, section As  XmlNode) As  Object _
09.        Implements IConfigurationSectionHandler.Create
10. 
11.        If section Is Nothing  Then
12.            Throw New  ArgumentNullException($"XMLNode passed in is null.")
13.        End If
14. 
15.        Dim type = AppDomain.CurrentDomain.GetAssemblies().
16.                SelectMany(Function(assembly) assembly.GetTypes()).
17.                FirstOrDefault(Function(itemType) itemType.Name = section.Name)
18. 
19.        If type Is Nothing  Then
20.            Throw New  ArgumentException($"Type with name {section.Name} couldn't be found.")
21.        End If
22. 
23.        Dim ser As New  XmlSerializer(type, New XmlRootAttribute(section.Name))
24. 
25.        Using reader As  XmlReader = New  XmlNodeReader(section)
26.            Return ser.Deserialize(reader)
27.        End Using
28. 
29.    End Function
30. 
31.End Class

BasicAppConfig is the namespace for the current project which means if the project namespace changes this needs to change too.

A concrete class for storing the two properties in the configuration file.

1.Public Class  SettingsConfig
2.    Public Property  ConnectionString() As String
3.    Public Property  WindowsAuthentication() As Boolean
4. 
5.    Public Overrides  Function ToString() As String
6.        Return $"{ConnectionString}, {WindowsAuthentication}"
7.    End Function
8.End Class

To get the properties.

Dim config = DirectCast(ConfigurationManager.GetSection("SettingsConfig"), SettingsConfig)

Then for instance, get the ConnectionString.

config.ConnectionString

Using appSettings in app.config

Another method is using appSettings section in app.config.

<appSettings>
    <add key="ConnectingString" value="Data Source=.\SQLEXPRESS;Initial Catalog=NorthWind2020;Integrated Security=True" />
</appSettings>

Example usage in a DbContext for a Entity Framework Core class.

  • Line 10 variable is for the connection string from app.config
  • Line 25 uses ConfigurationHelper class to read the connection string
  • Line 29 assigns the connection string to the DbContextOptionsBuilder which provides Entity Framework what is needed to work with data.
01.Imports System.Configuration
02.Imports Contexts.Configurations
03.Imports Microsoft.EntityFrameworkCore
04.Imports Models
05. 
06.Namespace Contexts
07.    Partial Public  Class NorthWindContext
08.        Inherits DbContext
09. 
10.        Private _connectionString As String
11. 
12.        Public Sub  New()
13. 
14.        End Sub
15. 
16.        Public Sub  New(options As DbContextOptions(Of NorthWindContext))
17.            MyBase.New(options)
18.        End Sub
19. 
20.        Public Overridable  Property ContactType() As DbSet(Of ContactType)
21.        Public Overridable  Property Contacts() As DbSet(Of Contacts)
22. 
23.        Protected Overrides  Sub OnConfiguring(optionsBuilder As DbContextOptionsBuilder)
24.            If Not  optionsBuilder.IsConfigured Then
25.                Dim configurationHelper = New ConfigurationHelper
26.                _connectionString = configurationHelper.ConnectionString
27. 
28.                If Not  optionsBuilder.IsConfigured Then
29.                    optionsBuilder.UseSqlServer(_connectionString)
30.                End If
31. 
32.            End If
33.        End Sub
34. 
35.        Protected Overrides  Sub OnModelCreating(modelBuilder As ModelBuilder)
36. 
37.            modelBuilder.ApplyConfiguration(New ContactTypeConfiguration())
38.            modelBuilder.ApplyConfiguration(New ContactsConfiguration())
39. 
40.            OnModelCreatingPartial(modelBuilder)
41. 
42.        End Sub
43. 
44.        Partial Private  Sub OnModelCreatingPartial(modelBuilder  As  ModelBuilder)
45.        End Sub
46.    End Class
47.End Namespace

Entity Framework Core self-contained connection

General practice is to use appsettings.json

{
  "database": {
    "DatabaseServer": ".\\SQLEXPRESS",
    "Catalog": "NorthWind2020",
    "IntegratedSecurity": "true",
    "UsingLogging": "true"
  }
}

Then to read the connection string from appsettings.json in the DbContext.

Create a private method

Private Shared  Function BuildConnection() As String
 
    Dim configuration = (New ConfigurationBuilder()).AddJsonFile("appsettings.json", True, True).Build()
 
    Dim sections = configuration.GetSection("database").GetChildren().ToList()
 
    Return $"Data Source={sections(1).Value};Initial Catalog={sections(0).Value};Integrated Security={sections(2).Value}"
 
End Function

Which is called in OnConfiguring method.

Protected Overrides Sub OnConfiguring(optionsBuilder As DbContextOptionsBuilder)
 
    If Not optionsBuilder.IsConfigured Then
        NormalConfiguration(optionsBuilder)
    End If
 
End Sub

Going a step farther consider creating methods which BuildConnection is used along with setting up for logging and or events and interceptors as per below.

Private Sub  NormalConfiguration(optionsBuilder As DbContextOptionsBuilder)
 
    optionsBuilder.UseSqlServer(BuildConnection())
 
End Sub
 
Private Sub  NormalConfigurationEnableDetailedErrors(optionsBuilder As DbContextOptionsBuilder)
 
    optionsBuilder.
        UseSqlServer(BuildConnection()).
        LogTo(AddressOf Console.WriteLine).EnableDetailedErrors()
 
End Sub
 
Private Sub  WithSaveChangesInterceptor(optionsBuilder As DbContextOptionsBuilder)
 
    optionsBuilder.
        AddInterceptors(New SavedChangesInterceptor).
        UseSqlServer(BuildConnection())
 
End Sub
 
Private Sub  WithCommandInterceptor(optionsBuilder As DbContextOptionsBuilder)
 
    optionsBuilder.
        AddInterceptors(New CommandInterceptor).
        UseSqlServer(BuildConnection())
 
End Sub
 
Private Shared  Sub LogQueryInfoToDebugOutputWindow(ByVal optionsBuilder As DbContextOptionsBuilder)
 
    optionsBuilder.
        UseSqlServer(BuildConnection()).
        EnableSensitiveDataLogging().LogTo(Sub(message) Debug.WriteLine(message))
 
End Sub

Then for multiple environments expand appsettings as per below.  Environment could be an  Enum where each member is numbered for dev, staging, prod environments instead of a Boolean.

{
  "ConnectionStrings": {
    "DevelopmentConnection": "Server=.\\SQLEXPRESS;Database=NorthWind2020;Integrated Security=true",
    "ProductionConnection": "Server=ProdServerDoesNotExists;Database=NorthWind2020Prod;Integrated Security=true"
  },
  "Environment": {
    "Production": false
  }
}

Custom configurations using appsettings.json

The appsettings.json file is a convenient way to store and retrieve your application’s configuration.  For VB coders working with desktop solutions, there are no code samples other than ASP.NET Core hence this section will provide the core code to use appsettings.json (and note a different name can be used while sticking with appsettings.json is the standard).

NuGet packages

In Visual Studio, double click on a .NET Core project and and the following, save/close the file.

<ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
</ItemGroup>

Version numbers may change and if so under manage NuGet packages an alert will appear if there are newer versions which allows updating to a current version.

Next, add a appsetings.json file and set copy to output folder to copy if newer under properties for the file.

{
 
  "Environment": {
    "ConnectionString": "Data Source=DevServer;Initial Catalog=NorthWindAzure;Integrated Security=True",
    "MailTo": "karen.payne@someDomain.com;fred.smith@comcast.net;billAdams@gmail.com",
    "UseGeoLocation": true
  }
}

Add the following class

  • The constructor makes a call to Setup which in turn invokes GetConfiguration for telling .NET where to find the configuration file
  • Lines 46,47,48 retrieve setting and cast as needed to the proper type defined by the read-only properties in ApplicationConfiguration.
01.Imports Microsoft.Extensions.Configuration
02. 
03.Public NotInheritable  Class ApplicationConfiguration
04.    Private Shared  ReadOnly Lazy As New  _
05.        Lazy(Of ApplicationConfiguration)(Function() New  ApplicationConfiguration())
06. 
07.    ''' <summary>
08.    ''' Access point to methods and properties
09.    ''' </summary>
10.    Public Shared  ReadOnly Property  Instance() As  ApplicationConfiguration
11.        Get
12.            Return Lazy.Value
13.        End Get
14.    End Property
15. 
16.    Private Property  Settings() As  IConfiguration
17. 
18.    Public Property  MailToAddressList() As List(Of String)
19. 
20.    Private Sub  New()
21.        SetUp()
22.    End Sub
23.    ''' <summary>
24.    ''' Initialize configuration to read json file
25.    ''' </summary>
26.    ''' <returns></returns>
27.    Private Function  GetConfiguration() As IConfiguration
28. 
29.        Dim builder = (New ConfigurationBuilder()).
30.                SetBasePath(AppContext.BaseDirectory).
31.                AddJsonFile("appsettings.json",
32.                            [optional]:=True,
33.                            reloadOnChange:=True)
34. 
35.        Return builder.Build()
36. 
37.    End Function
38.    ''' <summary>
39.    ''' Get values from json file, assign to properties
40.    ''' </summary>
41.    Private Sub  SetUp()
42. 
43.        MailToAddressList = New  List(Of String)
44.        Settings = GetConfiguration()
45. 
46.        _connectionString = Settings("Environment:ConnectionString")
47.        _mailAddress = Settings("Environment:MailTo").Split(";"c).ToList()
48.        _useGeoLocation = CType(Settings("Environment:UseGeoLocation"), Boolean)
49. 
50.    End Sub
51. 
52.    Private Shared  _mailAddress As  List(Of String)
53.    ''' <summary>
54.    ''' Mail addresses for sending error report
55.    ''' </summary>
56.    ''' <returns></returns>
57.    Public ReadOnly  Property MailAddresses() As List(Of String)
58.        Get
59.            Return _mailAddress
60.        End Get
61.    End Property
62.    Private Shared  _connectionString As  String
63.    ''' <summary>
64.    ''' Database connection string
65.    ''' </summary>
66.    ''' <returns></returns>
67.    Public ReadOnly  Property ConnectionString() As String
68.        Get
69.            Return _connectionString
70.        End Get
71.    End Property
72. 
73.    Private Shared  _useGeoLocation As  Boolean
74.    ''' <summary>
75.    ''' Get current location
76.    ''' </summary>
77.    ''' <returns>True collect, False do not collect geo location</returns>
78.    Public ReadOnly  Property UseGeoLocation() As Boolean
79.        Get
80.            Return _useGeoLocation
81.        End Get
82.    End Property
83.End Class

Reading the ConnectionString

ApplicationConfiguration.Instance.ConnectionString

Read mail addresses

ApplicationConfiguration.Instance.MailAddresses

Read UseGeoLocation

Dim geoLocation As Boolean  = ApplicationConfiguration.Instance.UseGeoLocation

In the above samples, all code is within the same project. If there will be many projects that used the same settings consider using a class project to read settings as presented in the following class project.

Complex settings

With reboust applications many times there will be a need to have multiple environments e.g. development, test and production. The following appsettings file is one fictitious example.

{
 
  "Environment": {
    "Name": "Development"
  },
  "GeneralSettings": [
    {
      "Environment": "Development",
      "ReloadApplicationOnEveryRequest": true,
      "Trace": false,
      "Reload": "reload",
      "Password": true,
      "ConnectionString": "Data Source=DevServer;Initial Catalog=NorthWindAzure;Integrated Security=True",
      "DiConfiguration": {
        "Dsn": "ABC",
        "Globals": "globals",
        "Globals2": "globals2",
        "MailTo": "karen.payne@someDomain.com;fred.smith@comcast.net;billAdams@gmail.com",
        "ExitLink": "/ocs4/",
        "OcsLink": null,
        "MfLink": "",
        "MfUser": null,
        "MfPass": "",
        "UseGeoLocation": true,
        "ResetPinLocation": "/pinchange/begin/",
        "BaseServerAddress": "xxx4",
        "UirTakeTest": false,
        "QryCacheShort": "00:00:10",
        "QryCacheLong": "00:00:05"
      }
    },
    {
      "Environment": "Test",
      "ReloadApplicationOnEveryRequest": false,
      "Trace": false,
      "Reload": "reload",
      "Password": true,
      "ConnectionString": "Data Source=TestServer;Initial Catalog=NorthWindAzure;Integrated Security=True",
      "DiConfiguration": {
        "Dsn": "DEF",
        "Globals": "globals",
        "Globals2": "globals2",
        "MailTo": "karen.payne@someDomain.com;fred.smith@comcast.net;billAdams@gmail.com",
        "ExitLink": "/ocs4/",
        "OcsLink": null,
        "MfLink": "",
        "MfUser": null,
        "MfPass": "",
        "UseGeoLocation": true,
        "ResetPinLocation": "/pinchange/begin/",
        "BaseServerAddress": "xxx4",
        "UirTakeTest": false,
        "QryCacheShort": "00:10:00",
        "QryCacheLong": "00:01:00"
      }
    },
    {
      "Environment": "Production",
      "ReloadApplicationOnEveryRequest": false,
      "Trace": false,
      "Reload": "reload",
      "Password": true,
      "ConnectionString": "Data Source=ProductionServer;Initial Catalog=NorthWindAzure;Integrated Security=True",
      "DiConfiguration": {
        "Dsn": "GHI",
        "Globals": "globals",
        "Globals2": "globals2",
        "MailTo": "karen.payne@someDomain.com;fred.smith@comcast.net;billAdams@gmail.com",
        "ExitLink": "/ocs4/",
        "OcsLink": null,
        "MfLink": "",
        "MfUser": null,
        "MfPass": "",
        "UseGeoLocation": true,
        "ResetPinLocation": "/pinchange/begin/",
        "BaseServerAddress": "xxx4",
        "UirTakeTest": false,
        "QryCacheShort": "06:00:00",
        "QryCacheLong": "00:30:00",
        "ConnectionString": "Prod connection"
      }
    }
  ]
}

To create this, see the following project and read the readme.md file for instructions. Now create a new .NET Console or Windows Form project, add a reference to this project which contains classes and methods to read settings. This is followed by adding appsettings.json, add the Json above, set copy to output folder to copy if newer under properties for the file.

The following shows reading settings, note that there are custom console method helpers included in ConsoleHelper project.

Imports Classes
Imports ConsoleHelpers
 
Module Program
    Sub Main(args As String())
 
        WriteHeader("Configuration code sample")
 
        Console.WriteLine($"Environment (Singleton): {ApplicationSettings.Instance.Environment}")
 
        Console.WriteLine($"            Environment: {Helper.Environment}")
        Console.WriteLine($"                    Dsn: {ApplicationSettings.Instance.Dsn}")
        Console.WriteLine($"      Connection string: {ApplicationSettings.Instance.ConnectionString}")
 
        EmptyLine()
 
        Dim mailAddress = ApplicationSettings.Instance.MailAddresses
 
        Console.WriteLine("Mail addresses")
        For Each address In mailAddress
            Console.WriteLine($"{vbTab}{address}")
        Next
 
        EmptyLine()
 
        Console.WriteLine($"QryCacheShort: {ApplicationSettings.Instance.QryCacheShort}")
 
        ReadLineWithTimeoutAndMessage()
 
    End Sub
End Module

Included projects

Rather than step through each project here take time to examine all the projects as each one has something different to offer. Start with Basic2 followed by Basic1/ConfigurationHelper together.

Summary

Two distinct ways to read settings from files have been presented with the focus on moving to appsettings.json over app.config when creating .NET Core desktop solutions with plenty of sample code to get started.

See also

Entity Framework Core 3.x database connection
.NET Core desktop application configurations (C#)

Source code

Use Visual Studio or Git-Desktop to clone the following GitHub repository.