user.config的自定义路径

时间:2010-02-15 10:31:20

标签: c# visual-studio settings

我使用VS2008中的设置设计器管理我的应用程序设置。

  

“user.config的确切路径   文件看起来像这样:“

<Profile Directory>\<Company Name>\
 <App Name>_<Evidence Type>_<Evidence Hash>\<Version>\user.config

是否有办法自定义此路径?我更喜欢这样的事情:

<Profile Directory>\<Company Name>\
 <App Name>\<Version>\user.config

我注意到新创建的文件夹(“Test Company” - &gt;“Test_Company”)中的“公司名称”中的下划线替换了空格。我真的希望关闭这种行为。

你知道,我可以编写一个新的基于XML的设置处理程序,但我想使用设置设计器。

4 个答案:

答案 0 :(得分:35)

要找到实现自定义设置提供程序的好信息并不容易,所以我在下面包含了一个完整的实现(底部)。保留了user.config文件的格式,以及其中的功能。 。设计师。我确定有些东西可以清理一下,所以不要麻烦我:)。

与其他人一样,我想更改user.config文件的位置,并且仍然可以在设计器中使用.settings文件获得乐趣和乐趣,包括为新安装创建默认值。重要的是,我们的应用程序还已经在我们已经决定的路径(appData \ local \ etc)中有其他已保存的设置对象,并且我们不希望在多个位置存在工件。

代码比我想要的长得多,但是我找不到简短的答案。虽然能够控制路径似乎有些痛苦,但创建自定义设置提供程序本身仍然非常强大。人们可以改变以下实现来存储数据,包括自定义加密文件,数据库或与Web服务交互。

根据我的阅读,Microsoft并不打算使配置文件的路径可配置。当他们说允许这会让人感到恐惧时,我会接受他们的话。见(this)帖子。唉,如果你想自己做,你必须实现自己的SettingsProvider

这里有..

在项目中添加参考 System.Configuration ,您需要它来实现SettingsProvider。

Easy bit ...创建一个实现SettingsProvider的类,使用ctrl+.来帮助你。

class CustomSettingsProvider : SettingsProvider

另一个简单的方法 ...转到.settings文件后面的代码(设计器中有一个按钮)并装饰类以指向它你的实施。必须这样做才能覆盖内置功能,但它不会改变设计师的工作方式。(抱歉这里的格式很奇怪)

[System.Configuration.SettingsProvider(typeof(YourCompany.YourProduct.CustomSettingsProvider))]

public sealed partial class Settings
{
    //bla bla bla
}

获取路径:有一个名为“SettingsKey”的属性(例如Properties.Settings.Default.SettingsKey)我用它来存储路径。我做了以下财产。

/// <summary>
/// The key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
    get
    {
        return Properties.Settings.Default.SettingsKey;
    }

}

存储设置值。我选择使用字典。这将被广泛使用。我创建了一个struct作为帮助器。

/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
    internal string name;
    internal string serializeAs;
    internal string value;
}

神奇。您必须覆盖2种方法, GetPropertyValues SetPropertyValues 。 GetPropertyValues接收为您在设计器中看到的参数,您必须更新值并返回新集合的机会。当用户保存对在运行时生成的值的任何更改时,将调用SetPropertyValues,这是我更新字典并写出文件的地方。

/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
    //load the file
    if (!_loaded)
    {
         _loaded = true;
         LoadValuesFromFile();
    }

    //collection that will be returned.
    SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

    //iterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
    foreach (SettingsProperty setting in collection)
    {
        SettingsPropertyValue value = new SettingsPropertyValue(setting);
        value.IsDirty = false;

        //need the type of the value for the strong typing
        var t = Type.GetType(setting.PropertyType.FullName);

        if (SettingsDictionary.ContainsKey(setting.Name))
        {
            value.SerializedValue = SettingsDictionary[setting.Name].value;
            value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
        }
        else //use defaults in the case where there are no settings yet
        {
            value.SerializedValue = setting.DefaultValue;
            value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
        }

            values.Add(value);
    }
    return values;
}

