6

The following is an example code from Microsoft documentation which shows how to use appsettings.json in console application:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace ConsoleJson.Example
{
    class Program
    {
        static async Task Main(string[] args)
        {
            using IHost host = CreateHostBuilder(args).Build();

            // Application code should start here.

            await host.RunAsync();
        }

        static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((hostingContext, configuration) =>
                {
                    configuration.Sources.Clear();

                    IHostEnvironment env = hostingContext.HostingEnvironment;

                    configuration
                        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);

                    IConfigurationRoot configurationRoot = configuration.Build();

                    TransientFaultHandlingOptions options = new();
                    configurationRoot.GetSection(nameof(TransientFaultHandlingOptions))
                                     .Bind(options);

                    Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
                    Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
                });
    }
}

I need to get the configuration values in a method in another class as below. I think I will need to use Dependency injection and send IConfigurationRoot to myClass as shown in this link but can not figure out exactly how??

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace ConsoleJson.Example
{
    class Program
    {
        static void Main(string[] args)
        {
            using IHost host = CreateHostBuilder(args).Build();

            IDatabase db = xyz.GetDatabase();

            ConfigureServices.Initialize(db);
        }

        static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((hostingContext, configuration) =>
                {
                    configuration.Sources.Clear();

                    IHostEnvironment env = hostingContext.HostingEnvironment;

                    configuration
                        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);

                    IConfigurationRoot configurationRoot = configuration.Build();

                    TransientFaultHandlingOptions options = new();
                    configurationRoot.GetSection(nameof(TransientFaultHandlingOptions))
                                     .Bind(options);

                    
                });
    }

    class ConfigureServices
    {   public static void Initialize(IDatabase db)
        {
             Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
             Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");

        }
    }
}

So, I need to get the values from appsettings.json and use them in my Initialize method. I'm able to use the rest of the code by hardcoding the values.

I tried adding the below line after ConfigureAppConfiguration but it's not able to recognize configurationRoot

.ConfigureServices((_, services) =>services.AddSingleton<IConfigurationRoot>(configurationRoot));

All help is sincerely appreciated. Thanks

Arnab
  • 2,324
  • 6
  • 36
  • 60
  • Can you clarify what it is you are actually trying to do with `IDatabase db = xyz.GetDatabase(); ConfigureServices.Initialize(db);` as this will determine what configuration needs to be done. what is `xyz` in this case and what actually needs to be access from configuration. – Nkosi May 07 '21 at 14:47
  • The configuration builder does not actually need to be built manually. The linked doc is meant to be a very simplified example. You should update the post with your actual intentions so a proper answer that actually addresses your problem can be provided. – Nkosi May 07 '21 at 14:51
  • There is also no need to manually add IConfiguration to services collection since it is added automatically by the host build process. – Nkosi May 07 '21 at 14:54
  • @Nkosi Sorry, I got busy with a couple of other things.. I will check your answer and get back asap – Arnab May 10 '21 at 16:41
  • I saw your answer. I'm extremely sorry for not being able to convey my exact problem. I can access my database from the other class in the initialize method as stated in question. What I need is to access the appsettings.json data in the initialize method from that class. I have made my problem statement in the question bold. Please do ask if I have not been able to clarify properly. Thanks for your help – Arnab May 10 '21 at 16:58

3 Answers3

2

You can try injecting the IOptions pattern from .NET Core into your class.

Do this as follows:

Suppose your appSettings.json is as shown:

"AppSettings": {
    "AppValue": "1"
 }

Create a class to hold your configuration values:

public class AppConfiguration
{
    public AppConfiguration() { } 
    public string AppValue { get; set; } 
}

In your Startup.cs add the configuration class to the service collection as shown:

using Microsoft.Extensions.Configuration;

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<AppConfiguration>(Configuration.GetSection("AppSettings"));
    …
}

In your class, inject IOptions into the class. Assign an internal variable to the configuration. Reference the configuration data within your class and obtain settings properties:

