다음을 통해 공유


.NET Core desktop application configurations (C#)

Introduction

In the .NET Framework prior to .NET Core application settings were in  app.config file. All settings e.g. database connection, email setting etc in one file along with each setting are strings which need to be converted to proper types (see the following for conversions) plus some functionality is missing like smtp sections. Moving to .NET Core, the standard is using a Json file generally named appsettings.json. The intent here is to layout how to use appsettings.json for desktop applications.

There is also a secondary repository source (main source repository) to check out that works with both SqlClient and Entity Framework Core.

Update for ASP.NET Core

See the following NuGet package which works with .NET Core 5 and .NET Core 6

Base class project

By adding the following class project to a Visual Studio 2019 solution targeting .NET Core 5 with C#9 any application can read from appsettings.json or even a file structured similarly to separate different types of settings.

Example 1 appsettings.json for storing a single connection string in parts

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

Example 2 appsettings.json storing different connection strings for development and production environments with a setting indicate the environment.

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

Example 3 appsettings.json with a single connection string along with email settings.

{
  "GeneralSettings": {
    "LogExceptions": true,
    "DatabaseSettings": {
      "DatabaseServer": ".\\SQLEXPRESS",
      "Catalog": "School",
      "IntegratedSecurity": true,
      "UsingLogging": true
    },
    "EmailSettings": {
      "Host": "smtp.gmail.com",
      "Port": 587,
      "EnableSsl": true,
      "DefaultCredentials": false,
      "PickupDirectoryLocation": "MailDrop"
    }
  }
}

Example 4 Alternate/secondary configuration file, in this case named columnssettings.json. In this case there is an array which can be done also with any setting like with email/smtp settings in the last configuration file.

{
  "GeneralSettings": {
    "LogExceptions": true,
    "DatabaseSettings": {
      "DatabaseServer": ".\\SQLEXPRESS",
      "Catalog": "School",
      "IntegratedSecurity": true,
      "UsingLogging": true
    },
    "EmailSettings": {
      "Host": "smtp.gmail.com",
      "Port": 587,
      "EnableSsl": true,
      "DefaultCredentials": false,
      "PickupDirectoryLocation": "MailDrop"
    }
  }
}

Connection string Entity Framework Core 5

Out of the box when reverse engineering a database the connection string is hard coded in the DbContext which means when changing environments the connection string needs to be updated followed by rebuilding the project as shown below.

protected override  void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
        optionsBuilder.UseSqlServer("Data Source=.\\SQLEXPRESS;Initial Catalog=School;Integrated Security=True");
    }
}

Using the following no rebuilding is required, simply edit the configuration file and deploy to the selected environment.

In this example (using setting file in example 2 above) on line 11, Helper class is in the base class project, using GetConnectionString (there is also GetConnectionStringSecure for encrypted setting) which reads appsettings.json in the frontend project to select a connection string based off the Environment.Production, a bool project read from the configuration file.

Line 30 Helper.UseLogging determines if the DbContext should, in this case log to a text file.

01.namespace EntityLibrary.Data
02.{
03.    public partial  class SchoolContext : DbContext
04.    {
05.        /// <summary>
06.        /// Connection string to interact with the database
07.        /// </summary>
08.        private static  string _connectionString;
09.        public SchoolContext()
10.        {
11.            _connectionString = Helper.GetConnectionString();
12.        }
13. 
14.        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
15.        {
16.        }
17. 
18.        public virtual  DbSet<Person> Person { get; set; }
19.         
20.        /*
21.         * Change the file name as desired along with a path if the file should be
22.         * in another location than the application executable path.
23.         */
24.        private readonly  StreamWriter _logStream = new StreamWriter("ef-log.txt", append: true);
25.         
26.        protected override  void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
27.        {
28.            if (!optionsBuilder.IsConfigured)
29.            {
30.                if (Helper.UseLogging())
31.                {
32. 
33.                    optionsBuilder.UseSqlServer(_connectionString)
34.                        .EnableSensitiveDataLogging()
35.                        .EnableDetailedErrors()
36.                        .LogTo(_logStream.WriteLine);
37.                     
38.                }
39.                else
40.                {
41.                     
42.                    optionsBuilder.UseSqlServer(_connectionString);
43.                     
44.                }
45.                 
46.            }
47.        }
48. 
49.        protected override  void OnModelCreating(ModelBuilder modelBuilder)
50.        {
51.            modelBuilder.ApplyConfiguration(new PersonConfiguration());
52. 
53.            OnModelCreatingPartial(modelBuilder);
54.        }
55. 
56.        partial void  OnModelCreatingPartial(ModelBuilder modelBuilder);
57. 
58.        #region Takes care of disposing stream used for logging
59.        public override  void Dispose()
60.        {
61.            base.Dispose();
62.            _logStream.Dispose();
63.        }
64. 
65.        public override  async ValueTask DisposeAsync()
66.        {
67.            await base.DisposeAsync();
68.            await _logStream.DisposeAsync();
69.        }
70.        #endregion        
71.    }
72.}

For secured connection string Helper.GetConnectionStringSecure is used rather than GetConnectionString.

Use Securityhelper class project Writer and Reader methods to encrypt connection strings, or any string setting. At runtime the connection string is encrypted until needed.

1.{
2.  "ConnectionStrings": {
3.    "DevelopmentConnection": "bQ3FJ8OaAQM5XVQ2iGMQfsn+b1dGsCXyov+iLDCRgtO3tU/lLwVKgpfKshQj+9muOD0pwgcKoOKyl1uWNg/0vg=="
4.  }
5.}