/// <summary>
/// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
    //grab the values from the collection parameter and update the values in our dictionary.
    foreach (SettingsPropertyValue value in collection)
    {
        var setting = new SettingStruct()
        {
            value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
            name = value.Name,
            serializeAs = value.Property.SerializeAs.ToString()
        };

        if (!SettingsDictionary.ContainsKey(value.Name))
        {
            SettingsDictionary.Add(value.Name, setting);
        }
        else
        {
            SettingsDictionary[value.Name] = setting;
        }
    }

    //now that our local dictionary is up-to-date, save it to disk.
    SaveValuesToFile();
}

完整的解决方案。所以这里是整个类,包括构造函数,Initialize和帮助器方法。随意剪切/粘贴切片和骰子。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;

namespace YourCompany.YourProduct
{
    class CustomSettingsProvider : SettingsProvider
    {
        const string NAME = "name";
        const string SERIALIZE_AS = "serializeAs";
        const string CONFIG = "configuration";
        const string USER_SETTINGS = "userSettings";
        const string SETTING = "setting";

        /// <summary>
        /// Loads the file into memory.
        /// </summary>
        public CustomSettingsProvider()
        {
            SettingsDictionary = new Dictionary<string, SettingStruct>();

        }

        /// <summary>
        /// Override.
        /// </summary>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(ApplicationName, config);
        }

        /// <summary>
        /// Override.
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
            }
            set
            {
                //do nothing
            }
        }

        /// <summary>
        /// Must override this, this is the bit that matches up the designer properties to the dictionary values
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            //load the file
            if (!_loaded)
            {
                _loaded = true;
                LoadValuesFromFile();
            }

            //collection that will be returned.
            SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

            //itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
            foreach (SettingsProperty setting in collection)
            {
                SettingsPropertyValue value = new SettingsPropertyValue(setting);
                value.IsDirty = false;

                //need the type of the value for the strong typing
                var t = Type.GetType(setting.PropertyType.FullName);

                if (SettingsDictionary.ContainsKey(setting.Name))
                {
                    value.SerializedValue = SettingsDictionary[setting.Name].value;
                    value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
                }
                else //use defaults in the case where there are no settings yet
                {
                    value.SerializedValue = setting.DefaultValue;
                    value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
                }

                values.Add(value);
            }
            return values;
        }

        /// <summary>
        /// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            //grab the values from the collection parameter and update the values in our dictionary.
            foreach (SettingsPropertyValue value in collection)
            {
                var setting = new SettingStruct()
                {
                    value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
                    name = value.Name,
                    serializeAs = value.Property.SerializeAs.ToString()
                };

                if (!SettingsDictionary.ContainsKey(value.Name))
                {
                    SettingsDictionary.Add(value.Name, setting);
                }
                else
                {
                    SettingsDictionary[value.Name] = setting;
                }
            }

            //now that our local dictionary is up-to-date, save it to disk.
            SaveValuesToFile();
        }

        /// <summary>
        /// Loads the values of the file into memory.
        /// </summary>
        private void LoadValuesFromFile()
        {
            if (!File.Exists(UserConfigPath))
            {
                //if the config file is not where it's supposed to be create a new one.
                CreateEmptyConfig();
            }

            //load the xml
            var configXml = XDocument.Load(UserConfigPath);

            //get all of the <setting name="..." serializeAs="..."> elements.
            var settingElements = configXml.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName).Elements(SETTING);

            //iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
            //using "String" as default serializeAs...just in case, no real good reason.
            foreach (var element in settingElements)
            {
                var newSetting = new SettingStruct()
                {
                    name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
                    serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value,
                    value = element.Value ?? String.Empty
                };
                SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
            }
        }

        /// <summary>
        /// Creates an empty user.config file...looks like the one MS creates.  
        /// This could be overkill a simple key/value pairing would probably do.
        /// </summary>
        private void CreateEmptyConfig()
        {
            var doc = new XDocument();
            var declaration = new XDeclaration("1.0", "utf-8", "true");
            var config = new XElement(CONFIG);
            var userSettings = new XElement(USER_SETTINGS);
            var group = new XElement(typeof(Properties.Settings).FullName);
            userSettings.Add(group);
            config.Add(userSettings);
            doc.Add(config);
            doc.Declaration = declaration;
            doc.Save(UserConfigPath); 
        }

        /// <summary>
        /// Saves the in memory dictionary to the user config file
        /// </summary>
        private void SaveValuesToFile()
        {
            //load the current xml from the file.
            var import = XDocument.Load(UserConfigPath);

            //get the settings group (e.g. <Company.Project.Desktop.Settings>)
            var settingsSection = import.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName);

            //iterate though the dictionary, either updating the value or adding the new setting.
            foreach (var entry in SettingsDictionary)
            {
                var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
                if (setting == null) //this can happen if a new setting is added via the .settings designer.
                {
                    var newSetting = new XElement(SETTING);
                    newSetting.Add(new XAttribute(NAME, entry.Value.name));
                    newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
                    newSetting.Value = (entry.Value.value ?? String.Empty);
                    settingsSection.Add(newSetting);
                }
                else //update the value if it exists.
                {
                    setting.Value = (entry.Value.value ?? String.Empty);
                }
            }
            import.Save(UserConfigPath);
        }

        /// <summary>
        /// The setting key this is returning must set before the settings are used.
        /// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
        /// </summary>
        private string UserConfigPath
        {
            get
            {
                return Properties.Settings.Default.SettingsKey;
            }

        }

        /// <summary>
        /// In memory storage of the settings values
        /// </summary>
        private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

        /// <summary>
        /// Helper struct.
        /// </summary>
        internal struct SettingStruct
        {
            internal string name;
            internal string serializeAs;
            internal string value;
        }

        bool _loaded;
    }
}    