using Microsoft.Extensions.Options;

…

public class MyService: IMyService
{
    private readonly IOptions<AppConfiguration> _appConfiguration;

    public MyService(..., IOptions<AppConfiguration> appConfiguration, …)
    {
       ...
       _appConfiguration = appConfiguration;
    }

    public SomeTask()   
    {
        // Do something with _appConfiguration.Value.AppValue; 
    }
}

This will allow your custom classes to obtain values from your appSettings.json.

Also, with your app builder, it is recommended that you application pipeline uses a startup class like this:

Host.CreateDefaultBuilder(args)
   .UseStartup<Startup>();

Then your Startup class should have the following methods:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
}

public void ConfigureServices(IServiceCollection services)
{
}

A similar skeleton startup class can be regenerated when you create a new .NET Core Web App. Try the above and let me know how it goes.

Andrew Halil
  • 1,195
  • 15
  • 15
  • 21
  • This is a console application not a web app. I do not have startup.cs – Arnab May 04 '21 at 08:13
  • I am aware of that. I've implemented .NET Core console apps before with a startup which is what the generic host service builder class allows you to do. Also, check your appSettings.json is in the output folder (set copy to Output directory) before building. If there is no appSettings file in your output folder then your configurationRoot variable cannot read any values. – Andrew Halil May 04 '21 at 08:48
1

Everything here can be done by configuring the host appropriately in the console.

Since a proper representation of what you are trying to do was not provided, I will have to make some assumptions based on what was actually provided in your example.

First refactor your classes to be instances and try to avoid static coupling so that dependency injection can be used properly

public class ConfigureServices {
    private readonly TransientFaultHandlingOptions options;
    private readonly IDatabase db;
    
    public ConfigureServices(TransientFaultHandlingOptions options, IDatabase db) {
        this.options = options;
        this.db = db;
    }

    public void Initialize() {
    
         Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
         Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");

        //... access db and options as needed here
    }
}

Review the comments below to understand how the host is being built.

class Program {
    static void Main(string[] args) {
        using IHost host = CreateHostBuilder(args).Build();

        ConfigureServices config = host.Services.GetRequiredService<ConfigureServices>();
        config.Initialize();
        
        host.Run();
    }

    static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, configuration) => {
                configuration.Sources.Clear();

                IHostEnvironment env = hostingContext.HostingEnvironment;

                configuration
                    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);
                    
                //After this delegate has been invoked, the configuration
                //will be built and added to the HostBuilderContext
            })
            .ConfigureServices((hostingContext, services) => {
                //Get built configuration from the host builder context
                IConfiguration configuration = hostingContext.Configuration;
            
                //Extract the options from configuration.
                TransientFaultHandlingOptions options = configuration
                    .GetSection(nameof(TransientFaultHandlingOptions))
                    .Get<TransientFaultHandlingOptions>();
                    
                //And add it to services so it can be injected as needed
                services.AddSingleton<TransientFaultHandlingOptions>(_ => options);

                //Add IDatabase so it to can be injected and resolved as needed.
                services.AddScoped<IDatabase>(_ => xyz.GetDatabase());
            });
}

With all the necessary dependencies added to the host, resolve the desired service as shown above

//...

ConfigureServices config = host.Services.GetRequiredService<ConfigureServices>();
config.Initialize();

//...

There really is no need to be manually passing parameters around.

Even the manual invocation of the ConfigureServices.Initialize() could have been injected into a IHostedService and that would invoked it for you when the host is started.

public class DatabaseInitializeService: BackgroundService {
    private readonly ConfigureServices config;
    
    public DatabaseInitializeService(ConfigureServices config) {
        this.config = config;
    }
    
    protected override Task ExecuteAsync(CancellationToken stoppingToken) {
        config.Initialize();
        
        return Task.CompletedTask;
    }    
}

The hosted service would be added in ConfigureServices extension

//...

