自定义文件存储和 XML 序列化
当用户在 Visual Studio 中保存特定于域的语言(DSL)的实例或 模型时,将创建或更新 XML 文件。 可以重新加载该文件,以在应用商店中重新创建模型。
可以通过在 DSL 资源管理器中调整“Xml 序列化行为”下的设置来自定义序列化方案。 对于每个域类、属性和关系,“Xml 序列化行为”下都有对应节点。 关系位于其源类下。 还有对应于形状、连接线和关系图类的节点。
还可以编写程序代码,以便进行更高级的自定义。
说明
如果要以特定格式保存模型,但不需要从该窗体重新加载模型,请考虑使用文本模板从模型生成输出,而不是自定义序列化方案。 有关详细信息,请参阅从域特定语言生成代码。
模型和关系图文件
每个模型保存在两个文件中:
模型文件具有一个名称,如
Model1.mydsl
。 它存储模型元素和关系及其属性。 文件扩展名(如.mydsl
)由 DSL 定义中 编辑器 节点的 FileExtension 属性确定。关系图文件有一个名称,如
Model1.mydsl.diagram
。 它存储形状、连接线及其位置、颜色、线条粗细和其他图表外观的详细信息。 如果用户删除.diagram
文件,模型中的基本信息不会丢失。 只是关系图的布局丢失了。 打开模型文件时,将创建一组默认的形状和连接线。
更改 DSL 的文件扩展名
打开 DSL 定义。 在 DSL 资源管理器中,单击“编辑器”节点。
在“属性”窗口中,编辑 FileExtension 属性。 不要包含文件扩展名的初始
.
。在解决方案资源管理器中,更改 DslPackage\ProjectItemTemplates中的两个项模板文件的名称。 这些文件具有以下格式的名称:
myDsl.diagram
myDsl.myDsl
默认序列化方案
若要为本主题创建示例,请使用以下 DSL 定义。
此 DSL 用于创建在屏幕上具有以下外观的模型。
此模型已保存,然后在 XML 文本编辑器中重新打开:
<?xml version="1.0" encoding="utf-8"?>
<familyTreeModel xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.0.0.0" Id="f817b728-e920-458e-bb99-98edc469d78f" xmlns="http://schemas.microsoft.com/dsltools/FamilyTree">
<people>
<person name="Henry VIII" birthYear="1491" deathYear="1547" age="519">
<children>
<personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Elizabeth I" />
<personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Mary" />
</children>
</person>
<person name="Elizabeth I" birthYear="1533" deathYear="1603" age="477" />
<person name="Mary" birthYear="1515" deathYear="1558" age="495" />
</people>
</familyTreeModel>
请注意有关序列化模型的以下几点:
每个 XML 节点的名称与域名相同,但首字母为小写。 例如,
familyTreeModel
和person
。Name 和 BirthYear 等域属性序列化为 XML 节点中的属性。 同样,属性名称的初始字符将转换为小写。
每个关系都序列化为嵌套在关系源端内的 XML 节点。 该节点的名称与源角色属性相同,但具有小写的初始字符。
例如,在 DSL 定义中,名为 People 的角色源自 FamilyTree 类。 在 XML 中,People 角色通过一个名为
people
的节点表示,该节点嵌套在familyTreeModel
节点内。每个嵌入关系的目标端都序列化为嵌套在关系下的节点。 例如,
people
节点包含多个person
节点。每个引用关系的目标端被序列化为 标识符,它对目标元素的引用进行编码。
例如,在
person
节点下,可能存在children
关系。 此节点包含名称,例如:<personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Elizabeth I" />
了解名字对象
名字对象用于表示模型和关系图文件的不同部分之间的交叉引用。 它们还用于 .diagram
文件中引用模型文件中的节点。 有两种形式的名字对象:
Id 名字对象引用目标元素的 GUID。 例如:
<personShapeMoniker Id="f79734c0-3da1-4d72-9514-848fa9e75157" />
限定键标识符 通过一个名为标识符键的指定域属性的值来标识目标元素。 目标元素的名称以其在嵌入关系树中父元素的名称为前缀。
以下示例取自 DSL,其中有一个名为 Album 的域类,该类与名为 Song 的域类具有嵌入关系:
<albumMoniker title="/My Favorites/Jazz after Teatime" /> <songMoniker title="/My Favorites/Jazz after Teatime/Hot tea" />
如果目标类具有域属性,且在“Xml 序列化行为”中该属性的“Is Moniker Key”选项设置为
true
,则使用限定键名字对象。 在此示例中,为域类“Album”和“Song”中名为“Title”的域属性设置此选项。
与 ID 标识符相比,限定的键标识符更易于理解。 如果您希望模型文件的 XML 易于人类阅读,请考虑使用限定的关键标识符。 但是,用户可以将多个元素设置为具有相同的标识符键。 重复键可能会导致文件无法正确重新加载。 因此,如果定义使用限定键名字对象引用的域类,应考虑阻止用户保存具有重复名字对象的文件的方法。
设置要由 ID 名字对象引用的域类
请确保类及其基类中每个域属性的“Is Moniker Key”设置为
false
。在 DSL 资源管理器中,展开“Xml 序列化行为\类数据\<域类>\元素数据”。
验证每个域属性的“Is Moniker Key”是否设置为
false
。如果域类具有基类,请重复该类中的过程。
为域类设置 序列化 ID =
true
。可以在 Xml 序列化行为下找到此属性。
设置要由限定键名字对象引用的域类
为现有域类的域属性设置“Is Moniker Key”。 属性的类型必须是
string
。在 DSL 资源管理器中,展开“Xml 序列化行为\类数据\<域类>\元素数据”,然后选择域属性。
在“属性”窗口中,将“Is Moniker Key”设置为
true
。
- 或 -
使用 命名域类 工具创建新的域类。
此工具创建一个名为 Name 的域属性的新类。 此域属性的“Is Element Name”和“Is Moniker Key”属性将初始化为
true
。- 或 -
创建从域类到具有别名键属性的另一个类的继承关系。
避免重复的名字对象
如果使用限定的键名字对象,则用户模型中的两个元素可能在键属性中具有相同的值。 例如,如果 DSL 具有具有属性名称的类 Person,则用户可以将两个元素的名称设置为相同。 尽管模型可以保存到文件中,但它无法正确重新加载。
有多种方法有助于避免这种情况:
为关键域属性将 设置为元素名称 =
true
。 选择 DSL 定义关系图上的域属性,然后在“属性”窗口中设置该值。当用户创建新类实例时,此值将导致域属性自动分配不同的值。 默认行为将数字添加到类名的末尾。 这不会阻止用户将名称更改为重复名称,但在保存模型之前用户未设置值的情况会有所帮助。
为 DSL 启用验证。 在 DSL 资源管理器中,选择编辑器\验证,并将 Uses... 属性设置为
true
。有一种自动生成的验证方法,用于检查歧义。 该方法位于
Load
验证类别中。 这将确保向用户发出警告,提示可能无法重新打开文件。有关详细信息,请参阅特定于域的语言中的验证。
名字对象路径和限定符
限定键名字对象以名字对象键结尾,在嵌入树中以其父级的名字对象作为前缀。 例如,如果专辑的名称是:
<albumMoniker title="/My Favorites/Jazz after Teatime" />
然后,该专辑中的一首歌曲可以是:
<songMoniker title="/My Favorites/Jazz after Teatime/Hot tea" />
但是,如果按 ID 引用专辑,则名字对象将如下所示:
<albumMoniker Id="77472c3a-9bf9-4085-976a-d97a4745237c" />
<songMoniker title="/77472c3a-9bf9-4085-976a-d97a4745237c/Hot tea" />
请注意,由于 GUID 是唯一的,所以它从不以其父对象的标识符为前缀。
如果你知道特定域属性在模型中始终具有唯一值,则可以将该属性的 Is Moniker Qualifier 设置为 true
。 它因此用作限定符,而不使用父级的名字对象。 例如,如果你为 Album 类的 Title 域属性同时设置了“Is Moniker Qualifier”和“Is Moniker Key”,则不会在 Album 及其嵌入子级的名字对象中使用该模型的名称或标识符:
<albumMoniker name="Jazz after Teatime" />
<songMoniker title="/Jazz after Teatime/Hot tea" />
自定义 XML 的结构
若要进行以下自定义,请在 DSL 资源管理器中展开“Xml 序列化行为”节点。 在域类中,展开“元素数据”节点以查看该类的属性和关系列表。 选择关系并在“属性”窗口中调整其选项。
将 省略元素 设置为 true 以省略源角色节点,只保留目标元素的列表。 如果源类和目标类之间存在多个关系,则不应设置此选项。
<familyTreeModel ...> <!-- The following node is omitted by using Omit Element: --> <!-- <people> --> <person name="Henry VIII" .../> <person name="Elizabeth I" .../> <!-- </people> --> </familyTreeModel>
设置“Use Full Form”,将目标节点嵌入表示关系实例的节点中。 将域属性添加到域关系时,将自动设置此选项。
<familyTreeModel ...> <people> <!-- The following node is inserted by using Use Full Form: --> <familyTreeModelHasPeople myRelationshipProperty="x1"> <person name="Henry VIII" .../> </familyTreeModelHasPeople> <familyTreeModelHasPeople myRelationshipProperty="x2"> <person name="Elizabeth I" .../> </familyTreeModelHasPeople> </people> </familyTreeModel>
设置 Representation = Element,以将域属性另存为元素而不是特性值。
<person name="Elizabeth I" birthYear="1533"> <deathYear>1603</deathYear> </person>
若要更改属性和关系序列化的顺序,请右键单击元素数据下的项,并使用 上移 或 下移 菜单命令。
使用程序代码进行重大自定义
可以替换部分或所有序列化算法。
建议在 Dsl\Generated Code\Serializer.cs 和 SerializationHelper.cs中研究代码。
自定义特定类的序列化
在“Xml 序列化行为”下该类的节点中设置“Is Custom”。
转换所有模板,生成解决方案,并调查生成的编译错误。 每个错误附近的注释说明了必须提供的代码。
为整个模型提供你自己的序列化
- 替代 Dsl\GeneratedCode\SerializationHelper.cs 中的方法
说明
从 Visual Studio 2022 17.13 开始,默认序列化实现不再支持使用 BinaryFormatter 的自定义数据类型的序列化或反序列化,因为 binaryFormatter 存在安全风险。
如果将自定义数据类型用于任何域属性,则要重写 SerializationHelper
类中的序列化方法,或实现能够将每个自定义数据类型与字符串进行转换的 TypeConverter
。
虽然出于安全原因不建议使用 BinaryFormatter
,但如果必须保持与使用 BinaryFormatter
序列化的较旧模型的向后兼容性,则可以实现反序列化二进制数据的 TypeConverter
。 以下代码片段用作实现此兼容性的模板:
class MyCustomDataTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string text)
{
// First, try to parse the string as if it were returned by MyCustomDataType.ToString().
if (MyCustomDataType.TryParse(text, out var custom))
return custom;
// Fall back to trying to deserialize the old BinaryFormatter serialization format.
var decoded = Convert.FromBase64String(text);
using (var memory = new MemoryStream(decoded, false))
{
var binaryFormatter = new BinaryFormatter();
return binaryFormatter.Deserialize(memory) as MyCustomDataType;
}
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string) && value is MyCustomDataType custom)
return custom.ToString();
return base.ConvertTo(context, culture, value, destinationType);
}
}
// ...
[TypeConverter(MyCustomDataTypeConverter)]
class MyCustomDataType
{
// ...
}
Xml 序列化行为中的选项
在 DSL 资源管理器中,Xml 序列化行为节点包含每个域类、关系、形状、连接线和关系图类的子节点。 在每个节点下,列出了从该元素获取的属性和关系。 关系在其自己的右侧和源类下显示。
下表汇总了可在 DSL 定义这一部分中设置的选项。 在每个情况下,在 DSL 资源管理器中选择一个元素,并在“属性”窗口中设置选项。
Xml 类数据
这些元素位于 DSL 资源管理器中的 Xml 序列化行为\类数据下。
财产 | 描述 |
---|---|
具有自定义元素架构 | 如果为 True,则指示域类具有自定义元素架构 |
是自定义的 | 如果要为此域类编写自己的序列化和反序列化代码,请将该值设置为 True。 生成解决方案并调查错误,以发现详细说明。 |
域类 | 此类数据节点应用到的域类。 只读。 |
元素名称 | 此类元素的 Xml 节点名称。 默认值是域类名称的小写版本。 |
Moniker Attribute Name | 名字对象元素中用于包含引用的特性的名称。 如果为空,则使用键属性或 ID 的名称。 在此示例中,其“name”: <personMoniker name="/Mike Nash"/> |
Moniker Element Name | 用于指代此类元素的标识符的 XML 元素名称。 默认值是带有“Moniker”后缀的类名的小写版本。 例如, personMoniker 。 |
Moniker Type Name | 为此类的元素的名字对象所生成 xsd 类型的名称。 XSD 位于 Dsl\Generated Code\*Schema.xsd 中 |
Serialize Id | 如果为 True,则文件中包含元素 GUID。 如果没有标记为“Is Moniker Key”的属性,并且 DSL 定义对此类的引用关系,则该值必须设置为 True。 |
类型名称 | 由指定域类在 xsd 中生成的 XML 类型的名称。 |
笔记 | 与此元素关联的非正式笔记 |
Xml 属性数据
Xml 属性节点位于类节点下。
财产 | 描述 |
---|---|
Domain Property | XML 序列化配置数据应用到的属性。 只读。 |
Is Moniker Key | 如果该值设置为 True,则属性将用作创建引用此域类实例的名字对象的关键。 |
Is Moniker Qualifier | 如果该值设置为 True,则该属性用于在名称标识符中创建限定符。 如果为 false,并且此域类的 SerializeId 不为 true,则名字对象由嵌入树中父元素的名字对象限定。 |
表示形式 | 如果该值设置为 Attribute,则该属性将序列化为 xml 属性;如果该值设置为 元素,则将其序列化为元素;如果值设置为 忽略,则不会序列化该值。 |
Xml Name | 用于表示属性的 xml 属性或元素的名称。 默认情况下,该值是域名的小写版本。 |
笔记 | 与此元素关联的非正式笔记 |
Xml Role data
角色数据节点位于源类节点下。
财产 | 描述 |
---|---|
Has Custom Moniker | 如果要提供自己的代码来生成和解析穿越此关系的标识符,请将此项设为 true。 对于详细的操作说明,请先构建解决方案,然后双击错误消息。 |
域关系 | 指定这些选项适用的关系。 只读。 |
省略元素 | 如果为 true,则从架构中省略与源角色对应的 XML 节点。 如果源类和目标类之间存在多个关系,则此角色节点区分属于这两个关系的链接。 因此,我们建议在这种情况下不要设置此选项。 |
角色元素名称 | 指定从源角色派生的 XML 元素的名称。 默认值为角色属性名称。 |
使用完整表单 | 如果为 true,则每个目标元素或名字对象都包含在表示关系的 XML 节点中。 如果关系具有自己的域属性,则应将其设置为 true。 |