71

I am building a class library using C# and Core .NET. I am trying to use configuration from a config.json file. Here are the contents of that file:

config.json

{
  "emailAddress":"someone@somewhere.com"
}

In an attempt to use config.json for my configuration, I'm referencing Microsoft.Framework.ConfigurationModel.Json in my project.json file. In my code, I have the following:

MyClass.cs

using Microsoft.Framework.ConfigurationModel;
public class MyClass
{
  public string GetEmailAddress()
  {
//    return ConfigurationManager.AppSettings["emailAddress"];  This is the approach I had been using since .NET 2.0
    return ?;  // What goes here?
  }
}

Since .NET 2.0, I had been using ConfigurationManager.AppSettings["emailAddress"]. However, I'm now trying to learn how to do it the new way via IConfiguration. My problem is, this is a class library. For that reason, I'm not sure how, or where, or when, the configuration file gets loaded. In traditional .NET, I just needed to name a file web.config for ASP.NET projects and app.config for other projects. Now, I'm not sure. I have both an ASP.NET MVC 6 project and an XUnit project. So, I'm trying to figure out how to use config.json in both of these scenarios.

Thank you!

Eilon
  • 25,582
  • 3
  • 84
  • 102
user70192
  • 13,786
  • 51
  • 160
  • 240

7 Answers7

112

IMO class libraries should be agnostic to application settings data. Generally, the library consumer is the one concerned with such details. Yes, this isn't always true (e.g. if you have a class that does RSA encryption/decryption, you may want some private configuration to allow for the private key gen/storage), but for the most part, it is true.

So, in general, try to keep application settings out of the class library and have the consumer provide such data. In your comment you mention a connection string to a database. This is a perfect example of data to be kept OUT of a class library. The library shouldn't care what database it's calling to to read, just that it needs to read from one. Example below (I apologize if there's some mistakes as I am writing this on the fly from memory):

Library

Library class that uses a connection string

public class LibraryClassThatNeedsConnectionString
{
    private string connectionString;

    public LibraryClassThatNeedsConnectionString(string connectionString)
    {
        this.connectionString = connectionString;
    }

    public string ReadTheDatabase(int somePrimaryKeyIdToRead)
    {
        var result = string.Empty;

        // Read your database and set result

        return result;
    }
}

Application

appsettings.json

{
  "DatabaseSettings": {
    "ConnectionString": "MySuperCoolConnectionStringWouldGoHere"
  }
}

DatabaseSettings.cs

public class DatabaseSettings
{
    public string ConnectionString { get; set; }
}

Startup.cs

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        Configuration = new ConfigurationBuilder()
                        .SetBasePath(env.ContentRootPath)
                        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                        .AddEnvironmentVariables()
                        .Build();
    }

    public IConfigurationRoot Configuration { get; }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // Setup logging
        // Configure app

    }

    public void ConfigureServices(IServiceCollection services)
    {
        // Configure services
        services.Configure<DatabaseSettings>(Configuration.GetSection("DatabaseSettings"));
        services.AddOptions();

        // Register our class that reads the DB into the DI framework
        services.AddTransient<IInterfaceForClass, ClassThatNeedsToReadDatabaseUsingLibrary>();
    }
}

Class that uses the library class to read the database

public interface IInterfaceForClass
{
    string ReadDatabaseUsingClassLibrary(int somePrimaryKeyIdToRead);
}

public class ClassThatNeedsToReadDatabaseUsingLibrary : IInterfaceForClass
{
    private DatabaseSettings dbSettings;
    private LibraryClassThatNeedsConnectionString libraryClassThatNeedsConnectionString;

    public ClassThatNeedsToReadDatabaseUsingLibrary(IOptions<DatabaseSettings> dbOptions)
    {
        this.dbSettings = dbOptions.Value;
        this.libraryClassThatNeedsConnectionString = new LibraryClassThatNeedsConnectionString(this.dbSettings.ConnectionString);
    }

    public string ReadDatabaseUsingClassLibrary(int somePrimaryKeyIdToRead)
    {
        return this.libraryClassThatNeedsConnectionString.ReadTheDatabase(somePrimaryKeyIdToRead);
    }
}

Some controller class that handles UI stuff to read from the DB

public class SomeController : Controller
{
    private readonly classThatReadsFromDb;

    public SomeController(IInterfaceForClass classThatReadsFromDb)
    {
        this.classThatReadsFromDb = classThatReadsFromDb;
    }

    // Controller methods
}

TL;DR

Try to avoid using application settings in a class library. Instead, have your class library be agnostic to such settings and let the consumer pass those settings in.

Edit:

I added in dependency injection into a controller class to demonstrate using dependency injection to build the class that reads from the DB. This lets the DI system resolve the necessary dependences (e.g. the DB options).

This is one way of doing it (and the best way). Another way is to inject the IOptions into the controller and manually newing up the class that reads from the DB and passing the options in (not best practice, DI is a better way to go)