In the DbContext for obtaining a plain text connection string from a encrypted string.

1.public SchoolContext()
2.{
3.    _connectionString = Helper.GetConnectionStringSecure();
4.}

Where without encryption

1.public SchoolContext()
2.{
3.    _connectionString = Helper.GetConnectionString();
4.}

There is a test project to try out a secure connection string included.

01.namespace SecurityHelperConfiguration
02.{
03.    class Program
04.    {
05.        /// <summary>
06.        /// Example to create encrypted connection string for, in this case sql-server
07.        /// </summary>
08.        /// <param name="args"></param>
09.        static void  Main(string[] args)
10.        {
11.             
12.            var plainText = "Server=.\\SQLEXPRESS;Database=School;Integrated Security=true";
13.            Console.WriteLine($"Original: {plainText}");
14.             
15.            var encryptedText = ApplicationConfiguration.Writer(plainText);
16.            Console.WriteLine($"Encrypted: {encryptedText}");
17.             
18.            var connectionString = ApplicationConfiguration.Reader(encryptedText);
19.            Console.WriteLine($"Connection string: {connectionString}");
20.             
21.            Console.ReadLine();
22.        }
23.    }
24.}

Base class properties and methods

The following properties and methods are available.

Settings file defaults to appsettings.json, to change for a alternate settings file use ConfigurationFileName

1.public static  string ConfigurationFileName { get; set; } = "appsettings.json";

Inner workings

The following method initializes were to get the json file.

01.private static  IConfigurationRoot InitMainConfiguration()
02.{
03. 
04.    var builder = new  ConfigurationBuilder()
05.        .SetBasePath(Directory.GetCurrentDirectory())
06.        .AddJsonFile(ConfigurationFileName);
07. 
08.    return builder.Build();
09. 
10.}

Figure 1 Generic method to strong type settings

1.public static  T InitOptions<T>(string section) where T : new()
2.{
3.    var config = InitMainConfiguration();
4.    return config.GetSection(section).Get<T>();
5.}

Figure 2 usage

01.public static  string ConnectionString()
02.{
03. 
04.    InitMainConfiguration();
05.    var applicationSettings = InitOptions<DatabaseSettings>("database");
06. 
07.    var connectionString =
08.        $"Data Source={applicationSettings.DatabaseServer};"  +
09.        $"Initial Catalog={applicationSettings.Catalog};" +
10.        "Integrated Security=True";
11. 
12.    return connectionString;
13.}

Figure 3 Another example

01.public static  bool UseLogging()
02.{
03.     
04.    InitMainConfiguration();
05.     
06.    var applicationSettings = InitOptions<ApplicationSettings>("database");
07. 
08.    return applicationSettings.UsingLogging;
09. 
10.}

Classes for setting are located in the ConfigurationHelper class.

Class for connections with multiple environments

01.public class  ConnectionStrings
02.{
03.    /// <summary>
04.    /// Development environment connection string
05.    /// </summary>
06.    public string  DevelopmentConnection { get; set; }
07.    /// <summary>
08.    /// Production environment connection string
09.    /// </summary>        
10.    public string  ProductionConnection { get; set; }
11.    /// <summary>
12.    /// true to use production, false to use test environment
13.    /// </summary>
14.    public bool  IsProduction { get; set; }
15.}

Single connection with logging

01.public class  DatabaseSettings
02.{
03.    /// <summary>
04.    /// Database server to work with
05.    /// </summary>        
06.    public string  DatabaseServer { get; set; }
07.    /// <summary>
08.    /// Initial catalog to data
09.    /// </summary>        
10.    public string  Catalog { get; set; }
11.    /// <summary>
12.    /// true for server authentication, false requires user name and password
13.    /// </summary>        
14.    public bool  IntegratedSecurity { get; set; }
15.    /// <summary>
16.    /// true to use production, false to use test environment
17.    /// </summary>        
18.    public bool  UsingLogging { get; set; }
19.    /// <summary>
20.    /// Connection string ready to use.
21.    /// </summary>
22.    public string  ConnectionString => $"Data Source={DatabaseServer};" +
23.                                      $"Initial Catalog={Catalog};" +
24.                                      $"Integrated Security={IntegratedSecurity}";
25. 
26.}

Email settings

1.public class  EmailSettings
2.{
3.    public string  Host { get; set; }
4.    public int  Port { get; set; }
5.    public bool  EnableSsl { get; set; }
6.    public bool  DefaultCredentials { get; set; }
7.    public string  PickupDirectoryLocation { get; set; }
8.}

To implement settings that do not fit into the supplied classes above, add them to the Classes folder under the project ConfigurationHelper using figure 1 and 2 as models.

Summary

By using ConfigurationHelper class .NET Core 5 applications can use json files to store configuration settings from connection string, email settings to settings specific to an application.

Requires

  • Microsoft Visual Studio 2019 or higher
  • Projects setup for .NET Core 5 or higher
  • C# 9, if using a lesser version modifications will be needed which need intermediate to advance level level skills to change.

Setting up source code provided

  • Run the data script in a .sql file in Visual Studio or use SSMS (SQL-Server Management Studio)
  • Build the solution, Visual Studio should auto-restore any missing NuGet Packages, if not run "restored NuGet packages" and build again.

See also

Entity Framework/Entity Framework Core dynamic connection strings
Entity Framework Core/Windows forms tips and tricks (using EF Core 3x)
C# Working with SQL-Server connection

GitHub resource

Moving from .NET Framework to .NET Core connections

Source code

Clone the following GitHub repository for full source with several code samples.