How to use .settings files in .NET core?

19,569

Solution 1

For .NET Core 2.x, use the Microsoft.Extensions.Configuration namespace (see note below), and there are tons of extensions on NuGet you'll want to grab for reading from sources ranging from environment variables to Azure Key Vault (but more realistically, JSON files, XML, etc).

Here's an example from a console program that retrieves settings the same way we use them when Kestrel starts up for our Azure sites:

public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)

    // This allows us to set a system environment variable to Development
    // when running a compiled Release build on a local workstation, so we don't
    // have to alter our real production appsettings file for compiled-local-test.
    //.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)

    .AddEnvironmentVariables()
    //.AddAzureKeyVault()
    .Build();

Then in your code that needs settings, you just reference Configuration or you register IConfiguration for dependency injection or whatever.

Note: IConfiguration is read-only and will likely never get persistence per this comment. So if reading AND writing are required, you'll need a different option. Probably System.Configuration sans designer.

Solution 2

When porting existing projects I usually copy the generated Settings.Designer.cs from the old to the new project. But I know, this is bad for changing the Settings-file or adding new Settings-Keys. I also noticed that the user's settings were deleted after installing a new version, what was not the case with .net-Framework-Settings.

Solution 3

There's no way this is "proper", as I asked in the question, but I'm using this as a stop-gap until something more reasonable comes along. I cannot guarantee it will work for anyone else.

Include your .settings file as an embedded resource, then use it like this:

private static readonly ConfigurationShim Configuration = new ConfigurationShim("MyApp.Settings.settings");
public static bool MyBoolSetting => (bool) Configuration["MyBoolSetting"];

Code:

internal class ConfigurationShim
{
    private static readonly XNamespace ns = "http://schemas.microsoft.com/VisualStudio/2004/01/settings";

    private readonly Lazy<IDictionary<string, object>> configuration;

    public ConfigurationShim(string settingsResourceName)
    {
        configuration = new Lazy<IDictionary<string, object>>(
            () =>
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                using (Stream stream = assembly.GetManifestResourceStream(settingsResourceName))
                using (var reader = new StreamReader(stream))
                {
                    XDocument document = XDocument.Load(reader);
                    return document.Element(ns + "SettingsFile")
                                   .Element(ns + "Settings")
                                   .Elements(ns + "Setting")
                                   .Select(ParseSetting)
                                   .ToDictionary(kv => kv.Item1, kv => kv.Item2);
                }
            });
    }

    public object this[string property] => configuration.Value[property];

    private static (string, object) ParseSetting(XElement setting)
    {
        string name = setting.Attribute("Name").Value;
        string typeName = setting.Attribute("Type").Value;
        string value = setting.Element(ns + "Value").Value;

        Type type = Type.GetType(typeName);
        IEnumerable<ConstructorInfo> ctors = GetSuitableConstructors(type);
        IEnumerable<MethodInfo> staticMethods = GetSuitableStaticMethods(type);

        object obj = null;
        foreach (MethodBase method in ctors.Cast<MethodBase>().Concat(staticMethods))
        {
            try
            {
                obj = method.Invoke(null, new object[] {value});
                break;
            }
            catch (TargetInvocationException)
            {
                // ignore and try next alternative
            }
        }

        return (name, obj);
    }

    private static IEnumerable<MethodInfo> GetSuitableStaticMethods(Type type)
    {
        // To use a static method to construct a type, it must provide a method that
        // returns a subtype of itself and that method must take a single string as
        // an argument. It cannot be generic.
        return type.GetMethods().Where(method =>
        {
            ParameterInfo[] parameters = method.GetParameters();
            return !method.ContainsGenericParameters &&
                   method.IsStatic &&
                   parameters.Length == 1 &&
                   parameters[0].ParameterType.IsAssignableFrom(typeof(string)) &&
                   type.IsAssignableFrom(method.ReturnType);
        });
    }

    private static IEnumerable<ConstructorInfo> GetSuitableConstructors(Type type)
    {
        // We need a constructor of a single string parameter with no generics.
        return type.GetConstructors().Where(ctor =>
        {
            ParameterInfo[] parameters = ctor.GetParameters();
            return !ctor.ContainsGenericParameters &&
                   parameters.Length == 1 &&
                   parameters[0].ParameterType.IsAssignableFrom(typeof(string));
        });
    }
}
Share:
19,569
Alex Reinking
Author by