Stephen P.
  • 2,221
  • 2
  • 15
  • 16
  • 10
    This was very helpful in setting a new project for me. :) – Syed Mar 16 '17 at 09:59
  • 2
    I don't disagree on the DI approach within a class library. Definitely the way to go. But as far as access layers go, my belief is static, leaving the dependency to be injected at the method level. Not constructor. I realize it's an age old argument with no right answer, but I tend to drift to the static side. – pim May 31 '17 at 13:56
  • 2
    @PimBrouwers If you're referring to injecting the connection string on the constructor level, then this isn't personal preference. This is how .NET Core handles getting configuration settings into your App now. They recommend the Options Pattern which is what you're seeing here rather than the old way of accessing them through the static configuration class. – Stephen P. Jun 08 '17 at 19:54
  • It seems like this is going to add a lot of extra code. You're saying that a class library with lots of static methods to retrieve data from the database should request the database settings on every method? That doesn't seem to be a step forward to me. – McGaz Jun 19 '17 at 09:13
  • 1
    @McGaz That's not even anywhere close to what my answer entails. Look at my code again. 1) The only 'extra' code here is adding settings to the appsettings.json, creating a class to match those settings, and injecting the settings using the options pattern (standard practice in .NET Core 1.0). 2) The library only needs the connection string once, upon initialization (which you can't avoid. You will always need to know where you are reading data from). Seriously, look at the code again and put it into practice. This is very basic stuff and the proper way to do it. – Stephen P. Jun 24 '17 at 17:42
  • 4
    IMHO this should have been the chosen answer. It not only is well explained and architected, it is also follows the best practices that the .NET Core proposed. @StephenPorter thanks so much for this. I had not seen an example on how to configure IOptions for an external library. It does seem obvious now that I've implemented it, but I had to read your answer for the penny to drop :) – JSancho Aug 28 '18 at 19:14
  • 1
    @StephenP. this is really useful. Do you know if this still valid for .NET 5.0? Is there any documentation of the official approach? Thanks for enlighting us. – Mauro Gagna Feb 08 '21 at 08:21
  • 1
    @MauroGagna .NET 5.0 is still very close to this, so you should be able to take this and set it up practically the same way. For official docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0 – Stephen P. Mar 05 '21 at 20:22
  • I am wondering how this can work where class library is a separate project then `private DatabaseSettings dbSettings;` how am i going to get reference of this in that class library project. Mine is API project and 3 class libraries which makes a 1 big solution. I am using .net 5. Not able to understand why I am not able to add config file in my class library. @StephenP. i started using this solution but got stuck at the above issue how I will get `DatabaseSettings` class reference from API project to class library project – GKhedekar Sep 18 '22 at 06:00
  • I think this is how MS have thought and why they have thrown out the old approach using static methods on ConfigurationManager. BUT: I do NOT agree this doesn't add extra code - ALL of the code you provided is "extra", and what's more, those who want to use my library have to make modifications in three different places to follow the pattern. Not all software development is for the enterprise, and in my opinion, making a class library with some database logic, and providing a factory method like `public static Repository FromConfig(string connectionStringName)` is a perfectly good use case. – Dojo May 10 '23 at 06:11
30

Supports both appSettings.json and appSettings.Development.json:

Config class implementation:

using Microsoft.Extensions.Configuration;
using System.IO;

public static class Config
{
    private static IConfiguration configuration;

    static Config()
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appSettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile("appSettings.Development.json", optional: true, reloadOnChange: true);
        configuration = builder.Build();
    }

    public static string Get(string name)
    {
        string appSettings = configuration[name];
        return appSettings;
    }

    public static IConfigurationSection GetSection(string name)
    {
        return configuration.GetSection(name);
    }
}

Config class usage:

Section

var cosmosDb = new CosmosDbProviderConfiguration();
Config.GetSection(CosmosDbProviderConfiguration.CosmosDbProvider).Bind(cosmosDb);

Key

var email = Config.Get("no-reply-email");
N-ate
  • 6,051
  • 2
  • 40
  • 48
Nayas Subramanian
  • 2,269
  • 21
  • 28
21

Never used it but a quick search lead me to this...

var configuration = new Configuration();
configuration.AddJsonFile("config.json");
var emailAddress = configuration.Get("emailAddress");

Maybe you could try that.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
chosenbreed37
  • 1,292
  • 9
  • 10
  • 14
    I've seen that too. However, it seems really inefficient to load a JSON file in every single class that needs configuration information. For instance, what if someone needs a database connection string. There has to be some way of loading the configuration information once so that its available throughout the scope of the app. – user70192 Jan 11 '15 at 13:17
  • 5
    @user70192 check out this answer to a similar question: http://stackoverflow.com/questions/28232364/how-can-we-store-configuration-data-in-new-asp-net-vnext/28247809#28247809 it shows how to grab the data just *once* at app startup, and then use dependency injection to allow any other component in the app to get the same data. – Eilon Feb 05 '15 at 04:42
  • 4
    @Eilon My .net core library class doesn't have a Startup class... – letie Dec 02 '19 at 12:33
  • 1
    @LetieTechera a class library won't have a Startup class. Only "application" projects would have those - such as ASP.NET Core web apps. But you can still have some similar code in any code in any class in any library (just not the Startup class itself). – Eilon Dec 02 '19 at 17:10
  • 2
    @Eilon Thanks for the info! .net core libraries are not quite compatible with .net framework apps, I ended up migrating my app to .net core 3.0 which is compatible with win forms. And referencing my .net core 2.2 library works great – letie Dec 03 '19 at 15:37
