使用 Visual Studio ModelBus 集成模型
Visual Studio ModelBus 提供用于在模型之间创建链接以及将其从其他工具创建到模型中的方法。 例如,你可以链接域特定语言 (DSL) 模型和 UML 模型。 可以创建一组集成 DSL。
警告
本文中所述的 Visual Studio ModelBus 扩展不再可供下载。 但是,这些说明仍然适用于已安装此扩展的用户。
ModelBus 允许你创建对模型或模型中特定元素的唯一引用。 此引用可存储在该模型外部,例如另一个模型的元素中。 在随后的情况中,当工具想要获取对元素的访问权限时,ModelBus 基础结构会加载相应的模型并返回元素。 如果需要,可以向用户显示该模型。 如果不能在文件之前的位置中访问该文件,ModelBus 会要求用户进行查找。 如果用户找到了文件,ModelBus 会更新对该文件的所有引用。
注意
在 ModelBus 的当前 Visual Studio 实现中,链接模型必须是同一 Visual Studio 解决方案中的项。
有关 ModelBus 扩展的更多信息,请查看以下资源:
注意
“文本模板转换”组件将作为“Visual Studio 扩展开发”工作负载的一部分自动安装 。 还可以从 Visual Studio 安装程序的“SDK、库和框架”类别下的“单个组件”选项卡进行安装 。 从“单个组件”选项卡安装“建模 SDK”组件 。
提供对 DSL 的访问权限
请先定义用于 DSL 的 ModelBusAdapter,然后才能创建对模型或其元素的 ModelBus 引用。 进行定义的最简单方法是使用 Visual Studio ModelBus 扩展,它可将命令添加到 DSL 设计器。
向 ModelBus 公开 DSL 定义
打开 DSL 定义文件。 右键单击设计图面,然后选择“启用 Modelbus”。
在对话框中,选择“我希望向 ModelBus 公开此 DSL”。 如果希望此 DSL 同时公开其模型并使用对其他 DSL 的引用,则可选择这两个选项。
选择“确定”。 新项目
ModelBusAdapter
已添加到 DSL 解决方案。如果要从文本模板访问 DSL,则必须修改新项目中的 AdapterManager.tt。 如果要从其他代码(例如命令和事件处理程序)访问 DSL,则忽略此步骤。 有关详细信息,请参阅在文本模板中使用 Visual Studio ModelBus。
将
AdapterManagerBase
的基类更改为 VsTextTemplatingModelingAdapterManager。在文件末尾附近,将此特性插入到类
AdapterManager
的前面:[Microsoft.VisualStudio.Modeling.Integration.HostSpecific(HostName)]
在
ModelBusAdapter
项目的 NuGet 包管理器中,将 NuGet PackageReference 添加到Microsoft.VisualStudio.TextTemplating.Modeling
。如果要同时从文本模板和其他代码访问 DSL,则需要两个适配器:一个已经过修改,另一个未经过修改。
选择“转换所有模板”。
重新生成解决方案。
ModelBus 现在可以打开此 DSL 的实例。
文件夹 ModelBusAdapters\bin* 包含由
Dsl
项目和ModelBusAdapters
项目生成的程序集。 若要从另一个 DSL 引用此 DSL,应导入这些程序集。
确保元素可被引用
默认情况下,Visual Studio ModelBus 适配器使用元素的 GUID 来标识它。 这些标识符必须保留在模型文件中。
确保保留元素 ID:
打开 DslDefinition.dsl。
在 DSL 资源管理器中,展开“XML 序列化行为”,然后展开“类数据” 。
对于想要为其创建 ModelBus 引用的每个类:
选择类节点,并在“属性”窗口中,确保将“序列化 ID”设置为
true
。
或者,如果要使用元素名称来标识元素而不是 GUID,则可重写生成的适配器的各个部分。 在适配器类中重写以下方法:
- 重写
GetElementId
以返回要使用的标识符。 在创建引用时将调用此方法。 - 替代
ResolveElementReference
以从 ModelBus 引用中查找正确元素。
从另一个 DSL 访问 DSL
可将 ModelBus 引用存储在 DSL 的域属性中,并编写使用它们的自定义代码。 还可以允许用户通过选取模型文件和其中的元素来创建 ModelBus 引用。
若要允许 DSL 使用对另一个 DSL 的引用,应首先使它成为模型总线引用的使用者。
允许 DSL 使用对公开的 DSL 的引用
在 DSL 定义关系图中,右键单击该关系图的主要部分,然后选择“启用 Modelbus”。
在对话框中,选择“我希望允许此模型使用模型总线引用”。
在正在使用的 DSL 的
Dsl
项目中,将以下程序集添加到项目引用。 这些程序集(.dll 文件)位于公开的 DSL 的 ModelBusAdapter\bin\* 目录中。公开的 DSL 程序集,例如 Fabrikam.FamilyTree.Dsl.dll
公开的模型总线适配器程序集,例如 Fabrikam.FamilyTree.ModelBusAdapter.dll
将以下 .NET 程序集添加到使用 DSL 项目的项目引用。
- Microsoft.VisualStudio.Modeling.Sdk.Integration.dll
- Microsoft.VisualStudio.Modeling.Sdk.Integration.Shell.dll
将 ModelBusReference 存储在域属性中
在使用 DSL 的 DSL 定义中,将域属性添加到域类并设置其名称。
在“属性”窗口中,选定域属性后,将“类型”设置为
ModelBusReference
。在此阶段,程序代码可设置属性值。 在“属性”窗口中,该值为只读。
可允许用户使用专用的 ModelBusReference 编辑器来设置属性。 此编辑器(也称为选取器)有两个版本。 一个版本让用户可选择模型文件,另一个让用户能够选择模型文件和其中包含的元素。
允许用户在域属性中设置 ModelBusReference
右键单击域属性,然后选择“编辑 ModelBusReference 特定属性”。 这会打开“模型总线选取器”对话框。
将相应的“ModelBusReference 的类型”设置为一个模型或一个模型内的元素。
在文件对话框筛选器字符串中,输入字符串(如
Family Tree files |*.ftree
)。 替换公开的 DSL 的文件扩展名。如果选择引用模型中的元素,则可添加用户可选择的类型(例如
Company.FamilyTree.Person
)的列表。选择“确定”,然后选择“解决方案资源管理器”工具栏中的“转换所有模板”。
警告
如果未选择有效的模型或实体,则即使“确定”按钮可能显示为“已启用”,也不起作用。
如果指定了目标类型(如
Company.FamilyTree.Person
)的列表,则必须将程序集引用添加到 DSL 项目,从而引用目标 DSL 的 DLL(例如 Company.FamilyTree.Dsl.dll)。
测试 ModelBusReference
同时生成公开的 DSL 和使用的 DSL。
通过按 F5 或 Ctrl+F5,在实验模式下运行一个 DSL。
在 Visual Studio 的实验实例的调试项目中,添加作为每个 DSL 的实例的文件。
注意
Visual Studio ModelBus 只能解析对同一 Visual Studio 解决方案中的项的模型的引用。 例如,你无法创建对位于文件系统另一部分中的模型文件的引用。
在公开的 DSL 的实例中创建一些元素和链接,并将其保存。
打开使用的 DSL 的实例,并选择一个具有模型总线引用属性的模型元素。
在“属性”窗口中,双击模型总线引用属性。 这将打开选取器对话框。
选择“浏览”,然后选择公开的 DSL 的实例。
如果你指定了特定于元素的模型总线引用,则还可通过选取器来选择模型中的项。
在程序代码中创建引用
当你想要存储对模型或模型内的元素的引用时,请创建 ModelBusReference
。 有两种 ModelBusReference
:模型引用和元素引用。
若要创建模型引用,你需要 DSL(模型是其实例)的 AdapterManager,还需要模型的文件名或 Visual Studio 项目项。
若要创建元素引用,你需要用于模型文件的适配器,以及要引用的元素。
注意
利用 Visual Studio ModelBus,只能创建对同一 Visual Studio 解决方案中的项的引用。
导入公开的 DSL 程序集
在使用的项目中,将项目引用添加到 DSL 和公开的 DSL 的 ModelBusAdapter
程序集。
例如,假设要将 ModelBus 引用存储在 MusicLibrary DSL 的元素中。 ModelBus 引用将引用 FamilyTree DSL 的元素。 在“引用”节点中,在 MusicLibrary 解决方案的 Dsl
项目中,将引用添加到以下程序集:
Fabrikam.FamilyTree.Dsl.dll。 公开的 DSL。
Fabrikam.FamilyTree.ModelBusAdapters.dll:公开的 DSL 的 ModelBus 适配器。
Microsoft.VisualStudio.Modeling.Sdk.Integration
Microsoft.VisualStudio.Modeling.Sdk.Integration.Shell
可在 *bin\** 下的公开 DSL 的
ModelBusAdapters
项目中找到这些程序集。在创建引用所在的代码文件中,通常必须导入这些命名空间:
// The namespace of the DSL you want to reference:
using Fabrikam.FamilyTree; // Exposed DSL
using Fabrikam.FamilyTree.ModelBusAdapters;
using Microsoft.VisualStudio.Modeling.Integration;
using System.Linq;
...
创建对模型的引用
若要创建模型引用,请访问公开的 DSL 的 AdapterManager,并将其用于创建对该模型的引用。 可以指定文件路径或 EnvDTE.ProjectItem
。
你可以从 AdapterManager 中获取一个适配器,该适配器提供了对模型中各个元素的访问权限。
注意
你必须在使用完适配器后将其公开。 实现此目的的最简便方式是使用 using
语句。 下面的示例对此进行了演示。
// The file path of a model instance of the FamilyTree DSL:
string targetModelFile = "TudorFamilyTree.ftree";
// Get the ModelBus service:
IModelBus modelBus =
this.Store.GetService(typeof(SModelBus)) as IModelBus;
// Get an adapterManager for the target DSL:
FamilyTreeAdapterManager manager =
(modelbus.GetAdapterManager(FamilyTreeAdapter.AdapterId)
as FamilyTreeAdapterManager;
// or: (modelBus.FindAdapterManagers(targetModelFile).First())
// or could provide an EnvDTE.ProjectItem
// Create a reference to the target model:
// NOTE: the target model must be a file in this project.
ModelBusReference modelReference =
manager.CreateReference(targetModelFile);
// or can use an EnvDTE.ProjectItem instead of the filename
// Get the root element of this model:
using (FamilyTreeAdapter adapter =
modelBus.CreateAdapter(modelReference) as FamilyTreeAdapter)
{
FamilyTree modelRoot = adapter.ModelRoot;
// Access elements under the root in the usual way:
foreach (Person p in modelRoot.Persons) {...}
// You can create adapters for individual elements:
ModelBusReference elementReference =
adapter.GetElementReference(person);
...
} // Dispose adapter
如果希望以后可以使用 modelReference
,可将它存储在具有外部类型 ModelBusReference
的域属性中:
using Transaction t = this.Store.TransactionManager
.BeginTransaction("keep reference"))
{
artist.FamilyTreeReference = modelReference;
t.Commit();
}
若要允许用户编辑此域属性,请将 ModelReferenceEditor
用作 Editor
特性中的参数。 有关详细信息,请参阅允许用户编辑引用。
创建对元素的引用
为模型创建的适配器可用于创建和解析引用。
// person is an element in the FamilyTree model:
ModelBusReference personReference =
adapter.GetElementReference(person);
如果希望以后可以使用 elementReference
,可将它存储在具有外部类型 ModelBusReference
的域属性中。 若要允许用户编辑它,请将 ModelElementReferenceEditor
用作 Editor
特性中的参数。 有关详细信息,请参阅允许用户编辑引用。
解析引用
如果你具有 ModelBusReference
(MBR),则可获取它所引用的模型或模型元素。 如果元素存在于关系图或其他视图上,则可打开视图,然后选择该元素。
可从 MBR 创建适配器。 你可以从适配器中获取模型的根。 还可解析引用模型内特定元素的 MBR。
using Microsoft.VisualStudio.Modeling.Integration; ...
ModelBusReference elementReference = ...;
// Get the ModelBus service:
IModelBus modelBus =
this.Store.GetService(typeof(SModelBus)) as IModelBus;
// Use a model reference or an element reference
// to obtain an adapter for the target model:
using (FamilyTreeAdapter adapter =
modelBus.CreateAdapter(elementReference) as FamilyTreeAdapter)
// or CreateAdapter(modelReference)
{
// Get the root of the model:
FamilyTree tree = adapter.ModelRoot;
// Get a model element:
MyDomainClass mel =
adapter.ResolveElementReference<MyDomainClass>(elementReference);
if (mel != null) {...}
// Get the diagram or other view, if there is one:
ModelBusView view = adapter.GetDefaultView();
if (view != null)
{
view.Open();
// Display the diagram:
view.Show();
// Attempt to select the shape that presents the element:
view.SetSelection(elementReference);
}
} // Dispose the adapter.
在文本模板中解析 ModelBusReference
要访问的 DSL 必须具有 ModelBus 适配器,已配置该适配器以供文本模板访问。 有关详细信息,请参阅提供对 DSL 的访问权限。
通常,使用存储在源 DSL 中的模型总线引用 (MBR) 访问目标 DSL。 因此模板包括源 DSL 的指令,以及用于解析 MBR 的代码。 有关文本模板的详细信息,请参阅从域特定语言生成代码。
<#@ template debug="true" hostspecific="true"
inherits="Microsoft.VisualStudio.TextTemplating.Modeling.ModelBusEnabledTextTransformation" #>
<#@ SourceDsl processor="SourceDslDirectiveProcessor" requires="fileName='Sample.source'" #>
<#@ output extension=".txt" #>
<#@ assembly name = "Microsoft.VisualStudio.Modeling.Sdk.Integration.11.0" #>
<#@ assembly name = "System.Core" #>
<#@ assembly name = "Company.CompartmentDragDrop.Dsl.dll" #>
<#@ assembly name = "Company.CompartmentDragDrop.ModelBusAdapter.dll" #>
<#@ import namespace="Microsoft.VisualStudio.Modeling.Integration" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="Company.CompartmentDragDrop" #>
<#@ import namespace="Company.CompartmentDragDrop.ModelBusAdapters" #>
<# // Get source root from directive processor:
ExampleModel source = this.ExampleModel;
// This DSL has an MBR in its root:
using (ModelBusAdapter adapter = this.ModelBus.CreateAdapter(source.ModelReference) as ModelBusAdapter)
{
ModelBusAdapterManager manager = this.ModelBus.FindAdapterManagers(this.Host.ResolvePath("Sample.compDD1")).FirstOrDefault();
ModelBusReference modelReference =
manager.CreateReference(this.Host.ResolvePath("Sample.compDD1"));
// Get the root element of this model:
using (CompartmentDragDropAdapter adapter =
this.ModelBus.CreateAdapter(modelReference) as CompartmentDragDropAdapter)
{
ModelRoot root = adapter.ModelRoot;
#>
[[<#= root.Name #>]]
<#
}
#>
有关详细信息和演练,请参阅在文本模板中使用 Visual Studio ModelBus
序列化 ModelBusReference
如果想要以字符串的形式存储 ModelBusReference
(MBR),则可将其序列化:
string serialized = modelBus.SerializeReference(elementReference);
// Store it anywhere, then get it back again:
ModelBusReference elementReferenceRestored =
modelBus.DeserializeReference(serialized, null);
以这种方式序列化的 MBR 与上下文无关。 如果要使用简单的基于文件的模型总线适配器,则 MBR 将包含绝对文件路径。 如果实例模型文件永不移动,则这种序列化就已足够。 但是,模型文件通常是 Visual Studio 项目中的项。 用户希望能够将整个项目移动到文件系统的不同部分。 他们还希望能够将项目保持在源代码管理之下并在不同的计算机上打开它。 因此应相对于包含文件的项目的位置对路径名称进行序列化。
相对于指定的文件路径进行序列化
ModelBusReference
包含 ReferenceContext
,它是可将信息(例如应相对于其进行序列化的文件路径)存储在其中的字典。
相对于路径进行序列化:
elementReference.ReferenceContext.Add(
ModelBusReferencePropertySerializer.FilePathSaveContextKey,
currentProjectFilePath);
string serialized = modelBus.SerializeReference(elementReference);
从字符串检索引用:
ReferenceContext context = new ReferenceContext();
context.Add(ModelBusReferencePropertySerializer.FilePathLoadContextKey,
currentProjectFilePath);
ModelBusReference elementReferenceRestored =
modelBus.DeserializeReference(serialized, context);
由其他适配器创建的 ModelBus 引用
如果想要创建自己的适配器,以下信息十分有用。
ModelBusReference
(MBR) 由两部分组成:由模型总线反序列化的 MBR 标头,以及由特定适配器管理器处理的特定于适配器的部分。 此方法允许你提供自己的适配器序列化格式。 例如,你可以引用数据库而不是文件,或者可以将附加信息存储在适配器引用中。 你自己的适配器可将附加信息放在 ReferenceContext
中。
在反序列化 MBR 时,必须提供随后将存储在 MBR 对象中的 ReferenceContext。 在序列化 MBR 时,适配器将使用存储的 ReferenceContext 以帮助生成字符串。 反序列化的字符串并不包含 ReferenceContext 中的所有信息。 例如,在简单的基于文件的适配器中,ReferenceContext 包含根文件路径。 此路径未存储在序列化的 MBR 字符串中。
反序列化 MBR 分为两个阶段:
ModelBusReferencePropertySerializer
是处理 MBR 标头的标准序列化程序。 它使用标准 DSLSerializationContext
属性包,该属性包使用键ReferenceContext
存储在ModelBusReferencePropertySerializer.ModelBusLoadContextKey
中。 具体而言,SerializationContext
应包含ModelBus
的实例。ModelBus 适配器将处理 MBR 的特定于适配器的部分。 它可使用存储在 MBR 的 ReferenceContext 中的附加信息。 简单的基于文件的适配器使用键
FilePathLoadContextKey
和FilePathSaveContextKey
来保留根文件路径。仅当使用模型文件中的适配器引用时,才对该适配器引用进行反序列化。
创建模型
创建、打开和编辑模型
以下片段摘自 VMSDK 网站上的状态机示例。 它阐释了如何使用 ModelBusReferences 创建和打开模型,以及获取与该模型相关联的关系图。
在此示例中,目标 DSL 的名称是 StateMachine。 可从该名称派生多个名称,例如模型类的名称和 ModelBusAdapter 的名称。
using Fabrikam.StateMachine.ModelBusAdapters;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Integration;
using Microsoft.VisualStudio.Modeling.Integration.Shell;
using Microsoft.VisualStudio.Modeling.Shell;
...
// Create a new model.
ModelBusReference modelReference =
StateMachineAdapterManager .CreateStateMachineModel(modelName, fileName);
//Keep reference of new model in this model.
using (Transaction t = ...)
{
myModelElement.ReferenceProperty = modelReference;
t.Commit();
}
// Get the ModelBus service from Visual Studio.
IModelBus modelBus = Microsoft.VisualStudio.Shell.Package.
GetGlobalService(typeof(SModelBus)) as IModelBus;
// Get a modelbus adapter on the new model.
ModelBusAdapter modelBusAdapter;
modelBus.TryCreateAdapter(modelReference,
this.ServiceProvider, out modelBusAdapter);
using (StateMachineAdapter adapter =
modelBusAdapter as StateMachineAdapter)
{
if (adapter != null)
{
// Obtain a Diagram from the adapter.
Diagram targetDiagram =
((StandardVsModelingDiagramView)
adapter.GetDefaultView()
).Diagram;
using (Transaction t =
targetDiagram.Store.TransactionManager
.BeginTransaction("Update diagram"))
{
DoUpdates(targetDiagram);
t.Commit();
}
// Display the new diagram.
adapter.GetDefaultView().Show();
}
}
验证引用
BrokenReferenceDetector 测试可保留 ModelBusReferences 的“存储”中的所有域属性。 它将调用你提供的操作,并可在其中查找所有操作。 此测试适用于验证方法。 以下验证方法将在尝试保存模型时测试存储,并在错误窗口中报告中断的引用:
[ValidationMethod(ValidationCategories.Save)]
public void ValidateModelBusReferences(ValidationContext context)
{
BrokenReferenceDetector.DetectBrokenReferences(this.Store,
delegate(ModelElement element, // parent of property
DomainPropertyInfo property, // identifies property
ModelBusReference reference) // invalid reference
{
context.LogError(string.Format(INVALID_REF_FORMAT,
property.Name,
referenceState.Name,
new ModelBusReferenceTypeConverter().
ConvertToInvariantString(reference)),
"Reference",
element);
});
}
private const string INVALID_REF_FORMAT =
"The '{0}' domain property of this ReferenceState instance "
+ "named '{1}' contains reference value '{2}' which is invalid";
由 ModelBus 扩展执行的操作
如果要大量使用 ModelBus,以下信息可能十分有用。
ModelBus 扩展将在 DSL 解决方案中进行以下更改。
右键单击 DSL 定义关系图后,请选择“启用 Modelbus”,然后选择“允许此 DSL 使用 ModelBus”:
在 DSL 项目中,应将该引用添加到 Microsoft.VisualStudio.Modeling.Sdk.Integration.dll。
在 DSL 定义中,外部类型引用将添加到:
Microsoft.VisualStudio.Modeling.Integration.ModelBusReference
。可在“DSL 资源管理器”的“域类型”下查看该引用 。 若要手动添加外部类型引用,请右键单击根节点。
将添加一个新的模板文件 Dsl\GeneratedCode\ModelBusReferencesSerialization.tt。
将域属性的类型设置为 ModelBusReference 后,右键单击该属性,然后选择“启用 ModelBusReference 特定属性”:
多个 CLR 特性已添加到域属性。 可在“属性”窗口的“自定义特性”字段中查看它们。 在 Dsl\GeneratedCode\DomainClasses.cs 中,可以在属性声明中查看属性:
[System.ComponentModel.TypeConverter(typeof( Microsoft.VisualStudio.Modeling.Integration.ModelBusReferenceTypeConverter))] [System.ComponentModel.Editor(typeof( Microsoft.VisualStudio.Modeling.Integration.Picker .ModelReferenceEditor // or ModelElementReferenceEditor ), typeof(System.Drawing.Design.UITypeEditor))] [Microsoft.VisualStudio.Modeling.Integration.Picker .SupplyFileBasedBrowserConfiguration ("Choose a model file", "Target model|*.target")]
右键单击 DSL 定义关系图后,请选择“启用 ModelBus”,然后选择“向 ModelBus 公开此 DSL”:
新项目
ModelBusAdapter
已添加到解决方案。对
ModelBusAdapter
的引用已添加到DslPackage
项目。ModelBusAdapter
具有对Dsl
项目的引用。在 DslPackage\source.extention.tt 中,
|ModelBusAdapter|
作为 MEF 组件添加。