答案 1 :(得分:7)

您必须实施自己的SettingsProvider来自定义路径。

请参阅此Client Settings FAQ

  问:为什么路径如此模糊?有没有办法改变/定制它?

     

答:路径构建算法必须满足安全性,隔离性和鲁棒性方面的某些严格要求。虽然我们尝试通过使用友好的,应用程序提供的字符串尽可能容易地发现路径,但是不可能保持路径完全简单而不会遇到与其他应用程序冲突,欺骗等问题。

     

LocalFileSettingsProvider不提供更改存储设置的文件的方法。请注意,提供程序本身并不首先确定配置文件位置 - 它是配置系统。如果由于某种原因需要将设置存储在其他位置,建议的方法是编写自己的SettingsProvider。这实现起来相当简单,您可以在.NET 2.0 SDK中找到演示如何执行此操作的示例。但请记住,您可能遇到上述相同的隔离问题。

答案 2 :(得分:1)

以下是创建自定义设置类的更简单,更简洁的替代方法:更改应用的证据,以便“url”部分是常量,而不是基于可执行文件的位置。为此,您需要在程序启动时修改默认的AppDomain。有两个部分:设置app.config以使用AppDomainManager,以及创建AppDomainManager和HostSecurityManager以自定义Url证据。听起来很复杂,但它比创建自定义设置类简单得多。这仅适用于未签名的程序集。如果您有签名的程序集,它将使用该证据而不是Url。但好消息是,无论如何,您的路径将始终保持不变(只要签名密钥不会改变)。

您可以复制下面的代码,只需替换YourAppName位。

DefaultAppDomainManager.cs:

using System;
using System.Security;
using System.Security.Policy;

namespace YourAppName
{
    /// <summary>
    /// A least-evil (?) way of customizing the default location of the application's user.config files.
    /// </summary>
    public class CustomEvidenceHostSecurityManager : HostSecurityManager
    {
        public override HostSecurityManagerOptions Flags
        {
            get
            {
                return HostSecurityManagerOptions.HostAssemblyEvidence;
            }
        }

