West Wind Toolkit for .NET
AppConfiguration provider settings to store configuration in CommonApplicationData path?
Gravatar is a globally recognized avatar based on your email address. AppConfiguration provider settings to store configuration in CommonApplicationData path?
  mwaldrop
  All
  May 28, 2020 @ 05:23pm

I'm trying to use AppConfiguration to store a C# windows application (VS2017 framework 4.5) configuration data (application specific data intended to be written by admin and accessible to all users) to the CommonApplicationData folder (e.g. in my case C:\ProgramData\CompanyName\AppName).

So far following your documentation I've implemented the standard .AppConfiguration class (with static single instance per example) which successfully reads and writes my application settings to the AppName.Exe.Config file in the app's exe folder.

However, it's my understanding msoft now frowns on any user writing to program files folders, instead recommending ProgramData folder. I would like to do this the simplest possible way! Upon study of your documentation: "Using Other Providers with AppConfiguration", "Configuration with IConfigurationProviders", and "Storing Configuration Settings in .NET Configuration Files", I'm trying to "customize the ConfigurationFile provider" by setting the ConfigurationFile to the appropriate path/name in ProgramData folder. Unsure whether this can use the default provider (customized) or I need to use optional XmsFileConfigurationProvider?

Your example code below (from Storing Configuration Settings in .NET Configuration Files) is not working for me. C# doesn't like var (where I substitute class name) nor the CustomConfig in brackets part and seems to want a ", new" in the declaration. I'm somewhat novice c#/VisualStudio but it seems I need to declare a provider class (per below) and then use that in my .AppConfiguration declaration.

var provider = new ConfigurationFileConfigurationProvider<CustomConfig>()
{                    
   //ConfigurationFile = "CustomConfiguration.config",
   ConfigurationSection = "MyApplicationConfiguration",
   EncryptionKey = "ultra-seekrit",  // use a generated value here
   PropertiesToEncrypt = "Password,AppConnectionString"
};

var config = new CustomConfig();
config.Initialize(provider);


Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  Rick Strahl
  mwaldrop
  May 28, 2020 @ 06:20pm

You need to provide the file name to the provider configuration. You've got that commented out.

personally if I'm going to store configuration externally then I'd use the JSON provider or XmlFile provider (not the application Settings one) because they are simpler and meant to be used for standalone files. Using Application Configuration might also work but there's really not much of a benefit of using that if you're not storing it in the app folder. The Raw XML or JSON formats are simpler to view and edit since they use simple property values rather than Configuration name/value pair attributes.

Here's what a configuration looks like for a JSON Provider in common folder that I use for West Wind WebSurge.

public WebSurgeConfiguration()
{
    StressTester = new StressTesterConfiguration();
    UrlCapture = new UrlCaptureConfiguration();
    WindowSettings = new WindowSettings();
    CheckForUpdates = new CheckForUpdates();

    AppName = "West Wind WebSurge";
}

protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)
{
    // this is where you configure the provider you want to use
    var provider = new JsonFileConfigurationProvider<WebSurgeConfiguration>()
    {
        // here set the file name for the provider
        JsonConfigurationFile = Path.Combine(App.UserDataPath,"WebSurgeConfiguration.json"),

        EncryptionKey = App.EncryptionMachineKey,
        PropertiesToEncrypt = "super-secreeet"
    };
    Provider = provider;

    return provider;
}

Then when you need instantiate it:

public class App
{
 
   public static UserDataPath {get; set; }

   public static WebSurgeConfiguration Configuration {get; set; }
    
    static App()
    {
            // figure out where you're going to store the file
            UserDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
                           "\\West Wind WebSurge\\";
            if (!Directory.Exists(UserDataPath))
                Directory.CreateDirectory(UserDataPath);


        Configuration = new WebSurgeConfiguration();
        
        // This loads the configuration from the provider
        Configuration.Initialize();
    }
}

And finally to use it:

var instanceCount = App.Configuration.TestInstances;

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  mwaldrop
  Rick Strahl
  May 29, 2020 @ 05:00pm

Thanks for your prompt reply Rick!!

I think I'm getting close. Per below code I tried both naming the file with the default 'ConfigurationFileConfigurationProvider' and 'XmlFileConfigurationProvider' with the override IConfigurationProvider.

I got both attempts to compile but at run time upon attempting to read a config item, I got a System.TypeInitialization error "The type initializer for ns199Hydro.App threw an exception (Invalid operation exception).

