gpt4 book ai didi

c# - 当存储的数据类型发生变化时,如何升级 Settings.settings?

转载 作者:太空狗 更新时间:2023-10-30 01:11:52 27 4
gpt4 key购买 nike

我有一个在用户设置中存储对象集合的应用程序,并通过 ClickOnce 进行部署。下一个版本的应用程序对存储的对象进行了修改。例如,以前版本的类型是:

public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

新版本的类型是:

public class Person
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}

显然,ApplicationSettingsBase.Upgrade 不知道如何执行升级,因为 Age 需要使用 (age) => DateTime.Now.AddYears(-age),因此只会升级 Name 属性,而 DateOfBirth 的值将仅为 Default(DateTime)。

所以我想提供一个升级例程,通过覆盖 ApplicationSettingsBase.Upgrade,它会根据需要转换值。但是我遇到了三个问题:

  1. 当尝试使用 ApplicationSettingsBase.GetPreviousVersion 访问以前版本的值时,返回值将是当前版本的对象,它没有 Age 属性并且有一个空的 DateOfBirth 属性 (因为它无法将 Age 反序列化为 DateOfBirth)。
  2. 我无法找到一种方法来确定我要升级的应用程序版本。如果有一个从v1到v2的升级过程和一个从v2到v3的过程,如果用户是从v1升级到v3,我需要依次运行两个升级过程,但是如果用户是从v2升级,我只需要运行第二个升级程序。
  3. 即使我知道应用程序的以前版本是什么,并且我可以在他们以前的结构中访问用户设置(比如通过获取原始 XML 节点),如果我想链式升级过程(如问题中所述2),我应该在哪里存储中间值?如果从 v2 升级到 v3,升级过程将从 v2 读取旧值并将它们直接写入 v3 中的强类型设置包装类。但是,如果从 v1 升级,我应该把 v1 到 v2 升级过程的结果放在哪里,因为应用程序只有 v3 的包装类?

我认为如果升级代码直接在 user.config 文件上执行转换,我可以避免所有这些问题,但我发现没有简单的方法来获取以前版本的 user.config 的位置,因为 LocalFileSettingsProvider.GetPreviousConfigFileName(bool) 是私有(private)方法。

有没有人有用于升级在应用程序版本之间更改类型的用户设置的 ClickOnce 兼容解决方案,最好是可以支持跳过版本的解决方案(例如,从 v1 升级到 v3 而无需用户安装 v2)?

最佳答案

我最终使用了一种更复杂的方式来进行升级,即从用户设置文件中读取原始 XML,然后运行一系列升级例程,将数据重构为下一个新版本中的预期方式。此外,由于我在 ClickOnce 的 ApplicationDeployment.CurrentDeployment.IsFirstRun 属性中发现的错误(您可以查看 Microsoft Connect 反馈 here ),我不得不使用自己的 IsFirstRun 设置来了解何时执行升级。整个系统对我来说工作得很好(但由于一些非常顽固的障碍,它是用血和汗水制成的)。忽略注释标记我的应用程序特定的内容,而不是升级系统的一部分。

using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Xml;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Reflection;
using System.Text;
using MyApp.Forms;
using MyApp.Entities;

