在 .net 中如何跨不同程序集版本保留 user.config 设置?

165

问题在于每次装上新版应用程序时,程序集版本都会更改,导致所有用户设置被重设为默认值(或者更准确地说,一个名字带有不同版本号的文件夹中创建了一个新的user.config文件)。

如何在升级版本时保留相同的设置,因为使用ini文件或注册表似乎受到反对?

当我们使用ClickOnce时,它似乎可以处理这个问题,所以看起来应该能够解决,但我不确定具体怎么做。


类似问题:半可编辑文件(例如配置文件)和版本控制的最佳实践。 - Allen Rice
不,这是指默认不将文件检入版本控制(或者我这样理解)。这涉及到最终用户的(Windows)用户特定设置。 - Davy8
正是我需要的问题,谢谢 :) - Binary Worrier
我在以下帖子中发布了可能的解决方案:https://dev59.com/4GAg5IYBdhLWcg3wI4LD#47921377 希望能有所帮助! - dontbyteme
我在这个帖子中发布了一个可能的解决方案。希望能有所帮助! - dontbyteme
7个回答

270

ApplicationSettingsBase有一个名为Upgrade的方法,可以迁移先前版本的所有设置。

为了在每次发布新版本应用程序时运行合并,您可以在设置文件中定义一个布尔标志,默认值为true。将其命名为UpgradeRequired或类似名称。

然后,在应用程序启动时,检查标志是否已设置,如果是,则调用Upgrade方法,将标志设置为false并保存配置。

if (Settings.Default.UpgradeRequired)
{
    Settings.Default.Upgrade();
    Settings.Default.UpgradeRequired = false;
    Settings.Default.Save();
}

请前往MSDN了解更多关于升级方法的信息。如果需要进行一些自定义合并,GetPreviousVersion也值得一看。


2
一个小问题,什么构成了新版本?是四个数字中的任何一部分吗?我使用ClickOnce,那是不是不同的东西? - Refracted Paladin
4
UpgradeRequired 应该放在哪种设置下?是 appSettingsuserSettings 还是 applicationSettings?作为 Settings.Settings 上的用户设置,一旦第一次将其更改为 false,它将永远不会再变为 true。新版本不会将该 UpgradeRequired 重置为 True。 - dialex
4
这是一个用户设置,类型为应用程序的设置是只读的。由于设置存储在特定版本的路径中,因此新版本号确实会导致设置重置。 - Leonard Thieu
3
除了“UpgradeRequired”之外,我建议将应用程序的版本作为设置存储。这样可以执行自定义升级转换(即将无效值/有效值转换为除最新版本默认值/相同值以外的其他值)。您可以编写代码,将需要转换的每个适用版本转换为下一个需要它的最低版本,并将代码链接在一起,从而:a)减少最新版本转换代码的复杂性; b)允许潜在的旧转换代码退役。 - Tom
5
我想我已经回答了自己的问题。如果之前版本的设置文件存在,每次应用程序启动时它会将其值复制到最新版本中,这可能不是你想要的! - Hugh Jeffner
显示剩余4条评论

13

当我们每个版本只需要升级一次时,下一个简短的解决方案适用于我。它不需要额外的设置,如UpgradeRequired

if (!ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).HasFile)
    Settings.Default.Upgrade();

谢谢,它在调试期间确实有效。 - Neha

4

我知道已经有一段时间了...

在一个WinForms应用程序中,只需在加载设置之前调用My.Settings.Upgrade()。这将获取最新的设置,无论是当前版本还是以前的版本。