        public override Evidence ProvideAssemblyEvidence(System.Reflection.Assembly loadedAssembly, Evidence inputEvidence)
        {
            if (!loadedAssembly.Location.EndsWith("YourAppName.exe"))
                return base.ProvideAssemblyEvidence(loadedAssembly, inputEvidence);

            // override the full Url used in Evidence to just "YourAppName.exe" so it remains the same no matter where the exe is located
            var zoneEvidence = inputEvidence.GetHostEvidence<Zone>();
            return new Evidence(new EvidenceBase[] { zoneEvidence, new Url("YourAppName.exe") }, null);
        }
    }

    public class DefaultAppDomainManager : AppDomainManager
    {
        private CustomEvidenceHostSecurityManager hostSecurityManager;
        public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
        {
            base.InitializeNewDomain(appDomainInfo);

            hostSecurityManager = new CustomEvidenceHostSecurityManager();
        }

        public override HostSecurityManager HostSecurityManager
        {
            get
            {
                return hostSecurityManager;
            }
        }
    }
}

app.config excerpt:

<runtime>
    <appDomainManagerType value="YourAppName.DefaultAppDomainManager" />
    <appDomainManagerAssembly value="DefaultAppDomainManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</runtime>

答案 3 :(得分:0)

以Chucks为基础的优秀答案:

基于 Settings.Designer.cs 实施新的部分类,因此设置设计器不会在进行更改时清除该属性:

namespace Worker.Properties
{
    [System.Configuration.SettingsProvider(
        typeof(SettingsProviders.DllFileSettingsProvider<Settings>))]
    internal sealed partial class Settings
    {

    }
}

我创建了以下可以放在外部项目中的类。它允许配置为 project.dll.config 。通过继承和覆盖 ConfigPath 允许您喜欢的任何路径。我还添加了对阅读 System.Collections.Specialized.StringCollection 的支持。 此版本适用于应用程序设置。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;


//http://stackoverflow.com/questions/2265271/custom-path-of-the-user-config
namespace SettingsProviders
{
    public  class DllFileSettingsProvider<Properties_Settings> : SettingsProvider where Properties_Settings : new() 
    {
        const string NAME = "name";
        const string SERIALIZE_AS = "serializeAs";
        const string CONFIG = "configuration";
        const string APPLICATION_SETTINGS = "applicationSettings";
        const string SETTING = "setting";

        /// <summary>
        /// Loads the file into memory.
        /// </summary>
        public DllFileSettingsProvider()
        {
            SettingsDictionary = new Dictionary<string, SettingStruct>();

        }

        /// <summary>
        /// Override.
        /// </summary>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(ApplicationName, config);
        }

