gpt4 book ai didi

c# - 如何更改.NET应用程序的预定义userconfig目录?

转载 作者:太空宇宙 更新时间:2023-11-03 18:24:34 26 4
gpt4 key购买 nike

当前,我的应用程序的用户设置存储在此默认目录中:

C:\Users\{User Name}\AppData\Roaming\{Company Name}\{Assembly Name}.vshos_Url_{Hash}\{Assembly Version}


我知道默认Microsoft命名规则的含义,我的问题是:如何在执行时更改defaut文件夹或通过修改appconfig文件?

我的意图是只能处理将应用程序的用户设置保存到的目录,例如,我想将用户设置文件保存在以下目录中:

C:\Users\{User Name}\AppData\Roaming\{Assembly Name}


我知道这是可以实现的,因为我已经看到很多.NET应用程序可以将其userconfig文件存储在自定义漫游文件夹中,该文件夹不遵循Microsoft默认规则以及未处理的哈希和其他令人讨厌的命名规则。

最佳答案

存在该命名约定,以便NET可以确保已加载正确的设置。由于您已放弃了对NET Framework / VB应用程序框架的设置进行管理的控制,因此它还负责确保应用程序正在加载正确的设置集。在这种情况下,证据散列用于将一个WindowsApplication1与另一个唯一地标识。

I know this is possible to acchieve, because I've seen much .NET applications that can store its userconfig file in a custom Roaming folder

有可能,但我不确定所有内容是否都符合您的结论。我非常严重地怀疑,许多应用程序使用自定义设置类可以更轻松地将XML文件保存到该位置时,会麻烦地实现自定义提供程序。

简单的解决方案

编写自己的用户选项类,然后自己进行序列化。例如,可以使用Shared / static方法以很少的代码反序列化类(这恰好使用JSON):



Friend Shared Function Load() As UserOptions
' create instance for default on new install
Dim u As New UserOptions

If File.Exists(filePath) Then
' filepath can be anywhere you have access to!
Dim jstr = File.ReadAllText(filePath)
If String.IsNullOrEmpty(jstr) = False Then
u = JsonConvert.DeserializeObject(Of UserOptions)(jstr)
End If
End If

Return u
End Function


实现它的应用程序:

UOpt = UserOptions.Load()


在专业人士中,您可以完全控制文件的保存位置,并且可以使用任何喜欢的序列化程序。最重要的是,它很简单-比下面介绍的代码少得多。

缺点是使用它的代码必须手动加载和保存它们(在Application事件中很容易处理),并且没有花哨的设计器。

漫长而曲折的道路:自定义设置提供商

自定义 SettingsProvider允许您更改处理,保存和加载设置的方式,包括更改文件夹位置。

这个问题只集中在更改文件位置上。问题在于,应用程序没有一种(简洁,便捷的方式)与您的 SettingsProvider进行对话以指定文件夹的方法。提供者需要能够在内部进行工作,当然必须保持一致。

除了更改所使用的文件夹名称之外,大多数人还希望做更多的事情。例如,在游戏中,我使用了一个SQLite数据库来代替XML,该数据库镜像了代码使用的结构。这使得加载本地和正确的漫游值非常容易。如果始终采用这种方法,则可以大大简化代码,甚至可以简化整个升级过程。因此,该提供商考虑了一些更广泛的需求。

即使您只想更改文件名,也有两个关键注意事项:

本地与漫游

编码提供程序以始终存储在 AppData\Roaming中,但是写不合格的本地设置将是不负责任的。区分它们是不应该为了消除文件夹名称中的证据哈希而牺牲的功能。

注意:可以将每个 Setting设置为 RoamingLocal值:在“设置编辑器”中选择“设置”,打开“属性”窗格-将 Roaming更改为True。

似乎在(很少)关于自定义 SettingsProvider的几个问题中已经达成共识,以将本地和漫游保存到同一文件中的不同部分。这非常有意义-比从2个文件加载更简单-因此使用的XML结构为:

<configuration>
<CommonShared>
<setting name="FirstRun">True</setting>
<setting name="StartTime">15:32:18</setting>
...
</CommonShared>
<MACHINENAME_A>
<setting name="MainWdwLocation">98, 480</setting>
<setting name="myGuid">d62eb904-0bb9-4897-bb86-688d974db4a6</setting>
<setting name="LastSaveFolder">C:\Folder ABC</setting>
</MACHINENAME_A>
<MACHINENAME_B>
<setting name="MainWdwLocation">187, 360</setting>
<setting name="myGuid">a1f8d5a5-f7ec-4bf9-b7b8-712e80c69d93</setting>
<setting name="LastSaveFolder">C:\Folder XYZ</setting>
</MACHINENAME_B>
</configuration>