namespace MyApp.Properties
{
public sealed partial class Settings
{
private static readonly Version CurrentVersion = Assembly.GetExecutingAssembly().GetName().Version;

private Settings()
{
InitCollections(); // ignore
}

public override void Upgrade()
{
UpgradeFromPreviousVersion();
BadDataFiles = new StringCollection(); // ignore
UpgradePerformed = true; // this is a boolean value in the settings file that is initialized to false to indicate that settings file is brand new and requires upgrading
InitCollections(); // ignore
Save();
}

// ignore
private void InitCollections()
{
if (BadDataFiles == null)
BadDataFiles = new StringCollection();

if (UploadedGames == null)
UploadedGames = new StringDictionary();

if (SavedSearches == null)
SavedSearches = SavedSearchesCollection.Default;
}

private void UpgradeFromPreviousVersion()
{
try
{
// This works for both ClickOnce and non-ClickOnce applications, whereas
// ApplicationDeployment.CurrentDeployment.DataDirectory only works for ClickOnce applications
DirectoryInfo currentSettingsDir = new FileInfo(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath).Directory;

if (currentSettingsDir == null)
throw new Exception("Failed to determine the location of the settings file.");

if (!currentSettingsDir.Exists)
currentSettingsDir.Create();

// LINQ to Objects for .NET 2.0 courtesy of LINQBridge (linqbridge.googlecode.com)
var previousSettings = (from dir in currentSettingsDir.Parent.GetDirectories()
let dirVer = new { Dir = dir, Ver = new Version(dir.Name) }
where dirVer.Ver < CurrentVersion
orderby dirVer.Ver descending
select dirVer).FirstOrDefault();

if (previousSettings == null)
return;

XmlElement userSettings = ReadUserSettings(previousSettings.Dir.GetFiles("user.config").Single().FullName);
userSettings = SettingsUpgrader.Upgrade(userSettings, previousSettings.Ver);
WriteUserSettings(userSettings, currentSettingsDir.FullName + @"\user.config", true);

Reload();
}
catch (Exception ex)
{
MessageBoxes.Alert(MessageBoxIcon.Error, "There was an error upgrading the the user settings from the previous version. The user settings will be reset.\n\n" + ex.Message);
Default.Reset();
}
}

private static XmlElement ReadUserSettings(string configFile)
{
// PreserveWhitespace required for unencrypted files due to https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=352591
var doc = new XmlDocument { PreserveWhitespace = true };
doc.Load(configFile);
XmlNode settingsNode = doc.SelectSingleNode("configuration/userSettings/MyApp.Properties.Settings");
XmlNode encryptedDataNode = settingsNode["EncryptedData"];
if (encryptedDataNode != null)
{
var provider = new RsaProtectedConfigurationProvider();
provider.Initialize("userSettings", new NameValueCollection());
return (XmlElement)provider.Decrypt(encryptedDataNode);
}
else
{
return (XmlElement)settingsNode;
}
}

private static void WriteUserSettings(XmlElement settingsNode, string configFile, bool encrypt)
{
XmlDocument doc;
XmlNode MyAppSettings;

if (encrypt)
{
var provider = new RsaProtectedConfigurationProvider();
provider.Initialize("userSettings", new NameValueCollection());
XmlNode encryptedSettings = provider.Encrypt(settingsNode);
doc = encryptedSettings.OwnerDocument;
MyAppSettings = doc.CreateElement("MyApp.Properties.Settings").AppendNewAttribute("configProtectionProvider", provider.GetType().Name);
MyAppSettings.AppendChild(encryptedSettings);
}
else
{
doc = settingsNode.OwnerDocument;
MyAppSettings = settingsNode;
}

doc.RemoveAll();
doc.AppendNewElement("configuration")
.AppendNewElement("userSettings")
.AppendChild(MyAppSettings);

using (var writer = new XmlTextWriter(configFile, Encoding.UTF8) { Formatting = Formatting.Indented, Indentation = 4 })
doc.Save(writer);
}

private static class SettingsUpgrader
{
private static readonly Version MinimumVersion = new Version(0, 2, 1, 0);

public static XmlElement Upgrade(XmlElement userSettings, Version oldSettingsVersion)
{
if (oldSettingsVersion < MinimumVersion)
throw new Exception("The minimum required version for upgrade is " + MinimumVersion);

var upgradeMethods = from method in typeof(SettingsUpgrader).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
where method.Name.StartsWith("UpgradeFrom_")
let methodVer = new { Version = new Version(method.Name.Substring(12).Replace('_', '.')), Method = method }
where methodVer.Version >= oldSettingsVersion && methodVer.Version < CurrentVersion
orderby methodVer.Version ascending
select methodVer;

foreach (var methodVer in upgradeMethods)
{
try
{
methodVer.Method.Invoke(null, new object[] { userSettings });
}
catch (TargetInvocationException ex)
{
throw new Exception(string.Format("Failed to upgrade user setting from version {0}: {1}",
methodVer.Version, ex.InnerException.Message), ex.InnerException);
}
}

return userSettings;
}

private static void UpgradeFrom_0_2_1_0(XmlElement userSettings)
{
// ignore method body - put your own upgrade code here

var savedSearches = userSettings.SelectNodes("//SavedSearch");

foreach (XmlElement savedSearch in savedSearches)
{
string xml = savedSearch.InnerXml;
xml = xml.Replace("IRuleOfGame", "RuleOfGame");
xml = xml.Replace("Field>", "FieldName>");
xml = xml.Replace("Type>", "Comparison>");
savedSearch.InnerXml = xml;


if (savedSearch["Name"].GetTextValue() == "Tournament")
savedSearch.AppendNewElement("ShowTournamentColumn", "true");
else
savedSearch.AppendNewElement("ShowTournamentColumn", "false");
}
}
}
}
}