Alex Reinking

I'm a PhD candidate at UC Berkeley studying programming language design. I work on Halide with my adviser, Jonathan Ragan-Kelley. I also work on Koka, specifically the Perceus reference counting system. In the past, I wrote the compiler for the P programming language at Microsoft. I play guitar and like to cook.

Updated on July 25, 2022

Comments

  • Alex Reinking
    Alex Reinking almost 2 years

    I'm porting an application to .NET core which relies on a .settings file. Unfortunately, I can't find a way to read it from .NET core. Normally, adding the following lines to the .csproj would generate a TestSettings class that would let me read the settings.

    <ItemGroup>
        <None Include="TestSettings.settings">
            <Generator>SettingsSingleFileGenerator</Generator>
        </None>
    </ItemGroup>
    

    Unfortunately, this no longer seems to do anything. I can't even verify that the SettingsSingleFileGenerator runs at all. This GitHub issue suggests that this is a bug with the new .csproj format, but no one has offered an alternative.

    What is the proper way of reading .settings files in .NET core?

  • Alex Reinking
    Alex Reinking about 6 years
    Please, someone, find an alternative to this. I hesitated to post it here; someone might use it.
  • Achim Stuy
    Achim Stuy about 6 years
    See that net Core-Apps can have several configuration Providers: https://msdn.microsoft.com/en-us/magazine/mt632279.aspx
  • Alex Reinking
    Alex Reinking about 6 years
    What's the difference between Microsoft.Extensions.Configuration and System.Configuration.ConfigurationManager in .NET Core 2.0?
  • McGuireV10
    McGuireV10 about 6 years
    It's a complete rewrite / redesign. The System version was a monolithc, static, XML-focused feature. The new one is extensible and DI-friendly (which also makes it easier to unit test, if you're into that).
  • McGuireV10
    McGuireV10 about 6 years
    I should also note that if you're using ASP.NET Core (not just .NET Core, or a .NET Standard class library), there is a different concept you should read about. Oh and the new system supports configuration-binding, which is deserializing a config section to a class with matching properties, very handy.
  • Louis Somers
    Louis Somers over 5 years
    There would not be much reason to embed a settings file in your resources unless you only use it to write a default file when no settings file was found. The idea behind having a settings file in the first place is to enable users to configure stuff by editing it, which is not easy when it's embedded.
  • Felice Pollano
    Felice Pollano over 5 years
    I actually think than using Directory.GetCurrentDirectory() being a potential cause of bugs ( typically is a newbie error )
  • Ani
    Ani over 4 years
    This answer is only half-correct. If you came here looking for a store to read from and persist data to, Don't go down the Microsoft.Extensions.Configuration road, it's currently immutable (github.com/aspnet/Configuration/issues/385). It seems like System.Configuration.ConfigurationManager or a custom Json.Net impl is the way to go. Ugh.
  • McGuireV10
    McGuireV10 over 4 years
    @Ani This isn't "half-correct" in any sense. Persistent data storage and configuration are two entirely separate concepts. If you landed here looking for persistent storage, you're on completely the wrong track. And it isn't "currently" immutable, if you actually read the backlogged item (also closed) it won't ever support persistence: comment
  • Ani
    Ani over 4 years
    Q: I'm porting an application to .NET core which relies on a .settings file. A: For .NET Core 2.x, use the Microsoft.Extensions.Configuration namespace To me, that answer works only for READING values out of said file. Any nuances you may assume that "configuration" vs Settings is doesn't change that your answer seems to conflate the two by not explaining the differences. Also, the first line of your answer (quoted above) is clear in trying to steer OP to use Microsoft.Extensions.Configuration when it clearly wouldn't work like a Settings File does at all.
  • Ani
    Ani over 4 years
    That the Microsoft.Extensions.Configuration team decided to KEEP this readonly does not make this answer more relevant, only less.
  • McGuireV10
    McGuireV10 over 4 years
    @Ani The OP never mentions writing to the file. You're literally making up a use-case that is completely unrelated to the question.
  • Ani
    Ani over 4 years
    I think the note makes your answer crystal clear as opposed to the fact that it simply left out persistence entirely in its original form. Feel free to reject edits or rephrase your answer. Take it how you wish.