services.AddHostedService<DatabaseInitializeService>();

//...

And the Main method simplified as before.

static void Main(string[] args) {
    using IHost host = CreateHostBuilder(args).Build();

    host.Run();
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
1

I was having similar situation. I want to access AppSettings.Json custom keys into my asp.net core mvc project's controller. I am sharing console application example code which might be helpful to you.

I have used standard VS 2019 console app template with asp.net core with target as .net 5.0.

How to read AppSettings.Json (specific) key values into C# console app.


My Project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.6" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
    <PackageReference Include="Serilog" Version="2.10.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
    <PackageReference Include="Serilog.Sinks.MSSqlServer" Version="5.6.0" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

My AppSettings.Json is:

  {
  "AppSettings": {

    "FTPLocation": "\\\\FTPHostname\\\\c$\\\\Folder\\\\ftpSite\\\\Prod\\",
    "FTPUri": "ftp://ftphostname/myFolder/",
    "CSVFileName": "TestData.csv",
    },
  "ConnectionStrings": {
    "AppDbConnString": "Server=sqlserverhost.domain.com;Database=DBName; Trusted_Connection=True; MultipleActiveResultSets=true"
    ,
"IdentityConnection": "Server=hostname.domain.com;Database=IdentityDBName; Trusted_Connection=True; MultipleActiveResultSets=true"
      },  
    "EmailAddresses": [
          "email1@test.com",
          "email2@test.com",
          "email3@test.com"
        ],  
      "Logging": {
        "LogLevel": {
          "Default": "Warning"
        }
      }
 }

My Program.cs is:

using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Microsoft.Extensions.Logging;
using System.IO;

namespace AppSettingsExample
{
    class Program
    {
        public static IConfigurationRoot configuration;
        static void Main(string[] args)
        {
            ServiceCollection serviceDescriptors = new ServiceCollection();

            ConfigureServices(serviceDescriptors);

            Serilog.Log.Information("Building service provider");

            IServiceProvider serviceProvider = serviceDescriptors.BuildServiceProvider();

            Console.WriteLine(configuration.GetConnectionString("AppDbConnString"));

            try
            {
                Serilog.Log.Information("start service provider");
                _ = serviceProvider.GetService<App>().Run();
                Serilog.Log.Information("end service provider");
            }
            catch (Exception ex)
            {
                Serilog.Log.Fatal(ex, "Error in building service ");
                throw;
            }
            finally
            {
                Serilog.Log.CloseAndFlush();
            }

            var connectionString = configuration.GetSection("ConnectionStrings:AppDbConnString").Value;

            Console.WriteLine(connectionString);

            var securityGroups = configuration.GetSection("ADSecurityGroups:SupervisorSecurityGroups").Value.Split(';');

            foreach (var securityGroup in securityGroups)
            {
                Console.WriteLine(securityGroup);
            }
        }
        private static void ConfigureServices(IServiceCollection serviceCollection)
        {
            serviceCollection.AddSingleton(LoggerFactory.Create(builder =>
            {
                builder.AddSerilog(dispose: true);
            }));

            serviceCollection.AddLogging();

            configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName)
                .AddJsonFile("appsettings.json")
                .Build();

            serviceCollection.AddSingleton<IConfigurationRoot>(configuration);

            serviceCollection.AddTransient<App>();

        }


    }
}

My App.cs is:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AppSettingsExample
{
    public class App
    {
        private readonly IConfigurationRoot _config;
        private readonly ILogger<App> _logger;

        public App(IConfigurationRoot config, ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<App>();
            _config = config;
        }

        public async Task Run()
        {
            List<string> emailAddresses = _config.GetSection("EmailAddresses").Get<List<string>>();
            foreach (string emailAddress in emailAddresses)
            {

                Console.WriteLine(emailAddress);
            }
        }
    }
}

Output of this console app:

enter image description here

Chinmay T
  • 745
  • 1
  • 9
  • 17