.Net 6.0 Configuration files

Viewed 12051

Been getting a app that serves up data to a VBA application. Now at point trying to get configuration files (appsettings.json & appsettings.Development.Json). Right now it's a .net6.0 console app, later I'm making it an IconTrayApp.

Ran into a problem, the main Program.cs method gets the appsettings.json and the worker gets the appssettings.Developement.json. I understand why Main() get the none environment json, because I explicitly load it. But I want the app to use the same file both in an out of the IDE.

here is program.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PostDataService.InterfaceModels;
using Serilog;
using Serilog.Events;
using System;

namespace PostDataService
{
  public class Program
  {
    public static void Main(string[] args)
    {

      var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
      IConfiguration config = builder.Build();
      string _logFQN = config["PostConfig:Basefolder"] + config["PostConfig:LogsFolder"] + config["PostConfig:LogFname"];
      string _LogTemplate = config["PostConfig:LogTemplate"];

      var loggerConfig = new LoggerConfiguration()
        .MinimumLevel.Override("Microsoft", LogEventLevel.Verbose)
        .Enrich.FromLogContext()
        .Enrich.WithThreadId()
        .Enrich.WithEnvironmentUserName()
        .WriteTo.File(_logFQN,
        fileSizeLimitBytes: 524288000,
        rollOnFileSizeLimit: true,
        rollingInterval: RollingInterval.Day,
        outputTemplate: _LogTemplate);

      Log.Logger = loggerConfig.CreateLogger();
      try
      {
        Log.Write(LogEventLevel.Information, "Start USPS Data Server");
        CreateHostBuilder(args).Build().Run();
      }
      catch (Exception ex)
      {
        Log.Fatal(ex, "Host terminated unexpectedly");
      }
      finally
      {
        Log.Information("USPS Server stopped");
        Log.CloseAndFlush();
      }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog()
            .ConfigureServices((hostContext, services) =>
            {
              IConfiguration configuration = hostContext.Configuration;
              WorkerOptions options = configuration.GetSection("PostConfig").Get<WorkerOptions>();
              services.AddSingleton(options);
              services.AddHostedService<Worker>();
            });

  }
}

and here is appsettings.json (appsettings.Development.json)

{
  "PostConfig": {
    "BaseFolder": "./App_Data/",
    "LogsFolder": "logs/",
    "SystemFQN": "C:/Test/PostalDB/PostalData.db",
    "CustFQN": "C:/Test/PostalDB/PostalCustomer.db",
    "LogFname": "USPS_.log",
    "LogTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}\t{EnvironmentUserName}\t[{Level:u4}]\t<{ThreadId}>\t{Message:lj}{NewLine}{Exception}"
  }
}

I did try and use the .AddEnvironmentVariables() in the configuration builder and that did not seem to change things. I suspect because of the explicit addjsonfile call.

2 Answers

I may have been a bit unclear, the Worker gets the appsettings.Development.json file which is correct as I'm running in VS. What I'm looking for is a clean way to get same behavior within the Main() method.

I've taken Guru Stron's comment and forced it to include the appsettings.dev... file as follows:

      var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{Environments.Development}.json", optional: true, reloadOnChange: true);

It seems to work ok just feels like I'm hard coding something that should be baked in.

Also if I run the application from the Windows File Explorer I get the appsettings.developement.json values in the main but the appsettings.json in the worker class. THis makes sense as the file appsettings.development.json overrides appsettings.json per the builder calls. Not ideal.

Your main app(Primary app- 1st) uses any appsettings.json and you need to use the same file in the called console app(Secondary app - 2nd).

You can call the 2nd with args

1. The settings file from de primary program
Located for example in "C:\appsettings.json"

{
  "AppName": "Value from main/primary/1st program AppSettings.json"
}

2. Call from primary program
You can send any filename with the arg SettingsFile

dotnet .\CommandLineArgs.dll --SettingsFile=C:\appsettings.json
or
.\CommandLineArgs.exe --SettingsFile=C:\appsettings.json

3. The secondary program's code

public class Program
{
    public static void Main(string[] args)
    {
        var builder = new ConfigurationBuilder()
            .AddJsonFile(args.Where(a => a.Contains("SettingsFile")).First().Split("=")[1], optional: false, reloadOnChange: true);
        IConfiguration config = builder.Build();
        Console.WriteLine(config.GetValue<string>("AppName"));
        Console.ReadLine();
    }
}

Output
Simulating that powershell is the primary program. Primary sends the arg SettingsFile with value C:\appsettings.json to Secondary. The secondary program add the json filename to configuration builder and builds configuration object.

1

2

References

Main() and command-line arguments
Command-line arguments

██████████████████ ██████████████████

Another implementation. You can check the code on https://github.com/JomaStackOverflowAnswers/CommandLineArgs

PrimaryApp Code
The primary app directory must contain the executable of secondary app. The primary app launches secondary with the args with settings filename.

using System.Diagnostics;

public class Program
{
    public async static Task Main(string[] args)
    {
        Console.WriteLine("██ PrimaryApp Started");
        var secondaryAppExeFilename = Path.GetFullPath("SecondaryApp/SecondaryApp.exe");
        var settingsFilename = Path.GetFullPath("appsettings.json"); // var settingsFilename = Path.GetFullPath("C:\appsettings.json");
        ProcessStartInfo info = new ProcessStartInfo();
        info.FileName = secondaryAppExeFilename;
        info.Arguments = $"--SettingsFile={settingsFilename}";
        info.WindowStyle = ProcessWindowStyle.Normal;
        Process process = new Process();
        process.StartInfo = info;
        process.Start();
        await process.WaitForExitAsync();
        Console.ReadLine();
        Console.WriteLine("█ PrimaryApp Exited");
    }
}

SecondaryAppCode
The secondary app capture the SettingsFilename arg from command line args and builds the configuration with this file.

using Microsoft.Extensions.Configuration;

public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("██ SecondaryApp Started");
        string settingsFile = args.Where(a => a.Contains("SettingsFile")).First().Split("=")[1];
        var builder = new ConfigurationBuilder()
            .AddJsonFile(settingsFile, optional: false, reloadOnChange: true);
        IConfiguration config = builder.Build();
        Console.WriteLine($"SettingsFile: {settingsFile}");
        Console.WriteLine($"AppName value: {config.GetValue<string>("AppName")}");
        Console.ReadLine();
        Console.WriteLine("█SecondaryApp Exited");
    }
}

appsettings.json

{
  "AppName": "PrimaryApp"
}

Output
1

2

Related