Note that without the protected override section (as below with both commented out) everything works with the config data writes to the expected windows app.exe.config file locations (i.e. all over the place depending on user since Windows doesn't want to write to the 'real' program files exe location).

using System;
using Westwind.Utilities.Configuration;

namespace ns199Hydro
{
    public class MyConfiguration : AppConfiguration
    {
        public string Operator { get; set; }
        public DateTime GageCalDue { get; set; }
        public int TestInt { get; set; }
        public string Password { get; set; }

        public MyConfiguration()
        {
            Operator = "DefaultOperator";
            GageCalDue = DateTime.Parse("2020-12-05");
            TestInt = 123;
            Password = "TestPW";
        }

        /*
        //try default configuration provider specify path/filename
        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)
        {
            var provider = new ConfigurationFileConfigurationProvider<MyConfiguration>()
            {
                ConfigurationFile = App.UserDataPath
            };
            return provider;
        }
        */
        
        /*
        //try XmlFileConfigurationProvider
        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)
        {
            // this is where you configure the provider you want to use
            var provider = new XmlFileConfigurationProvider<MyConfiguration>()
            {
                // here set the file name for the provider
                XmlConfigurationFile = App.UserDataPath
            };
            return provider;
        }
        */
    }

    public class App
    {
        public static string UserDataPath { get; set; }

        // create 'singleton' global instance of MyConfiguration accessible throughout namespace
        // (alternate is to declare an instance and initialize in main or MainForm and pass it as parameter to sub forms)
        public static MyConfiguration Config { get; set; }

        static App()
        {
            UserDataPath = "C:\\ProgramData\\Cameron\\199Hydro\\199Hydro.config";
            Config = new MyConfiguration();
            Config.Initialize();
        }
    }
}
Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  Rick Strahl
  mwaldrop
  May 30, 2020 @ 02:40pm

You're not providing a configuration provider?

All that code is commented out.

I don't know why there's a type initialization error but I'm guessing it's failing trying to read/write the data. You need to step through he code to see where it's actually failing during construction of the type. Set a breakpoint inside of your provider code and then keep stepping.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  mwaldrop
  mwaldrop
  Jun 1, 2020 @ 11:16am

Yes, in my example code I had commented out the two attempts to use the override to specify path/filename by overriding IConfigurationProvider.

With neither of those, i.e. with the default provider settings, I am able to read and write from my Config instance of MyConfiguration class. But when I try either with the override (one at a time, not both), I get the run time:

System.TypeInitializationException: 'The type initializer for 'ns199Hydro.App' threw an exception'

(ns199Hydro is my project namespace per prior code sample).

Stepping through the code, the exception does occur upon first read from the Config class:

txtTest.Text = App.Config.Operator;

Again, this does work OK without the override.

Since this is a run time error (compile is OK), I wonder if the problem is something with the generic type specifier i.e.

var provider = new ConfigurationFileConfigurationProvider<MyConfiguration>()

In my code, MyConfiguration is my configuration class (derived from your AppConfiguration) and the IConfigurationProvider override is inside that class. So maybe that's completely wrong, can't really tell from your example?

In your example

public WebSurgeConfiguration()

and the IConfigurationProvider override aren't indented, maybe they aren't supposed to be inside the AppConfiguration derived class and/or maybe WebSurgeConfiguration is a standalone type definition and not the configuration class initializer?

Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  Rick Strahl
  mwaldrop
  Jun 1, 2020 @ 04:22pm

I don't have a quick answer for you. But I suggest if you want to track this down that you step into your constructor and ensure the constructor works.

You can run the tests in CustomConfigFileConfiguration.cs to play around with this more easily.

But I just tried this out using that code:

public class CustomConfigFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration
{
    // Added this only so I can easily pass an explicit path 
    // Otherwise you can ust a config value for this assuming it comes from somewhere else
    // and not this class (otherwise you get a stackoverflow).
    public string ConfigFile  {get; set; }

    public string ApplicationName { get; set; }
    public DebugModes DebugMode { get; set; }
    public int MaxDisplayListItems { get; set; }
    public bool SendAdminEmailConfirmations { get; set; }
    public string Password { get; set; }
    public string AppConnectionString { get; set; }

    public LicenseInformation License { get; set; }


    [XmlIgnore]
    public string IgnoredProperty {get; set;}

    public List<string> ServerList { get; set;  }

    
        public CustomConfigFileConfiguration()
        {
            ApplicationName = "Configuration Tests";
            DebugMode = DebugModes.Default;
            MaxDisplayListItems = 15;
            SendAdminEmailConfirmations = false;
            Password = "seekrit";
            AppConnectionString = "server=.;database=hosers;uid=bozo;pwd=seekrit;";
            License = new LicenseInformation()
            {
                Company = "West Wind",
                Name = "Rick", 
                LicenseKey = "westwindrick-51123"
            };
            ServerList = new List<string>()
            {
                "DevServer",
                "Maximus",
                "Tempest"
            };
        }

        /// <summary>
        /// Override to provide a custom default provider (created when Initialize() is
        /// called with no parameters).
        /// </summary>
        /// <param name="sectionName"></param>
        /// <param name="configData"></param>
        /// <returns></returns>
        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)
        {
            var provider = new ConfigurationFileConfigurationProvider<CustomConfigFileConfiguration>()
            {
                // Config file assigned here 
                ConfigurationFile = ConfigFile,

                ConfigurationSection = sectionName,
                EncryptionKey = "ultra-seekrit",  // use a generated value here
                PropertiesToEncrypt = "Password,AppConnectionString,License.LicenseKey"
            };

            return provider;
        }
    }
}