漫游项存储在使用它们的MachineName命名的节中。保留 <NameSpace>.My.MySettings节点可能会有一些价值,但是我不确定它的作用是什么。

由于未使用 SerializeAs元素,因此删除了它。

版本号

调用 My.Settings.Upgrade不会发生任何事情。即使它是 Settings方法,但实际上是 ApplicationSettingsBase中的内容,因此不涉及您的提供程序。

结果,如果您自动增加最后一个元素,则使用完整版本字符串作为文件夹的一部分会导致问题。简单的重建将创建一个新文件夹,并使旧设置丢失并孤立。当没有当前文件时,也许您可​​以查找并加载先前版本的值。然后也许删除该旧文件/文件夹,因此始终只有一组可能的旧设置。随意添加面条和合并代码。

为了仅更改数据存储文件夹的主要目的,我删除了版本文件夹段。使用全局提供程序时,代码会自动累积设置。已删除的设置不会“泄漏”到应用程序中,因为NET不会要求它提供值。唯一的问题是XML中将有一个值。

我添加了清除这些代码。如果您以后再使用其他类型的设置名称,则可以防止出现问题。例如,旧保存的 Foo值为 Decimal不能与新的 Foo用作 Size。如果您从根本上更改类型,事情仍然会很糟糕。不要那样做



这个答案 Custom path of the user.config为自定义提供程序提供了一个很好的起点。它有一些问题,缺少一些东西,但是提供了任何提供程序特有的一些步骤和样板代码的快速入门指南。由于许多人可能需要在此处进一步修改提供程序,因此可能值得阅读(并赞成)。

这里的代码从该答案中借用了一些东西,并且:


添加各种改进
提供自定义路径
检测设置为漫游的设置
文件中的“本地和漫游”部分
正确处理 PointSize等复杂类型
检测并修剪删除的设置
在VB中


1.设定

在大多数情况下,您无法以增量方式编写/调试此文件-在完成之前几乎没有用。


添加对 System.Configuration的引用
向您的项目添加新类


例:

Imports System.Configuration 
Public Class CustomSettingsProvider
Inherits SettingsProvider
End Class


接下来,转到“设置”设计器并添加一些测试设置。将某些标记为“漫游”以进行完整测试。然后单击此处显示的 <> View Code按钮:

enter image description here
每个人都喜欢写意圈子!

显然,有两种方法可以实现自定义提供程序。此处的代码将使用您的代码代替 My.MySettings。您还可以通过在“属性”窗格中键入提供程序名称来按设置指定自定义提供程序,然后跳过此步骤的其余部分。我没有对此进行测试,但是它应该是这样工作的。

为了使用新的设置提供程序“您”编写,需要使用一个属性将其与 MySettings关联:

Imports System.Configuration 

<SettingsProvider(GetType(ElectroZap.CustomSettingsProvider))>
Partial Friend NotInheritable Class MySettings
End Class


顺便说一句,“ ElektroZap”是您的根NameSpace,而“ ElektroApp”是您的应用程序名称。可以将构造函数中的代码更改为使用产品名称或模块名称。

我们已经完成了该文件。保存并关闭它。

2. SettingsProvider

首先,请注意,此CustomProvider是通用的,只需将其指定为 SettingsProvider即可与任何应用一起使用。但这实际上只做两件事:


使用自定义路径
将本地和漫游设置合并到一个文件中


通常,在求助于自定义提供程序之前,一个待办事项列表会更长,因此对于许多人来说,这可能只是“其他事物”的起点。请记住,某些更改可能使其特定于项目。



添加的功能之一是支持更复杂的类型,例如 PointSize。这些被序列化为不变字符串,以便可以解析它们。这意味着什么:

Console.WriteLine(myPoint.ToString())


结果 {X=64, Y=22}无法直接转换回,并且 Point缺少 Parse/TryParse方法。使用不变的字符串形式 64,22可以将其转换回正确的类型。原始的链接代码简单地使用了:

Convert.ChangeType(setting.DefaultValue, t);


这将适用于简单类型,但不适用于 PointFont等。我不能肯定地记得,但是我认为这是使用 SettingsPropertyValue.Value而不是 .SerializedValue的简单错误。

3.守则

Public Class CustomSettingsProvider
Inherits SettingsProvider

' data we store for each item
Friend Class SettingsItem
Friend Name As String
'Friend SerializeAs As String ' not needed
Friend Value As String
Friend Roamer As Boolean
Friend Remove As Boolean ' mutable
'Friend VerString As String ' ToDo (?)
End Class

' used for node name
Private thisMachine As String

' loaded XML config
'Private xDoc As XDocument
Private UserConfigFilePath As String = ""
Private myCol As Dictionary(Of String, SettingsItem)


