forked from dotnet/project-system
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSettingsSerializer.vb
347 lines (299 loc) · 18 KB
/
SettingsSerializer.vb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
' Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
Imports System.IO
Imports System.Xml
Namespace Microsoft.VisualStudio.Editors.SettingsDesigner
''' <summary>
''' Utility class to (de)serialize the contents of a DesignTimeSetting object
''' given a stream reader/writer
''' </summary>
Friend NotInheritable Class SettingsSerializer
Friend Class SettingsSerializerException
Inherits ApplicationException
Public Sub New(message As String)
MyBase.New(message)
End Sub
Public Sub New(message As String, inner As Exception)
MyBase.New(message, inner)
End Sub
End Class
Public Const SettingsSchemaUri As String = "http://schemas.microsoft.com/VisualStudio/2004/01/settings"
Public Const SettingsSchemaUriOLD As String = "uri:settings"
Public Const CultureInvariantVirtualTypeNameConnectionString As String = "(Connection string)"
Public Const CultureInvariantVirtualTypeNameWebReference As String = "(Web Service URL)"
#If USE_SETTINGS_XML_SCHEMA_VALIDATION Then
' We have disabled the schema validation for now - it caused perf problems for a couple of user scenarios
' (i.e. adding a database to an empty project, which in turn adds a connection string to the settings object)
'
' Loading the schema added ~1s which was not acceptable. I have left the code in here in case we find another
' way to load it....
'
' #define:ing USE_SETTINGS_XML_SCHEMA_VALIDATION will re-enable schema validation...
Private Shared s_SchemaLoadFailed As Boolean = False
''' <summary>
''' Demand create an XML Schema instance for .settings files
''' </summary>
Private Shared ReadOnly Property Schema() As System.Xml.Schema.XmlSchema
Get
Static schemaInstance As System.Xml.Schema.XmlSchema
If schemaInstance Is Nothing AndAlso Not s_SchemaLoadFailed Then
Dim SchemaStream As Stream
SchemaStream = GetType(SettingsSerializer).Assembly.GetManifestResourceStream(GetType(SettingsSerializer), "SettingsSchema")
schemaInstance = System.Xml.Schema.XmlSchema.Read(SchemaStream, AddressOf SchemaValidationEventHandler)
End If
Return schemaInstance
End Get
End Property
''' <summary>
''' If we fail to load the schema, things are bad indeed...
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Shared Sub SchemaValidationEventHandler(sender As Object, e As System.Xml.Schema.ValidationEventArgs)
System.Diagnostics.Debug.Fail("Failed to load XML schema from manifest resource stream!")
s_SchemaLoadFailed = True
End Sub
''' <summary>
''' Stores all validation errors from a ValidatingReader
''' </summary>
Private Class ValidationErrorBag
Private m_ValidationErrors As New System.Collections.ArrayList
Friend ReadOnly Property Errors() As System.Collections.ICollection
Get
Return m_ValidationErrors
End Get
End Property
Friend Sub ValidationEventHandler(sender As Object, e As System.Xml.Schema.ValidationEventArgs)
m_ValidationErrors.Add(e)
End Sub
End Class
#End If
''' <summary>
''' Deserialize XML stream of settings
''' </summary>
''' <param name="Settings">Instance to populate</param>
''' <param name="Reader">Text reader on stream containing serialized settings</param>
Public Shared Sub Deserialize(Settings As DesignTimeSettings, Reader As TextReader, getRuntimeValue As Boolean)
Dim XmlDoc As New XmlDocument With {
.XmlResolver = Nothing
}
#If USE_SETTINGS_XML_SCHEMA_VALIDATION Then
Dim ValidationErrors As New ValidationErrorBag
If Schema IsNot Nothing Then
Dim ValidatingReader As New XmlValidatingReader(New XmlTextReader(Reader))
Dim SchemaCol As New System.Xml.Schema.XmlSchemaCollection()
ValidatingReader.Schemas.Add(Schema)
Try
AddHandler ValidatingReader.ValidationEventHandler, AddressOf ValidationErrors.ValidationEventHandler
XmlDoc.Load(ValidatingReader)
Finally
RemoveHandler ValidatingReader.ValidationEventHandler, AddressOf ValidationErrors.ValidationEventHandler
End Try
Else
#End If
' CONSIDER, should I throw here to prevent the designer loader from blowing up / loading only part
' of the file and clobber it on the next write
Dim xmlReader As XmlTextReader = New XmlTextReader(Reader) With {
.DtdProcessing = DtdProcessing.Prohibit,
.Normalization = False
}
XmlDoc.Load(xmlReader)
#If USE_SETTINGS_XML_SCHEMA_VALIDATION Then
End If
If ValidationErrors.Errors.Count > 0 Then
Dim sb As New System.Text.StringBuilder
For Each e As System.Xml.Schema.ValidationEventArgs In ValidationErrors.Errors
sb.AppendLine(e.Message)
Next
Throw New XmlException(sb.ToString())
End If
#End If
Dim XmlNamespaceManager As New XmlNamespaceManager(XmlDoc.NameTable)
XmlNamespaceManager.AddNamespace("Settings", SettingsSchemaUri)
Dim RootNode As XmlNode = XmlDoc.SelectSingleNode("Settings:SettingsFile", XmlNamespaceManager)
' Enable support of pre-Beta2 settings namespace files -- if we didn't find the root node
' using the new namespace, then try the old one
'
If RootNode Is Nothing Then
XmlNamespaceManager.RemoveNamespace("Settings", SettingsSchemaUri)
XmlNamespaceManager.AddNamespace("Settings", SettingsSchemaUriOLD)
' now that we have the old namespace set up, try selecting the root node again
'
RootNode = XmlDoc.SelectSingleNode("Settings:SettingsFile", XmlNamespaceManager)
End If
' Deserialize setting group/description
If RootNode IsNot Nothing Then
' Deserialize persisted namespace
If RootNode.Attributes("GeneratedClassNamespace") IsNot Nothing Then
Settings.PersistedNamespace = RootNode.Attributes("GeneratedClassNamespace").Value
End If
' In some cases, we want to use a specific class name and not base it on the name of the
' .settings file...
Dim mungeClassNameAttribute As XmlAttribute = RootNode.Attributes("UseMySettingsClassName")
If mungeClassNameAttribute IsNot Nothing Then
Try
Settings.UseSpecialClassName = XmlConvert.ToBoolean(mungeClassNameAttribute.Value)
Catch ex As Exception When Common.ReportWithoutCrash(ex, NameOf(Deserialize), NameOf(SettingsSerializer))
Settings.UseSpecialClassName = False
End Try
End If
End If
' Deserialize settings
Dim SettingNodes As XmlNodeList = XmlDoc.SelectNodes("Settings:SettingsFile/Settings:Settings/Settings:Setting", XmlNamespaceManager)
For Each SettingNode As XmlNode In SettingNodes
Dim typeAttr As XmlAttribute = SettingNode.Attributes("Type")
Dim scopeAttr As XmlAttribute = SettingNode.Attributes("Scope")
Dim nameAttr As XmlAttribute = SettingNode.Attributes("Name")
Dim generateDefaultValueAttribute As XmlAttribute = SettingNode.Attributes("GenerateDefaultValueInCode")
Dim descriptionAttr As XmlAttribute = SettingNode.Attributes("Description")
Dim providerAttr As XmlAttribute = SettingNode.Attributes("Provider")
Dim RoamingAttr As XmlAttribute = SettingNode.Attributes("Roaming")
If typeAttr Is Nothing OrElse scopeAttr Is Nothing OrElse nameAttr Is Nothing Then
Throw New SettingsSerializerException(My.Resources.Microsoft_VisualStudio_Editors_Designer.SD_Err_CantLoadSettingsFile)
End If
Dim newSettingName As String = Settings.CreateUniqueName(nameAttr.Value)
If Not Settings.IsValidName(newSettingName) Then
Throw New SettingsSerializerException(My.Resources.Microsoft_VisualStudio_Editors_Designer.GetString(My.Resources.Microsoft_VisualStudio_Editors_Designer.SD_ERR_InvalidIdentifier_1Arg, nameAttr.Value))
End If
Dim Instance As DesignTimeSettingInstance = Settings.AddNew(typeAttr.Value,
newSettingName,
True)
If scopeAttr.Value.Equals(SettingsDesigner.ApplicationScopeName, StringComparison.Ordinal) Then
Instance.SetScope(DesignTimeSettingInstance.SettingScope.Application)
Else
Instance.SetScope(DesignTimeSettingInstance.SettingScope.User)
End If
If descriptionAttr IsNot Nothing Then
Instance.SetDescription(descriptionAttr.Value)
End If
If providerAttr IsNot Nothing Then
Instance.SetProvider(providerAttr.Value)
End If
If RoamingAttr IsNot Nothing Then
Instance.SetRoaming(XmlConvert.ToBoolean(RoamingAttr.Value))
End If
If generateDefaultValueAttribute IsNot Nothing AndAlso generateDefaultValueAttribute.Value <> "" AndAlso Not XmlConvert.ToBoolean(generateDefaultValueAttribute.Value) Then
Instance.SetGenerateDefaultValueInCode(False)
Else
Instance.SetGenerateDefaultValueInCode(True)
End If
'
' Deserialize the value
'
Dim ValueNode As XmlNode = Nothing
' First, unless explicitly told to only get runtime values,
' let's check if we have design-time specific values for this setting...
If Not getRuntimeValue Then
ValueNode = SettingNode.SelectSingleNode("./Settings:DesignTimeValue[@Profile=""(Default)""]", XmlNamespaceManager)
End If
If ValueNode Is Nothing Then
' ...and if we didn't find any design-time specific info, let's check the "normal" value
' element
ValueNode = SettingNode.SelectSingleNode("./Settings:Value[@Profile=""(Default)""]", XmlNamespaceManager)
End If
If ValueNode IsNot Nothing Then
Instance.SetSerializedValue(ValueNode.InnerText)
End If
Next SettingNode
Common.Switches.TraceSDSerializeSettings(TraceLevel.Info, "Deserialized {0} settings", Settings.Count)
End Sub
''' <summary>
''' Serialize design time settings instance
''' </summary>
''' <param name="Settings">Instance to serialize</param>
''' <param name="Writer">Text writer on stream to serialize settings to</param>
Public Shared Sub Serialize(Settings As DesignTimeSettings, GeneratedClassNameSpace As String, ClassName As String, Writer As TextWriter, DeclareEncodingAs As System.Text.Encoding)
Common.Switches.TraceSDSerializeSettings(TraceLevel.Info, "Serializing {0} settings", Settings.Count)
' Gotta store the namespace here in case it changes from under us!
Settings.PersistedNamespace = GeneratedClassNameSpace
Dim SettingsWriter As New XmlTextWriter(Writer) With {
.Formatting = Formatting.Indented,
.Indentation = 2
}
' NOTE, VsWhidbey 294747, Can I assume UTF-8 encoding? Nope, it seems that the DocDataTextWriter uses
' Unicode.Default! Probably should file a bug / change request for this... gotta make 100%
' sure what encoding is actually used first!
'
If DeclareEncodingAs Is Nothing Then
DeclareEncodingAs = System.Text.Encoding.UTF8
End If
Dim EncodingString As String = "encoding='" & DeclareEncodingAs.BodyName & "'"
SettingsWriter.WriteProcessingInstruction("xml", "version='1.0' " & EncodingString)
SettingsWriter.WriteStartElement("SettingsFile")
SettingsWriter.WriteAttributeString("xmlns", Nothing, SettingsSchemaUri)
SettingsWriter.WriteAttributeString("CurrentProfile", SettingsDesigner.CultureInvariantDefaultProfileName)
If Settings.Count > 0 Then
' We only want to scribble this into the file if we actually have some settings to generate.
' The main purpose for this is to be able to clean up any default values that we may have persisted
' in the app.config file (which includes the generated namespace) If we don't save anything, we
' know we don't have anything to clean up!
SettingsWriter.WriteAttributeString("GeneratedClassNamespace", GeneratedClassNameSpace)
SettingsWriter.WriteAttributeString("GeneratedClassName", ClassName)
End If
If Settings.UseSpecialClassName Then
' Make sure we persist the fact that we are using a special naming convention for this class
SettingsWriter.WriteAttributeString("UseMySettingsClassName", XmlConvert.ToString(True))
End If
'
' Write (empty) Profiles element - Settings profiles were cut for Whidbey (VsWhidbey 483350)
'
SettingsWriter.WriteStartElement("Profiles")
SettingsWriter.WriteEndElement() ' End of Profiles element
SettingsWriter.WriteStartElement("Settings")
For Each Instance As DesignTimeSettingInstance In Settings
SettingsWriter.WriteStartElement("Setting")
SettingsWriter.WriteAttributeString("Name", Instance.Name)
If Instance.Description <> "" Then
SettingsWriter.WriteAttributeString("Description", Instance.Description)
End If
If Instance.Provider <> "" Then
SettingsWriter.WriteAttributeString("Provider", Instance.Provider)
End If
If Instance.Roaming Then
SettingsWriter.WriteAttributeString("Roaming", XmlConvert.ToString(Instance.Roaming))
End If
If Not Instance.GenerateDefaultValueInCode Then
SettingsWriter.WriteAttributeString("GenerateDefaultValueInCode", XmlConvert.ToString(False))
End If
SettingsWriter.WriteAttributeString("Type", Instance.SettingTypeName)
SettingsWriter.WriteAttributeString("Scope", Instance.Scope.ToString())
Dim designTimeValue As String = Nothing
Dim defaultValue As String
' If this is a connection string, we have different values at design time and runtime.
' We serialize the design time value in the DesignTimeValue node, and add a Value node
' that contain the value that's going to be used at runtime...
If String.Equals(Instance.SettingTypeName, CultureInvariantVirtualTypeNameConnectionString, StringComparison.Ordinal) Then
designTimeValue = Instance.SerializedValue
Dim scs As VSDesigner.VSDesignerPackage.SerializableConnectionString
scs = DirectCast(SettingsValueSerializer.Deserialize(GetType(VSDesigner.VSDesignerPackage.SerializableConnectionString),
designTimeValue,
Globalization.CultureInfo.InvariantCulture),
VSDesigner.VSDesignerPackage.SerializableConnectionString)
If scs IsNot Nothing AndAlso scs.ConnectionString IsNot Nothing Then
defaultValue = scs.ConnectionString
Else
defaultValue = ""
End If
Else
defaultValue = Instance.SerializedValue
End If
' If we did find a design-time specific value, we better write it out...
If designTimeValue IsNot Nothing Then
SettingsWriter.WriteStartElement("DesignTimeValue")
SettingsWriter.WriteAttributeString("Profile", SettingsDesigner.CultureInvariantDefaultProfileName)
SettingsWriter.WriteString(designTimeValue)
SettingsWriter.WriteEndElement() ' End of DesignTimeValue element
End If
' And we should always have a "normal" value as well...
SettingsWriter.WriteStartElement("Value")
SettingsWriter.WriteAttributeString("Profile", SettingsDesigner.CultureInvariantDefaultProfileName)
SettingsWriter.WriteString(defaultValue)
SettingsWriter.WriteEndElement() ' End of Value element
SettingsWriter.WriteEndElement() ' End of Setting element
Next
SettingsWriter.WriteEndElement() ' End of Settings element
SettingsWriter.WriteEndElement() ' End of SettingsFile element
SettingsWriter.Flush()
SettingsWriter.Close()
End Sub
End Class
End Namespace