Note the ConfigFile property is not required - you can figure out the path some other way if you know it'll be in AppData or some other place you can just use that path. I'm using hte above to pass the path explicitly and use it in the provider creation.

Then to use it:

        [TestMethod]
        public void DefaultConstructorInstanceTest()
        {
            var configFile  = "c:\\temp\\testconfig.config";

            var config = new CustomConfigFileConfiguration();
            config.ConfigFile = configFile; // custom property to pass in the path
            config.Initialize();

            Assert.IsNotNull(config);
            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));

            string text = File.ReadAllText(configFile); // TestHelpers.GetTestConfigFilePath());
            Assert.IsTrue(text.Contains(@"<add key=""MaxDisplayListItems"" value=""15"" />"));
            Console.WriteLine(text);          
        }

This works without a problem, creating the .config file in the temp folder and produces:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="CustomConfigFileConfiguration" requirePermission="false" type="System.Configuration.NameValueSectionHandler,System,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </configSections>
  <CustomConfigFileConfiguration>
    <add key="ConfigFile" value="c:\temp\testconfig.config" />
    <add key="ApplicationName" value="Configuration Tests" />
    <add key="DebugMode" value="Default" />
    <add key="MaxDisplayListItems" value="15" />
    <add key="SendAdminEmailConfirmations" value="False" />
    <add key="Password" value="xGJaHypTv5A=" />
    <add key="AppConnectionString" value="z6+T5mzXbtJBEgWqpQNYbFOBooVi+6Dt1cRfX/ubH5avtEcD/IGXfkQAUc5hkUTr" />
    <add key="License" value=",West Wind,YV7FKXCJPtAoY5h2B0j+nMdhrZxY3tgQ" />
    <add key="ServerList1" value="DevServer" />
    <add key="ServerList2" value="Maximus" />
    <add key="ServerList3" value="Tempest" />
  </CustomConfigFileConfiguration>
</configuration>

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  mwaldrop
  Rick Strahl
  Jun 1, 2020 @ 05:44pm

Thanks that should give me something to work with. Where can I find CustomConfigFileConfiguration.cs ? I added your configuration class to my project with NuGet so I don't think there's any source code there?

Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  Rick Strahl
  mwaldrop
  Jun 1, 2020 @ 06:54pm

The project is on Github. But it may be a bit of effort to get that set up and those integration tests to run...

The most common cause for initialization failures are accessing properties that are not accessible (nested CTOR access) or types that can't be instantiated in the CTOR. Followed by problems trying to access the configuration file on disk.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  mwaldrop
  Rick Strahl
  Jun 5, 2020 @ 04:47pm

I got it working. It turned out I had a typo in my config file path (specified path didn't exist), which is what threw System.TypeInitializationException when the Configuration class Initialize method was attempted.

I'm now able to write to desired location with either ConfigurationFileConfigurationProvider or XmlFileConfigurationProvider

Thanks again for all your help Rick!

Gravatar is a globally recognized avatar based on your email address. re: AppConfiguration provider settings to store configuration in CommonApplicationData path?
  Rick Strahl
  mwaldrop
  Jun 5, 2020 @ 05:12pm

Like I mentioned - 99% of the time if there's a type initialization error the failure is in the Constructor 😃 That's why I wanted you to set a break point in your CTOR...

+++ Rick ---

© 1996-2020