Public Sub New()
myCol = New Dictionary(Of String, SettingsItem)

Dim asm = Assembly.GetExecutingAssembly()
Dim verInfo = FileVersionInfo.GetVersionInfo(asm.Location)
Dim Company = verInfo.CompanyName
' product name may have no relation to file name...
Dim ProdName = verInfo.ProductName

' use this for assembly file name:
Dim modName = Path.GetFileNameWithoutExtension(asm.ManifestModule.Name)
' dont use FileVersionInfo;
' may want to omit the last element
'Dim ver = asm.GetName.Version


' uses `SpecialFolder.ApplicationData`
' since it will store Local and Roaming val;ues
UserConfigFilePath = Path.Combine(GetFolderPath(SpecialFolder.ApplicationData),
Company, modName,
"user.config")

' "CFG" prefix prevents illegal XML,
' the FOO suffix is to emulate a different machine
thisMachine = "CFG" & My.Computer.Name & "_FOO"

End Sub

' boilerplate
Public Overrides Property ApplicationName As String
Get
Return Assembly.GetExecutingAssembly().ManifestModule.Name
End Get
Set(value As String)

End Set
End Property

' boilerplate
Public Overrides Sub Initialize(name As String, config As Specialized.NameValueCollection)
MyBase.Initialize(ApplicationName, config)
End Sub

' conversion helper in place of a 'Select Case GetType(foo)'
Private Shared Conversion As Func(Of Object, Object)

Public Overrides Function GetPropertyValues(context As SettingsContext,
collection As SettingsPropertyCollection) As SettingsPropertyValueCollection
' basically, create a Dictionary entry for each setting,
' store the converted value to it
' Add an entry when something is added
'
' This is called the first time you get a setting value
If myCol.Count = 0 Then
LoadData()
End If

Dim theSettings = New SettingsPropertyValueCollection()
Dim tValue As String = ""

' SettingsPropertyCollection is like a Shopping list
' of props that VS/VB wants the value for
For Each setItem As SettingsProperty In collection
Dim value As New SettingsPropertyValue(setItem)
value.IsDirty = False

If myCol.ContainsKey(setItem.Name) Then
value.SerializedValue = myCol(setItem.Name)
tValue = myCol(setItem.Name).Value
Else
value.SerializedValue = setItem.DefaultValue
tValue = setItem.DefaultValue.ToString
End If

' ToDo: Enums will need an extra step
Conversion = Function(v) TypeDescriptor.
GetConverter(setItem.PropertyType).
ConvertFromInvariantString(v.ToString())

value.PropertyValue = Conversion(tValue)
theSettings.Add(value)
Next

Return theSettings
End Function

Public Overrides Sub SetPropertyValues(context As SettingsContext,
collection As SettingsPropertyValueCollection)
' this is not called when you set a new value
' rather, NET has one or more changed values that
' need to be saved, so be sure to save them to disk
Dim names As List(Of String) = myCol.Keys.ToList
Dim sItem As SettingsItem

For Each item As SettingsPropertyValue In collection
sItem = New SettingsItem() With {
.Name = item.Name,
.Value = item.SerializedValue.ToString(),
.Roamer = IsRoamer(item.Property)
}
'.SerializeAs = item.Property.SerializeAs.ToString(),

names.Remove(item.Name)
If myCol.ContainsKey(sItem.Name) Then
myCol(sItem.Name) = sItem
Else
myCol.Add(sItem.Name, sItem)
End If
Next

' flag any no longer used
' do not use when specifying a provider per-setting!
For Each s As String In names
myCol(s).Remove = True
Next

SaveData()
End Sub

' detect if a setting is tagged as Roaming
Private Function IsRoamer(prop As SettingsProperty) As Boolean
Dim r = prop.Attributes.
Cast(Of DictionaryEntry).
FirstOrDefault(Function(q) TypeOf q.Value Is SettingsManageabilityAttribute)

Return r.Key IsNot Nothing
End Function

Private Sub LoadData()
' load from disk
If File.Exists(UserConfigFilePath) = False Then
CreateNewConfig()
End If

Dim xDoc = XDocument.Load(UserConfigFilePath)
Dim items As IEnumerable(Of XElement)
Dim item As SettingsItem

items = xDoc.Element(CONFIG).
Element(COMMON).
Elements(SETTING)

' load the common settings
For Each xitem As XElement In items
item = New SettingsItem With {.Name = xitem.Attribute(ITEMNAME).Value,
.Roamer = False}
'.SerializeAs = xitem.Attribute(SERIALIZE_AS).Value,

item.Value = xitem.Value
myCol.Add(item.Name, item)
Next