4

First in your .csproj file add a target that hocks in the build process, see the link for more options if the following doesn't fit your needs, like publication

<Target Name="AddConfig" AfterTargets="AfterBuild">
    <Copy SourceFiles="config.json" DestinationFolder="$(OutDir)" />
</Target>

you can use it like follows

using Microsoft.Framework.ConfigurationModel;
using Microsoft.Extensions.Configuration;
using System;

public class MyClass {
    public string GetEmailAddress() {
        //For example purpose only, try to move this to a right place like configuration manager class
        string basePath= System.AppContext.BaseDirectory;
        IConfigurationRoot configuration= new ConfigurationBuilder()
            .SetBasePath(basePath)
            .AddJsonFile("config.json")
            .Build();

        return configuration.Get("emailAddress");
    }
}
William Ardila
  • 1,049
  • 13
  • 22
2

In .NET 6.0+ This was the solution I found for getting the connectionString for entity framework. There were some issues finding the correct nuget package (Microsoft.Extensions.Configuration.Json). Hopefully this saves everyone some trouble.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; 
//nuget package: Microsoft.Extensions.Configuration.Json

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        var path = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
        var builder = new ConfigurationBuilder();
        builder.AddJsonFile(path);
        var root = builder.Build();
        var connectionString = root.GetSection("ConnectionStrings").GetSection("DefaultConnection").Value;
        optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
    }
}
N-ate
  • 6,051
  • 2
  • 40
  • 48
-1

You can also set properties of the class library with right-click on a .csproject -> properties-> settings-> add a new property in the right window. Make sure to select access modifier as public in Access Modifier dropdown.

Now, add a class library project reference to your .net core project.

Create appSettings.cs class as mentioned below

public class AppSettings
{
    public string MyConnectionString { get; set; }
}

Set key-value appSettings.json

"AppSettings": {
"MyConnectionString": "yourconnectionstring",

},

Now, we just need to get connection string from appSettings.json and set properties into class library in Startup.cs as below.

// This method gets called by the runtime. Use this method to add services to the container
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        // inject App setting
        var appSettingSection = Configuration.GetSection("AppSettings");
        services.Configure<AppSettings>(appSettingSection);
        var appsetting = appSettingSection.Get<AppSettings>();
        // set connection string in .csproject properties.
        classLibraryProject.Properties.Settings.Default.Properties["MyConnectionString"].DefaultValue = appsetting.MyconnectionString;


    }

Note:

  • Make sure about the MyConnectionString key. It should be same in all three files.
  • Make sure to set Access modifier to Public in ClassLibrary project.

I hope this may help.

Milan
  • 1
-1

How to read AppSettings.Json Key values into C# Controller using IConfiguration.

In case someone want to see it, for Asp.net Core .Net 5.0 example. I have gone through above answers and tweak my code little bit for my application.

If you want to see how to use this into console application visit my answer on this link, I have added example with email address as well.


My AppSettings.Json is:

{
"AppSettings": {
    "FTPLocation": "\\\\hostname\\\\c$\\\\FTPMainFolder\\\\ftpFolder\\\\Test\\",
    "FTPUri": "ftp://hostname.domainname.com/foldername/",
    "CSVFileName": "Test Load Planning.csv"  
                },
"ConnectionStrings": 
 {
 "AppDbConnString": "Server=sqlserverhostname.domainname.com;Database=DBName;Trusted_Connection=True; MultipleActiveResultSets=true"   },
 "ADSecurityGroups": { "UserSecurityGroups": "AD-DL-GROUP-NAME;AD-DL-GROUP2-NAME"},
 "Logging": 
  {
    "LogLevel": {
        "Default": "Warning"    
       }  
   }
}

My LoginController.cs is:

using Microsoft.Extensions.Configuration;
public class LoginController : BaseController
{
    
    private readonly ILoginDataServices _loginDataServices;
    private readonly IConfiguration _configuration;
    public IActionResult Index()
    {
        return View();
    }


    public LoginController(ILoginDataServices loginDataServices, IConfiguration configuration)
    {
       
            _loginDataServices = loginDataServices;
            _configuration = configuration;
        
    }


    public bool CheckLogin(string userName, string password)
    {
        if (CheckIfValidEmployee(userName))
        {
            //////checking code here....
        }
        else
        {
            return false;
        }
    }

    bool CheckIfValidEmployee(string userName)
    {

        var securityGroups = _configuration.GetSection("ADSecurityGroups:UserSecurityGroups").Value.Split(';');
         Console.WriteLine(securityGroups);
       ////////Code to check user exists into security group or not using variable value
     }
Chinmay T
  • 745
  • 1
  • 9
  • 17
  • When you instantiate LoginController, you still need to pass the loginDataServices and IConfiguration from somewhere. This does not solve the problem – Chiaro Jun 29 '22 at 13:45