2
以下是我的研究成果,如果其他人在迁移已更改/删除的设置时遇到困难,请参考。基本问题是,如果您在应用程序的新版本中重命名或删除了设置,则GetPreviousVersion()无法正常工作。因此,您需要将设置保留在您的Settings类中,但是添加一些属性/工件以确保您不会在代码其他地方意外使用它,使其过时。在VB.NET中,一个过时的设置示例如下(可以轻松转换为C#):
<UserScopedSetting(),
DebuggerNonUserCode(),
DefaultSettingValue(""),
Obsolete("Do not use this property for any purpose. Use YOUR_NEW_SETTING_NAME instead."),
NoSettingsVersionUpgrade()>
Public Property OldSettingName() As String
  Get
    Throw New NotSupportedException("This property is obsolete")
  End Get
  Set
    Throw New NotSupportedException("This property is obsolete")
  End Set
End Property

请确保将此属性添加到与您的应用程序设置相同的命名空间/类中。在VB.NET中,这个类的名称是MySettings,并且可以在My命名空间中使用。您可以使用部分类功能来防止混淆已过时的设置和当前设置。

感谢jsharrison发布了一篇关于这个问题的优秀文章。您可以在那里阅读更多详细信息。


1
这里有一种方案的变体,将升级逻辑封装到一个抽象类中,该类作为设置类的基类。有些提议的解决方案使用 DefaultSettingsValue 属性来指定一个值,表示未加载先前的设置。我更喜欢使用默认值表示此功能的类型。额外奖励是 DateTime? 会提供有用的调试信息。
public abstract class UserSettingsBase : ApplicationSettingsBase
{
    public UserSettingsBase() : base()
    {
        // Accessing a property attempts to load the settings for this assembly version
        // If LastSaved has no value (default) an upgrade might be needed
        if (LastSaved == null)
        {
            Upgrade();
        }
    }

    [UserScopedSetting]
    public DateTime? LastSaved
    {
        get { return (DateTime?)this[nameof(LastSaved)]; }
        private set { this[nameof(LastSaved)] = value; }
    }

    public override void Save()
    {
        LastSaved = DateTime.Now;
        base.Save();
    }
}

从UserSettingsBase派生:
public class MySettings : UserSettingsBase
{
    [UserScopedSetting]
    public string SomeSetting
    {
        get { return (string)this[nameof(SomeSetting)]; }
        set { this[nameof(SomeSetting)] = value; }
    }

    public MySettings() : base() { }
}

并使用它:

// Existing settings are loaded and upgraded if needed
MySettings settings = new MySettings();
...
settings.SomeSetting = "SomeValue";
...
settings.Save();

0

这是我处理它的方式:

public virtual void LoadSettings(ServiceFileFormBaseSettings settings = null, bool resetSettingsToDefaults = false)
{
    if (settings == null)
            return;

    if (resetSettingsToDefaults)
        settings.Reset();
    else
    {
        settings.Reload();

        if (settings.IsDefault)
            settings.Upgrade();
    }

    this.Size = settings.FormSize;

}

在设置类中,我定义了IsDefault属性:
// SaveSettings always sets this to be FALSE.
// This will have the default value TRUE when first deployed, or immediately after an upgrade.
// When the settings exist, this is false.
//
[UserScopedSettingAttribute()]
[DefaultSettingValueAttribute("true")]
public virtual bool IsDefault
{
    get { return (bool)this["IsDefault"]; }
    set { this["IsDefault"] = value; }
}

在SaveSettings中,我将IsDefault设置为false:
public virtual void SaveSettings(ServiceFileFormBaseSettings settings = null)
{
    if (settings == null) // ignore calls from this base form, if any
        return;

    settings.IsDefault = false;
    settings.FormSize = this.Size;
    settings.Save();
}

0
如果您对user.settings进行的更改是通过编程完成的,那么如何在一个单独的文件中维护(仅)对user.settings的修改,例如user.customized.settings?
您可能仍然希望在user.settings中维护和加载修改后的设置。但是这样,当您安装具有其新版本user.settings的应用程序的新版本时,您可以询问用户是否要通过将它们复制回新的user.settings中来继续使用其修改后的设置。您可以整体导入它们,或者更高级一些,询问用户确认他们想要继续使用哪些设置。
编辑:我太快地阅读了关于程序集版本更准确的部分,导致新的user.settings被安装到新的版本特定目录中。因此,上面的想法可能对您没有帮助,但可能会提供一些思路。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接