' First check if there is a machine node
If xDoc.Element(CONFIG).Element(thisMachine) Is Nothing Then
' nope, add one
xDoc.Element(CONFIG).Add(New XElement(thisMachine))
End If
items = xDoc.Element(CONFIG).
Element(thisMachine).
Elements(SETTING)

For Each xitem As XElement In items
item = New SettingsItem With {.Name = xitem.Attribute(ITEMNAME).Value,
.Roamer = True}
'.SerializeAs = xitem.Attribute(SERIALIZE_AS).Value,

item.Value = xitem.Value
myCol.Add(item.Name, item)
Next
' we may have changed the XDOC, by adding a machine node
' save the file
xDoc.Save(UserConfigFilePath)
End Sub

Private Sub SaveData()
' write to disk

Dim xDoc = XDocument.Load(UserConfigFilePath)
Dim roamers = xDoc.Element(CONFIG).
Element(thisMachine)

Dim locals = xDoc.Element(CONFIG).
Element(COMMON)

Dim item As XElement
Dim section As XElement

For Each kvp As KeyValuePair(Of String, SettingsItem) In myCol
If kvp.Value.Roamer Then
section = roamers
Else
section = locals
End If

item = section.Elements().
FirstOrDefault(Function(q) q.Attribute(ITEMNAME).Value = kvp.Key)

If item Is Nothing Then
' found a new item
Dim newItem = New XElement(SETTING)
newItem.Add(New XAttribute(ITEMNAME, kvp.Value.Name))
'newItem.Add(New XAttribute(SERIALIZE_AS, kvp.Value.SerializeAs))
newItem.Value = If(String.IsNullOrEmpty(kvp.Value.Value), "", kvp.Value.Value)
section.Add(newItem)
Else
If kvp.Value.Remove Then
item.Remove()
Else
item.Value = If(String.IsNullOrEmpty(kvp.Value.Value), "", kvp.Value.Value)
End If
End If

Next
xDoc.Save(UserConfigFilePath)

End Sub

' used in the XML
Const CONFIG As String = "configuration"
Const SETTING As String = "setting"
Const COMMON As String = "CommonShared"
Const ITEMNAME As String = "name"
'Const SERIALIZE_AS As String = "serializeAs"

' https://stackoverflow.com/a/11398536
Private Sub CreateNewConfig()
Dim fpath = Path.GetDirectoryName(UserConfigFilePath)
Directory.CreateDirectory(fpath)

Dim xDoc = New XDocument
xDoc.Declaration = New XDeclaration("1.0", "utf-8", "true")
Dim cfg = New XElement(CONFIG)

cfg.Add(New XElement(COMMON))
cfg.Add(New XElement(thisMachine))

xDoc.Add(cfg)
xDoc.Save(UserConfigFilePath)
End Sub

End Class


这是很多代码,只是为了从路径中消除证据哈希,但这是MS建议的。这也可能是唯一的方法:获取文件的 ConfigurationManager中的属性是只读的,并由代码支持。

结果:

实际的XML如前面显示的本地/公共和计算机特定部分所示。我使用了几个不同的应用程序名称,并测试了各种内容:

enter image description here

忽略版本部分。如前所述,已被删除。否则,文件夹是正确的-如上所述,在AppName段中,您可以使用一些选项。

重要笔记


除非且直到相关应用程序访问Settings属性,否则不会调用提供程序中的Load方法。
加载后,无论代码是否更改,应用程序结束时(使用VB Framework)将调用Save方法。
NET似乎只保存与默认值不同的设置。使用自定义提供程序时,所有值都将 IsDirty标记为true,将 UsingDefaultValue标记为false。
如果/在加载时,将返回所有值,并且NET会在应用程序的整个生命周期内从该集合中获取值


我主要关心的是类型和本地/漫游支持的正确转换。我没有检查所有可能的类型。特别是自定义类型和枚举(我知道枚举将需要额外的处理)。



值得注意的是,使用 DataTable可以使此过程更加简单。您不需要 SettingsItem类,集合,也不需要XDoc(使用 .WriteXML / .ReadXml)。创建和组织XElement的所有代码也都消失了。

生成的XML文件是不同的,但这仅是表单跟随功能。总共可以删除大约60行代码,这很简单。

资源资源


SettingsProvider Class
MSDN blog entry about Settings
Stackoverflow: Custom path of the user.config“ Chuck”使用更好命名的暂存变量
Stackoverflow: MachineName problems in XML
在完成之前,我没有看到这篇CodeProject文章: Creating a Custom Settings Provider这似乎是一种基于每个设置使用自定义提供程序的不同方法。

关于c# - 如何更改.NET应用程序的预定义userconfig目录?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38429461/

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