使用了以下自定义扩展方法和辅助类:

using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Xml;


namespace MyApp
{
public static class ExtensionMethods
{
public static XmlNode AppendNewElement(this XmlNode element, string name)
{
return AppendNewElement(element, name, null);
}
public static XmlNode AppendNewElement(this XmlNode element, string name, string value)
{
return AppendNewElement(element, name, value, null);
}
public static XmlNode AppendNewElement(this XmlNode element, string name, string value, params KeyValuePair<string, string>[] attributes)
{
XmlDocument doc = element.OwnerDocument ?? (XmlDocument)element;
XmlElement addedElement = doc.CreateElement(name);

if (value != null)
addedElement.SetTextValue(value);

if (attributes != null)
foreach (var attribute in attributes)
addedElement.AppendNewAttribute(attribute.Key, attribute.Value);

element.AppendChild(addedElement);

return addedElement;
}
public static XmlNode AppendNewAttribute(this XmlNode element, string name, string value)
{
XmlAttribute attr = element.OwnerDocument.CreateAttribute(name);
attr.Value = value;
element.Attributes.Append(attr);
return element;
}
}
}

namespace MyApp.Forms
{
public static class MessageBoxes
{
private static readonly string Caption = "MyApp v" + Application.ProductVersion;

public static void Alert(MessageBoxIcon icon, params object[] args)
{
MessageBox.Show(GetMessage(args), Caption, MessageBoxButtons.OK, icon);
}
public static bool YesNo(MessageBoxIcon icon, params object[] args)
{
return MessageBox.Show(GetMessage(args), Caption, MessageBoxButtons.YesNo, icon) == DialogResult.Yes;
}

private static string GetMessage(object[] args)
{
if (args.Length == 1)
{
return args[0].ToString();
}
else
{
var messegeArgs = new object[args.Length - 1];
Array.Copy(args, 1, messegeArgs, 0, messegeArgs.Length);
return string.Format(args[0] as string, messegeArgs);
}

}
}
}

下面的Main方法被用来让系统工作:

[STAThread]
static void Main()
{
// Ensures that the user setting's configuration system starts in an encrypted mode, otherwise an application restart is required to change modes.
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
SectionInformation sectionInfo = config.SectionGroups["userSettings"].Sections["MyApp.Properties.Settings"].SectionInformation;
if (!sectionInfo.IsProtected)
{
sectionInfo.ProtectSection(null);
config.Save();
}

if (Settings.Default.UpgradePerformed == false)
Settings.Default.Upgrade();

Application.Run(new frmMain());
}

我欢迎任何意见、批评、建议或改进。我希望这对某个地方的人有所帮助。

关于c# - 当存储的数据类型发生变化时,如何升级 Settings.settings?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1599945/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com