        /// <summary>
        /// Override.
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
            }
            set
            {
                //do nothing
            }
        }

        /// <summary>
        /// Must override this, this is the bit that matches up the designer properties to the dictionary values
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            //load the file
            if (!_loaded)
            {
                _loaded = true;
                LoadValuesFromFile();
            }

            //collection that will be returned.
            SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

            //itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
            foreach (SettingsProperty setting in collection)
            {
                SettingsPropertyValue value = new SettingsPropertyValue(setting);
                value.IsDirty = false;

                //need the type of the value for the strong typing
                var t = Type.GetType(setting.PropertyType.FullName);

                if (setting.PropertyType == typeof(System.Collections.Specialized.StringCollection))
                {
                    var xml = SettingsDictionary[setting.Name].value;
                    var stringReader = new System.IO.StringReader(xml);
                    var xmlreader = System.Xml.XmlReader.Create(stringReader);
                    var ser = new System.Xml.Serialization.XmlSerializer(typeof(System.Collections.Specialized.StringCollection));
                    var obj = ser.Deserialize(xmlreader);
                    var col = (System.Collections.Specialized.StringCollection)obj;
                    value.PropertyValue = col;
                }
                else if (SettingsDictionary.ContainsKey(setting.Name))
                {
                    value.SerializedValue = SettingsDictionary[setting.Name].value;
                    value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
                }
                else //use defaults in the case where there are no settings yet
                {
                    value.SerializedValue = setting.DefaultValue;
                    value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
                }

                values.Add(value);
            }
            return values;
        }

        /// <summary>
        /// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            //grab the values from the collection parameter and update the values in our dictionary.
            foreach (SettingsPropertyValue value in collection)
            {
                var setting = new SettingStruct()
                {
                    value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
                    name = value.Name,
                    serializeAs = value.Property.SerializeAs.ToString()
                };

                if (!SettingsDictionary.ContainsKey(value.Name))
                {
                    SettingsDictionary.Add(value.Name, setting);
                }
                else
                {
                    SettingsDictionary[value.Name] = setting;
                }
            }

            //now that our local dictionary is up-to-date, save it to disk.
            SaveValuesToFile();
        }

        /// <summary>
        /// Loads the values of the file into memory.
        /// </summary>
        private void LoadValuesFromFile()
        {
            if (!File.Exists(ConfigPath))
            {
                //if the config file is not where it's supposed to be create a new one.
                throw new Exception("Config file not found: " + ConfigPath);
            }

            //load the xml
            var configXml = XDocument.Load(ConfigPath);

            //get all of the <setting name="..." serializeAs="..."> elements.
            var settingElements = configXml.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName).Elements(SETTING);

            //iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
            //using "String" as default serializeAs...just in case, no real good reason.
            foreach (var element in settingElements)
            {

                var newSetting = new SettingStruct()
                {
                    name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
                    serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value ,
                    value = element.Value ?? String.Empty
                };

                if (newSetting.serializeAs == "Xml")
                {
                    var e = (XElement)element.Nodes().First();
                    newSetting.value = e.LastNode.ToString() ?? String.Empty;
                };

                SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
            }
        }

        /// <summary>
        /// Creates an empty user.config file...looks like the one MS creates.  
        /// This could be overkill a simple key/value pairing would probably do.
        /// </summary>
        private void CreateEmptyConfig()
        {
            var doc = new XDocument();
            var declaration = new XDeclaration("1.0", "utf-8", "true");
            var config = new XElement(CONFIG);
            var userSettings = new XElement(APPLICATION_SETTINGS);
            var group = new XElement(typeof(Properties_Settings).FullName);
            userSettings.Add(group);
            config.Add(userSettings);
            doc.Add(config);
            doc.Declaration = declaration;
            doc.Save(ConfigPath);
        }

        /// <summary>
        /// Saves the in memory dictionary to the user config file
        /// </summary>
        private void SaveValuesToFile()
        {
            //load the current xml from the file.
            var import = XDocument.Load(ConfigPath);

            //get the settings group (e.g. <Company.Project.Desktop.Settings>)
            var settingsSection = import.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName);

            //iterate though the dictionary, either updating the value or adding the new setting.
            foreach (var entry in SettingsDictionary)
            {
                var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
                if (setting == null) //this can happen if a new setting is added via the .settings designer.
                {
                    var newSetting = new XElement(SETTING);
                    newSetting.Add(new XAttribute(NAME, entry.Value.name));
                    newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
                    newSetting.Value = (entry.Value.value ?? String.Empty);
                    settingsSection.Add(newSetting);
                }
                else //update the value if it exists.
                {
                    setting.Value = (entry.Value.value ?? String.Empty);
                }
            }
            import.Save(ConfigPath);
        }

        /// <summary>
        /// The setting key this is returning must set before the settings are used.
        /// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
        /// </summary>
        public virtual string ConfigPath
        {
            get
            {
                var name = new Properties_Settings().GetType().Module.Name + ".config";
                if (System.IO.File.Exists(name)==false)
                {
                    System.Diagnostics.Trace.WriteLine("config file NOT found:" + name);
                }
                System.Diagnostics.Trace.WriteLine("config file found:" + name);

                return name;
               // return Properties.Settings.Default.SettingsKey;
            }

        }

        /// <summary>
        /// In memory storage of the settings values
        /// </summary>
        internal Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

        /// <summary>
        /// Helper struct.
        /// </summary>
        internal struct SettingStruct
        {
            internal string name;
            internal string serializeAs;
            internal string value;
        }

        bool _loaded;
    }
}

通过添加构建后事件,确保依赖项目包含 project.dll.config 文件:

copy $(SolutionDir)Worker\$(OutDir)Worker.dll.config $(TargetDir) /y