Professional Documents
Culture Documents
f1700cf9aeefee60a35b3a3d529f3693
f1700cf9aeefee60a35b3a3d529f3693
f1700cf9aeefee60a35b3a3d529f3693
Make sure your app meets architectural requirements by using Visual Studio architecture and modeling tools to
design and model your app.
1. Understand existing program code better by visualizing the code structure, behavior, and relationships
with code maps and dependency diagrams.
See the code's organization and relationships by creating code maps .
Visualize dependencies between assemblies, namespaces, classes, methods, and so on.
Find conflicts between your code and its design by creating dependency diagrams to validate code.
See the class structure and members for a specific project by creating class diagrams from code.
Generate text using T4 templates with text blocks and control logic inside templates to generate text-
based files.
2. Educate your team in the need for respecting architectural dependencies.
3. Create models at different levels of detail throughout the application lifecycle as part of your
development process.
See Scenario: Change your design using visualization and modeling.
Code maps
Code maps are one type of model that helps you see the organization and relationships in your code.
Use maps to examine program code so you can better understand its structure and its dependencies, how to
update it, and estimate the cost of proposed changes.
Learn more:
Install architecture code tools
Map dependencies across your solutions
Use code maps to debug your applications
Find potential problems using code map analyzers
Dependency diagrams
Dependency diagrams let you define the structure of an application as a set of layers or blocks with explicit
dependencies. Live validation shows conflicts between dependencies in the code and dependencies described on
a dependency diagram.
Use dependency diagrams to:
Stabilize the structure of the application through numerous changes over its life.
Discover unintentional dependency conflicts before checking in changes to the code.
Learn more:
Install architecture code tools
Create dependency diagrams from your code
Dependency Diagrams: Reference
Validate code with dependency diagrams
The authoring experience has changed to make dependency validation more discoverable and more accessible.
The terminology has changed from "Layer diagram" to "Dependency diagram".
The Architecture menu now contains a command to directly create a Dependency diagram:
Layer property names and descriptions have been changed to make them more meaningful:
You immediately see the impact of your changes in the analysis results for the current code in the solution each
time you save the diagram. You don't have to wait for the completion of the Validate Dependencies
command.
For more details, see this blog post.
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
To create a code map in Visual Studio, first install the Code Map and Live Dependency Validation
components:
1. Open Visual Studio Installer . You can open it from the Windows Start menu, or within Visual Studio by
selecting Tools > Get Tools and Features .
2. Select the Individual components tab.
3. Scroll down to the Code tools section and select Code Map and Live Dependency Validation .
4. Select Modify .
The Code Map and Live Dependency Validation components begin installing. You may be asked to
close Visual Studio.
Scenario: Change your design using visualization
and modeling
4/13/2022 • 20 minutes to read • Edit Online
Make sure that your software system meets users' needs by using the visualization and modeling tools in Visual
Studio. Use tools such as code maps, dependency diagrams, and class diagrams to:
To see which versions of Visual Studio support each tool, see Version support for architecture and modeling
tools.
Clarify users' requirements and business processes.
Visualize and explore existing code.
Describe changes to an existing system.
Verify that the system meets its requirements.
Keep code consistent with the design.
This walkthrough:
Describes how these tools can benefit your software project.
Shows how you might use these tools, regardless your development approach, with an example scenario.
To find out more about these tools and the scenarios that they support, see:
Analyzing and Modeling Architecture
Visualize code
Scenario overview
This scenario describes episodes from the software development lifecycles of two fictitious companies: Dinner
Now and Lucerne Publishing. Dinner Now provides a Web-based meal delivery service in Seattle. Customers
can order meals and pay for them on the Dinner Now website. The orders are then sent to the appropriate local
restaurant for delivery. Lucerne Publishing, a company in New York, runs several businesses both off and on the
Web. For example, they run a website where customers can post restaurant reviews.
Lucerne recently acquired Dinner Now and wants to make the following changes:
Integrate their websites by adding restaurant review capabilities to Dinner Now.
Replace Dinner Now's payment system with Lucerne's system.
Expand the Dinner Now service across the region.
Dinner Now uses SCRUM and eXtreme Programming. They have very high test coverage and very little
unsupported code. They minimize risks by creating small but working versions of a system and then adding
functionality incrementally. They develop their code over short and frequent iterations. This lets them embrace
change confidently, refactor code frequently, and avoid "big design up front".
Lucerne maintains a vastly larger and complex collection of systems, some of which are more than 40 years old.
They are very cautious about making changes because of the complexity and scope of legacy code. They follow
a more rigorous development process, preferring to design detailed solutions and to document the design and
changes that occur during development.
Both teams use modeling diagrams in Visual Studio to help them develop systems that meet the users' needs.
They use Team Foundation Server alongside other tools to help them plan, organize, and manage their work.
For more information about Team Foundation Server, see:
Plan and track work
Testing, validating, and checking in updated code
To draw dependency diagrams, you must create a modeling project as part of an existing solution or a new one.
These diagrams must be created in the modeling project. Items on dependency diagrams are located in the
modeling project, but they are not stored in the common model. Code maps and .NET class diagrams created
from code exist outside the modeling project.
See:
Create dependency diagrams from your code
Map dependencies across your solutions
How to: Add Class Diagrams to Projects (Class Designer)
Modeling SDK for Visual Studio - Domain-Specific Languages
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Both teams also use dependency validation to make sure that code under development remains consistent with
the design. See:
Keeping Code Consistent with the Design
Describe the Logical Architecture: Dependency Diagrams
Validate code with dependency diagrams
NOTE
Some versions of Visual Studio support dependency validation and read-only versions of code maps for visualization and
modeling. To see which editions of Visual Studio support this feature, see Edition support for architecture and modeling
tools.
For example, the developer creates a code map. She adjusts its scope to focus on the areas that will be affected
by the new scenario. These areas are selected and highlighted on the map:
For example, the following dependency diagram describes dependencies between layers and the number of
artifacts that are associated with each layer:
Dependency Diagram
To make sure that conflicts with the design do not occur during code development, the teams uses dependency
validation on builds that are run on Azure DevOps. They also create a custom MSBuild task to require
dependency validation in their check-in operations. They use build reports to collect validation errors.
See:
Use the visual designer
TFVC gated check-in
Build and release tasks
General Tips for Creating and Using Models
Most diagrams consist of nodes that are connected by lines. For each diagram type, the toolbox provides
different kinds of nodes and lines.
To open the toolbox, on the View menu, click Toolbox .
To create a node, drag it from the toolbox to the diagram. Certain kinds of nodes must be dragged onto
existing nodes. For example, on a component diagram, a new port must be added to an existing
component.
To create a line or a connection, click the appropriate tool in the toolbox, click the source node, and then
click the target node. Some lines can be created only between certain kinds of nodes. When you move the
pointer over a possible source or target, the pointer indicates whether you can create a connection.
Plan and track work
Visual Studio modeling diagrams are integrated with Team Foundation Server so that you can plan, manage,
and track work more easily. Both teams use models to identify test cases and development tasks and to estimate
their work. Lucerne creates and links Team Foundation Server work items to model elements, such as use cases
or components. This helps them monitor their progress and trace their work back to the users' requirements.
This helps them make sure that their changes continue to meet those requirements.
As their work progresses, the teams update their work items to reflect the time that they spent on their tasks.
They also monitor and report status on their work by using the following Team Foundation Server features:
Daily burn down reports that show whether they will complete the planned work in the expected time.
They generate other similar reports from Team Foundation Server to track the progress of bugs.
An iteration worksheet that uses Microsoft Excel to help the team monitor and balance the workload
between its members. This worksheet is linked to Team Foundation Server and provides focus for
discussion during their regular progress meetings.
A development dashboard that uses Office Project to keep the team informed about important project
information.
See:
About Agile tools and Agile project management
Charts, dashboards, and widgets (Azure DevOps Services)
Create your backlog and tasks using Project
Test, Validate, and Check In Code
As the teams complete each task, they check their code into source control and receive reminders from Team
Foundation Server, if they forget. Before Team Foundation Server accepts their check-ins, the teams run unit
tests and dependency validation to verify the code against their test cases and the design. They use Team
Foundation Server to run builds, automated unit tests, and dependency validation regularly. This helps make
sure that the code meets the following criteria:
It works.
It does not break previously working code.
It does not conflict with the design.
Dinner Now has a large collection of automated tests, which Lucerne can reuse because almost all still apply.
Lucerne can also build on these tests and add new ones to cover new functionality. Both also use Visual Studio
to run manual tests.
To make sure that the code conforms to the design, the teams configure their builds in Azure DevOps to include
dependency validation. If any conflicts occur, a report is generated with the details.
See:
Testing the application
Validate your system during development
Use version control
Azure Pipelines
See:
See:
See:
External Resources
C AT EGO RY L IN K S
See also
Visualize code
Use models in your development process
Use models in Agile development
Validate your system during development
Visualize code
4/13/2022 • 2 minutes to read • Edit Online
You can use the visualization and modeling tools in Visual Studio to help you understand existing code and
describe your application. This lets you visually learn how your changes might affect the code and help you
assess the work and risks that result from those changes. For example:
To understand the relationships in your code, map those relationships visually.
To describe your system's architecture and keep the code consistent with its design, create dependency
diagrams and validate code against these diagrams.
To describe class structures, create class diagrams.
These tools also help you communicate more easily with the people involved with your project.
To see which editions of Visual Studio support each feature, see Edition support for architecture and modeling
tools
Understand code and its relationships: - Map dependencies across your solutions
- Use code maps to debug your applications
Map relationships between specific pieces of code. - Find potential problems using code map analyzers
- Map methods on the call stack while debugging
See an overview of the relationships in your code for the
entire solution.
Understand class structures: How to: Add Class Diagrams to Projects (Class Designer)
Describe the high-level system design and validate - Create dependency diagrams from your code
code against this design: - Dependency Diagrams: Reference
- Dependency Diagrams: Guidelines
Describe the high-level system design and its intended - Validate code with dependency diagrams
dependencies by creating dependency diagrams. Validate
code against this design to make sure dependencies in code
remain consistent with the design.
See also
Install architecture code tools
Scenario: Change your design using visualization and modeling
Analyze and Model Architecture
Model your app's architecture
Use models in your development process
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Map dependencies with code maps
4/13/2022 • 9 minutes to read • Edit Online
In this article, you'll learn how to visualize dependencies across your code with code maps.
NOTE
For projects other than C# or Visual Basic, there are fewer options for starting a code map or adding items to an existing
code map. For example, you cannot right-click an object in the text editor of a C++ project and add it to a code map.
However, you can drag and drop individual code elements or files from Solution Explorer , Class View , and Object
Browser .
Prerequisites
To create a code map in Visual Studio, first install the Code Map and Live Dependency Validation
components
To create and edit code maps, you need Visual Studio Enterprise edition . However, in Visual Studio
Community and Professional editions, you can open diagrams that were generated in Enterprise edition, but you
cannot edit them.
NOTE
Before you share maps created in Visual Studio Enterprise with others who use Visual Studio Professional, make sure that
all the items on the map (such as hidden items, expanded groups, and cross-group links) are visible.
TIP
This template may not appear alphabetically, so scroll down to the bottom of the template list if you don't see it.
This example solution contains Solution Folders (Tests and Components ), Test Projects, Web Projects,
and assemblies. By default, all containment relationships appear as groups, which you can expand and
collapse. The Externals group contains anything outside your solution, including platform dependencies.
External assemblies show only those items that are used. By default, system base types are hidden on the
map to reduce clutter.
3. To drill down into the map, expand the groups that represent projects and assemblies. You can expand
everything by pressing CTRL+A to select all the nodes and then choosing Group , Expand from the
shortcut menu.
4. However, this may not be useful for a large solution. In fact, for complex solutions, memory limitations
may prevent you from expanding all the groups. Instead, to see inside an individual node, expand it. Move
your mouse pointer on top of the node and then click the chevron (down arrow) when it appears.
Or use the keyboard by selecting the item then pressing the plus key (+ ). To explore deeper levels of
code, do the same for namespaces, types, and members.
TIP
For more details about working with code maps using the mouse, keyboard, and touch, see Browse and rearrange
code maps.
5. To simplify the map and focus on individual parts, choose Filters on the code map toolbar and select just
the types of nodes and links you are interested in. For example, you can hide all the Solution Folder and
Assembly containers.
You can also simplify the map by hiding or removing individual groups and items from the map, without
affecting the underlying solution code.
6. To see the relationships between items, select them in the map. The colors of the links indicate the types
of relationship, as shown in the Legend pane.
In this example, the purple links are calls, the dotted links are references, and the light blue links are field
access. Green links can be inheritance, or they may be aggregate links that indicate more than one type of
relationship (or category).
TIP
If you see a green link, it might not mean there's just an inheritance relationship. There might also be method
calls, but these are hidden by the inheritance relationship. To see specific types of links, use the checkboxes in the
Filters pane to hide the types you aren't interested in.
7. To get more information about an item or link, move the pointer on top of it until a tooltip appears. This
shows details of a code element or the categories that a link represents.
8. To examine items and dependencies represented by an aggregate link, first select the link and then open
its shortcut menu. Choose Show Contributing Links (or Show Contributing Links on New Code
Map ). This expands the groups at both ends of the link and shows only those items and dependencies
that participate in the link.
9. To focus in on specific parts of the map, you can continue to remove items you aren't interested in. For
example, to drill into class and member view, simply filter all the namespace nodes in the Filters pane.
10. Another way to focus in on a complex solution map is to generate a new map containing selected items
from an existing map. Hold Ctrl while selecting the items you want to focus on, open the shortcut menu,
and choose New Graph from Selection .
11. The containing context is carried over to the new map. Hide Solution Folders and any other containers
you don't want to see using the Filters pane.
12. Expand the groups and select items in the map to view the relationships.
Also see:
Browse and rearrange code maps
Customize code maps by editing the DGML files
Find potential problems in your code by running an analyzer
View dependencies
Suppose you have a code review to perform in some files with pending changes. To see the dependencies in
those changes, you can create a code map from those files.
1. In Solution Explorer , select the projects, assembly references, folders, files, types, or members that you
want to map.
2. On the Solution Explorer toolbar, choose Show on Code Map . Or, open the shortcut menu for one
or a group of items and choose Show on Code Map .
You can also drag items from Solution Explorer , Class View , or Object Browser , into a new or
existing code map. To include the parent hierarchy for your items, press and hold the Ctrl key while you
drag items, or use the Include Parents button on the code map toolbar to specify the default action. You
can also drag assembly files from outside Visual Studio, such as from Windows Explorer .
NOTE
When you add items from a project that's shared across multiple apps, like Windows Phone or Microsoft Store,
those items appear on the map with the currently active app project. If you change context to another app project
and add more items from the shared project, those items now appear with the newly active app project.
Operations that you perform with an item on the map apply only to those items that share the same context.
3. The map shows the selected items within their containing assemblies.
4. To explore items, expand them. Move the mouse pointer on top of an item, then click the chevron (down
arrow) icon when it appears.
To expand all items, select them using Ctrl +A , then open the shortcut menu for the map and choose
Group > Expand . However, this option isn't available if expanding all groups creates an unusable map or
memory issues.
5. Continue to expand items you are interested in, right down to the class and member level if necessary.
To see members that are in the code but don't appear on the map, click the Refetch Children icon in
the top left corner of a group.
6. To see more items related to those on the map, select one and choose Show Related on the code map
toolbar, then select the type of related items to add to the map. Alternatively, select one or more items,
open the shortcut menu, and then choose the Show option for the type of related items to add to the
map. For example:
For an assembly , choose:
O P T IO N DESC RIP T IO N
Show Assemblies This References Add assemblies that this assembly references. External
assemblies appear in the Externals group.
Show Assemblies Referencing This Add assemblies in the solution that reference this
assembly.
O P T IO N DESC RIP T IO N
O P T IO N DESC RIP T IO N
Show Base Types For a class, add the base class and the implemented
interfaces.
Show Types This References Add all classes and their members that this class uses.
Show Types Referencing This Add all classes and their members that use this class.
Show Containing Namespace and Assembly Add the parent container hierarchy.
Show All Base Types Add the base class or interface hierarchy recursively.
Show All Derived Types For a class, add all the derived classes recursively.
O P T IO N DESC RIP T IO N
Show Methods This Calls Add methods that this method calls.
Show Fields This References Add fields that this method references.
Show Containing Type, Namespace, and Add the parent container hierarchy.
Assembly
O P T IO N DESC RIP T IO N
Show Containing Type, Namespace, and Add the parent container hierarchy.
Assembly
7. The map shows the relationships. In this example, the map shows the methods called by the Find
method and their location in the solution or externally.
8. To simplify the map and focus on individual parts, choose Filters on the code map toolbar and select just
the types of nodes and links you are interested in. For example, turn off display of Solution Folders,
Assemblies, and Namespaces.
See also
Share code maps
Create code maps for C++
Improve code map performance
Use code maps to debug your applications
Map methods on the call stack while debugging
Find potential problems using code map analyzers
Browse and rearrange code maps
Customize code maps by editing the DGML files
Share code maps
4/13/2022 • 2 minutes to read • Edit Online
You can save code maps as part of a Visual Studio project, as an image, or as an XPS file.
Visual Studio saves the map as a .dgml file that you can share with other users of Visual Studio Enterprise and
Visual Studio Professional.
NOTE
Before you share a map with those who use Visual Studio Professional, make sure to expand any groups, show hidden
nodes and cross-group links, and retrieve any deleted nodes that you want others to see on your map. Otherwise, other
users won't be able to see these items.
The following error might occur when you save a map that is in a modeling project or was copied from a modeling project
to another location:
"Cannot save fileName outside the project directory. Linked items are not supported."
Visual Studio shows the error, but creates the saved version anyway. To avoid the error, create the map outside the
modeling project. You can then save it to the location that you want. Just copying the file to another location in the
solution, and then trying to save it will not work.
See also
Map dependencies with code maps
Code maps for C++ projects
4/13/2022 • 2 minutes to read • Edit Online
If you want to create more complete maps for C++ projects, set the browse information compiler option (/FR )
on those projects. Otherwise, a message appears and prompts you to set this option. If you select OK , this sets
the option for just the current map. You can choose to hide the message for all later maps.
When you open a solution that contains Visual C++ projects, it might take some time to update the IntelliSense
database. During this time, you might not be able to create code maps for header (.h or #include ) files until the
IntelliSense database finishes updating. You can monitor the update progress in the Visual Studio status bar.
To see dependencies between all source files and header files in your solution, select Architecture >
Generate Graph of Include Files .
To see dependencies between the currently open file and related source files and header files, open either
the source file or the header file. Open the file shortcut menu anywhere inside the file. Choose Generate
Graph of Include Files .
The code map failed to generate. No projects in the solution were built Fix the build errors that occurred and
successfully. then regenerate the map.
ISSUE P O SSIB L E C A USE RESO L UT IO N
Visual Studio becomes unresponsive The program database (.pdb) file might Rebuild the solution and then try
when you try to generate a code map be corrupted. again.
from the Architecture menu.
A .pdb file stores debugging
information, such as type, method, and
source file information.
Certain settings for the IntelliSense Certain IntelliSense settings might be Turn on the settings to enable them.
browsing database are disabled. disabled in the Visual Studio Options
dialog box. See Options, Text Editor, C/C++,
Advanced.
The message Unknown Methods The binary file might not have a base Turn on the /FIXED:NO option in the
appears on a method node. relocation table. linker.
The program database (.pdb) file might Turn on the /DEBUG option in the
not be built. linker.
Cannot open or find the .pdb file in the Make sure that the .pdb file exists in
expected locations. the expected locations.
The caller is not a function and is either When the caller is a thunk, try using
a thunk in the binary file or a pointer _declspec(dllimport) to avoid the
in the data section. thunk.
See also
Map dependencies with code maps
Improve performance for code maps
4/13/2022 • 2 minutes to read • Edit Online
When you generate a map for the first time, Visual Studio indexes all the dependencies that it finds. This process
might take some time, especially for large solutions, but improves later performance. If your code changes,
Visual Studio reindexes just the updated code. To minimize the time taken for the map to finish rendering,
consider the following suggestions:
Map only the dependencies that interest you.
Before you generate the map for an entire solution, reduce the solution scope.
Turn off automatic build for the solution by selecting Skip Build on the code map toolbar.
Turn off automatic adding of parent items by selecting Include Parents on the code map toolbar.
Edit the code map file directly to remove nodes and links you don't need. Changing the map doesn't
affect the underlying code. See Customize code maps by editing the DGML files.
It might take more time to create maps or add items to a map from Solution Explorer when a project item's
Copy to Output Director y property is set to Copy Always . To increase performance, change this property to
Copy if newer or PreserveNewest . See Incremental builds.
The completed map shows dependencies only for successfully built code. If build errors occur for certain
components, these errors appear on the map. Make sure that a component actually builds and has
dependencies on it before you make architectural decisions based on the map.
Use code maps to debug your applications
4/13/2022 • 5 minutes to read • Edit Online
Code maps in Visual Studio can help you avoid getting lost in large code bases, unfamiliar code, or legacy code.
For example, when you're debugging, you might have to look at code across many files and projects. Use code
maps to navigate around pieces of code and understand the relationships between them. That way, you don't
have to keep track of this code in your head, or draw a separate diagram. So, when your work is interrupted,
code maps help refresh your memory about the code you're working on.
NOTE
To create and edit code maps, you need Visual Studio Enterprise edition. In Visual Studio Community and Professional
editions, you can open diagrams that were generated in Enterprise edition, but you cannot edit them.
Change the layout to rearrange the flow of relationships and make the map easier to read. You can also move
items around the map by dragging them.
TIP
By default, Incremental Layout is turned on. This rearranges the map as little as possible when you add new items. To
rearrange the entire map every time you add new items, turn off Incremental Layout .
Let's examine these methods. On the map, double-click the PaintCanvas method, or select this method and
press F12 . You learn that this method creates history and paintObjects as empty lists.
Now repeat the same steps to examine the clear method definition. You learn that clear performs some tasks
with paintObjects and history . It then calls the Repaint method.
Now examine the addPaintObject method definition. It also performs some tasks with history and
paintObjects . It also calls Repaint .
For example, you can add comments to the map and flag items using colors.
If you have Microsoft Outlook installed, you can email the map to others. You can also export the map as an
image or another format.
To confirm your fix, you restart your debugging session and try to reproduce the bug. Now choosing Undo my
last stroke works as you expect and confirms you made the correct fix.
You can update the map to show the fix you made.
Now you're done with your investigation. You successfully found and fixed the problem by mapping the code.
You also have a map that helps you navigate around the code, remember what you learned, and shows the steps
you took to fix the problem.
See also
Map methods on the call stack while debugging
Visualize code
Map methods on the call stack while debugging in
Visual Studio
4/13/2022 • 4 minutes to read • Edit Online
Create a code map to visually trace the call stack while you're debugging. You can make notes on the map to
track what the code is doing so you can focus on finding bugs.
You'll need:
Visual Studio Enterprise
Visual Studio Enterprise
Code that you can debug, such as Visual C#, Visual Basic, C++, JavaScript, or X++
See:
Map the call stack
Make notes about the code
Update the map with the next call stack
Add related code to the map
Find bugs using the map
Q&A
For details of the commands and actions you can use when working with code maps, see Browse and
rearrange code maps.
The map will update automatically while you continue debugging. See Update the map with the next call
stack.
NOTE
By default, adding items to the map also adds the parent group nodes such as the class, namespace, and assembly. While
this is useful, you can keep the map simple by turning off this feature using the Include Parents button on the map
toolbar, or by pressing CTRL when you add items.
Here you can easily see which methods use the same fields. The most recently added items appear in green.
Continue building the map to see more code.
Find bugs using the map
Visualizing your code can help you find bugs faster. For example, suppose you're investigating a bug in a
drawing program. When you draw a line and try to undo it, nothing happens until you draw another line.
So you set breakpoints in the clear , undo , and Repaint methods, start debugging, and build a map like this
one:
You notice that all the user gestures on the map call Repaint , except for undo . This might explain why undo
doesn't work immediately.
After you fix the bug and continue running the program, the map adds the new call from undo to Repaint :
Q&A
Not all calls appear on the map. Why?
By default, only your own code appears on the map. To see external code, turn it on in the Call Stack
window:
or turn off Enable Just My Code in the Visual Studio debugging options:
How do I stop the map from adding new call stacks automatically?
Choose on the map toolbar. To manually add the current call stack to the map, press Ctrl + Shift + ` .
The map will continue highlighting existing call stacks on the map while you're debugging.
What do the item icons and arrows mean?
To get more info about an item, move the mouse pointer over it and look at the item's tooltip. You can
also look at the Legend to learn what each icon means.
See:
Map the call stack
Make notes about the code
Update the map with the next call stack
Add related code to the map
Find bugs using the map
See also
Map dependencies across your solutions
Use code maps to debug your applications
Find potential problems using code map analyzers
Browse and rearrange code maps
Find potential problems using code map analyzers
4/13/2022 • 2 minutes to read • Edit Online
Run analyzers on code maps to help you identify code that might be overly complex or that might need
improvement. For example, you can use these analyzers:
Loops or circular dependencies You can simplify them and consider whether you can break
these cycles.
Too many dependencies They are performing too many functions or to determine the
impact of changing these areas. A well-formed code map will
show a minimal number of dependencies. To make code
easier to maintain, change, test, and reuse, consider whether
you can refactor these areas so that they are more clearly
defined, or whether you can merge code that performs
similar functions.
No dependencies They are necessary or whether you should remove this code.
A N A LY Z ER TO IDEN T IF Y N O DES T H AT
Circular References Analyzer Have circular dependencies on each other. Note: Circular
dependencies that are in the Generics group are not shown
on the map when you expand the group.
Unreferenced Nodes Analyzer Do not have references from any other nodes. Caution:
Verify each of these cases before assuming that the code is
not used. Certain dependencies such as XAML dependencies
and run-time dependencies cannot be found statically in the
code.
Code map analyzers will continue to run after you apply them. If you change the map, any applied analyzers will
automatically reprocess the updated map. To stop running an analyzer, on the map toolbar, choose Layout >
Analyzers . Turn off the selected analyzer.
TIP
If you have a very large map, running an analyzer might cause an out of memory exception. If this occurs, edit the map to
reduce its scope or generate a smaller one, and then run the analyzer.
See also
Map dependencies across your solutions
Use code maps to debug your applications
Map methods on the call stack while debugging
Browse and rearrange code maps
4/13/2022 • 24 minutes to read • Edit Online
Rearrange items on code maps to make them easier to read and improve their performance.
You can customize code maps without affecting the underlying code in a solution. This is useful when you want
to focus on key code elements or communicate ideas about the code. For example, to highlight interesting areas,
you can select code elements on the map and filter them, change the style of code elements and links, hide or
delete code elements, and organize code elements using properties, categories, or groups.
Requirements
To create code maps, you must have Visual Studio Enterprise.
You can view code maps and make limited edits to code maps in Visual Studio Professional.
Organize the map into smaller areas by grouping related nodes. Collapse those groups to see only the
intergroup dependencies, which appear automatically. See Group nodes.
Use filters to simplify the map and focus on the types of nodes or links you are interested in. See Filter
nodes and links.
Maximize the performance of large maps. See Map dependencies across your solutions for more
information.For example, turn on Skip Build on the map toolbar so that Visual Studio doesn't rebuild
your solution when you update items on the map.
Arrange the dependency flow for the entire map in a specific On the map toolbar, choose Layout , and then:
direction. This can help you see architectural layers in the
code. - Top to Bottom
- Bottom to Top
- Left to Right
- Right to Left
See natural dependency clusters in the code with the most On the map toolbar, choose Layout , and then Quick
dependent nodes at the center of the clusters and the least Clusters .
dependent nodes at the outside of those clusters.
Select one or more nodes on the map. Click on a node to select it. To select or deselect more than
one node, hold CTRL while clicking.
Move specific nodes around on the map. Drag nodes to move them. To move other nodes and links
out of the way as you drag nodes, press and hold the SHIFT
key.
Change the layout inside a group independently of the other Select a node and open the shortcut menu. Choose Layout
nodes and groups on the map. and select a layout style.
- or -
Undo an action in the map. Press CTRL + Z or use the Visual Studio Undo command.
Scan the map. Drag the map in any direction using the mouse.
- or -
- or -
- or -
Zoom in on a specific area using the mouse. Hold the right mouse button while drawing a rectangle
around that area you are interested in.
Resize and fit the map in its window. Choose Zoom to Fit from the Zoom list on the code map
toolbar.
- or -
Find a node on the map by its name. Tip: This works only 1. Choose the icon on the code map toolbar
for items on the map. To find items in your solution but not Find
on the map, find them in Solution Explorer , and then drag (Keyboard: press CTRL + F ) to show the search box in the
them to the map. (Drag your selection, or on the Solution upper right corner of the map.
Explorer toolbar, click Show on Code Map ). 2. Type the item name and press Return or click the
"magnifier" icon. The first item that matches your search
appears selected on the map.
3. To customize your search, open the drop-down list and
choose a search option. The options are Find Next , Find
Previous , and Select All. Then click the corresponding
button next to the search textbox.
Select all unselected nodes. Open the shortcut menu for the selected nodes. Choose
Select , Inver t Selection .
TO P ERF O RM T H ESE ST EP S
Select additional nodes that link to the selected ones. Open the shortcut menu for the selected nodes. Choose
Select and one of these:
Show or hide the Filters pane. Choose the Filters button on the code map toolbar. The
Filters pane is displayed as a tabbed page in Solution
Explorer , by default.
Filter the types of nodes that are shown on the map. Set or clear the checkboxes in the Code Elements list in the
Filters pane.
Filter the types of links that are shown on the map. Set or clear the checkboxes in the Relationships list in the
Filters pane.
Show or hide Test project nodes on the map. Set or clear the Test Assets checkbox in the
Miscellaneous list in the Filters pane.
The icons shown in the Legend panel of the map reflect the settings you make in the list. To show or hide the
Legend panel, click the Legend button on the code map toolbar.
TIP
By default, the map shows cross-group links only for selected nodes. To change this behavior to show or hide aggregated
links between groups, click Layout on the code map toolbar and choose Advanced , then Show All Cross-Group
Links or Hide All Cross-Group Links . See Hide or show nodes and links for more details.
TO P ERF O RM T H ESE ST EP S
TO P ERF O RM T H ESE ST EP S
See more information about a node or a link. Move the mouse pointer on top of the node or link until a
tooltip appears.
- or -
Open the shortcut menu for the node or the link. Choose
Edit , Proper ties .
Show or hide the contents of a group. - To expand a group, open the shortcut menu for the node
and choose Group , Expand .
- or -
Move the mouse pointer on top of the node until the
chevron (down arrow) button appears. Click this button to
expand the group. Keyboard: to expand or collapse the
selected group, press the PLUS key (+ ) or the MINUS key
(- ).
- To collapse a group, open the shortcut menu for the node
and choose Group , Collapse .
- or -
Move the mouse pointer on top of a group until the
chevron (up arrow) button appears. Click this button to
collapse the group.
- To expand all groups, press CTRL + A to select all the
nodes. Open the shortcut menu for the map and choose
Group , Expand . Note: This command is not available if
expanding all groups generates an unusable map or
memory issues. It is recommended that you expand the map
only to the level of detail that you care about.
- To collapse all groups, open the shortcut menu for a node
or for the map. Choose Group , Collapse All.
TO P ERF O RM T H ESE ST EP S
See the code definition for a namespace, type, or member. Open the shortcut menu for the node and choose Go To
Definition .
-or-
-or-
For example:
- For a namespace containing one class, the code file for the
class opens to show the definition of that class. In other
cases, the Find Symbol Results window shows a list of
code files. Note: When you perform this task on a Visual
Basic namespace, the code file behind the namespace does
not open. This issue also occurs when you perform this task
on a group of selected nodes that include a Visual Basic
namespace. To work around this issue, browse manually to
the code file behind the namespace, or omit the node for the
namespace from your selection.
- For a class or a partial class, the code file for that class
opens to show the class definition.
- For a method, the code file for the parent class opens to
show the method definition.
Examine dependencies and items that participate in an Select the links you're interested in and open the shortcut
aggregate link. menu for your selection. Choose Show Contributing
Links or Show Contributing Links on New Code Map .
Examine dependencies across multiple nodes in different Expand the groups so you can see all their children. Select all
groups. the nodes that interest you, including their children. The
map shows the cross-group links between the selected
nodes.
To select all nodes in a group, press and hold SHIFT and the
left mouse button while you draw a rectangle around that
group. To select all nodes on a map, press CTRL +A . Tip: To
show cross-group links at all times, choose Layout on the
map toolbar, Advanced , Show All Cross-Group Links .
See the items that a node or link references. Open the shortcut menu for the node and choose Find All
References . Note: This applies only when the Reference
attribute is set for the node or link in the map's .dgml file. To
add references to items from nodes or links, see Customize
code maps by editing the DGML files.
Before you share a map that was created in Visual Studio Enterprise with those who use Visual Studio
Professional, make sure to unhide any nodes or cross-group links that you want others to see. Otherwise, those
users won't be able to unhide those items.
To hide or show nodes
TO P ERF O RM T H ESE ST EP S
Show hidden nodes. - To show all hidden nodes inside a group, first make sure
the group is expanded. Open the shortcut menu and choose
Select , Unhide Children .
- or -
Click the Unhide Children icon in the upper left corner
of the group (this is visible only when there are hidden child
nodes).
- To show all hidden nodes, open the shortcut menu for the
map or a node and choose Select , Unhide All.
Show cross-group links at all times. Show All Cross-Group Links . This hides aggregated links
between groups.
Show only cross-group links for selected nodes. Show Cross-Group Links On Selected Nodes
Hide all links. Hide All Links . To show links again, choose one of the
options listed above.
Group nodes
TO P ERF O RM T H ESE ST EP S
Show container nodes as group nodes or leaf nodes. To show container nodes as leaf nodes: select the nodes,
open the shortcut menu for your selection, and choose
Group , Conver t To Leaf .
Change the layout inside a group. Select the group, open the shortcut menu, choose Layout ,
and select the layout style you want.
- or -
Add a node to a non-group node. Drag the node onto the target node. You can convert any
target node into a group by adding nodes to it.
TO P ERF O RM T H ESE ST EP S
Group selected nodes. 1. Select the nodes that you want to group. A pop-up
toolbar appears above the last node you select.
You can rename a group. Open the shortcut menu for the
group and choose Edit , Proper ties to open the Visual
Studio Properties window. In the Label property, rename
the group as required.
Remove groups. Select the group or groups that you want to remove. Open
the shortcut menu for your selection and choose Group ,
Remove Group .
Remove nodes from their parent group. Select the nodes that you want to move. Open the shortcut
menu for your selection and choose Group , Remove From
Parent . This removes nodes up to their grandparent, or to
outside the group if they have no grandparent group.
- or -
Before you share a map that was created using Visual Studio Enterprise with those who use Visual Professional,
make sure any code elements you want others to see are visible on the map. Otherwise, those users won't be
able to retrieve deleted code elements.
Add a node for a code element
TO P ERF O RM T H ESE ST EP S
Add a new generic node at the current mouse pointer 1. Move the mouse pointer to the place on the map where
location. you want to put the new code element and press Inser t .
- or -
Open the shortcut menu for the map and choose Edit , Add ,
Generic Node .
2. Type the name for the new node and press Return .
TO P ERF O RM T H ESE ST EP S
Add a specific type of code element node at the current 1. Move the mouse pointer to the place on the map where
mouse pointer location. you want to put the new code element and open the
shortcut menu for the map.
2. Choose Edit , Add , and select the type of node you want.
3. Type the name for the new node and press Return .
Add a generic or a specific type of code element node to a 1. Select the group node and open the shortcut menu.
group. 2. Choose Edit , Add , and select the type of node you want.
3. Type the name for the new node and press Return .
Add a new node of the same type, and linked from, an 1. Select the code element. A pop-up toolbar appears above
existing node. it.
Add a new generic node that is linked from an existing code 1. Using the keyboard, press Tab until the code element to
element that has focus. link from has the focus (dotted rectangle).
2. Press Alt +Inser t .
3. Type the name for the new node and press Return .
Add a new generic node that links to an existing code 1. Using the keyboard, press Tab until the code element to
element that has focus. link to has the focus (dotted rectangle).
2. Press Alt +Shift +Inser t .
3. Type the name for the new node and press Return .
TO A DD C O DE EL EM EN T S F O R P ERF O RM T H ESE ST EP S
TO A DD C O DE EL EM EN T S F O R P ERF O RM T H ESE ST EP S
Code elements in the solution. 1. Find the code element in Solution Explorer . Use the
Solution Explorer search box or browse the solution. Tip:
To find code elements that have dependencies on a type or a
member, open the shortcut menu for the type or the
member in Solution Explorer . Choose the relationship that
interests you. Solution Explorer shows only those code
elements with the specified dependencies.
2. Drag the code elements that interest you to the map
surface. You can also drag code elements from Class View or
Object Browser.
- or -
In Solution Explorer , select the code elements that you
want to map. Then, on the Solution Explorer toolbar, click
Show on Code Map .
Code elements related to code elements on the map. Click the Show Related button on the code map toolbar
and choose the type of related items you are interested in.
- or -
Open the shortcut menu for the code element. Choose one
of the Show ... items on the menu depending on the kind
of relationship that interests you. For example, you can see
items that the current item references, items that reference
the current item, base and derived types for classes, method
callers, and the containing classes, namespaces, and
assemblies.
Compiled .NET assemblies (.dll or .exe) or binaries. Drag the assemblies or binaries from outside Visual Studio to
a map.
2. On the toolbar, choose the first icon, Create new link from this node to which ever node that you
click on next .
3. Select the target code element. A link appears between the two code elements.
OR
1. Select the source code element on the map.
2. If you have a mouse installed, move the mouse pointer outside the bounds of the map.
3. Open the code element's shortcut menu and choose Edit > Add > Generic Link .
4. Tab to and select the target code element for the link.
5. Press Enter .
2. On the toolbar, choose the third icon, Create a new comment node with a new link to the selected
node .
- or -
Open the shortcut menu for the code element and choose Edit > New Comment .
3. Type your comments. To type on a new line, press Shift + Enter .
Add a comment to the map itself
1. Open the shortcut menu for the map and choose Edit > New Comment .
2. Type your comments. To type on a new line, press Shift + Enter .
NOTE
To create and assign a category or a property to a code element, you can edit the map's .dgml file; see Customize
code maps by editing the DGML files.
4. In the Legend box, click the icon next to the category or property you added or you want to change.
5. Use the following table to select the style that you want to change:
TO C H A N GE T H E C H O O SE
Icon Icons
The Color Set Picker or Icon Set Picker dialog box appears for you to select a color or icon.
6. In the Color Set Picker or Icon Set Picker dialog box, do one of the following:
TO A P P LY A P ERF O RM T H ESE ST EP S
Set of colors or icons Open the Select color (or icon ) set list. Select a set of
colors or icons.
Specific color or icon Open the category or property value list. Select a color
or icon.
NOTE
You can rearrange, delete, or temporarily inactivate styles in the Legend box. See Edit the Legend box.
TO C H O O SE
See also
Map dependencies across your solutions
Use code maps to debug your applications
Find potential problems using code map analyzers
Customize code maps by editing the DGML files
Directed Graph Markup Language (DGML) reference
Customize code maps by editing the DGML files
4/13/2022 • 13 minutes to read • Edit Online
To customize a code map, you can edit its Directed Graph Markup Language (.dgml) file. For example, you can
edit elements to specify custom styles, assign properties and categories to code elements and links, or link
documents or URLs to code elements or to links. For more information about DGML elements, see Directed
Graph Markup Language (DGML) reference.
Edit the code map's .dgml file in a text or XML editor. If the map is part of your Visual Studio solution, select it in
Solution Explorer , open the shortcut menu, and choose Open With , XML (Text) Editor .
NOTE
To create code maps, you must have Visual Studio Enterprise edition. When you edit a code map in Visual Studio, it cleans
up any unused DGML elements and attributes by deleting them when you save the .dgml file. It also creates code
elements automatically when you manually add new links. When you save the .dgml file, any attributes that you added to
an element might rearrange themselves in alphabetical order.
<Nodes>
<Node Id="MyFirstGroup" Group="Expanded" />
<Node Id="MySecondGroup" Group="Collapsed" />
</Nodes>
4. In the <Links> section, make sure that a <Link/> element that has the following attributes exist for each
relationship between a group code element and its child code elements:
A Source attribute that specifies the group code element
A Target attribute that specifies the child code element
A Category attribute that specifies a Contains relationship between the group code element and
its child code element
For example:
<Links>
<Link Category="Contains" Source="MyFirstNewGroup" Target="FirstGroupChildOne" />
<Link Category ="Contains" Source="MyFirstNewGroup" Target="FirstGroupChildTwo" />
<Link Category ="Contains" Source="MySecondNewGroup" Target="SecondGroupChildOne" />
<Link Category="Contains" Source="MySecondNewGroup" Target="SecondGroupChildTwo" />
</Links>
For more information about the Category attribute, see Assign categories to code elements and links.
Background="ColorNameOrHexadecimalValue"
Border color
Stroke="StrokeValue"
For example:
TIP
If you have repeating styles across many code elements or links, you might consider applying a category to those code
elements or links, and then applying a style to that category. For more information, see Assign Categories to Code
elements and Links and Assign Properties to Code elements and Links.
To a p p l y a c u st o m st y l e t o a si n g l e c o d e e l e m e n t
Outline
Stroke="ColorNameOrHexadecimalValue"
Outline thickness
StrokeThickness="StrokeValue"
Text color
Foreground="ColorNameOrHexadecimalValue"
Icon
Icon="IconFilePathLocation"
Text size
FontSize="FontSizeValue"
Text type
FontFamily="FontFamilyName"
Text weight
FontWeight="FontWeightValue"
Text style
FontStyle="FontStyleName"
Style="Glass"
or -
Style="Plain"
Shape
To replace the shape with an icon, set the Shape property to None and set the Icon property to the path
with the icon file.
Shape="ShapeFilePathLocation"
For example:
<Nodes>
<Node Id="MyNode" Background="#FF008000" Stroke="#FF000000"
Foreground="#FFFFFFFF" Icon="...\Icons\Globe.png"/>
</Nodes>
To a p p l y a c u st o m st y l e t o a si n g l e l i n k
Stroke="ColorNameOrHexadecimalValue"
Outline thickness
StrokeThickness="StrokeValue"
Outline style
StrokeDashArray="StrokeArrayValues"
For example:
<Links>
<Link Source="MyFirstNode" Target="MySecondNode" Background="Green" Stroke="#FF000000"
StrokeDashArray="2,2"/>
</Links>
To a p p l y c u st o m st y l e s t o a g r o u p o f c o d e e l e m e n t s o r l i n k s
<Condition Expression="MyCategory"/>
or -
or -
<Condition Expression="HasCategory('MyCategory')"/>
As a simple complete example, the following condition specifies that a code element appears green or red
based on whether its Passed category is set to True or False :
<?xml version="1.0" encoding="utf-8"?>
<DirectedGraph xmlns="http://schemas.microsoft.com/vs/2009/dgml">
<Nodes>
<Node Id="MyFirstNode" Passed="True" />
<Node Id="MySecondNode" Passed="False" />
</Nodes>
<Links>
</Links>
<Styles>
<Style TargetType="Node" GroupLabel="Passed" ValueLabel="True">
<Condition Expression="Passed='True'"/>
<Setter Property="Background" Value="Green"/>
</Style>
<Style TargetType="Node" GroupLabel="Passed" ValueLabel="False">
<Condition Expression="Passed='False'"/>
<Setter Property="Background" Value="Red"/>
</Style>
</Styles>
</DirectedGraph>
The following table includes some example conditions that you can use:
Set the font size as a function of the number of lines of code, which also changes the size of the code element.
This example uses a single conditional expression to set multiple properties, FontSize and FontFamily .
Set the background color of a code element based on the Coverage property. The styles are evaluated in the
order that they appear, similar to if-else statements.
In this example:
1. If Coverage is > 80, then set the Background property to green.
2. Else if Coverageis > 50, then set the Background property to a shade of orange based on the value of the
Coverage property.
3. Else set the Background property to a shade of red based on the value of the Coverage property.
<?xml version="1.0" encoding="utf-8"?>
<DirectedGraph xmlns="http://schemas.microsoft.com/vs/2009/dgml">
<Nodes>
<Node Id="Class1" Coverage="58" />
<Node Id="Class2" Coverage="95" />
<Node Id="Class3" Coverage="32" />
</Nodes>
<Properties>
<Property Id="Coverage" Label="Coverage" Description="Code coverage as a percentage of blocks"
DataType="Double" />
</Properties>
<Styles>
<Style TargetType="Node" GroupLabel="Coverage" ValueLabel="Good">
<Condition Expression="Coverage > 80" />
<Setter Property="Background" Value="Green" />
</Style>
<Style TargetType="Node" GroupLabel="Coverage" ValueLabel="OK">
<Condition Expression="Coverage > 50" />
<Setter Property="Background" Expression="Color.FromRgb(180 * Math.Max(1, (80 - Coverage) / 30), 180,
0)" />
</Style>
<Style TargetType="Node" GroupLabel="Coverage" ValueLabel="Bad">
<Setter Property="Background" Expression="Color.FromRgb(180, 180 * Coverage / 50, 0)" />
</Style>
</Styles>
</DirectedGraph>
Set the Shape property to None so that the icon replaces the shape. Use the Icon property to specify the
location of the icon.
<DirectedGraph xmlns="http://schemas.microsoft.com/vs/2009/dgml">
<Nodes>
<Node Id="Automation" Category="Test" Label="Automation" />
<Node Id="C# Provider" Category="Provider" Label="C# Provider" />
</Nodes>
<Categories>
<Category Id="Provider" Icon="...\Icons\Module.png" Shape="None" />
<Category Id="Test" Icon="...\Icons\Page.png" Shape="None" />
</Categories>
<Properties>
<Property Id="Icon" DataType="System.String" />
<Property Id="Label" Label="Label" Description="Displayable label of an Annotatable object"
DataType="System.String" />
<Property Id="Shape" DataType="System.String" />
</Properties>
<Styles>
<Style TargetType="Node" GroupLabel="Group" ValueLabel="Has category">
<Condition Expression="HasCategory('Group')" />
<Setter Property="Background" Value="#80008080" />
</Style>
<Style TargetType="Node">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</Styles>
</DirectedGraph>
<Nodes>
<Node Id="MyNode" MyPropertyName="PropertyValue" />
</Nodes>
3. Add a <Property/> element to the <Properties> section to specify attributes such as its visible name and
data type:
<Properties>
<Property Id="MyPropertyName" Label="My Property" DataType="System.DataType"/>
</Properties>
<Links>
<Link Source="MyFirstNode" Target="MySecondNode" MyPropertyName="PropertyValue" />
</Links>
4. Add a <Property/> element to the <Properties> section to specify attributes such as its visible name and
data type:
<Properties>
<Property Id="MyPropertyName" Label="My Property Name" DataType="System.DataType"/>
</Properties>
<Nodes>
<Node Id="MyNode" Category="MyCategory" />
</Nodes>
Add a <Category/> element to the <Categories> section so that you can use the Label attribute to
specify the display text for that category:
<Categories>
<Category Id="MyCategory" Label="My Category" />
</Categories>
<Links>
<Link Source="MyFirstNode" Target="MySecondNode" Category="MyCategory"
</Links>
4. Add a <Category/> element to the <Categories> section so that you can use the Label attribute to
specify the display text for that category:
<Categories>
<Category Id="MyCategory" Label="My Category" />
</Categories>
<Nodes>
<Node Id="MyFirstNode" Label="My First Node" Category= "MyCategory" />
<Node Id="MySecondNode" Label="My Second Node" />
</Nodes>
<Links>
<Link Source="MyFirstNode" Target="MySecondNode" />
</Links>
<Categories>
<Category Id="MyCategory" Label="My Category" BasedOn="MyParentCategory"/>
<Category Id="MyParentCategory" Label="My Parent Category" Background="Green"/>
</Categories>
In this example, the background of MyFirstNode is green because its Category attribute inherits the
Background attribute of MyParentCategory .
If you use relative paths, and the .dgml file is moved to a different location, then those paths will no longer
resolve. When you try to open and view the linked content, an error stating that the content cannot be viewed
will occur.
For example, you might want to link the following code elements:
To describe the changes to a class, you might link the URL of a work code element, document, or another
.dgml file to the code element for a class.
You might link a dependency diagram to a group code element that represents a layer in the software's
logical architecture.
To show more information about a component that exposes an interface, you might link a component
diagram to the code element for that interface.
Link a code element to a Team Foundation Server work item or bug, or some other information that is
related to the code element.
To link a document or URL to a code element
1. Open the .dgml file in a text or XML editor.
2. Find the <Node/> element for the code element that you want.
3. Perform one of the tasks in the following table:
A single code element
In the <Node/> or <Link/> element, add a Reference attribute to specify the location of the code
element.
NOTE
You can have only one Reference attribute per element.
For example:
<Nodes>
<Node Id="MyNode" Reference="MyDocument.txt" />
</Nodes>
<Properties>
<Property Id="Reference" Label="My Document" DataType="System.String" IsReference="True" />
</Properties>
On the map, the name of the code element appears underlined. When you open the shortcut menu for
the code element or the link, you will see a Go To Reference shortcut menu that contains the linked
code elements for you to choose.
4. Use the ReferenceTemplate attribute to specify a common string, such as a URL, that is used by multiple
references instead of repeating that string in the reference.
The ReferenceTemplate attribute specifies a placeholder for the value of the reference. In the following
example, the {0} placeholder in the ReferenceTemplate attribute will be replaced by the values of the
MyFirstReference and MySecondReference attributes in the <Node/> element to produce a full path:
<Nodes>
<Node Id="MyNode" MyFirstReference="MyFirstDocument" MySecondReference="MySecondDocument"/>
<Node Id="MySecondNode" MyFirstReference="AnotherFirstDocument"
MySecondReference="AnotherSecondDocument"/>
</Nodes>
<Properties>
<Property Id="MyFirstReference" Label="My First Document" DataType="System.String"
IsReference="True" ReferenceTemplate="http://www.Fabrikam.com/FirstDocuments/{0}.asp"/>
<Property Id="MySecondReference" Label="My Second Document" DataType="System.String"
IsReference="True" ReferenceTemplate=" http://www.Fabrikam.com/SecondDocuments/{0}.asp"/>
</Properties>
5. To view the referenced code element or code elements from the map, open the shortcut menu for the
code element or the link. Choose Go To Reference and then the code element.
See also
Map dependencies across your solutions
Use code maps to debug your applications
Find potential problems using code map analyzers
Browse and rearrange code maps
Directed Graph Markup Language (DGML) reference
Directed Graph Markup Language (DGML)
reference
4/13/2022 • 7 minutes to read • Edit Online
Directed Graph Markup Language (DGML) describes information used for visualization and to perform
complexity analysis, and is the format used to persist code maps in Visual Studio. It uses simple XML to describe
both cyclical and acyclic directed graphs. A directed graph is a set of nodes that are connected by links, or edges.
Nodes and links can be used represent network structures, such as elements in a software project.
Note that some versions of Visual Studio support only a subset of DGML capabilities, see Version support for
architecture and modeling tools.
NOTE
When you edit a .dgml file, IntelliSense helps you identify attributes that are available for each element and their values. To
specify color in an attribute, use names for common colors, such as "Blue", or ARGB hexadecimal values, such as
"#ffa0b1c3". DGML uses a small subset of Windows Presentation Foundation (WPF) color definition formats. For more
information, see Colors Class.
DGML syntax
The following table describes kinds of elements that are used in DGML:
<DirectedGraph></DirectedGraph>
This element is the root element of code map (.dgml) document. All other DGML elements appear within
the scope of this element.
The following list describes optional attributes that you can include:
Background - The color of the map background
BackgroundImage - The location of an image file to use as the map background.
GraphDirection - When the map is set to tree layout ( Sugiyama ), arrange the nodes so that most of the
links flow in the specified direction: TopToBottom , BottomToTop , LeftToRight , or RightToLeft . See
Change the map layout.
Layout - Set the map to the following layouts: None , Sugiyama (tree layout), ForceDirected (quick
clusters), or DependencyMatrix . See Change the map layout.
NeighborhoodDistance - When the map is set to tree layout or quick clusters layout, show only those
nodes that are a specified number (1-7) of links away from selected nodes. See Change the map layout.
Example:
<?xml version="1.0" encoding="utf-8"?>
<DirectedGraph Title="DrivingTest" Background="Blue"
xmlns="http://schemas.microsoft.com/vs/2009/dgml">
<Nodes>
...
</Nodes>
<Links>
...
</Links>
<Categories>
...
</Categories>
<Properties>
...
</Properties>
</DirectedGraph>
<Nodes></Nodes>
This optional element contains a list of <Node/> elements, which define nodes on the map. For more
information, see the <Node/> element.
NOTE
When you reference an undefined node in a <Link/> element, the map creates a <Node/> element
automatically.
Example:
<Node/>
This element defines a single node. It appears within the <Nodes><Nodes/> element list.
This element must include the following attributes:
Id - The unique name of the node and the default value of the Label attribute, if no separate Label
attribute is specified. This name must match the Source or Target attribute of the link that references it.
The following list describes some of the optional attributes that you can include:
Label - The display name of the node.
Style attributes. See Customize code maps by editing the DGML files.
Category - The name of a category that identifies elements that share this attribute. For more
information, see the <Category/> element.
Property - The name of a property that identifies elements that have the same property value. For more
information, see the <Property/> element.
Group - If the node contains other nodes, set this attribute to Expanded or Collapsed to show or hide its
contents. There must be a <Link/> element that includes the Category="Contains" attribute and specifies
the parent node as the source node and the child node as the target node. See Group code elements.
Visibility - Set this attribute to Visible , Hidden , or Collapsed . Uses System.Windows.Visibility . See
Hide or show nodes and links.
Reference - Set this attribute to link to a document or URL. See Link documents or URLs to code
elements and links.
Example:
<Links></Links>
This element contains the list of <Link> elements, which define links between nodes. For more
information, see the <Link/> element.
Example:
<Link/>
This element defines a single link that connects a source node to a target node. It appears within the
<Links></Links> element list.
NOTE
If this element references an undefined node, the map document automatically creates a node that has the
specified attributes, if any.
<Categories></Categories>
This element contains the list of <Category/> elements. For more information, see the <Category/>
element.
Example:
<Category/>
This element defines a Category attribute, which is used to identify elements that share this attribute. A
Category attribute can be used to organize map elements, provide for shared attributes through
inheritance, or define additional metadata.
This element must include the following attributes:
Id - The unique name of the category and the default value of the Label attribute, if no separate Label
attribute is specified.
The following list describes some of the optional attributes that you can include:
Label - A reader-friendly name for the category.
BasedOn - The parent category from which the <Category/> of the current element inherits.
In the example for this element, the FailedTest category inherits its Stroke attribute from the
PassedTest category. See "To create hierarchical categories" in Customize code maps by editing the
DGML files.
Categories also provide some basic template behavior that controls the appearance of nodes and links
when they are displayed on a map. See Customize code maps by editing the DGML files.
Example:
<Properties></Properties>
This element contains the list of <Property/> elements. For more information, see the <Property/>
element.
Example:
<Property/>
This element defines a Property attribute that you can use to assign a value to any DGML element or
attribute, including categories and other properties.
This element must include the following attributes:
Id - The unique name of the property and the default value of the Label attribute, if no separate
Label attribute is specified.
<Paths>
<Path Id="MyPathAlias" Value="C:\...\..." />
</Paths>
To reference the alias from an element in the .dgml file, enclose the Id of the <Path/> element with a dollar
sign ($) and parentheses (()):
<Nodes>
<Node Id="MyNode" Reference="$(MyPathAlias)MyDocument.txt" />
</Nodes>
<Properties>
<Property Id="Reference" Label="My Document" DataType="System.String" IsReference="True" />
</Properties>
See also
Map dependencies across your solutions
Use code maps to debug your applications
Find potential problems using code map analyzers
Create dependency diagrams from your code
4/13/2022 • 9 minutes to read • Edit Online
To visualize your software system's high-level, logical architecture, create a dependency diagram in Visual
Studio. To make sure that your code stays consistent with this design, validate your code with a dependency
diagram. You can create dependency diagrams for Visual C# and Visual Basic projects. To see which editions of
Visual Studio support this feature, see Edition support for architecture and modeling tools.
A dependency diagram lets you organize Visual Studio solution items into logical, abstract groups called layers.
You can use layers to describe major tasks that these artifacts perform or the system's major components.
Each layer might contain other layers that describe more detailed tasks. You can also specify the intended or
existing dependencies between layers. These dependencies, represented as arrows, show the layers that use or
can use the functionality represented by other layers. To maintain architectural control of the code, show the
intended dependencies on the diagram and then validate the code against the diagram.
Video: Validate your architecture dependencies in real time
IMPORTANT
Don't add, drag, or copy an existing dependency diagram from one modeling project to another modeling project, or to
another place in the solution. This preserves the references from the original diagram, even if you change the diagram.
This also prevents layer validation from working correctly and might cause other issues, such as missing elements or other
errors when you try to open the diagram.
Instead, add a new dependency diagram to the modeling project. Copy the elements from the source diagram to the new
diagram. Save both, the modeling project and the new dependency diagram.
NOTE
Dependency diagrams for .NET Core projects are supported starting Visual Studio 2019 version 16.2.
NOTE
The dependency diagram must exist inside a modeling project. However, you can link it to items anywhere in the
solution.
5. Make sure to save both, the modeling project and the dependency diagram.
TO F O L LO W T H ESE ST EP S
Create a layer for a single artifact 1. Drag the item onto the dependency diagram from
these sources:
Solution Explorer
For example, you can drag files or projects.
Code maps
See Map dependencies across your solutions
and Use code maps to debug your
applications.
Class View or Object Browser
A layer appears on the diagram and is linked to the
artifact.
2. Rename the layer to reflect the responsibilities of the
associated code or artifacts.
Create a single layer for all selected artifacts Drag all the artifacts to the dependency diagram at the
same time.
Create a layer for each selected artifact Press and hold the SHIFT key while you drag all of the
artifacts to the dependency diagram at the same time.
Note: If you use the SHIFT key to select a range of items,
release the key after you select the artifacts. Press and hold
it again when you drag the artifacts to the diagram.
Create a new unlinked layer In the Toolbox, expand the Dependency Diagram section,
and then drag a Layer to the dependency diagram.
- or -
- or -
Open the shortcut menu for a layer, choose Add , and then
choose Layer .
Create a new layer that contains two or more existing layers Select the layers, open the shortcut menu for your selection,
and then choose Group .
Change the color of a layer Set its Color property to the color that you want.
Specify that artifacts associated with a layer shouldn't belong Type the namespaces in the layer's Forbidden
to the specified namespaces Namespaces property. Use a semicolon (; ) to separate the
namespaces.
Specify that artifacts associated with a layer can't depend on Type the namespaces in the layer's Forbidden Namespace
the specified namespaces Dependencies property. Use a semicolon (; ) to separate
the namespaces.
Specify that artifacts associated with a layer must belong to Type the namespace in the layer's Required Namespaces
one of the specified namespaces property. Use a semicolon (; ) to separate the namespaces.
The number on a layer indicates the number of artifacts that are linked to the layer. However, when reading this
number, remember:
If a layer links to an artifact that contains other artifacts, but the layer doesn't link directly to the other
artifacts, then the number includes only the linked artifact. However, the other artifacts are included for
analysis during layer validation.
For example, if a layer is linked to a single namespace, then the number of linked artifacts is 1, even if the
namespace contains classes. If the layer also has links to each class in the namespace, then the number
will include the linked classes.
If a layer contains other layers that are linked to artifacts, then the container layer is also linked to those
artifacts, even though the number on the container layer doesn't include those artifacts.
TO IN L AY ER EXP LO RER
Delete the link between the layer and an artifact Open the shortcut menu for the artifact link, and then
choose Delete .
Move the link from one layer to another Drag the artifact link to an existing layer on the diagram.
- or -
1. Open the shortcut menu for the artifact link, and then
choose Cut .
2. On the dependency diagram, open the shortcut menu for
the layer, and then choose Paste .
Copy the link from one layer to another 1. Open the shortcut menu for the artifact link, and then
choose Copy .
2. On the dependency diagram, open the shortcut menu for
the layer, and then choose Paste .
Create a new layer from an existing artifact link Drag the artifact link to a blank area on the diagram.
Verify that a linked artifact supports validation against the Look at the Suppor ts Validation column for the artifact
dependency diagram. link.
NOTE
Dependencies can't be reverse-engineered for certain kinds of artifacts. For example, no dependencies will be reverse-
engineered from or to a layer that is linked to a text file. To see which artifacts have dependencies that you can reverse-
engineer, open the shortcut menu for one or multiple layers, and then choose View Links . In Layer Explorer , examine
the Suppor ts Validation column. Dependencies won't be reverse-engineered for artifacts for which this column shows
False .
Select one or multiple layers, open the shortcut menu for a selected layer, and then choose Generate
Dependencies .
If you see some dependencies that shouldn't exist, you can edit these dependencies to align them with the
intended design.
TO P ERF O RM T H ESE ST EP S
Specify that artifacts associated with a layer can't depend on Type the namespaces in the layer's Forbidden Namespace
the specified namespaces Dependencies property. Use a semicolon (; ) to separate
the namespaces.
Specify that artifacts associated with a layer must not belong Type the namespaces in the layer's Forbidden
to the specified namespaces Namespaces property. Use a semicolon (; ) to separate the
namespaces.
Specify that artifacts associated with a layer must belong to Type the namespace in the layer's Required Namespaces
one of the specified namespaces property. Use a semicolon (; ) to separate the namespaces.
See also
Edition support for architecture and modeling tools
Video: Validate your architecture dependencies in real time
Dependency Diagrams: Reference
Dependency Diagrams: Guidelines
Validate code with dependency diagrams
Visualize code
Create models for your app
4/13/2022 • 2 minutes to read • Edit Online
Modeling diagrams help you understand, clarify, and communicate ideas about your code and the user
requirements that your software system must support.
To see which versions of Visual Studio support each type of diagram, see Version support for architecture and
modeling tools.
To visualize the architecture of a system or existing code, create the following diagrams:
DIA GRA M SH O W S
Related Tasks
TO P IC TA SK
Model user requirements Use models to clarify and communicate the users' needs.
Model your app's architecture Use models to describe the overall structure and behavior of
your system and to make sure that it meets the users'
needs.
Validate your system during development Make sure that your software stays consistent with your
users' needs and the overall architecture of your system.
Use models in your development process Use models to help you understand and change your
system during its development.
Use models in Agile development
Resources
Visual Studio Visualization & Modeling Tools Forum
Visual Studio Extensibility Forum
Dependency diagrams: reference
4/13/2022 • 4 minutes to read • Edit Online
In Visual Studio, you can use a dependency diagram to visualize the high-level, logical architecture of your
system. A dependency diagram organizes the physical artifacts in your system into logical, abstract groups
called layers. These layers describe major tasks that the artifacts perform or the major components of your
system. Each layer can also contain nested layers that describe more detailed tasks.
To see which editions of Visual Studio support this feature, see Edition support for architecture and modeling
tools.
NOTE
Dependency diagrams for .NET Core projects are supported starting in Visual Studio 2019 version 16.2.
You can specify the intended or existing dependencies between layers. These dependencies, which are
represented as arrows, indicate which layers can use or currently use the functionality represented by other
layers. By organizing your system into layers that describe distinct roles and functions, a dependency diagram
can help make it easier for you to understand, reuse, and maintain your code.
Use a dependency diagram to help you perform the following tasks:
Communicate the existing or intended logical architecture of your system.
Discover conflicts between your existing code and the intended architecture.
Visualize the impact of changes on the intended architecture when you refactor, update, or evolve your
system.
Reinforce the intended architecture during the development and maintenance of your code by including
validation with your check-in and build operations.
This topic describes the elements that you can use on a dependency diagram. For more detailed information
about how to create and draw dependency diagrams, see Dependency Diagrams: Guidelines. For more
information about layering patterns, visit the Patterns & Practices site.
The following table describes the elements that you can use on a dependency diagram.
SH A P E EL EM EN T DESC RIP T IO N
- Forbidden Namespace
Dependencies - Specifies that
artifacts associated with this layer
cannot depend on the specified
namespaces.
- Forbidden Namespaces - Specifies
that artifacts associated with this layer
must not belong to the specified
namespaces.
- Required Namespaces - Specifies
that artifacts associated with this layer
must belong to one of the specified
namespaces.
Layer Explorer
You can link each layer to artifacts in your solution, such as projects, classes, namespaces, project files, and other
parts of your software. The number on a layer shows the number of artifacts that are linked to the layer.
However, when reading the number of artifacts on a layer, remember the following:
If a layer links to an artifact that contains other artifacts, but the layer does not link directly to the other
artifacts, then the number includes only the linked artifact. However, the other artifacts are included for
analysis during layer validation.
For example, if a layer is linked to a single namespace, then the number of linked artifacts is 1, even if the
namespace contains classes. If the layer also has links to each class in the namespace, then the number
will include the linked classes.
If a layer contains other layers that are linked to artifacts, then the container layer is also linked to those
artifacts, even though the number on the container layer does not include those artifacts.
For more information about linking layers and artifacts, see:
Dependency diagrams: Guidelines
Create dependency diagrams from your code
Examine the linked artifacts
On the dependency diagram, open the shortcut menu for one or more layers, and then choose View Links .
Layer Explorer opens and shows the artifacts that are linked to the selected layers. Layer Explorer has a
column that shows each of the properties of the artifact links.
NOTE
If you cannot see all of these properties, expand the Layer Explorer window.
Suppor ts Validation If True , then the layer validation process can verify that the
project conforms to dependencies to or from this element.
See also
Create models for your app
Dependency diagrams: guidelines
4/13/2022 • 10 minutes to read • Edit Online
Describe your app's architecture at a high level by creating dependency diagrams in Visual Studio. Make sure
that your code stays consistent with this design by validating your code with a dependency diagram. You can
also include layer validation in your build process.
To see which editions of Visual Studio support this feature, see Edition support for architecture and modeling
tools.
NOTE
Dependency diagrams for .NET Core projects are supported starting in Visual Studio 2019 version 16.2.
NOTE
These steps appear in approximate order. You will probably want to overlap the tasks, reorder them to suit your own
situation, and revisit them at the start of each iteration in your project.
1. Create a dependency diagram for the whole application, or for a layer within it.
2. Define layers to represent primary functional areas or components of your application. Name these
layers according to their function, for example, "Presentation" or "Services". If you have a Visual Studio
solution, you can associate each layer with a collection of artifacts, such as projects, namespaces, files,
and so on.
3. Discover the existing dependencies between layers.
4. Edit the layers and dependencies to show the updated design that you want the code to reflect.
5. Design new areas of your application by creating layers to represent the principal architectural blocks or
components and defining dependencies to show how each layer uses the others.
6. Edit the layout and appearance of the diagram to help you discuss it with colleagues.
7. Validate the code against the dependency diagram to highlight the conflicts between the code and the
architecture you require.
8. Update the code to conform to your new architecture. Iteratively develop and refactor code until the
validation shows no conflicts.
9. Include layer validation in the build process to ensure that the code continues to adhere to your design.
IMPORTANT
Do not add, drag, or copy an existing dependency diagram from a modeling project to another modeling project or to
another location in the solution. A dependency diagram that is copied in this way will have the same references as the
original diagram, even if you modify the diagram. This will prevent layer validation from working correctly and might cause
other issues, such as missing elements or other errors when trying to open the diagram.
TIP
There are certain types of artifacts that you can link to layers but that do not support validation against the dependency
diagram. To see whether the artifact supports validation, open Layer Explorer to examine the Suppor ts Validation
property of the artifact link. See Discover existing dependencies between layers.
When updating an unfamiliar application, you might also create code maps. These diagrams can help you
discover patterns and dependencies while you explore the code. Use Solution Explorer to explore namespaces
and classes, which often correspond well to existing layers. Assign these code artifacts to layers by dragging
them from Solution Explorer to dependency diagrams. You can then use dependency diagrams to help you
update the code and keep it consistent with your design.
See:
Create dependency diagrams from your code
Use code maps to debug your applications
Map dependencies across your solutions
Discover existing dependencies between layers
A dependency exists wherever an artifact that is associated with one layer has a reference to an artifact that is
associated with another layer. For example, a class in one layer declares a variable that has a class in another
layer. You can discover existing dependencies by reverse-engineering them.
NOTE
Dependencies cannot be reverse-engineered for certain kinds of artifacts. For example, no dependencies will be reverse-
engineered from or to a layer that is linked to a text file. To see which artifacts have dependencies that you can reverse-
engineer, right-click one or multiple layers, and then click View Links . In Layer Explorer , examine the Suppor ts
Validation column. Dependencies will not be reverse-engineered for artifacts for which this column shows False .
TO P ERF O RM T H ESE ST EP S
Delete a dependency that should not exist Click the dependency, and then press DELETE .
Specify that artifacts associated with a layer cannot depend Type the namespaces in the layer's Forbidden Namespace
on the specified namespaces Dependencies property. Use a semicolon (; ) to separate
the namespaces.
Specify that artifacts associated with a layer must not belong Type the namespaces in the layer's Forbidden
to the specified namespaces Namespaces property. Use a semicolon (; ) to separate the
namespaces.
Specify that artifacts associated with a layer must belong to Type the namespace in the layer's Required Namespaces
one of the specified namespaces property. Use a semicolon (; ) to separate the namespaces.
NOTE
As you develop or refactor the code, you might have new artifacts to link to the dependency diagram. However, this
might not be necessary, for example, when you have layers that represent existing namespaces, and the new code only
adds more material to those namespaces.
During the development process, you might want to suppress some of the reported conflicts during validation.
For example, you might want to suppress errors that you are already addressing or that are not relevant to your
particular scenario. When you suppress an error, it is a good practice to log a work item in Team Foundation. To
perform this task, see Validate code with dependency diagrams.
See also
Dependency Diagrams: Reference
Create dependency diagrams from your code
Share models and exporting diagrams
4/13/2022 • 2 minutes to read • Edit Online
In Visual Studio, you can share models and diagrams with colleagues in several ways.
To see which editions of Visual Studio support this feature, see Edition support for architecture and modeling
tools.
Related Topics
T IT L E DESC RIP T IO N
Export diagrams as images You can copy parts of diagrams into applications such as
PowerPoint or Word, or print a diagram to an XPS file.
See also
Use models in your development process
Read models and diagrams in other Visual Studio
editions
4/13/2022 • 2 minutes to read • Edit Online
When you open a model in a version of Visual Studio that does not support model creation, the model opens in
read-only mode. In this mode, you can change the layout of the diagrams, but you cannot change the model.
To see which versions of Visual Studio support model creation, see Version support for architecture and
modeling tools.
NOTE
This does not apply to code maps and .NET class diagrams generated from code. Those diagrams can be viewed
independently of a modeling project.
To read a dependency diagram, the minimum set of files that you need is as follows:
The two diagram files for the diagram that you want to read, for example, MyDiagram.classdiagram
and MyDiagram.classdiagram.layout .
NOTE
For dependency diagrams, you should also have the file that is named MyDiagram.layerdiagram.suppressions .
In Visual Studio, you can save modeling diagrams in read-only formats so that you can share them with
stakeholders, users, or project team members who do not use the same version of Visual Studio.
To see which editions of Visual Studio support this feature, see Edition support for architecture and modeling
tools.
See also
Create models for your app
Share models and exporting diagrams
Use models in your development process
4/13/2022 • 10 minutes to read • Edit Online
In Visual Studio, you can use a model to help you understand and change a system, application, or component. A
model can help you visualize the world in which your system works, clarify users' needs, define the architecture
of your system, analyze the code, and ensure that your code meets the requirements.
To see which versions of Visual Studio support each type of model, see Version support for architecture and
modeling tools.
Models can help you in several ways:
Drawing modeling diagrams helps you clarify the concepts involved in requirements, architecture, and
high-level design. For more information, see Model user requirements.
Working with models can help you expose inconsistencies in requirements.
Communicating with models helps you communicate important concepts less ambiguously than with
natural language. For more information, see Model your app's architecture.
You can sometimes use models to generate code or other artifacts such as database schemas or
documents. For example, the modeling components of Visual Studio are generated from a model. For
more information, see Generate and configure your app from models.
You can use models in a wide variety of processes, from extreme agile to high ceremony.
Levels of abstraction
Models have a range of abstraction in relation to the software. The most concrete models directly represent
program code, and the most abstract models represent business concepts that might or might not be
represented in the code.
A model can be viewed through several kinds of diagrams. For information about models and diagrams, see
Create models for your app.
Different kinds of diagram are useful for describing the design at different levels of abstraction. Many of the
diagram types are useful at more than one level. This table shows how each type of diagram can be used.
External resources
C AT EGO RY L IN K S
See also
Use models in Agile development
Create models for your app
Model user requirements
Model your app's architecture
Develop tests from a model
Structure your modeling solution
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Model user requirements
4/13/2022 • 4 minutes to read • Edit Online
Visual Studio helps you understand, discuss, and communicate your users' needs by drawing diagrams about
their activities and the part your system plays in helping them achieve their goals. A requirements model is a set
of these diagrams, each of which focuses on a different aspect of the users' needs.
To see which versions of Visual Studio support each type of model, see Version support for architecture and
modeling tools.
A requirements model helps you:
Focus on the system's external behavior, separately from its internal design.
Describe the users' and stakeholders' needs with much less ambiguity than you can in natural language.
Define a consistent glossary of terms that can be used by users, developers, and testers.
Reduce gaps and inconsistencies in the requirements.
Reduce the work needed to respond to requirements changes.
Plan the order in which features will be developed.
Use the models as a basis for system tests, making a clear relationship between the tests and the
requirements. When the requirements change, this relationship helps you update the tests correctly. This
makes sure that the system meets the new requirements.
A requirements model provides greatest benefit if you use it to focus discussions with the users or their
representatives, and revisit it at the beginning of each iteration. You do not have to complete it in detail before
writing code. A partially working application, even if very much simplified, generally forms the most stimulating
basis for discussion of the requirements with users. The model is an effective way to summarize the results of
those discussions. For more information, see Use models in your development process.
NOTE
Throughout these topics, "system" means the system or the application that you are developing. It might be a large
collection of many software and hardware components; or a single application; or a software component inside a larger
system. In every case, the requirements model describes the behavior that is visible from outside your system, whether
through a user interface or API.
Common tasks
You can create several different views of the users' requirements. Each view provides a particular type of
information. When you create these views, it is best to move frequently from one to another. You can start from
any view.
W H AT IT DESC RIB ES IN A
DIA GRA M O R DO C UM EN T REQ UIREM EN T S M O DEL SEC T IO N
Additional documents or work items Performance, security, usability and Describing quality of service
reliability criteria. requirements
Additional documents or work items Constraints and rules not specific to a Showing business rules
particular use case
Notice that most of the diagram types can be used for other purposes. For an overview of diagram types, see
Create models for your app.
Dynamic business rules constrain the allowable sequences of events. For example, you use a sequence or
activity diagram to show that a user must log in before performing other operations on your system.
However, many dynamic rules can be more effectively and generically stated by replacing them with static rules.
For example, you could add a Boolean attribute 'Logged In' to a class in the conceptual class model. You would
add Logged In as the postcondition of the log in use case, and add it as a precondition of most of the other use
cases. This approach lets you avoid defining all the possible combinations of sequences of events. It is also more
flexible when you need to add new use cases to the model.
Notice that the choice here is about how you define the requirements, and that this is independent of how you
implement the requirements in the program code.
The following topics provide more information:
TO L EA RN A B O UT REA D
How to develop code that adheres to business rules Model your app's architecture
See also
Use models in your development process
Model your app's architecture
Model your app's architecture
4/13/2022 • 7 minutes to read • Edit Online
To help ensure that your software system or application meets your users' needs, you can create models in
Visual Studio as part of your description of the overall structure and behavior of your software system or
application. Using models, you can also describe patterns that are used throughout the design. These models
help you understand the existing architecture, discuss changes, and communicate your intentions clearly.
To see which editions of Visual Studio support this feature, see Edition support for architecture and modeling
tools.
The purpose of a model is to reduce the ambiguities that occur in natural-language descriptions, and to help you
and your colleagues to visualize the design and to discuss alternative designs. A model should be used together
with other documents or discussions. By itself, a model does not represent a complete specification of the
architecture.
NOTE
Throughout this topic, "system" means the software that you are developing. It might be a large collection of many
software and hardware components, or a single application, or a part of an application.
High-level Design
A high-level design describes the major components of your system and how they interact with one another to
achieve the goals of the design. The activities in the following list are involved in developing the high level
design, although not necessarily in a particular sequence.
If you are updating existing code, you might begin by describing the major components. Make sure you
understand any changes to the user requirements and then add or modify interactions between the
components. If you are developing a new system, begin by understanding the main features of the users' needs.
You can then explore sequences of interactions for the main use cases, and then consolidate the sequences into
a component design.
In every case, it is helpful to develop the different activities in parallel, and to develop code and tests at an early
stage. Avoid trying to complete one of these aspects before you start another. Typically, both the requirements
and your understanding of the best way to design the system will change while you are writing and testing the
code. Therefore, you should begin by understanding and coding the main features of the requirements and your
design. Fill in the details in later iterations of the project.
Understanding the Requirements. The starting point of any design is a clear understanding of the users'
needs.
Architectural Patterns. The choices you made about core technologies and architectural elements of the
system.
Data Model of the Components and Interfaces. You can draw class diagrams to describe the information
that is passed between components and stored inside the components.
Architectural Patterns
Early in a development, you have to choose the major technologies and elements on which the design depends.
The areas in which these choices must be made include the following:
Base technology choices, such as the choice between a database and a file system, and the choice
between a networked application and a web client, and so on.
Frameworks choices, such as a choice between Windows Workflow Foundation or ADO.NET Entity
Framework.
Integration method choices, for example between an enterprise service bus or a point-to-point channel.
These choices are frequently determined by quality of service requirements such as scale and flexibility,
and can be made before the detailed requirements are known. In a large system, the configuration of
hardware and software are strongly interrelated.
The selections that you make affect how you use and interpret the architectural model. For example, in a
system that uses a database, associations in a class diagram might represent relations or foreign keys in
the database, whereas in a system that is based on XML files, associations might indicate cross-references
that use XPath. In a distributed system, messages in a sequence diagram can represent messages on a
wire; in a self-contained application, they can represent function calls.
Design Patterns
A design pattern is an outline of how to design a particular aspect of the software, especially one that recurs in
different parts of the system. By adopting a uniform approach across the project, you can reduce the cost of
design, ensure consistency in the user interface, and reduce the cost of understanding and changing the code.
Some general design patterns such as Observer are well-known and widely applicable. In addition, there are
patterns that are applicable just to your project. For example, in a web sales system, there will be several
operations in the code where changes are made to a customer's order. To ensure that the state of the order is
accurately displayed at every stage, all these operations must follow a particular protocol to update the
database.
Part of the work of software architecture is to determine what patterns should be adopted across the design.
This is usually an ongoing task, because new patterns and improvements to existing patterns will be discovered
as the project progresses. It is helpful to organize the development plan so that you exercise each of your major
design patterns at an early stage.
Most design patterns can be partly embodied in framework code. Part of the pattern can be reduced to
requiring the developer to use particular classes or components, such as a database access layer that ensures
the database is handled correctly.
A design pattern is described in a document, and typically includes these parts:
Name.
Description of the context in which it is applicable. What criteria should make a developer consider
applying this pattern?
Brief explanation of the problem it solves.
Model of the major parts and their relationships. These might be classes or components and interfaces,
with associations and dependencies between them. The elements usually fall into two categories:
Naming conventions.
Description of how the pattern solves the problem.
Description of variations that developers might be able to adopt.
See also
Visualize code
Model user requirements
Develop tests from a model
Use models in your development process
Generate and configure your app from models
4/13/2022 • 5 minutes to read • Edit Online
See also
Generating Code from a Domain-Specific Language
How to: Open a Model from File in Program Code
Design-Time Code Generation by using T4 Text Templates
Structure your modeling solution
4/13/2022 • 5 minutes to read • Edit Online
To use models effectively in a development project, the team members must be able to work on models of
different parts of the project at the same time. This topic suggests a scheme for dividing the application into
different parts that correspond to the layers in an overall layering diagram.
To start on a project or subproject quickly, it is useful to have a project template that follows the project structure
that you have chosen. This topic describes how to create and use such a template.
This topic assumes that you are working on a project that is large enough to require several team members, and
perhaps has several teams. The code and models of the project are stored on a source control system such as
Team Foundation Server. At least some team members use Visual Studio to develop models, and other team
members can view the models by using other Visual Studio versions.
To see which versions of Visual Studio support each tool and modeling feature, see Version support for
architecture and modeling tools.
Solution structure
In a medium or large project, the structure of the team is based on the structure of the application. Each team
uses a Visual Studio solution.
To divide an application into layers
1. Base the structure of your solutions on the structure of your application, such as web application, service
application, or desktop application. A variety of common architectures is discussed in Application
Archetypes in the Microsoft Application Architecture Guide.
2. Create a Visual Studio solution, which we'll call the Architecture solution. This solution will be used to
create the overall design of the system. It will contain models but no code.
Add a dependency diagram to this solution. On the dependency diagram, draw the architecture you have
chosen for your application. For example, the diagram might show these layers and the dependencies
between them: Presentation; Business logic; and Data.
3. Create a separate Visual Studio solution for each layer in the Architecture dependency diagram.
These solutions will be used to develop the code of the layers.
4. Create models that represent the designs of the layers and the concepts that are common to all the
layers. Arrange the models so that all the models can be seen from the Architecture solution, and the
relevant models can be seen from each layer.
You can achieve this by using either of the following procedures. The first alternative creates a separate
modeling project for each layer, and the second creates a single modeling project that is shared between
the layers.
Use a separate modeling project for each layer
1. Create a modeling project in each layer solution.
This model will contain diagrams that describe the requirements and design of that layer. It can also
contain dependency diagrams that show nested layers.
You now have a model for each layer, plus a model for the application architecture. Each model is
contained in its own solution. This enables team members to work on the layers at the same time.
2. To the Architecture solution, add the modeling project of each layer solution. To do this, open the
Architecture solution. In Solution Explorer , right-click the solution node, point to Add, and then click
Existing Project . Navigate to the modeling project (.modelproj) in one layer solution.
Each model is now visible in two solutions: its "home" solution and the Architecture solution.
3. To the modeling project of each layer, add a dependency diagram. Start with a copy of the Architecture
dependency diagram. You can delete parts that are not dependencies of the dependency diagram.
You can also add dependency diagrams that represent the detailed structure of this layer.
These diagrams are used to validate the code that is developed in this layer.
4. In the Architecture solution, edit the requirements and design models of all the layers by using Visual
Studio.
In each layer solution, develop the code for that layer, referring to the model. If you are content to do the
development without using the same computer to update the model, you can read the model and
develop code by using versions of Visual Studio that cannot create models. You can also generate code
from the model in these versions.
This method guarantees that no interference will be caused by developers who edit the layer models at
the same time.
However, because the models are separate, it is difficult to refer to common concepts. Each model must
have its own copy of the elements on which it is dependent from other layers and the architecture. The
dependency diagram in each layer must be kept in sync with the Architecture dependency diagram. It is
difficult to maintain synchronization when these elements change, although you could develop tools to
accomplish this.
Use a separate package for each layer
1. In the solution for each layer, add the Architecture modeling project. In Solution Explorer , right-click the
solution node, point to Add , and then click Existing Project . The single modeling project can now be
accessed from every solution: the Architecture project, and the development project for each layer.
2. In the shared model, create a package for each layer: In Solution Explorer , select the modeling project.
In UML Model Explorer , right-click the model root node, point to Add , and then click Package .
Each package will contain diagrams that describe the requirements and design of the corresponding
layer.
3. If required, add local dependency diagrams for the internal structure of each layer.
This method allows the design elements of each layer to refer directly to those of the layers and common
architecture on which it depends.
Although concurrent work on different packages can cause some conflicts, they are fairly easy to manage
because the packages are stored in separate files.
See also
Use models in your development process
Validate your system during development
4/13/2022 • 2 minutes to read • Edit Online
Visual Studio can help keep your software consistent with user requirements and with the architecture of your
system.
To see which versions of Visual Studio support each of these features, see Version support for architecture and
modeling tools.
Key Tasks
Use the following tasks to validate your software:
TA SK S A SSO C IAT ED TO P IC S
Make sure your software meets the users' - Develop tests from a model
requirements :
Make sure that your software remains consistent - Create dependency diagrams from your code
with the intended design of your system: - Validate code with dependency diagrams
External Resources
Forums - Visual Studio Visualization & Modeling Tools
- Visual Studio extensibility
See also
Model user requirements
Analyze and model architecture
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Develop tests from a model
4/13/2022 • 11 minutes to read • Edit Online
You can use requirements and architectural models to help you organize the tests of your system and its
components. This practice helps ensure that you test the requirements that are important to the users and other
stakeholders, and it helps you update the tests quickly when the requirements change. If you use Microsoft Test
Manager, you can also maintain links between the models and the tests.
To see which versions of Visual Studio support these features, see Version support for architecture and
modeling tools.
Notice that this test method uses the classes of the requirements model. Associations and attributes are realized
as .NET properties.
To make this work, the properties of the classes must be defined as read-only functions or accessors, which
access the system to retrieve information about its current state. Methods that simulate use cases such as
AddItemToOrder must drive the system through its API or through a layer underneath its user interface. The
constructors of test objects such as Order and MenuItem must also drive the system to create corresponding
items inside the system.
Many of the accessors and updaters will already be available through the application's normal API. But some
additional functions might have to be written in order to enable the tests. These additional accessors and
updaters are sometimes known as 'test instrumentation'. Because they depend on the internal design of the
system, it is the responsibility of the system's developers to provide them, whereas the testers write the code of
the tests in terms of the requirements model.
When you write automated tests, you can use Generic Tests to wrap the accessors and updaters.
Tests for Business Rules
Some requirements are not directly related to any one use case. For example, the DinnerNow business allows
customers to choose from many Menus, but requires that in every Order, all the chosen Items shall be from a
single Menu. This business rule can be expressed as an invariant about the associations between Orders, Menus,
and Items in the requirements class model.
An invariant rule of this kind governs not only all the use cases that are currently defined, but also any other use
cases that will be defined later. Therefore, it is useful to write it separately from any use case, and to test it
separately from the use cases.
See also
Create models for your app
Model user requirements
Model your app's architecture
Analyzing and Modeling Architecture
Validate code with dependency diagrams
4/13/2022 • 10 minutes to read • Edit Online
IMPORTANT
If you want to run layer validation using Team Foundation Server (TFS), you must also install the same version of Visual
Studio on your build server.
NOTE
By default, the Build Action property on the dependency diagram (.layerdiagram) file is set to Validate so that
the diagram is included in the validation process.
The Error List window reports any errors that occur. For more information about validation errors, see
Troubleshoot layer validation issues.
2. To view the source of each error, double-click the error in the Error List window.
NOTE
Visual Studio might show a code map instead of the source of the error. This occurs when either the code has a
dependency on an assembly that is not specified by the dependency diagram, or the code is missing a
dependency that is specified by the dependency diagram. Review the code map or the code to determine whether
the dependency should exist. For more information about code maps, see Map dependencies across your
solutions.
or -
Browse to the folder that contains the modeling project (.modelproj) file and the
dependency diagram and then run MSBuild with the following custom property:
msbuild /p:ValidateArchitecture=true
To validate code against all modeling projects in the solution, run MSBuild with the following
custom property:
or -
Browse to the solution folder, which must contain a modeling project that contains a
dependency diagram, and then run MSBuild with the following custom property:
msbuild /p:ValidateArchitecture=true
Any errors that occur will be listed. For more information about MSBuild, see MSBuild and
MSBuild Task.
For more information about validation errors, see Troubleshoot layer validation issues.
Manage validation errors
During the development process, you might want to suppress some of the reported conflicts during validation.
For example, you might want to suppress errors that you are already addressing or that are not relevant to your
particular scenario. When you suppress an error, it is a good practice to log a work item in Team Foundation.
WARNING
You must already be connected to TFS Source Code Control (SCC) to create or link to a work item. If you try to open a
connection to a different TFS SCC, Visual Studio closes the current solution automatically. Ensure that you are already
connected to the appropriate SCC before attempting to create or link to a work item. In later releases of Visual Studio, the
menu commands are not available if you are not connected to an SCC.
TO F O L LO W T H ESE ST EP S
Suppress selected errors during validation Right-click the one or multiple selected errors, point to
Manage Validation Errors , and then click Suppress
Errors .
Stop suppressing selected errors Right-click the selected suppressed error or errors, point to
Manage Validation Errors , and then click Stop
Suppressing Errors .
Restore all suppressed errors in the Error List window Right-click anywhere in the Error List window, point to
Manage Validation Errors , and then click Show All
Suppressed Errors .
Hide all suppressed errors from the Error List window Right-click anywhere in the Error List window, point to
Manage Validation Errors , and then click Hide All
Suppressed Errors .
<ValidateArchitecture>true</ValidateArchitecture>
- or -
1. In Solution Explorer , right-click the modeling project that contains the dependency diagram or
diagrams, and then click Proper ties .
2. In the Proper ties window, set the modeling project's Validate Architecture property to True .
This includes the modeling project in the validation process.
3. In Solution Explorer , click the dependency diagram (.layerdiagram) file that you want to use for
validation.
4. In the Proper ties window, make sure that the diagram's Build Action property is set to Validate .
This includes the dependency diagram in the validation process.
To manage errors in the Error List window, see Resolve layer validation errors.
Validation errors do not occur as Validation does not work on Add a new dependency diagram to the
expected. dependency diagrams that are copied modeling project.
from other dependency diagrams in
Solution Explorer and that are in the Copy the elements from the source
same modeling project. dependency dependency diagram to the new
diagrams that are copied in this way diagram.
contain the same references as the
original dependency diagram.
SY N TA X DESC RIP T IO N
MySolution.MyProject.MyClass.MyMethod(Method)
DV0001: Invalid Dependency This issue is reported when a code element (namespace,
type, member) mapped to a Layer references a code element
mapped to another layer, but there is no dependency arrow
between these layers in the dependency validation diagram
containing this layers. This is a dependency constraint
violation.
DV1001: Invalid namespace name This issue is reported on a code element associated with a
layer which "Allowed Namespace Names" property does not
contain the namespace in which this code element is defined.
This is a naming constraint violation. Note that the syntax of
"Allowed Namespace Names" is to be a semi-colon list of
namespaces in which code elements associated with are
layer are permitted to be defined.
DV1002: Dependency on unreferenceable namespace This issue is reported on a code element associated with a
layer and referencing another code element defined in a
namespace which is defined in the "Unreferenceable
Namespace" property of the layer. This is a naming
constraint violation. Note that the "Unreferenceable
Namespaces" property is defined as a Semi-colon separated
list of namespaces that should not be referenced in code
elements associated with this layer.
DV1003: Disallowed namespace name This issue is reported on a code element associated with a
layer which "Disallowed Namespace Names" property
contains the namespace in which this code element is
defined. This is a naming constraint violation. Note that the
"Disallowed namespace name" property is defined as a Semi-
colon separated list of namespaces in which code elements
associated with this Layer should not be defined.
DV2001: Layer Diagram Presence This issue is reported on a project that does not include a
dependency diagram file, but refers to the dependency
validation analyzers. If Dependency Validation has not been
used, you can remove
"Microsoft.DependencyValidation.Analyzers" directly from
Solution Explorer or suppress this warning. To add a
dependency diagram see Create dependency diagrams from
your code.
DV2002: Unmapped Types Base This issue is reported when a code element is not mapped to
any layer.
DV3001: Missing Link Layer 'LayerName' links to 'Artifact' which cannot be found.
Are you missing an assembly reference?
ERRO R SY N TA X ERRO R DESC RIP T IO N
DV9001: Architectural analysis found internal errors Results might not be complete. For more information, see
the detailed build event log or output window.
See also
Live dependency validation in Visual Studio
Validate your system during development
Video: Validate your architecture dependencies in real time
Extend dependency diagrams
4/13/2022 • 2 minutes to read • Edit Online
You can write code to create and update dependency diagrams and to validate the structure of your program
code against dependency diagrams in Visual Studio. You can add commands that appear in the shortcut
(context) menu of the diagrams, customize drag-and-drop gestures, and access the layer model from text
templates. You can package these extensions into a Visual Studio Integration Extension (VSIX) and distribute
them to other Visual Studio users.
Requirements
You must have the following installed on the computer where you want to develop your layer extensions:
Visual Studio
Visual Studio SDK
Modeling SDK for Visual Studio
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
You must have a suitable edition of Visual Studio installed on the computer where you want to run your layer
extensions. To see which editions of Visual Studio support dependency diagrams, see Edition support for
architecture and modeling tools.
See also
Dependency Diagrams: Reference
Dependency Diagrams: Guidelines
Create dependency diagrams from your code
Validate code with dependency diagrams
Add commands and gestures to dependency
diagrams
4/13/2022 • 6 minutes to read • Edit Online
You can define right-click menu commands and gesture handlers on dependency diagrams in Visual Studio. You
can package these extensions into a Visual Studio Integration Extension (VSIX) that you can distribute to other
Visual Studio users.
You can define several command and gesture handlers in the same Visual Studio project if you want. You can
also combine several such projects into one VSIX. For example, you could define a single VSIX that includes layer
commands, and a domain-specific language.
NOTE
You can also customize architecture validation, in which users' source code is compared with dependency diagrams. You
should define architecture validation in a separate Visual Studio project. You can add it to the same VSIX as other
extensions. For more information, see Add custom architecture validation to dependency diagrams.
Requirements
See Requirements.
NOTE
You can define more than one command or gesture handler class in one class library, but you should define layer
validation classes in a separate class library.
2. Add or create a VSIX project in your solution. A VSIX project contains a file that's named
source.extension.vsixmanifest .
3. In Solution Explorer , right-click the VSIX project and choose Set as Star tup Project .
4. In source.extension.vsixmanifest , under Assets , add the command or gesture handler project as a
MEF component.
a. In the Assets .tab, choose New .
b. At Type , select Microsoft.VisualStudio.MefComponent .
c. At Source , select Project in current solution and select the name of your command or gesture
handler project.
d. Save the file.
5. Return to the command or gesture handler project and add the following project references:
REF EREN C E W H AT T H IS A L LO W S Y O U TO DO
6. Edit the class file in the C# class library project to contain the code for your extension. For more
information, see one of the following sections:
Defining a Menu Command
Defining a Gesture Handler
7. To test the feature, press Ctrl +F5 or F5 .
An experimental instance of Visual Studio opens. In this instance, create or open a dependency diagram.
8. To install the VSIX in the main instance of Visual Studio, or on another computer, find the .vsix file in the
bin directory of the VSIX project. Copy it to the computer where you want to install the VSIX. Double-
click the VSIX file in File Explorer.
[Export(typeof(ICommandExtension))]
...
DiagramContext.CurrentDiagram.SelectedShapes.Count()...
To add a new command, create a new code file that contains the following sample. Then test and edit it.
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.ExtensionEnablement;
using System.ComponentModel.Composition;
using System.Linq;
[Import]
public ILinkedUndoContext LinkedUndoContext { get; set; }
// Menu command label:
public string Text
{
get { return "Duplicate layers"; }
}
Handlers for some types of dragged item are already defined. For example, the user can drag items from
Solution Explorer onto a dependency diagram. You cannot define a drag handler for these types of item.
In these cases, your DragDrop methods will not be invoked.
See also
Add custom architecture validation to dependency diagrams
Add custom architecture validation to dependency
diagrams
4/13/2022 • 8 minutes to read • Edit Online
In Visual Studio, users can validate the source code in a project against a layer model so that they can verify that
the source code conforms to the dependencies on a dependency diagram. There is a standard validation
algorithm, but you can define your own validation extensions.
When the user selects the Validate Architecture command on a dependency diagram, the standard validation
method is invoked, followed by any validation extensions that have been installed.
NOTE
In a dependency diagram, the main purpose of validation is to compare the diagram with the program code in other
parts of the solution.
You can package your layer validation extension into a Visual Studio Integration Extension (VSIX), which you can
distribute to other Visual Studio users. You can either place your validator in a VSIX by itself, or you can combine
it in the same VSIX as other extensions. You should write the code of the validator in its own Visual Studio
project, not in the same project as other extensions.
WARNING
After you have created a validation project, copy the example code at the end of this topic and then edit that to your own
needs.
Requirements
See Requirements.
WARNING
To make the template work properly:
Edit calls to LogValidationError to remove the optional arguments errorSourceNodes and
errorTargetNodes .
If you use custom properties, apply the update mentioned in Add custom properties to dependency diagrams.
2. Edit the code to define your validation. For more information, see Programming Validation.
3. To test the extension, see Debugging Layer Validation.
NOTE
Your method will be called only in specific circumstances, and breakpoints will not work automatically. For more
information, see Debugging Layer Validation.
4. To install the extension in the main instance of Visual Studio, or on another computer, find the .vsix file in the
bin directory. Copy it to the computer where you want to install it, and then double-click it. To uninstall it,
choose Extensions and Updates on the Tools menu.
4. To install the extension in the main instance of Visual Studio, or on another computer, find the .vsix file in the
bin directory. Copy it to the computer where you want to install it, and then double-click it. To uninstall it,
choose Manage Extensions on the Extensions menu.
REF EREN C E W H AT T H IS A L LO W S Y O U TO DO
7. Copy the example code at the end of this topic into the class file in the validator library project to contain
the code for your validation. For more information, see Programming Validation.
8. To test the extension, see Debugging Layer Validation.
NOTE
Your method will be called only in specific circumstances, and breakpoints will not work automatically. For more
information, see Debugging Layer Validation.
9. To install the VSIX in the main instance of Visual Studio, or on another computer, find the .vsix file in the
bin directory of the VSIX project. Copy it to the computer where you want to install the VSIX. Double-
click the VSIX file in Windows Explorer.
Programming Validation
To define a layer validation extension, you define a class that has the following characteristics:
The overall form of the declaration is as follows:
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.CodeSchema;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer;
using Microsoft.VisualStudio.GraphModel;
...
[Export(typeof(IValidateArchitectureExtension))]
public partial class Validator1Extension :
IValidateArchitectureExtension
{
public void ValidateArchitecture(Graph graph)
{
GraphSchema schema = graph.DocumentSchema;
...
} }
WARNING
Do not use the optional parameters of LogValidationError .
When the user invokes the Validate Architecture menu command, the layer runtime system analyses the
layers and their artifacts to produce a graph. The graph has four parts:
The layer models of the Visual Studio solution that are represented as nodes and links in the graph.
The code, project items, and other artifacts that are defined in the solution and represented as nodes, and
links that represent the dependencies discovered by the analysis process.
Links from the layer nodes to the code artifact nodes.
Nodes that represent errors discovered by the validator.
When the graph has been constructed, the standard validation method is called. When this is complete, any
installed extension validation methods are called in unspecified order. The graph is passed to each
ValidateArchitecture method, which can scan the graph and report any errors that it finds.
NOTE
This is not the same as the validation process that can be used in domain-specific languages.
Validation methods should not change the layer model or the code that is being validated.
The graph model is defined in Microsoft.VisualStudio.GraphModel. Its principal classes are GraphNode and
GraphLink.
Each Node and each Link has one or more Categories which specify the type of element or relationship that it
represents. The nodes of a typical graph have the following categories:
Dsl.LayerModel
Dsl.Layer
Dsl.Reference
CodeSchema_Type
CodeSchema_Namespace
CodeSchema_Type
CodeSchema_Method
CodeSchema_Field
CodeSchema_Property
Links from layers to elements in the code have the category "Represents".
Debugging Validation
To debug your layer validation extension, press CTRL+F5. An experimental instance of Visual Studio opens. In
this instance, open or create a layer model. This model must be associated with code, and must have at least one
dependency.
Test with a Solution that contains Dependencies
Validation is not executed unless the following characteristics are present:
There is at least one dependency link on the dependency diagram.
There are layers in the model that are associated with code elements.
The first time that you start an experimental instance of Visual Studio to test your validation extension, open or
create a solution that has these characteristics.
Run Clean Solution before Validate Architecture
Whenever you update your validation code, use the Clean Solution command on the Build menu in the
experimental solution, before you test the Validate command. This is necessary because the results of validation
are cached. If you have not updated the test dependency diagram or its code, the validation methods will not be
executed.
Launch the Debugger Explicitly
Validation runs in a separate process. Therefore, the breakpoints in your validation method will not be triggered.
You must attach the debugger to the process explicitly when validation has started.
To attach the debugger to the validation process, insert a call to System.Diagnostics.Debugger.Launch() at the
start of your validation method. When the debugging dialog box appears, select the main instance of Visual
Studio.
Alternatively, you can insert a call to System.Windows.Forms.MessageBox.Show() . When the message box appears,
go to the main instance of Visual Studio and on the Debug menu click Attach to Process . Select the process
that is named Graphcmd.exe .
Always start the experimental instance by pressing CTRL+F5 (Star t without Debugging ).
Deploying a Validation Extension
To install your validation extension on a computer on which a suitable version of Visual Studio is installed, open
the VSIX file on the target computer.
Example code
using System;
using System.ComponentModel.Composition;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.CodeSchema;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer;
using Microsoft.VisualStudio.GraphModel;
namespace Validator3
{
[Export(typeof(IValidateArchitectureExtension))]
public partial class Validator3Extension : IValidateArchitectureExtension
{
/// <summary>
/// Validate the architecture
/// </summary>
/// <param name="graph">The graph</param>
public void ValidateArchitecture(Graph graph)
{
if (graph == null) throw new ArgumentNullException("graph");
// Get all referenced types in this layer including those from nested layers so each
// type is validated against all containing layer constraints.
foreach (GraphNode containedType in layer.FindDescendants().Where(node =>
node.HasCategory("CodeSchema_Type")))
{
// Check the type name against the required regex
CodeGraphNodeIdBuilder builder = new CodeGraphNodeIdBuilder(containedType.Id,
graph);
string typeName = builder.Type.Name;
if (!regEx.IsMatch(typeName))
{
// Log an error
string message = string.Format(CultureInfo.CurrentCulture,
Resources.InvalidTypeNameMessage, typeName);
this.LogValidationError(graph, typeName + "TypeNameError", message,
GraphErrorLevel.Error, layer);
}
}
}
}
}
}
See also
Extend dependency diagrams
Add custom properties to dependency diagrams
4/13/2022 • 3 minutes to read • Edit Online
When you write extension code for dependency diagrams, you can store values with any element on a
dependency diagram. The values will persist when the diagram is saved and re-opened. You can also have these
properties appear in the Proper ties window so that users can see and edit them. For example, you could let
users specify a regular expression for each layer, and write validation code to verify that the names of classes in
each layer conform to the pattern specified by the user.
Non-visible properties
If you just want your code to attach values to any element in a dependency diagram, you don't need to define a
MEF component. There is a dictionary named Properties in ILayerElement. Simply add marshalable values to
the dictionary of any layer element. They will be saved as part of the dependency diagram.
Editable properties
Initial preparation
IMPORTANT
To make properties appear, make the following change on each computer where you want layer properties to be visible:
1. Run Notepad by using Run as Administrator . Open %ProgramFiles%\Microsoft Visual Studio
[version]\Common7\IDE\Extensions\Microsoft\Architecture Tools\ExtensibilityRuntime\extension.vsixmanifest.
2. Inside the Content element, add:
<MefComponent>Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer.Provider.dll</MefCompo
nent>
3. Under the Visual Studio Tools section of the Visual Studio application start menu, open Developer Command
Prompt . Enter:
devenv /rootSuffix /updateConfiguration
You can define properties on ILayerElement or any of its derived classes, which include:
ILayerModel - the model
ILayer - each layer
ILayerDependencyLink - the links between layers
ILayerComment
ILayerCommentLink
Example
The following code is a typical custom property descriptor. It defines a Boolean property on the layer model (
ILayerModel ) that lets the user provide values for a custom validation method.
using System;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer;
namespace MyNamespace
{
/// <summary>
/// Custom properties are added to the Layer Designer via a custom
/// Property Descriptor. We have to export this Property Descriptor
/// using MEF to make it available in the Layer Designer.
/// </summary>
[Export(typeof(IPropertyExtension))]
public class AllTypesMustBeReferencedProperty
: PropertyExtension<ILayerModel>
{
/// <summary>
/// Each custom property must have a unique name.
/// Usually we use the full name of this class.
/// </summary>
public static readonly string FullName =
typeof(AllTypesMustBeReferencedProperty).FullName;
/// <summary>
/// Construct the property. Notice the use of FullName.
/// </summary>
public AllTypesMustBeReferencedProperty()
: base(FullName)
{ }
/// <summary>
/// The display name is shown in the Properties window.
/// We therefore use a localizable resource.
/// </summary>
public override string DisplayName
{
get { return Strings.AllTypesMustBeReferencedDisplayName; }
}
/// <summary>
/// Description shown at the bottom of the Properties window.
/// We use a resource string for easier localization.
/// </summary>
public override string Description
{
get { return Strings.AllTypesMustBeReferencedDescription; }
}
/// <summary>
/// This is called to set a new value for this property. We must
/// throw an exception if the value is invalid.
/// </summary>
/// <param name="component">The target ILayerElement</param>
/// <param name="value">The new value</param>
public override void SetValue(object component, object value)
{
ValidateValue(value);
base.SetValue(component, value);
}
/// <summary>
/// Helper to validate the value.
/// </summary>
/// <param name="value">The value to validate</param>
private static void ValidateValue(object value)
{ }
/// <summary>
/// The segment label of the properties window.
/// </summary>
public override string Category
{
get
{
return Strings.AllTypesMustBeReferencedCategory;
}
}
}
}
See also
Extend dependency diagrams
Modeling SDK for Visual Studio - Domain-Specific
Languages
4/13/2022 • 2 minutes to read • Edit Online
By using the Modeling SDK for Visual Studio, you can create powerful model-based development tools that you
can integrate into Visual Studio. In the same manner, you can create one or more model definitions and
integrate them into a set of tools.
At the heart of MSDK is the definition of a model that you create to represent concepts in your business area.
You can surround the model with a variety of tools, such as a diagrammatic view, the ability to generate code
and other artifacts, commands for transforming the model, and the ability to interact with code and other
objects in Visual Studio. As you develop the model, you can combine it with other models and tools to form a
powerful toolset that is centered on your development.
MSDK lets you develop a model quickly in the form of a domain-specific language (DSL). You begin by using a
specialized editor to define a schema or abstract syntax together with a graphical notation. From this definition,
VMSDK generates:
A model implementation with a strongly-typed API that runs in a transaction-based store.
A tree-based explorer.
A graphical editor in which users can view the model or parts of it that you define.
Serialization methods that save your models in readable XML.
Facilities for generating program code and other artifacts using text templating.
You can customize and extend all of these features. Your extensions are integrated in such a way that you can
still update the DSL definition and re-generate the features without losing your extensions.
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
This topic explains the basic concepts in defining and using a domain-specific language (DSL) created with the
Modeling SDK for Visual Studio.
NOTE
The Text Template Transformation SDK and the Visual Studio Modeling SDK are installed automatically when you install
specific features of Visual Studio. For more details, see this blog post.
If you are new to DSLs, we recommend that you work through the DSL Tools Lab , which you can find in this
site: Visualization and Modeling SDK
The notation is only part of a DSL. Together with the notation, your VSIX package includes tools that users can
apply to help them edit and generate material from their models.
One of the principal applications of DSLs is to generate program code, configuration files, and other artifacts.
Especially in large projects and product lines, where several variants of a product will be created, generating
many of the variable aspects from DSLs can provide a large increase in reliability and a very rapid response to
requirements changes.
The rest of this overview is a walkthrough that introduces the basic operations of creating and using a domain-
specific language in Visual Studio.
Prerequisites
To define a DSL, you must have installed the following components:
C O M P O N EN T L IN K
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
NOTE
Most of the code that you can see in the folders in the two projects is generated from DslDefinition.dsl. For this
reason, most modifications to your DSL are made in this file.
This solution defines a domain specific language. For more information, see Overview of the Domain-Specific
Language Tools User Interface.
NOTE
When you change the DSL definition, the sample text template code will not work, unless you update it.
For more information, see Generating Code from a Domain-Specific Language and Writing Code to Customize a
Domain-Specific Language.
NOTE
After you have modified the DSL definition, you might lose information in the test models that you have created by using
earlier versions. For example, the debugging solution contains a file that is named Sample, which contains some shapes
and connectors. After you start to develop your DSL definition, they will not be visible, and they will be lost when you
save the file.
You can make a wide variety of extensions to your DSL. The following examples will give you an impression of
the possibilities.
After each change, save the DSL definition, click Transform All Templates in Solution Explorer , and then
press F5 to experiment with the changed DSL.
Rename the Types and Tools
Rename the existing domain classes and relationships. For example, starting from a Dsl Definition created from
the Minimal Language template, you could perform the following renaming operations, to make the DSL
represent family trees.
To rename domain classes, relationships and tools
1. In the DslDefinition diagram, rename ExampleModel to FamilyTreeModel , ExampleElement to
Person , Targets to Parents , and Sources to Children . You can click each label to change it.
NOTE
Every domain class except the root of the model must be the target of at least one embedding
relationship, or it must inherit from a class that is the target of an embedding. For this reason, it is
frequently convenient to create a domain class by using the Embedding Relationship tool.
NOTE
Reference relationships represent cross-references from one part of the model tree to another.
3. Add a shape to represent towns on the model diagrams.
a. Drag a Geometr y Shape from the toolbox to the diagram and rename it, for example
TownShape .
b. In the Properties window, set the Appearance fields of the new shape, such as Fill Color and
Geometry.
c. Add a Decorator to display the name of the town, and rename it NameDecorator. Set its Position
property.
4. Map the Town domain class to the TownShape.
a. Click the Diagram Element Map tool, then click the Town domain class, and then the TownShape
shape class.
b. In the Decorator Maps tab of the DSL Details window with the map connector selected, check
NameDecorator and set Display Proper ty to Name.
5. Create a connector to display the relationship between Person and Towns.
a. Drag a Connector from the toolbox to the diagram. Rename it and set its appearance properties.
b. Use the Diagram Element Map tool to link the new connector to the relationship between
Person and Town.
<#
foreach (Person person in this.FamilyTreeModel.People)
{
#>
<#= person.Name #><#if (person.Town != null) {#> of <#= person.Town.Name #> <#}#>
<#
foreach (Person child in person.Children)
{
#>
<#= child.Name #>
<#
}
}
#>
When you save the *.tt file, it will create a subsidiary file that contains the list of people and their
residences. For more information, see Generating Code from a Domain-Specific Language.
See also
Understanding Models, Classes and Relationships
How to Define a Domain-Specific Language
Understanding Models, Classes and Relationships
4/13/2022 • 9 minutes to read • Edit Online
A domain-specific language (DSL) is defined by its DSL Definition file, together with any custom program code
that you might write. Most of the program code in the DSL solution is generated from this file.
This topic explains the central features of the DSL definition.
The most important information in the DSL Definition is displayed in the DSL Definition diagram. Additional
information, which is also part of DslDefinition.dsl, is displayed in DSL Explorer, which usually appears at the
side of the diagram. You work with the diagram for the most frequent tasks, and with DSL Explorer for more
advanced customizations.
The DSL Definition diagram shows the domain classes that define model elements, and the relationships that
define links between model elements. It also shows the shapes and connectors that are used to display the
model elements to the user.
When you select an item in the DSL definition, either on the diagram or in DSL Explorer, information about it is
displayed in the Properties window. Additional information may be displayed in the DSL Details window.
Models are instances of DSLs
A model is an instance of your DSL created by a user. A model contains model elements, which are instances of
the domain classes that you define, and links between the elements, which are instances of the domain
relationships that you define. A model can also have shapes and connectors, which display the model elements
and links on a diagram. The DSL definition includes the shape classes, connector classes, and a class for the
diagram.
A DSL Definition is also known as a domain model. A DSL Definition or domain model is the design-time
representation of the domain-specific language, whereas the model is the run-time instantiation of the domain-
specific language.
The DSL Definition separates two aspects. The appearance of the model elements on the model diagram is
defined by using shape classes and connector classes. The information carried in the model is defined using
domain classes and domain relationships.
The following illustration shows the domain classes and relationships in the DSL Definition of the Music Library.
The illustration shows four domain classes: Music, Album, Artist and Song. The domain classes define domain
properties such as Name, Title, and so on. In the instance model, the values of some of these properties are
displayed on the diagram.
Between the classes are domain relationships: MusicHasAlbums, MusicHasArtists, AlbumbHasSongs, and
ArtistAppearedOnAlbums. The relationships have multiplicities such as 1..1, 0..*. For example, every Song must
be related to exactly one Album through the AlbumHasSongs relationship. Every Album can have any number
of Songs.
Rearranging the DSL Definition Diagram
Notice that a domain class can appear several times on the DSL Definition diagram, as Album does in this
picture. There is always one main view, and there can be some reference views.
To rearrange the DSL Definition diagram, you can:
Swap main and reference views by using the Bring Tree Here and Split Tree commands. Right-click a
single domain class to see these commands.
Re-order the domain classes and shape classes by pressing Ctrl+Up and Ctrl+Down.
Collapse or expand classes using the icon at the upper-right of each shape.
Collapse parts of the tree by clicking the minus sign (-) at the bottom of a domain class.
Inheritance
Domain classes can be defined using inheritance. To create an inheritance derivation, click the Inheritance tool,
click the derived class, and then click the base class. A model element has all the properties that are defined on
its own domain class, together with all the properties inherited from the base class. It also inherits its roles in
relationships.
Inheritance can also be used between Relationships, Shapes, and Connectors. Inheritance must keep within the
same group. A shape cannot inherit from a domain class.
Domain Relationships
Model elements can be linked by relationships. Links are always binary; they link exactly two elements. However,
any element can have many links to other objects, and there can even be more than one link between the same
pair of elements.
Just as you can define different classes of elements, you can define different classes of links. The class of a link is
called a domain relationship. A domain relationship specifies what classes of element its instances can connect.
Each end of a relationship is called a role, and the domain relationship defines names for the two roles, as well
as for the relationship itself.
There are two kinds of domain relationships: embedding relationships and reference relationships. On the DSL
Definition diagram, embedding relationships have solid lines at each role, and reference relationships have
dashed lines.
Embedding Relationships
Every element in a model, except for its root, is the target of one embedding link. Therefore, the whole model
forms a single tree of embedding links. An embedding relationship represents containment or ownership. Two
model elements that are related in this way are also known as parent and child. The child is said to be embedded
in the parent.
Embedding links are not usually shown explicitly as connectors on a diagram. Instead, they are usually
represented by containment. The root of the model is represented by the diagram, and elements embedded in it
are displayed as shapes on the diagram.
In the example, the root class Music has an embedding relationship MusicHasAlbums to Album, which has an
embedding AlbumHasSongs to Song. Songs are displayed as items in a list inside each Album. Music also has
an embedding MusicHasArtists to the Artist class, whose instances also appear as shapes on the diagram.
By default, embedded elements are automatically deleted when their parents are deleted.
When a model is saved to file in XML form, embedded elements are nested inside their parents, unless you have
customized the serialization.
NOTE
Embedding is not the same as inheritance. Children in an embedding relationship do not inherit the parent's properties.
An embedding is a type of link between model elements. Inheritance is a relationship between classes, and does not
create links between model elements.
Embedding rules
Every element in an instance model must be the target of exactly one embedding link, except for the root of the
model.
Therefore, every non-abstract domain class, except the root class, must be the target of at least one embedding
relationship, or it must inherit an embedding from a base class. A class can be the target of two or more
embeddings, but its instance model elements can only have one parent at a time. The multiplicity from target to
source must be 0..1 or 1..1.
The Explorer Displays the Embedding Tree
Your DSL Definition also creates an explorer, which users see alongside their model diagram.
The explorer shows all the elements in the model, even those for which you have not defined any shapes. It
shows elements and embedding relationships, but not reference relationships.
To see the values of the domain properties of an element, the user selects an element, either in the model
diagram or in the model explorer, and opens the Properties window. It displays all the domain properties,
including those that are not displayed on the diagram. In the example, each Song has both a Title and a Genre,
but only the value of the Title is shown on the diagram.
Reference Relationships
A reference relationship represents any kind of relationship that is not embedding.
Reference relationships are typically displayed on a diagram as connectors between shapes.
In the XML representation of the model, a reference link between two elements is represented using monikers.
That is, monikers are names that uniquely identify each element in the model. The XML node for each model
element contains a node that specifies the name of the relationship and the moniker of the other element.
Roles
Every domain relationship has two roles, a source role and a target role.
In the following picture, the line between the Publisher domain class and the PublisherCatalog domain
relationship is the source role. The line between the domain relationship and the Album domain class is the
target role.
The names associated with a relationship are especially important when you write program code that traverses
the model. For example, when you build the DSL solution, the generated class Publisher has a property Catalog
that is a collection of Albums. The class Album has a property Publisher that is a single instance of the class
Publisher.
When you create a relationship in a DSL Definition, the property and relationship names are given default
values. However, you can change them.
Multiplicities
Multiplicities specify how many elements can have the same role in a domain relationship. In the example, the
zero-to-many (0..*) multiplicity setting on the Catalog role specifies that any instance of the Publisher domain
class can have as many PublisherCatalog relationship links as you want to give it.
Configure the multiplicity of a role either by typing on the diagram or by modifying the Multiplicity property
in the Proper ties window. The following table describes the settings for this property.
0..* (Zero to many) Each instance of the domain class can have multiple
instances of the relationship or no instances of the
relationship.
0..1 (Zero to one) Each instance of the domain class can have no more than
one instance of the relationship or no instances of the
relationship.
1..1 (One) Each instance of the domain class can have one instance of
the relationship. You cannot create more than one instance
of this relationship from any instance of the role class. If
validation is enabled, a validation error will appear when any
instance of the role class has no instance of the relationship.
1..* (One to many) Each instance of the class on the role that has this
multiplicity can have multiple instances of the relationship,
and each instance must have at least one instance of the
relationship. If validation is enabled, a validation error will
appear when any instance of the role class has no instance
of the relationship.
See also
Domain-Specific Language Tools Glossary
How to Define a Domain-Specific Language
4/13/2022 • 32 minutes to read • Edit Online
To define a domain-specific language (DSL), you create a Visual Studio solution from a template. The key part of
the solution is the DSL Definition diagram, which is stored in DslDefinition.dsl. The DSL Definition defines the
classes and shapes of the DSL. After you modify and adding to these elements, you can add program code to
customize the DSL in more detail.
If you are new to DSLs, we recommend that you work through the DSL Tools Lab , which you can find in this
site: Visualization and Modeling SDK
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
To create a new domain-specific language, you create a new Visual Studio solution using the Domain-Specific
Language project template.
To create a DSL solution
1. Create a new Domain-Specific Language project.
The Domain-Specific Language Wizard opens and displays a list of template DSL solutions.
2. Click each template to see a description. Choose the solution that most closely resembles what you want
to create.
Each DSL template defines a basic working DSL. You will edit this DSL to fit your own requirements.
Click each sample for more information.
Select Task Flow to create a DSL that has swimlanes. Swimlanes are vertical or horizontal
partitions of the diagram.
Select Component Models to create a DSL that has ports. Ports are small shapes on the edge of
a larger shape.
Select Class Diagrams to define a DSL that has compartment shapes. Compartment shapes
contain lists of items.
Select Minimal Language in other cases, or if you are uncertain.
Select Minimal WinForm Designer or Minimal WPF Designer to create a DSL that is
displayed on a Windows Forms or WPF surface. You will have to write code to define the editor. For
more information, see the following topics:
Creating a Windows Forms-Based Domain-Specific Language
Creating a WPF-Based Domain-Specific Language
3. Enter a file name extension for your DSL in the appropriate wizard page. This is the extension that files
containing instances of your DSL will use.
Choose a file name extension that is not associated with any application in your computer, or in
any computer where you want to install the DSL. For example, docx and htm would be
unacceptable file name extensions.
The wizard will warn you if the extension that you have entered is being used as a DSL. Consider
using a different file name extension. You can also reset the Visual Studio SDK Experimental
instance to clear out old experimental designers. Click Star t , click All Programs , Microsoft
Visual Studio 2010 SDK , Tools , and then Reset the Microsoft Visual Studio 2010
Experimental instance .
4. You can either adjust the settings on the other pages, or leave the default values.
5. Click Finish .
The wizard creates a solution that contains two or three projects, and generates code from the DSL
definition.
The user interface now resembles the following picture.
This solution defines a domain specific language. For more information, see Overview of the Domain-
Specific Language Tools User Interface.
Test the Solution
The template solution provides a working DSL, which you can modify or use as it is.
To test the solution, press F5 or CTRL+F5. A new instance of Visual Studio opens in experimental mode.
In the new instance of Visual Studio, in Solution Explorer, open the Sample file. It opens as a diagram, with a
toolbox.
If you run a solution that you have created from the Minimal Language template, your experimental Visual
Studio will resemble the following example:
NOTE
When you have modified the DSL, you will no longer be able to see the shapes on the Sample test file. However, you will
be able to create new elements.
NOTE
After adding a feature, do not forget to click Transform All Templates in the toolbar of Solution Explorer before you
build and running your DSL.
The following figure shows classes and relationships part of the DSL that is used as an example in this topic.
NOTE
"Model" refers to an instance of your DSL that users create, and typically is displayed as a diagram. This topic discusses
both the DSL Definition diagram and the model diagrams that appear when your DSL is used.
EXA M P L E IN DSL SO L UT IO N
H O W EL EM EN T IS DISP L AY ED PA REN T ( EM B EDDIN G) C L A SS T EM P L AT E
Shape in swimlane. Domain class of elements that are Task Flow: Task class.
displayed as swimlanes.
Item in list in shape, where item is Domain class that is mapped to the Class diagram: Attribute class.
deleted if container is deleted. container shape.
Component diagram: Port class.
Port on edge of shape.
In the Music Library example, Albums are displayed as rectangles in which the titles of Songs are listed.
Therefore the parent of Album is the root class Music, and the parent of Song is Album.
To create a domain class and its embedding at the same time, click the Embedding Relationship tool, then
click the parent class, and then click on a blank part of the diagram.
It is not usually necessary to adjust the name of the embedding relationship and its roles, because they will track
the class names automatically.
For more information, see Properties of Domain Relationships and Properties of Domain Roles.
NOTE
Embedding is not the same as inheritance. Children in an embedding relationship do not inherit features from their
parents.
1. Click Transform All Templates in the toolbar of Solution Explorer, to generate the DSL designer code.
You can automate this step. For more information, see How to Automate Transform All Templates.
2. Build and run the DSL. Press F5 or CTRL+F5 to run a new instance of Visual Studio in experimental
mode. In the experimental instance of Visual Studio, open or create a file that has the file name extension
of your DSL.
3. Open the Explorer. At the side of the diagram is the language explorer window, which is usually named
YourLanguage Explorer. If you do not see this window, it might be on a tab underneath Solution Explorer.
If you cannot find it, on the View menu, point to Other Windows , and then click YourLanguage
Explorer .
Your explorer presents a tree view of the model.
4. Create new elements. Right-click the root node at the top, and then click Add New YourClass.
A new instance of your class appears in your language Explorer.
5. Verify that each instance has a different name when you create new instances. This will occur only if you
have set the Is Element Name flag on a domain property.
6. Examine the domain proper ties. With an instance of your class selected, inspect the Properties
window. It should show the domain properties that you defined on this domain class.
7. Save the file, close it, and re-open it . All the instances you created should be visible in the explorer,
after you expand the nodes.
NOTE
If you right-click the Tools node, you will not see Add Element Tool. Instead, click the node above it.
c. In the Properties window with the new element tool selected, set Class to the domain class that
you have recently added.
d. Set Caption and Tooltip .
e. Set Toolbox Icon to an icon that will appear in the toolbox. You can set it to a new icon or an icon
already used for another tool.
To create a new icon, open Dsl\Resources in Solution Explorer . Copy and paste one of the
existing element tool BMP files. Rename the pasted copy, and then double-click to edit it.
Return to the DSL Definition diagram, select the tool, and in the Properties window click [...] in
Toolbox Icon . In the Select Bitmap dialog box, select your .BMP file from the drop-down menu.
For more information, see Properties of Geometry Shapes and Properties of Image Shapes.
To Test Shapes
1. Click Transform All Templates in the toolbar of Solution Explorer, to generate the DSL designer code.
2. Build and run the DSL. Press F5 or CTRL+F5 to run a new instance of Visual Studio in experimental
mode. In the experimental instance of Visual Studio, open or create a file that has the file name extension
of your DSL.
3. Verify that the element tools appear on the toolbox.
4. Create shapes by dragging from a tool onto the model diagram.
5. Verify that each text decorator appears, and that:
a. You can edit it, unless you have set the Is UI Read Only flag on the domain property.
b. When you edit the property either in the Properties window or in the decorator, the other view is
updated.
After you first test a shape, you might want to adjust some its properties and add some more advanced
features. For more information, see Customizing and Extending a Domain-Specific Language.
Reference relationships can also link elements of the same type. For example, in a DSL representing a family
tree, the relationship between parents and their children is a reference relationship from Person to Person.
Define a Reference Relationship
Click the Reference Relationship tool, then click the source domain class of the relationship, and then click the
target domain class. The target class can be the same as the source class.
Each relationship has two roles, represented by the line at each side of the relationship box. You can select each
role and set its properties in the Properties window.
Consider renaming the roles . For example, in a relationship between Person and Person, you might want to
change the default names to Parents and Children, Manager and Subordinates, Teacher and Student, and so on.
Adjust the multiplicities of each role , if it is necessary. If you want each Person to have at most one
Manager, set the multiplicity that appears below the Manager label on the diagram to 0..1.
Add domain proper ties to the relationship. In the figure, the Artist-Album relationship has a property of
role.
Set the Allows Duplicates proper ty of the relationship, if more than one link of the same class can exist
between the same pair of model elements. For example, you could allow a Teacher to teach more than one
Subject to the same Student.
For more information, see Properties of Domain Relationships and Properties of Domain Roles.
Define a Connector to Display the Relationship
A connector displays a line between two shapes on the model diagram.
Drag the Connector tool onto the DSL definition diagram.
Add text decorators if you want to display labels on the connector. Set their positions. To let the user move a text
decorator, set its Is Moveable property.
Use the Diagram Element Map tool to link the connector to the reference relationship.
With the diagram element map selected, open the DSL Details window, and open the Decorator Maps tab.
Select each Decorator and set Display proper ty to the correct domain property.
Make sure that a check mark appears next to each item in the Decorators list.
Define a Connection Builder Tool
In the DSL Explorer window, expand the Editor node and all its subnodes.
Right-click the node that has the same name as your DSL, and then click Add New Connection Tool .
While the new tool is selected, in the Properties window:
Set the Caption and Tooltip .
Click Connection Builder and select the appropriate builder for the new relationship.
Set Toolbox Icon to the icon that you want to appear in the toolbox. You can set it to a new icon or an
icon already used for another tool.
To create a new icon, open Dsl\Resources in Solution Explorer . Copy and paste one of the existing
element tool BMP files. Rename the pasted copy, and then double-click to edit it.
Return to the DSL Definition diagram, select the tool, and in the Properties window click [...] in Toolbox
Icon . In the Select Bitmap dialog box, select your .BMP file from the drop-down menu.
To Te st a R e fe r e n c e R e l a t i o n sh i p a n d C o n n e c t o r
1. Click Transform All Templates in the toolbar of Solution Explorer, to generate the DSL designer code.
2. Build and run the DSL. Press F5 or CTRL+F5 to run a new instance of Visual Studio in experimental
mode. In the experimental instance of Visual Studio, open or create a file that has the file name extension
of your DSL.
3. Verify that the connection tool appears on the toolbox.
4. Create shapes by dragging from a tool onto the model diagram.
5. Create connections between the shapes. Click the connector tool, click a shape, and then click another
shape.
6. Verify that you cannot create connections between inappropriate classes. For example, if your
relationship is between Albums and Artists, verify that you cannot link Artists to Artists.
7. Verify that the multiplicities are correct. For example, verify that you cannot connect a
Person to more than one manager.
8. Verify that each text decorator appears, and that:
a. You can edit it, unless you have set the Is UI Read Only flag on the domain property.
b. When you edit the property either in the Properties window or in the decorator, the other view is
updated.
After you first test a connector, you might want to adjust some its properties and add some more
advanced features. For more information, see Customizing and Extending a Domain-Specific Language.
In the simplest method of achieving this effect in a DSL definition, you define one domain class for the container,
and one domain class for each list. The container class is mapped to the compartment shape.
NOTE
By using the Path fields in the Decorator Map and Compartment map fields, you can make more complex relationships
between the domain classes and the compartment shape.
NOTE
If you right-click the Tools node, you will not see Add Element Tool. Instead, click the node above it.
4. In the Properties window with the new element tool selected, set Class to the domain class that you have
recently added.
5. Set Caption and Tooltip .
6. Set Toolbox Icon to an icon that will appear in the toolbox. You can set it to a new icon or an icon already
used for another tool.
To create a new icon, open Dsl\Resources in Solution Explorer . Copy and paste one of the existing
element tool .BMP files. Rename the pasted copy, and then double-click to edit it.
Return to the DSL Definition diagram, select the tool, and in the Properties window click [...] in Toolbox
Icon . In the Select Bitmap dialog box, select your BMP file from the drop-down menu.
To test a compartment shape
1. Click Transform All Templates in the toolbar of Solution Explorer, to generate the DSL designer code.
2. Build and run the DSL. Press F5 or CTRL+F5 to run a new instance of Visual Studio in experimental
mode. In the experimental instance of Visual Studio, open or create a file that has the file name extension
of your DSL.
3. Verify that the tool appears on the toolbox.
4. Drag the tool onto the model diagram. A shape is created.
Verify that the name of the element appears and is set automatically to a default value.
5. Right-click the header of the new shape, and then click Add Your List Item. In the example, the command
is Add Song.
Verify that an item appears in the list and that it has a new name.
6. Click one of the list items, and then examine the Properties window. You should see the properties of the
list items.
7. Open the language Explorer. Verify that you can see the container nodes with the list item nodes inside.
After you first test a compartment shape, you might want to adjust some of its properties and add some
more advanced features. For more information, see Customizing and Extending a Domain-Specific
Language.
Displaying a Reference Link in a Compartment
Usually, an element that you display in a compartment is a child of the element that is represented by the
compartment shape. But sometimes, you would like to display an element that is linked to it with a reference
relationship.
For example, we could add a second compartment to AlbumShape that displays a list of the Artists that are
linked to the Album.
In this case, the compartment should display the link, instead of the referenced element. This is because when
the user selects the item in the compartment and presses DELETE, you want the link to be deleted, not the
referenced element.
Nevertheless, you can have the name of the referenced element appear in the compartment.
The following procedure assumes that you have already created the domain class, the reference relationship, the
compartment shape, and the diagram element map, as described earlier in this section.
To d i sp l a y a r e fe r e n c e l i n k i n a c o m p a r t m e n t
1. Add a compar tment to the compar tment shape . On the DSL Definition diagram, right-click the
compartment shape class, point to Add , and then click Compar tment .
2. Set Displayed elements collection path to navigate to the link, instead of its target element. Click the
drop-down menu and use the tree view to select the reference relationship instead of its target. In the
example, the relationship is Ar tistAppearedOnAlbums .
3. Set Path to Display Proper ty to navigate from the link to the target element. In the example, this is
Ar tist .
4. Set Display Proper ty to the appropriate property of the target element, for example Name .
5. Transform All Templates , build and run the DSL, and open a test model.
6. In the model diagram, create the appropriate classes of shape, set their names, and create a link between
them. In the compartment shape, the names of linked elements should appear.
7. Select either the link or the item in the compartment shape. Both the link and the item should disappear.
Troubleshooting
The following table lists some of the most common problems that are encountered when you design a DSL,
together with suggestions for their solution. More advice is available on the Visualization Tools Extensibility
Forum.
P RO B L EM SUGGEST IO N
The changes I have made in the DSL Definition file have no Click Transform All Templates in the toolbar above
effect. Solution Explorer, and then rebuild the solution.
Shapes show the name of a decorator instead of the Set up the decorator mapping. On the DSL Definition
property value. diagram, click the diagram element map, which is the gray
line between the domain class and the shape class.
Open the DSL Details window. If you cannot see it, on the
View menu, point to Other Windows , and then click DSL
Details .
In DSL Explorer, I cannot add to a collection. For example, Right-click the item above the node that you are trying.
when I right-click Tools, there is no "Add Tool" command in When you want to add to a list, the Add command is not in
the menu. the list node, but in its owner.
I created a domain class, but I can't create instances in the Every domain class except the root must be the target of an
language explorer. embedding relationship.
In the explorer for my DSL, elements are shown only with In the DSL Definition, select a domain property of the class
their type names. and in the Properties window, set Is Element Name to
true.
My DSL always opens in the XML editor. This can happen because of an error while the file was being
read. However, even after you fix that error, you must
explicitly reset the editor to be your DSL designer.
The toolbox of my DSL does not appear after I changed the Inspect and update
assembly names. DslPackage\GeneratedCode\Package.tt For more
information, see How to: Change the Namespace of a
Domain-Specific Language.
The toolbox of my DSL does not appear, but I have not Reset the experimental instance, and rebuild your solution.
changed the assembly name.
1. At the Windows Start menu, under All Programs ,
Or, a message box appears reporting the failure to load an expand Visual Studio SDK, then Tools , and then click Reset
extension. the Microsoft Visual Studio Experimental Instance .
2. On the Build menu, click Rebuild Solution .
See also
Getting Started with Domain-Specific Languages
Creating a Windows Forms-Based Domain-Specific Language
Creating a WPF-Based Domain-Specific Language
Customize and extend a domain-specific language
4/13/2022 • 6 minutes to read • Edit Online
Visual Studio Modeling and Visualization SDK (VMSDK) provides several levels at which you can define
modeling tools:
1. Define a domain-specific language (DSL) using the DSL Definition diagram. You can quickly create a DSL
with a diagrammatic notation, a readable XML form, and the basic tools that are required to generate
code and other artifacts. For more information, see How to Define a Domain-Specific Language.
2. Fine-tune the DSL by using more advanced features of the DSL Definition. For example, you can make
additional links appear when the user creates an element. These techniques are mostly achieved in the
DSL Definition, and some require a few lines of program code.
3. Extend your modeling tools by using program code. VMSDK is designed specifically to make it easy to
integrate your extensions with the code that is generated from the DSL Definition. For more information,
see Writing Code to Customize a Domain-Specific Language.
NOTE
When you have updated the DSL Definitions file, don't forget to click Transform All Templates in the toolbar of
Solution Explorer before rebuilding your solution.
Article reference
TO A C H IEVE T H IS EF F EC T REF ER TO T H IS TO P IC
Allow the user to set the color and style properties of a Right-click the shape or connector class, point to Add
shape. Exposed , and click an item.
Different classes of model element look similar on the Use inheritance between shapes or connector classes.
diagram, sharing properties such as initial height and width, Mappings between derived shapes and derived domain
color, tooltips. classes inherit the mapping details of the parents.
A class of model element is displayed by different shapes Map more than one shape class to the same domain class.
contexts. When you build the solution, follow the error report and
provide the requested code to decide what shape to use.
Shape color or other features such as font indicate current See Updating Shapes and Connectors to Reflect the Model.
state.
Create a rule that updates the exposed properties. See Rules
Propagate Changes Within the Model.
Icon on shape changes to indicate state. Set the visibility of the decorator mapping in the DSL Details
window. Locate several image decorators on the same
position. See Updating Shapes and Connectors to Reflect the
Model.
Nest shapes to any depth Set up a recursive embedding tree. Define BoundsRules to
contain the shapes.
Attach connectors at fixed points on an element's boundary. Define embedded terminal elements, represented by small
ports on the diagram. Use BoundsRules to fix the ports in
place. See the Circuit Diagram sample at Visualization and
Modeling SDK.
Text field displays a value derived from other values. Map the text decorator to a Calculated or Custom Storage
domain property. For more information, see Calculated and
Custom Storage Properties.
Propagate changes between model elements, or between See Validation in a Domain-Specific Language.
shapes
Propagate changes to resources such as other Visual Studio See Event Handlers Propagate Changes Outside the Model.
extensions outside the store.
Property window displays properties of a related element. Set up Property Forwarding. See Customizing the Properties
Window.
Control user access to domain properties Set Is Browsable false to prevent a domain property from
appearing in the Properties window at run time. You can still
map it to text decorators.
Change the name, icon, and visibility of nodes in your DSL's See Customizing the Model Explorer.
model explorer.
Enable copy, cut and paste Set the Enable Copy Paste property of the Editor node in
DSL Explorer.
TO A C H IEVE T H IS EF F EC T REF ER TO T H IS TO P IC
Copy reference links and their targets whenever an element Set the Propagates Copy property of the source role
is copied. For example, copy Comments attached to an item. (represented by the line at one side of the domain
relationship in the DSL Definition diagram).
Delete, reparent, or relink related elements when an element Set the Propagates Delete value of a relationship role. For
is deleted. more complex effects, override ShouldVisitRelationship
and ShouldVisitRolePlayer methods in the
MyDslDeleteClosure class, defined in DomainModel.cs .
Preserve shape layout and appearance on copy and drag- Add the shapes and connectors to the copied
drop. ElementGroupPrototype . The most convenient method to
override is
ElementOperations.CreateElementGroupPrototype()
Paste shapes in a chosen location, such as the current cursor Override ClipboardCommandSet.ProcessOnCopy() to use
position. the location-specific version of
ElementOperations.Merge(). See Customizing Copy
Behavior.
Enable drag and drop from this diagram, other DSLs and See How to: Add a Drag-and-Drop Handler
Windows elements
Allow a shape or tool to be dragged onto a child shape, such Define an Element Merge Directive on the target object
as a port, as if it were dragged onto the parent. class, to forward the dropped object to the parent. See
Customizing Element Creation and Movement.
Allow a shape or tool to be dragged onto a shape and have Define an Element Merge Directive on the target domain
additional links or objects created. For example, to allow a class, and define the links to be generated. In complex cases,
comment to be dropped onto an item to which it is to be you can add custom code. See Customizing Element
linked. Creation and Movement.
Create a group of elements with one tool. For example, a Override the toolbox initialization method in
component with a fixed set of ports. ToolboxHelper.cs. Create an Element Group Prototype (EGP)
containing the elements and their relationship links. See
Customizing Tools and the Toolbox.
Use one connection tool to instantiate several types of Add Link Connect Directives (LCD) to the Connection Builder
relationship. that is invoked by the tool. The LCDs determine the type of
the relationship from the types of the two elements. To make
this depend on the states of the elements, you can add
custom code. See Customizing Tools and the Toolbox.
TO A C H IEVE T H IS EF F EC T REF ER TO T H IS TO P IC
Sticky tools - the user can double-click any tool to create In DSL Explorer, select the Editor node. In the Properties
many shapes or connectors in succession. window, set Uses Sticky Toolbox Items .
Define menu commands See How to: Modify a Standard Menu Command
Constrain the model with validation rules See Validation in a Domain-Specific Language
Generate code, configuration files, or documents from a DSL. Generating Code from a Domain-Specific Language
Customize how models are saved to file. See Customizing File Storage and XML Serialization
Integrate several DSLs so that they work as part of one See Integrating Models by using Visual Studio Modelbus.
application.
Allow your DSL to be extended by third parties, and control Extend your DSL by using MEF
the extension.
Sharing Classes between DSLs by using a DSL Library
See also
How to Define a Domain-Specific Language
Writing Code to Customize a Domain-Specific Language
Modeling SDK for Visual Studio - Domain-Specific Languages
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Define shapes and connectors
4/13/2022 • 2 minutes to read • Edit Online
There are several basic types of shapes that you can used to display information on a diagram in a domain-
specific language (DSL).
SH A P E T Y P E DESC RIP T IO N
Shape inheritance
A shape can inherit from another shape. However, the shapes must be the same kind. For example, only a
geometry shape can inherit from a geometry shape. Inherited shapes have the compartments and decorators of
their base shape. Connectors can inherit from connectors.
Controlling Color, Line Style, and other Shape
Properties
4/13/2022 • 2 minutes to read • Edit Online
Some shape properties such as color can be 'exposed'. That is, the properties can be linked to a domain
property of the shape. Others have to be controlled directly.
Exposing a property
Some shape properties such as color can be linked to the value of a domain property.
In the DSL Definition, select a shape, connector or diagram class. On its right-click menu, choose Add Exposed ,
and then choose the property you want, such as Fill Color.
The shape now has a domain property that you can set in program code or as a user.
A decorator is an icon or line of text that appears on a shape in a domain-specific language (DSL). You can make
the decorator appear and disappear depending on the state of properties in the model. For example, on a shape
representing a Person, you could have different icons that appear depending on the person's gender, number of
children, and so on.
namespace Company.FamilyTree
{ partial class Person
{ bool GetDecoratorControlValue()
{
return this.Children.Count > 0;
} } }
For more information about customizing the model with program code, see Navigating and Updating a
Model in Program Code.
8. Rebuild and run the solution.
See also
Defining Shapes and Connectors
Setting a Background Image on a Diagram
Navigating and Updating a Model in Program Code
Writing Code to Customise a Domain-Specific Language
Customizing Text and Image Fields
4/13/2022 • 8 minutes to read • Edit Online
When you define a text decorator in a shape, it is represented by a TextField. For examples of the initialization of
TextFields and other ShapeFields, inspect Dsl\GeneratedCode\Shapes.cs in your DSL solution.
A TextField is an object that manages an area within a shape, such as the space assigned to a label. One TextField
instance is shared between many shapes of the same class. The TextField instance does not store the text of the
label separately for each instance: instead, the GetDisplayText(ShapeElement) method takes the shape as a
parameter, and can look up the text dependent on the current state of the shape and its model element.
There are several other pairs of Get methods and Default properties, such as
DefaultMultipleLine/GetMultipleLine() . You can assign a value to the Default property to change the value for
all instances of the shape field. To make the value vary from one shape instance to another, or dependent on the
state of the shape or its model element, override the Get method.
Static customizations
If you want to change every instance of this shape field, first find out whether you can set the property in the
DSL Definition. For example, you can set font size and style in the Properties window.
If not, then override the InitializeShapeFields method of your shape class, and assign a value to the
appropriate Default... property of the text field.
WARNING
To override InitializeShapeFields() , you must set the Generates Double Derived property of the shape class to
true in the DSL Definition.
In this example, a shape has a text field that will be used for user comments. We want to use the standard
comment font. Because it is a standard font from the style set, we can set the default font id:
Dynamic customizations
To make the appearance vary dependent on the state of a shape or its model element, derive your own subclass
of TextField and override one or more Get... methods. You must also override your shape's
InitializeShapeFields method, and replace the instance of the TextField with an instance of your own class.
The following example makes the font of a text field dependent on the state of a Boolean domain property of the
shape's model element.
To run this example code, create a new DSL solution using the Minimal Language template. Add a Boolean
domain property AlternateState to the ExampleElement domain class. Add an icon decorator to the
ExampleShape class, and set its image to a bitmap file. Click Transform All Templates . Add a new code file in
the DSL project, and insert the following code.
To test the code, press F5 and, in the debugging solution, open a sample diagram. The default state of the icon
should appear. Select the shape and in the Properties window, change the value of the AlternateState property.
The font of the element name should change.
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
...
Style sets
The preceding example shows how you can change the text field to any font that is available. However, a
preferable method is to change it to one of a set of styles that is associated with the shape or with the
application. To do this, you override GetFontId or GetTextBrushId().
Alternatively, consider changing the style set of your shape by overriding InitializeResources. This has the effect
of changing the fonts and brushes for all of the shape fields.
Dynamic icons
This example makes an icon change dependent on the state of the shape's model element.
WARNING
This example demonstrates how to make a dynamic image decorator. But if you only want to switch between one or two
images depending on the state of a model variable, it is simpler to create several image decorators, locate them in the
same position on the shape, and then set the Visibility filter to depend on specific values of the model variable. To set this
filter, select the shape map in the DSL Definition, open the DSL Details window, and click the Decorators tab.
To run this example code, create a new DSL solution using the Minimal Language template. Add a Boolean
domain property AlternateState to the ExampleElement domain class. Add an icon decorator to the
ExampleShape class, and set its image to a bitmap file. Click Transform All Templates . Add a new code file in
the DSL project, and insert the following code.
To test the code, press F5 and, in the debugging solution, open a sample diagram. The default state of the icon
should appear. Select the shape and in the Properties window, change the value of the AlternateState property.
The icon should then appear rotated through 90 degrees, on that shape.
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
...
partial class ExampleShape
{
/// <summary>
/// Compose a list of the fields in this shape.
/// Called once for each shape class.
/// </summary>
/// <param name="shapeFields"></param>
protected override void InitializeShapeFields(IList<ShapeField> shapeFields)
{
// Fields set up according to DSL Definition:
base.InitializeShapeFields(shapeFields);
/// <summary>
/// Get the image for this field in the given shape.
/// </summary>
public override System.Drawing.Image GetDisplayImage(ShapeElement parentShape)
{
ExampleElement element = parentShape.ModelElement as ExampleElement;
if (element.AlternateState == true)
return AlternateImage;
else
return base.GetDisplayImage(parentShape);
}
See also
Defining Shapes and Connectors
Setting a Background Image on a Diagram
Navigating and Updating a Model in Program Code
Writing Code to Customise a Domain-Specific Language
Setting a Background Image on a Diagram
4/13/2022 • 2 minutes to read • Edit Online
In Visual Studio Visualization and Modeling SDK, you can set the background image for a generated designer by
using custom code.
backgroundField.DefaultFocusable = false;
backgroundField.DefaultSelectable = false;
backgroundField.DefaultVisibility = true;
backgroundField.DefaultUnscaled = false;
shapeFields.Add(backgroundField);
backgroundField.AnchoringBehavior
.SetTopAnchor(AnchoringBehavior.Edge.Top, 0.01);
backgroundField.AnchoringBehavior
.SetLeftAnchor(AnchoringBehavior.Edge.Left, 0.01);
backgroundField.AnchoringBehavior
.SetRightAnchor(AnchoringBehavior.Edge.Right, 0.01);
backgroundField.AnchoringBehavior
.SetBottomAnchor(AnchoringBehavior.Edge.Bottom, 0.01);
base.InitializeInstanceResources();
}
}
}
For more information about customizing the model with program code, see Navigating and Updating a
Model in Program Code.
See also
Defining Shapes and Connectors
Customizing Text and Image Fields
Navigating and Updating a Model in Program Code
Writing Code to Customise a Domain-Specific Language
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Embed a Diagram in a Windows Form
4/13/2022 • 2 minutes to read • Edit Online
You can embed a DSL diagram in a Windows control, which appears in the Visual Studio window.
4. Add a new file to the DslPackage project, with the following content:
using System.Windows.Forms;
namespace Company.MyDSL
{
partial class MyDSLDocView
{
private UserControl1 container;
public override IWin32Window Window
{
get
{
if (container == null)
{
// Put our own form inside the DSL window:
container = new UserControl1(this,
(Control)base.Window);
}
return container;
} } } }
5. To test the DSL, press F5 and open a sample model file. The diagram appears inside the control. The
toolbox and other features work normally.
3. In the code behind the user control, insert methods to listen for elements added and removed:
See also
Navigating and Updating a Model in Program Code
Writing Code to Customise a Domain-Specific Language
Customizing Tools and the Toolbox
4/13/2022 • 12 minutes to read • Edit Online
You must define toolbox items for the elements that you want to let users add to their models. There are two
kinds of tools: element tools and connection tools. In the generated designer, a user can select an element tool to
drag shapes to the diagram, and can select a connection tool to draw links between the shapes. In general,
element tools let users add instances of domain classes to their models, and connection tools let them add
instances of domain relationships.
Editor
Toobox Tabs
MyDsl //a tab
Tools
ExampleElement // an element tool
ExampleRelationship // a connection tool
IMPORTANT
To add or paste items in a DSL Explorer, right-click the grandparent of the new node. For example, to add a tool, right-click
the tab, and not the Tools node. To add a tab, right-click the Editor node.
The Toolbox Icon property of every tool references a 16x16 bitmap file. These files are usually kept in the
Dsl\Resources folder.
The Class property of an element tool refers to a concrete domain class. By default, the tool will create instances
of this class. However, you can write code to have the tool create groups of elements, or elements of different
types.
The Connection Builder property of a connection tool refers to a connection builder, which defines what types
of elements the tool can connect, and what relationships it creates between them. Connection builders are
defined as nodes in the DSL Explorer. Connection builders are created automatically when you define domain
relationships, but you can write code to customize them.
To add a tool to the toolbox
1. You usually create an element tool after you have created a shape class and mapped it to a domain class.
You usually create a connector tool after you have created a connector class and mapped it to a reference
relationship.
2. In DSL Explorer, expand the Editor node and the Toolbox Tabs node.
Right-click a toolbox tab node, and then click Add New Element Tool or Add New Connection Tool .
3. Set the Toolbox Icon property to refer to a 16x16 bitmap.
If you want to define a new icon, create a bitmap file in Solution Explorer in the Dsl\Resources folder.
The file should have the following property values: Build Action = Content ; Copy to Output
Director y = Do not copy .
4. For an element tool: Set the Class property of the tool to refer to a concrete domain class that is
mapped to a shape.
For a connector tool: Set the Connection Builder property of the tool to one of the items that are
offered in the drop-down list. Connection builders are automatically created when you map a connector
to a domain relationship. If you have recently created a connector, you would normally select the
associated connection builder.
5. To test the DSL, press F5 or CTRL+F5, and in the experimental instance of Visual Studio, open a sample
model file. The new tool should appear on the toolbox. Drag it onto the diagram to verify that it creates a
new element.
If the tool does not appear, stop the experimental Visual Studio. In the Windows Star t menu, run Reset
the Microsoft Visual Studio 2010 Experimental Instance . On the Build menu, click Rebuild
Solution . Then test the DSL again.
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
transistor.Base.Name = "base";
transistor.Collector.Name = "collector";
transistor.Emitter.Name = "emitter";
return elementGroup.CreatePrototype();
}
else
{
return base.CreateElementToolPrototype(store, domainClassId);
} } }
Therefore, you might want to specify that a connection can come from a nested component to an OutPort. To
specify such a connection, you set Uses Custom Accept on the InPor t type as source role and the OutPor t
type as target role in the DSL Details window as shown in the following illustrations:
Link Connect Directive in DSL Explorer
/// <summary>
/// Only if source is on parent of target.
/// </summary>
private static bool CanAcceptInPortAndInPortAsSourceAndTarget (InPort sourceInPort,
InPort targetInPort)
{
return sourceInPort.Component == targetInPort.Component.Parent;
}
// And similar for OutPorts...
For more information about customizing the model by using program code, see Navigating and Updating a
Model in Program Code.
You can use similar code, for example, to prevent users from creating loops with parent-child links. These
restrictions are considered 'hard' constraints because users cannot violate them at any time. You can also create
'soft' validation checks that users can bypass temporarily by creating invalid configurations that they cannot
save.
Good Practice in Defining Connection Builders
You should define one connection builder to create different types of relationships only if they are conceptually
related. In the task flow sample, you use the same builder to create flows between tasks and also between tasks
and objects. However, it would be confusing to use the same builder to create relationships between comments
and tasks.
If you define a connection builder for multiple types of relationships, you should ensure that it cannot match
more than one type from the same pair of source and target objects. Otherwise, the results will be
unpredictable.
You use custom code to apply 'hard' constraints, but you should consider whether users should be able to
temporarily make invalid connections. If they should, you can modify the constraints so that connections are not
validated until users try to save changes.
See also
Customizing Element Creation and Movement
Customizing Copy Behavior
How to: Add a Drag-and-Drop Handler
Navigating and Updating a Model in Program Code
Circuit Diagrams sample DSL
Customizing Element Creation and Movement
4/13/2022 • 14 minutes to read • Edit Online
You can allow an element to be dragged onto another, either from the toolbox or in a paste or move operation.
You can have the moved elements linked to the target elements, using the relationships that you specify.
An element merge directive (EMD) specifies what happens when one model element is merged into another
model element. This happens when:
The user drags from the toolbox onto the diagram or a shape.
The user creates an element by using an Add menu in the explorer or a compartment shape.
The user moves an item from one swimlane to another.
The user pastes an element.
Your program code calls the element merge directive.
Although the creation operations might seem to be different from the copy operations, they actually work in the
same way. When an element is added, for example from the toolbox, a prototype of it is replicated. The
prototype is merged into the model in the same manner as elements that have been copied from another part
of the model.
The responsibility of an EMD is to decide how an object or group of objects should be merged into a particular
location in the model. In particular, it decides what relationships should be instantiated to link the merged group
into the model. You can also customize it to set properties and to create additional objects.
An EMD is generated automatically when you define an embedding relationship. This default EMD creates an
instance of the relationship when users add new child instances to the parent. You can modify these default
EMDs, for example by adding custom code.
You can also add your own EMDs in the DSL definition, to let users drag or paste different combinations of
merged and receiving classes.
The Indexing Class is the domain class of elements that can be merged into members of the receiving class.
Instances of subclasses of the Indexing Class will also be merged by this EMD, unless you set Applies to
subclasses to False.
There are two kinds of merge directive:
A Process Merge directive specifies the relationships by which the new element should be linked into
the tree.
A For ward Merge directive redirects the new element to another receiving element, typically a parent.
You can add custom code to merge directives:
Set Uses custom accept to add your own code to determine whether a particular instance of the
indexing element should be merged into the target element. When the user drags from the toolbox, the
"invalid" pointer shows if your code disallows the merge.
For example, you could allow the merge only when the receiving element is in a particular state.
Set Uses custom merge to add provide own code to define the changes that are made to the model
when the merge is performed.
For example, you could set properties in the merged element by using data from its new location in the
model.
NOTE
If you write custom merge code, it affects only merges that are performed by using this EMD. If there are other EMDs
that merge the same type of object, or if there is other custom code that creates these objects without using the EMD,
then they will not be affected by your custom merge code.
If you want to make sure that a new element or new relationship is always processed by your custom code, consider
defining an AddRule on the embedding relationship and a DeleteRule on the element's domain class. For more
information, see Rules Propagate Changes Within the Model.
For this example, select ExampleElements , so that the user can drag new elements onto existing elements.
Notice that the Indexing class becomes the name of the EMD in DSL Explorer.
4. Under Process merge by creating links , add two paths:
One path links the new element to the parent model. The path expression that you need to enter
navigates from the existing element, up through the embedding relationship to the parent model.
Finally, it specifies the role in the new link to which the new element will be assigned. The path is
as follows:
ExampleModelHasElements.ExampleModel/!ExampleModel/.Elements
The other path links the new element to the existing element. The path expression specifies the
reference relationship and the role to which the new element will be assigned. This path is as
follows:
ExampleElementReferencesTargets.Sources
You can use the path navigation tool to create each path:
a. Under Process merge by creating links at paths , click <add path> .
b. Click the drop-down arrow to the right of the list item. A tree view appears.
c. Expand the nodes in the tree to form the path that you want to specify.
5. Test the DSL:
a. Press F5 to rebuild and run the solution.
Rebuilding will take longer than usual because the generated code will be updated from text
templates to conform to the new DSL Definition.
b. When the experimental instance of Visual Studio has started, open a model file of your DSL. Create
some example elements.
c. Drag from the Example Element tool onto an existing shape.
A new shape appears, and it is linked to the existing shape with a connector.
d. Copy an existing shape. Select another shape and paste.
A copy of the first shape is created. It has a new name and it is linked to the second shape with a
connector.
Notice the following points from this procedure:
By creating Element Merge Directives, you can allow any class of element to accept any other. The EMD is
created in the receiving domain class, and the accepted domain class is specified in the Index class field.
By defining paths, you can specify what links should be used to connect the new element to the existing
model.
The links that you specify should include one embedding relationship.
The EMD affects both creation from the toolbox and also paste operations.
If you write custom code that creates new elements, you can explicitly invoke the EMD by using the
ElementOperations.Merge method. This makes sure that your code links new elements into the model in
the same way as other operations. For more information, see Customizing Copy Behavior.
This simple example restricts the number of elements that can be merged into the parent model. For
more interesting conditions, the method can inspect any of the properties and links of the receiving
object. It can also inspect the properties of the merging elements, which are carried in a
ElementGroupPrototype. For more information about ElementGroupPrototypes , see Customizing Copy
Behavior. For more information about how to write code that reads a model, see Navigating and
Updating a Model in Program Code.
6. Test the DSL:
a. Press F5 to rebuild the solution. When the experimental instance of Visual Studio opens, open an
instance of your DSL.
b. Create new elements in several ways:
Drag from the Example Element tool onto the diagram.
In the Example Model Explorer , right-click the root node and then click Add New
Example Element .
Copy and paste an element on the diagram.
c. Verify that you cannot use any of these ways to add more than four elements to the model. This is
because they all use the Element Merge Directive.
6. In a new code file, write a partial class for the receiving class, and override the MergeRelate method.
Remember to call the base method. For example:
// Custom actions:
ExampleElement mergingElement = sourceElement as ExampleElement;
if (mergingElement != null)
{
mergingElement.Name = DateTime.Now.ToLongTimeString();
}
}
}
See also
Navigating and Updating a Model in Program Code
Customizing Tools and the Toolbox
Circuit Diagrams sample DSL
Customizing Copy Behavior
4/13/2022 • 14 minutes to read • Edit Online
In a domain-specific language (DSL) created with the Visual Studio Visualization and Modeling SDK, you can
alter what happens when the user copies and pastes elements.
The copied elements and links are serialized and stored in an ElementGroupPrototype (EGP), which is
placed on the clipboard.
An image of the copied elements is also placed on the clipboard. This allows the user to paste into other
applications such as Word.
The user can paste copied elements onto a target that can accept the elements according to the DSL
Definition. For example, in a DSL generated from the components solution template, the user can paste
ports onto components, but not onto the diagram; and can paste components onto the diagram, but not
onto other components.
namespace Company.MyDsl {
using System.Linq;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Shell;
partial class MyDslClipboardCommandSet
{
protected override void ProcessOnMenuPasteCommand()
{
// Deselect the current selection after copying:
Diagram diagram = (this.CurrentModelingDocView as SingleDiagramDocView).Diagram;
this.CurrentModelingDocView
.SelectObjects(1, new object[] { diagram }, 0);
}
} }
Create additional links when the user pastes onto a selected target. For example, when a comment box
is pasted onto an element, a link is made between them. Add an Element Merge Directive to the target domain
class, and set it to process the merge by adding links. This will have the same effect on drag operations. For
more information, see Customizing Element Creation and Movement.
- or -
Override ClipboardCommandSet.ProcessOnPasteCommand() to create the additional links after calling the base
method.
Customize the formats in which elements can be copied to external applications - for example, to add a
border to the bitmap form. Override MyDsl ClipboardCommandSet.ProcessOnMenuCopyCommand() in the DslPackage
project.
Customize how elements are copied to the clipboard by the copy command, but not in a drag
operation. Override MyDsl ClipboardCommandSet.CopyModelElementsIntoElementGroupPrototype() in the
DslPackage project.
Preser ve shape layout through copy and paste. When the user copies multiple shapes, you can preserve
their relative positions when they are pasted. This technique is demonstrated by the example at VMSDK: Circuit
Diagrams sample.
To achieve this effect, add the shapes and connectors to the copied ElementGroupPrototype. The most
convenient method to override is ElementOperations.CreateElementGroupPrototype(). To do this, add the
following code to the Dsl project:
public class MyElementOperations : DesignSurfaceElementOperations
{
// Create an EGP to add to the clipboard.
// Called when the elements to be copied have been
// collected into an ElementGroup.
protected override ElementGroupPrototype CreateElementGroupPrototype(ElementGroup elementGroup,
ICollection<ModelElement> elements, ClosureType closureType)
{
// Add the shapes and connectors:
// Get the elements already in the group:
ModelElement[] mels = elementGroup.ModelElements
.Concat(elementGroup.ElementLinks) // Omit if the paste target is not the diagram.
.ToArray();
// Get their shapes:
IEnumerable<PresentationElement> shapes =
mels.SelectMany(mel =>
PresentationViewsSubject.GetPresentation(mel));
elementGroup.AddRange(shapes);
return base.CreateElementGroupPrototype
(elementGroup, elements, closureType);
}
Paste shapes in a chosen location, such as the current cursor position. When the user copies multiple
shapes, you can preserve their relative positions when they are pasted. This technique is demonstrated by the
example at VMSDK: Circuit Diagrams sample.
To achieve this effect, override ClipboardCommandSet.ProcessOnMenuPasteCommand() to use the location-specific
version of ElementOperations.Merge() . To do this, add the following code in the DslPackage project:
partial class MyDslClipboardCommandSet // EDIT NAME
{
/// <summary>
/// This method assumes we only want to paste things onto the diagram
/// - not onto anything contained in the diagram.
/// The base method pastes in a free space on the diagram.
/// But if the menu was used to invoke paste, we want to paste in the cursor position.
/// </summary>
protected override void ProcessOnMenuPasteCommand()
{
if (!docView.IsContextMenuShowing)
{
// User hit CTRL+V - just use base method.
// Utility class:
DesignSurfaceElementOperations op = diagram.ElementOperations;
Let the user drag and drop elements. See How to: Add a Drag-and-Drop Handler.
The changes that you make will affect both the elements and the image that is copied.
TIP
For more information about customizing the model by using program code, see Navigating and Updating a Model in
Program Code.
To define your own ElementOperations
1. In a new file in your DSL project, create a class that is derived from DesignSurfaceElementOperations.
2. Add a partial class definition for your diagram class. The name of this class can be found in
Dsl\GeneratedCode\Diagrams.cs .
In the diagram class, override ElementOperations to return an instance of your ElementOperations
subclass. You should return the same instance at every call.
Add this code in a custom code file in the DslPackage project:
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
MergeElementGroupPrototype()
This method is called when the user drops an element onto a diagram, a shape, or a connector. It should merge
the dragged content into the target element. In this example, the code determines whether it recognizes the
combination of target and prototype types; if so, the method converts the dragged elements into a prototype of
the elements that should be added to the model. The base method is called to perform the merge, either of the
converted or unconverted elements.
This example deals with UML class elements dragged from a UML class diagram. The DSL is not designed to
store UML classes directly, but instead, we create a DSL element for each dragged UML class. This would be
useful, for example, if the DSL is an instance diagram. The user could drag classes onto the diagram to create
instances of those classes.
private ElementGroupPrototype ConvertDraggedTypeToLocal (MyTargetShape snapshot, ElementGroupPrototype
prototype)
{
// Find the UML project:
EnvDTE.DTE dte = snapshot.Store.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
foreach (EnvDTE.Project project in dte.Solution.Projects)
{
IModelingProject modelingProject = project as IModelingProject;
if (modelingProject == null) continue; // not a modeling project
IModelStore store = modelingProject.Store;
if (store == null) continue;
// Look for the shape that was dragged:
foreach (IDiagram umlDiagram in store.Diagrams())
{
// Get modeling diagram that implements UML diagram:
Diagram diagram = umlDiagram.GetObject<Diagram>();
Guid elementId = prototype.SourceRootElementIds.FirstOrDefault();
ShapeElement shape = diagram.Partition.ElementDirectory.FindElement(elementId) as ShapeElement;
if (shape == null) continue;
IClass classElement = shape.ModelElement as IClass;
if (classElement == null) continue;
bitmap = this.CreateBitmapForClipboard(shapes);
if (bitmap != null)
{
dataObject.SetData(DataFormats.Bitmap, bitmap);
}
#endregion
Each diagram has a singleton instance of ElementOperations. You can supply your own derivative. This file,
which can be placed in the DSL project, would behave the same as the code that it overrides:
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
namespace Company.MyDsl
{
partial class MyDslDiagram
{
/// <summary>
/// Singleton ElementOperations attached to this diagram.
/// </summary>
public override DesignSurfaceElementOperations ElementOperations
{
get
{
if (this.elementOperations == null)
{
this.elementOperations = new MyElementOperations(this.Store as IServiceProvider, this);
}
return this.elementOperations;
}
}
private MyElementOperations elementOperations = null;
}
/// <summary>
/// Copy elements to the clipboard data.
/// Provides a hook for adding custom data.
/// </summary>
public override void Copy(System.Windows.Forms.IDataObject data,
ICollection<ModelElement> elements,
ClosureType closureType,
System.Drawing.PointF sourcePosition)
{
if (CanAddElementGroupFormat(elements, closureType))
{
AddElementGroupFormat(data, elements, closureType);
}
// Mark all the elements that are not embedded under other elements:
this.MarkRootElements(elementGroup, elements, closureType);
/// <summary>
/// Create an element group from the given starting elements, using the
/// copy propagation rules specified in the DSL Definition.
/// By default, this includes all the embedded descendants of the starting elements,
/// and also includes reference links where both ends are already included.
/// </summary>
/// <param name="startElements">model elements to copy</param>
/// <param name="closureType"></param>
/// <returns></returns>
protected override ElementGroup CreateElementGroup(ICollection<ModelElement> startElements, ClosureType
closureType)
{
// ElementClosureWalker finds all the connected elements,
// according to the propagate copy rules specified in the DSL Definition:
ElementClosureWalker walker = new ElementClosureWalker(this.Partition,
closureType, // Normally ClosureType.CopyClosure
startElements,
true, // Do not load other models.
null, // Optional list of domain roles not to traverse.
true); // Include relationship links where both ends are already included.
walker.Traverse(startElements);
IList<ModelElement> closureList = walker.ClosureList;
Dictionary<object, object> closureContext = walker.Context;
return group;
}
}
}
See also
Customizing Element Creation and Movement
How to: Add a Drag-and-Drop Handler
Sample: VMSDK Circuit Diagrams sample
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Customize the Properties window
4/13/2022 • 11 minutes to read • Edit Online
You can customize the appearance and behavior of the properties window in your domain-specific language
(DSL) in Visual Studio. In your DSL Definition, you define domain properties on each domain class. By default,
when you select an instance of the class, either on a diagram or in Model Explorer, every domain property is
listed in the properties window. This lets you see and edit the values of domain properties, even if you have not
mapped them to shape fields on the diagram.
The names of elements that are linked through relationships that have a multiplicity of 0..1. This provides
a convenient method of seeing optionally linked elements, even if you have not defined a connector
mapping for the relationship.
Domain properties of the embedding relationship that targets the element. Because embedding
relationships are usually not displayed explicitly, this lets the user see their properties.
Domain properties that are defined on the selected shape or connector.
Add property forwarding
To forward a property, you define a domain type descriptor. If you have a domain relationship between two
domain classes, you can use a domain type descriptor to set a domain property in the first class to the value of a
domain property in the second domain class. For example, if you have a relationship between a Book domain
class and an Author domain class, you can use a domain type descriptor to make the Name property of a
Book's Author appear in the Properties window when the user selects the Book.
NOTE
Property forwarding affects only the Properties window when the user is editing a model. It does not define a domain
property on the receiving class. If you want to access the forwarded domain property in other parts of the DSL Definition
or in program code, you must access the forwarding element.
The following procedure assumes that you have created a DSL. The first few steps summarize the prerequisites.
Forward a property from another element
1. Create a Domain-Specific Language Tools solution that contains at least two classes, which in this
example are called Book and Author . There should be a relationship of either kind between Book and
Author .
The multiplicity of the source role (the role at the Book side) should be 0..1 or 1..1, so that each Book has
one Author .
2. In DSL Explorer , right-click the Book domain class, and then click Add New DomainTypeDescriptor .
A node named Paths of Custom Proper ty Descriptors appears under the Custom Type Descriptor
node.
3. Right-click the Custom Type Descriptor node, and then click Add New Proper tyPath .
A new property path appears under the Paths Of Custom Proper ty Descriptors node.
4. Select the new property path, and in the Proper ties window, set Path to Proper ty to the path of the
appropriate model element.
You can edit the path in a tree view by clicking the down arrow to the right of this property. For more
information about domain paths, see Domain Path Syntax. When you have edited it, the path should
resemble BookReferencesAuthor.Author/!Author .
5. Set Proper ty to the Name domain property of Author .
6. Set Display Name to Author Name .
7. Transform All Templates, build and run the DSL.
8. In a model diagram, create a book, an author, and link them using the reference relationship. Select the
book element, and in the Properties window you should see Author Name in addition to the properties of
the book. Change the name of the linked author, or link the book to a different author, and observe that
the Author Name of the book changes.
WARNING
The menu item is on the DSL root node, not the Domain Types node.
b. Set the name and the namespace of the new type in the Properties window.
3. Add a domain property to a domain class in the usual manner.
In the Properties window, select the external type from the drop-down list in the Type field.
At this stage, users can view the values of the property, but they cannot edit it. The displayed values are
obtained from the ToString() function. You could write program code that sets the value of the property,
for example in a command or rule.
Set a property editor
Add a CLR attribute to the domain property, in the following form:
[System.ComponentModel.Editor (
typeof(AnEditor),
typeof(System.Drawing.Design.UITypeEditor))]
You can set the attribute on a property by using the Custom Attribute entry in the Properties window.
The type of AnEditor must be derived from the type specified in the second parameter. The second parameter
should be either UITypeEditor or ComponentEditor. For more information, see EditorAttribute.
You can specify your own editor or a .NET editor, such as FileNameEditor or ImageEditor. For example, use the
following procedure to have a property in which the user can enter a file name.
Define a file name domain property
1. Add a domain property to a domain class in your DSL Definition.
2. Select the new property. In the Custom Attribute field in the Properties window, enter the following
attribute. To enter this attribute, click the ellipsis [...] and then enter the attribute name and the
parameters separately:
[System.ComponentModel.Editor (
typeof(System.Windows.Forms.Design.FileNameEditor)
, typeof(System.Drawing.Design.UITypeEditor))]
3. Leave the Type of the domain property at its default setting of String .
4. To test the editor, verify that users can open the file name editor to edit your domain property.
a. Press CTRL+F5 or F5. In the debugging solution, open a test file. Create an element of the domain
class and select it.
b. In the Properties window, select the domain property. The value field shows an ellipsis [...] .
c. Click the ellipsis. A file dialog box appears. Select a file and close the dialog box. The file path is
now the value of the domain property.
Define your own property editor
You can define your own editor. You would do this to allow the user either to edit a type that you have defined,
or to edit a standard type in a special way. For example, you could allow the user to input a string that represents
a formula.
You define an editor by writing a class that is derived from UITypeEditor. Your class must override:
EditValue, to interact with the user and update the property value.
GetEditStyle, to specify whether your editor will open a dialog or provide a drop-down menu.
You can also provide a graphical representation of the property's value that will be displayed in the property
grid. To do this, override GetPaintValueSupported , and PaintValue . For more information, see UITypeEditor.
NOTE
Add the code in a separate code file in the Dsl project.
For example:
internal class TextFileNameEditor : System.Windows.Forms.Design.FileNameEditor
{
protected override void InitializeDialog(System.Windows.Forms.OpenFileDialog openFileDialog)
{
base.InitializeDialog(openFileDialog);
openFileDialog.Filter = "Text files(*.txt)|*.txt|All files (*.*)|*.*";
openFileDialog.Title = "Select a text file";
}
}
To use this editor, set the Custom Attribute of a domain property to:
[System.ComponentModel.Editor (
typeof(MyNamespace.TextFileNameEditor)
, typeof(System.Drawing.Design.UITypeEditor))]
NOTE
This technique provides a list of values that can change at run time. If you want to provide a list that does not change,
consider instead using an enumerated type as the type of your domain property.
To define a list of standard values, you add to your domain property a CLR attribute that has the following form:
[System.ComponentModel.TypeConverter
(typeof(MyTypeConverter))]
Define a class that derives from TypeConverter. Add the code in a separate file in the Dsl project. For example:
/// <summary>
/// Type converter that provides a list of values
/// to be displayed in the property grid.
/// </summary>
/// <remarks>This type converter returns a list
/// of the names of all "ExampleElements" in the
/// current store.</remarks>
public class MyTypeConverter : System.ComponentModel.TypeConverter
{
/// <summary>
/// Return true to indicate that we return a list of values to choose from
/// </summary>
/// <param name="context"></param>
public override bool GetStandardValuesSupported
(System.ComponentModel.ITypeDescriptorContext context)
{
return true;
}
/// <summary>
/// Returns true to indicate that the user has
/// to select a value from the list
/// </summary>
/// <param name="context"></param>
/// <returns>If we returned false, the user would
/// be able to either select a value from
/// the list or type in a value that is not in the list.</returns>
public override bool GetStandardValuesExclusive
(System.ComponentModel.ITypeDescriptorContext context)
{
return true;
}
/// <summary>
/// Return a list of the values to display in the grid
/// </summary>
/// <param name="context"></param>
/// <returns>A list of values the user can choose from</returns>
public override StandardValuesCollection GetStandardValues
(System.ComponentModel.ITypeDescriptorContext context)
{
// Try to get a store from the current context
// "context.Instance" returns the element(s) that
// are currently selected i.e. whose values are being
// shown in the property grid.
// Note that the user could have selected multiple objects,
// in which case context.Instance will be an array.
Store store = GetStore(context.Instance);
if (store != null)
{
values.AddRange(store.ElementDirectory
.FindElements<ExampleElement>()
.Select<ExampleElement, string>(e =>
{
return e.Name;
}));
}
return new StandardValuesCollection(values);
}
/// <summary>
/// Attempts to get to a store from the currently selected object(s)
/// in the property grid.
/// </summary>
private Store GetStore(object gridSelection)
{
// We assume that "instance" will either be a single model element, or
// an array of model elements (if multiple items are selected).
See also
Navigating and Updating a Model in Program Code
Customizing the Model Explorer
4/13/2022 • 3 minutes to read • Edit Online
You can change the appearance and behavior of the explorer for your domain-specific language designer as
follows:
Change the window title.
Change the tab icon.
Change the icons for nodes.
Hide nodes.
Hiding Nodes
You can hide a node in your explorer by adding its path to the Hidden Nodes node of the DSL Explorer . The
following procedure shows how to hide Comment nodes.
To hide an explorer node
1. Open the solution that you created in the earlier procedure.
2. In the DSL Explorer , right-click Explorer Behavior and then click Add New Domain Path .
A Domain Path node appears under Hidden Nodes .
3. Select Domain Path , and then in the Proper ties window, click the value field of Path Definition . Select
FlowGraph , then FlowGraphHasComments . The resulting path should resemble
FlowGraphHasComments.Comments
4. Transform all templates, and then build and run your solution.
5. In the generated designer, open the Sample diagram.
The explorer should show only an Actors node, and should not show the Comments node.
See also
Domain-Specific Language Tools Glossary
Domain Path Syntax
4/13/2022 • 2 minutes to read • Edit Online
The syntax traverses the tree of the model. For example, the domain relationship
CommentReferencesSubjects in the illustration above has a Subjects role. The path segment /!Subjectt
specifies that the path finishes on elements accessed through the Subjects role.
Each segment starts with the name of a domain relationship. If the traversal is from an element to a relationship,
the path segment appears as Relationship.PropertyName. If the hop is from a link to an element, the path
segment appears as Relationship/!RoleName.
Slashes separate the syntax of a path. Each path segment is either a hop from an element to a link (an instance
of a relationship) or from a link to an element. Path segments frequently appear in pairs. One path segment
represents a hop from an element to a link, and the next segment represents a hop from the link to the element
at the other end. (Any link can also be the source or target of a relationship itself).
The name that you use for the element-to-link hop is the value of the role's Property Name . The name that you
use for the link-to-element hop is the target role name.
See also
Understanding Models, Classes and Relationships
Properties of a DSL Definition
4/13/2022 • 2 minutes to read • Edit Online
DslDefinition properties define domain-specific language definition properties such as version numbering. The
DslDefinition properties appear in the Proper ties window when you click an open area of the diagram in the
Domain-Specific Language Designer.
For more information, see How to Define a Domain-Specific Language. For more information about how to use
these properties, see Customizing and Extending a Domain-Specific Language.
DslDefinition has the properties in the following table:
Company Name The name of the current company Current company name
name in the system registry.
Package Guid The guid for the Visual Studio package <none>
generated for this DSL.
See also
Domain-Specific Language Tools Glossary
Properties of Domain Classes
4/13/2022 • 2 minutes to read • Edit Online
Domain classes have the properties in the following table. For information about domain classes, see
Understanding Models, Classes and Relationships. For more information about how to use these properties, see
Customizing and Extending a Domain-Specific Language.
A domain property is a feature of a model element that can hold a value. For example, the Person domain class
could have properties Name and BirthDate . In the DSL Definition, domain properties are listed in the domain
class box on the diagram and under the domain class in DSL Explorer. For more information, see How to Define
a Domain-Specific Language.
NOTE
The word "property" has two uses. A domain property is a feature that you define on a domain class. By contrast, many
elements of a DSL have properties, which are listed in the Proper ties window in the DSL Definition. For example, every
domain property has a set of properties, which are described in this topic.
At run time, when a user creates instances of the domain class, the values of the domain properties can be seen
in the Properties window, and can be displayed on the shapes.
Most domain properties are implemented as ordinary CLR properties. However, from a programming point of
view, domain properties have richer functionality than ordinary program properties:
You can define rules and events that monitor the state of a property. For more information, see
Responding to and Propagating Changes.
Transactions help prevent inconsistent states. For more information, see Navigating and Updating a
Model in Program Code.
When you select a Domain Property in a diagram or in DSL Explorer, you can see the following items in
the Properties window. For more information about how to use these items, see Customizing and
Extending a Domain-Specific Language.
Element Name Provider This is applicable only if you have set <none>
Is Element Name to true . You can
write code that provides a name for a
new element of a domain class,
overriding the default behavior.
Getter Access Modifier The level of access of the domain class public
( public or internal ). This controls
the scope in which program code can
access the property.
Setter Access Modifier The access modifier for the setter. This public
controls the scope in which program
code can set the property.
See also
Domain-Specific Language Tools Glossary
Properties of Domain Relationships
4/13/2022 • 2 minutes to read • Edit Online
The properties in the following table are associated with a domain relationship. For information about domain
relationships, see Understanding Models, Classes and Relationships. For more information about how to use
these properties, see Customizing and Extending a Domain-Specific Language.
See also
Domain-Specific Language Tools Glossary
Properties of Domain Roles
4/13/2022 • 3 minutes to read • Edit Online
The properties in the following table are associated with a domain role. For information about domain roles, see
Understanding Models, Classes and Relationships. For more information about how to use these properties, see
Customizing and Extending a Domain-Specific Language.
Property Getter Access Modifier The access modifier for the getter for public
the generated property ( public ,
internal , private , protected , or
protected internal ).
Property Setter Access Modifier The access modifier for the setter for public
the generated property ( public ,
internal , private , protected , or
protected internal ).
Multiplicity The number of model elements which Depends on the relationship type and
can play the opposite role ( 0..1 , whether this is the source or target
1..1 , 0..* , or 1..* ). If the role in the relationship.
multiplicity is 0..* or 1..* , then
the generated property represents a
collection; otherwise, the generated
property represents a single model
element.
P RO P ERT Y DESC RIP T IO N DEFA ULT
Name The name of the domain role. This The name of the domain class of the
property can not contain whitespace. role player for this role.
PropagateCopyToLinkAndOppositeRolePlayer
- The copied link points to a copy of
the opposite role player.
Propagates Delete True to delete the element that plays True for the target of an embedding
this role when the associated link is role.
deleted.
False for other roles.
Property Name The name of the property generated in The name of the opposite role if this
the code of the role player. This name role has a zero-to-one or a one-to-
cannot contain whitespace. one multiplicity; otherwise, the
pluralized name of the opposite role.
Role Player The domain class of the element that The domain class of the role player for
can play this role in the relationship. this role.
This property is read-only.
Description The description that is used to Description for the full name of
document code and is used in the UI the role
of the generated designer.
Display Name The name that is displayed in the The adjusted value of the Name
generated designer for the domain property.
role.
Property Display Name The name that is displayed in the The adjusted value of the Property
generated designer for the generated Name property.
role property.
NOTE
The default value of a display name is based on the associated property value by inserting spaces before each upper-case
character that is preceded by a lower-case character and that is not followed by another upper-case character.
See also
Properties of Domain Relationships
Properties of diagrams
4/13/2022 • 2 minutes to read • Edit Online
You can set properties that specify how diagrams will appear in the generated designer. For example, you can
specify a default color for text in the diagram.
For more information, see How to define a domain-specific language. For more information about how to use
these properties, see Customize and extend a domain-specific language.
The following table lists the properties of diagrams.
Class Represented The root domain class that this Current root class if applicable
diagram represents.
Exposes Fill Color As Property If True , the user can set the fill color False
of the diagram of the generated
designer. To set this property, right-
click the diagram shape and click Add
Exposed .
Exposes Text Color As Property If True , the user can set the text False
color of the diagram in the generated
designer. To set this property, right-
click the diagram shape and click Add
Exposed .
See also
Domain-specific language tools glossary
Properties of Swimlanes
4/13/2022 • 2 minutes to read • Edit Online
You can add swimlanes to a diagram. Swimlanes divide a diagram into vertical or horizontal areas. You can
define other shapes to be displayed inside swimlanes. For more information, see How to Define a Domain-
Specific Language. For more information about how to use these properties, see Customizing and Extending a
Domain-Specific Language.
Swimlanes have the properties that are listed in the following table.
Body Fill Color The fill color for the body of the White
swimlane.
Header Fill Color The fill color for the header of the DarkGray
swimlane.
Separator Line Style The style of the separator line ( Solid , Dash
Dash , Dot , DashDot , DashDotDot
, or Custom ).
Exposes Text Color If True , the user can set the color of False
a swimlane in the generated designer.
To set this, right-click the swimlane
shape and click Add Exposed .
Fixed Tooltip Text The text that is used for a fixed tooltip. <none>
See also
Domain-Specific Language Tools Glossary
Properties of Geometry Shapes
4/13/2022 • 2 minutes to read • Edit Online
You can use geometry shapes to specify how instances of domain classes are displayed in a domain-specific
language. For more information, see How to Define a Domain-Specific Language. For more information about
how to use these properties, see Customizing and Extending a Domain-Specific Language.
Geometry shapes have the properties that are listed in the following table.
Fill Gradient Mode The fill gradient mode of this shape Horizontal
(Horizontal, Vertical, Forward Diagonal,
Backward Diagonal, or None).
Has Default Connection Points If True , the shape will use top, False
bottom, left, and right connection
points in the generated designer.
Outline Dash Style The outline dash style of this shape Solid
(Solid, Dash, Dot, DashDot,
DashDotDot, or Custom).
Exposed Fill Color As Property If True , the user can set the stated False
property of a shape. To set this, right-
Exposed Fill Gradient Mode click the shape definition and click Add
Exposed .
Exposed Outline Color As Property
Fixed Tooltip Text The text that is used for a fixed tooltip. <none>
You can use image shapes to specify how domain classes appear in a generated designer. Define an image shape
by setting the Image property of the class to a predefined image file. The following formats are supported:
.gif
.jpg
.jpeg
.bmp
.wmf
.emf
.png
By default, designer resource files, such as image files, are located in the Resources folder in the Dsl project.
For more information, see How to Define a Domain-Specific Language. For more information about how to use
these properties, see Customizing and Extending a Domain-Specific Language.
Image shapes have the properties that are listed in the following table.
Fill Gradient Mode The fill gradient mode of this shape. Horizontal
Has Default Connection Points If True , the shape will use top, False
bottom, left, and right connection
points in the generated designer.
Outline Dash Style The outline dash style of this shape Solid
(Solid, Dash, Dot, DashDot,
DashDotDot, or Custom).
Exposed Fill Color As Property If True , the user can set the stated False
property of a shape. To set this, right-
Exposed Fill Gradient Mode click the shape definition and click Add
Exposed .
Exposed Outline Color As Property
Fixed Tooltip Text The text that is used for a fixed tooltip. <none>
See also
Domain-Specific Language Tools Glossary
Properties of Compartment Shapes
4/13/2022 • 2 minutes to read • Edit Online
Compartment shapes are one of the shapes you can use to display a domain class in a domain-specific
language. You can expand and collapse the compartments.
For more information, see How to Define a Domain-Specific Language. For more information about how to use
these properties, see Customizing and Extending a Domain-Specific Language.
Compartment shapes have the properties that are listed in the following table.
Fill Gradient Mode The fill gradient mode of this shape. Horizontal
Has Default Connection Points If True , the shape will use top, False
bottom, left, and right connection
points in the generated designer.
Is Single Compartment Header Visible If False , and the shape has a single True
compartment, the header of the
compartment is not visible.
Outline Dash Style The outline dash style of this shape Solid
(Solid, Dash, Dot, DashDot,
DashDotDot, Custom).
Text Color The color used for text decorators that Black
are associated with this shape.
Exposed Fill Color As Property If True , the user can set the stated False
property of a shape. To set this, right-
Exposed Fill Gradient Mode click the shape definition and click Add
Exposed .
Exposed Outline Color As Property
Fixed Tooltip Text The text that is used for a fixed tooltip. <none>
See also
Domain-Specific Language Tools Glossary
Properties of Port Shapes
4/13/2022 • 2 minutes to read • Edit Online
You can use port shapes to represent domain classes in the generated designer.
For more information, see How to Define a Domain-Specific Language. For more information about how to use
these properties, see Customizing and Extending a Domain-Specific Language.
Port shapes have the properties that are listed in the following table.
Fill Gradient Mode The fill gradient mode of this shape. Horizontal
Has Default Connection Points If True , the shape will use top, False
bottom, left, and right connection
points in the generated designer.
Outline Dash Style The outline dash style of this shape Solid
(Solid, Dash, Dot, DashDot,
DashDotDot, or Custom).
Exposed Fill Color As Property If True , the user can set the stated False
property of a shape. To set this, right-
Exposed Fill Gradient Mode click the shape definition and click Add
Exposed .
Exposed Outline Color As Property
Fixed Tool Tip Text The text that is used for a fixed tooltip. <none>
See also
Domain-Specific Language Tools Glossary
Properties of Decorators
4/13/2022 • 2 minutes to read • Edit Online
Decorators are icons, text, or expand/collapse chevrons that can appear on shapes or connectors on the
diagram. The following tables show the properties for the three kinds of decorator. Some of the properties
appear only on shape decorators or only on connector decorators.
For more information, see How to Define a Domain-Specific Language. For more information about how to use
these properties, see Customizing and Extending a Domain-Specific Language.
Expand/Collapse Decorator
P RO P ERT Y DESC RIP T IO N DEFA ULT
DisplayName The name of the decorator that will be Expand Collapse Decorator
displayed in the generated designer.
Icon Decorator
P RO P ERT Y DESC RIP T IO N DEFA ULT
TextDecorator
P RO P ERT Y DESC RIP T IO N DEFA ULT
See also
Domain-Specific Language Tools Glossary
Properties of Connectors
4/13/2022 • 2 minutes to read • Edit Online
Dash Style The dash style for the line for this Solid
connector (Solid, Dash, Dot, DashDot,
DashDotDot, or Custom).
Source End Style The source end style for this connector None
(HollowArrow, EmptyArrow,
FilledArrow, EmptyDiamond,
FilledDiamond, or None).
Target End Style The target end style for this connector None
(HollowArrow, EmptyArrow,
FilledArrow, EmptyDiamond,
FilledDiamond, or None).
Routing Style The style that is used for routing the Rectilinear
connector. A Rectilinear connector
makes right-angled turns as required;
a Straight connector does not.
Exposed Color As Property If True , the user can set the stated False
property of a shape. To set this, right-
Exposed Dash Style As Property click the shape definition and click Add
Exposed .
Exposed Thickness As Property
Fixed Tooltip Text The text that is used for a fixed tooltip. <none>
See also
Domain-Specific Language Tools Glossary
Sharing Classes between DSLs by using a DSL
Library
4/13/2022 • 2 minutes to read • Edit Online
In the Visual Studio Visualization and Modeling SDK, you can create an incomplete DSL Definition that you can
import into another DSL. This lets you factor common parts of similar models.
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Write Code to Customize a Domain-Specific
Language
4/13/2022 • 2 minutes to read • Edit Online
This section shows you how to use custom code to access, modify, or create a model in a domain-specific
language.
There are several contexts in which you can write code that works with a DSL:
Custom commands. You can create a command that users can invoke by right-clicking on the diagram,
and which can modify the model. For more information, see How to: Add a Command to the Shortcut
Menu.
Validation. You can write code that verifies that the model is in a correct state. For more information, see
Validation in a Domain-Specific Language.
Overriding the default behavior. You can modify many aspects of the code that is generated from
DslDefinition.dsl. For more information, see Overriding and Extending the Generated Classes.
Text Transformation. You can write text templates that contain code that accesses a model and
generates a text file, for example to generate program code. For more information, see Generating Code
from a Domain-Specific Language.
Other Visual Studio extensions. You can write separate VSIX extensions that read and modify models.
For more information, see How to: Open a Model from File in Program Code
Instances of the classes that you define in DslDefinition.dsl are kept in a data structure called the In-Memory
Store (IMS) or Store. The classes you define in a DSL always take a Store as an argument to the constructor. For
example, if your DSL defines a class called Example:
Example element = new Example (theStore);
keeping objects in the Store (instead of just as ordinary objects) provides several benefits.
Transactions . You can group a series of related changes into a transaction:
using (Transaction t = store.TransactionManager.BeginTransaction("updates"))
t.Commit();
If an exception occurs during the changes, so that the final Commit() is not performed, the Store will be
reset to its previous state. This helps you to make sure that errors do not leave the model in an
inconsistent state. For more information, see Navigating and Updating a Model in Program Code.
Binar y relationships . If you define a relationship between two classes, instances at both ends have a
property that navigates to the other end. The two ends are always synchronized. For example, if you
define a parenthood relationship with roles named Parents and Children, you could write:
John.Children.Add(Mary)
Both of the following expressions are now true:
John.Children.Contains(Mary)
Mary.Parents.Contains(John)
For more information, see Navigating and Updating a Model in Program Code.
Rules and Events . You can define rules that fire whenever specified changes are made. Rules are used,
for example, to keep shapes on the diagram up to date with the model elements they present. For more
information, see Responding to and Propagating Changes.
Serialization . The Store provides a standard way to serialize the objects it contains to a file. You can
customize the rules for serializing and deserializing. For more information, see Customizing File Storage
and XML Serialization.
See also
Customizing and Extending a Domain-Specific Language
Override and extend the generated classes
4/13/2022 • 4 minutes to read • Edit Online
Your DSL Definition is a platform on which you can build a powerful set of tools that are based on a domain-
specific language. Many extensions and adaptations can be made by overriding and extending the classes that
are generated from the DSL Definition. These classes include not just the domain classes that you have explicitly
defined in the DSL Definition diagram, but also other classes that define the toolbox, explorer, serialization, and
so on.
Extensibility Mechanisms
Several mechanisms are provided to allow you to extend the generated code.
Override methods in a partial class
Partial class definitions allow a class to be defined in more than one place. This allows you to separate the
generated code from the code that you write yourself. In your manually-written code, you can override classes
inherited by the generated code.
For example, if in your DSL Definition you define a domain class named Book , you could write custom code that
adds override methods:
NOTE
To override methods in a generated class, always write your code in a file that is separated from the generated files.
Typically, the file is contained in a folder that is named CustomCode. If you make changes to the generated code, they will
be lost when you regenerate the code from the DSL Definition.
To discover what methods you can override, type override in the class, followed by a space. The IntelliSense
tooltip will tell you what methods can be overridden.
Double -Derived Classes
Most of the methods in generated classes are inherited from a fixed set of classes in the Modeling namespaces.
However, some methods are defined in the generated code. Ordinarily, this means that you cannot override
them; you cannot override in one partial class the methods that are defined in another partial definition of the
same class.
Nevertheless, you can override these methods by setting the Generates Double Derived flag for the domain
class. This causes two classes to be generated, one being an abstract base class of the other. All the method and
property definitions are in the base class, and only the constructor is in the derived class.
For example, in the sample Library.dsl, the CirculationBook domain class has the Generates``Double Derived
property set to true . The generated code for that domain class contains two classes:
CirculationBookBase , which is an abstract and which contains all the methods and properties.
CirculationBook , which is derived from CirculationBookBase . It is empty, except for its constructors.
To override any method, you create a partial definition of the derived class such as CirculationBook . You can
override both the generated methods and the methods inherited from the modeling framework.
You can use this method with all types of element, including model elements, relationships, shapes, diagrams,
and connectors. You can also override methods of other generated classes. Some generated classes such as the
ToolboxHelper are always double-derived.
Custom Constructors
You cannot override a constructor. Even in double-derived classes, the constructor must be in the derived class.
If you want to provide your own constructor, you can do this by setting Has Custom Constructor for the domain
class in the DSL Definition. When you click Transform All Templates , the generated code will not include a
constructor for that class. It will include a call to the missing constructor. This causes an error report when you
build the solution. Double-click the error report to see a comment in the generated code that explains what you
should provide.
Write a partial class definition in a file that is separate from the generated files, and provide the constructor.
Flagged Extension Points
A flagged extension point is a place in the DSL Definition where you can set a property or a check box to indicate
that you will provide a custom method. Custom constructors are one example. Other examples include setting
the Kind of a domain property to Calculated or Custom Storage or setting the Is Custom flag in a connection
builder.
In each case, when you set the flag and regenerate the code, a build error will result. Double-click the error to
see a comment that explains what you have to provide.
Rules
The transaction manager allows you to define rules that run before the end of a transaction in which a
designated event has occurred, such as a change in a property. Rules are typically used to maintain synchronism
between different elements in the store. For example, rules are used to make sure that the diagram displays the
current state of the model.
Rules are defined on a per-class basis, so that you do not have to have code that registers the rule for each
object. For more information, see Rules Propagate Changes Within the Model.
Store Events
The modeling store provides an event mechanism that you can use to listen for specific types of change in the
store, including addition and deletion of elements, changes to property values, and so on. The event handlers
are called after the close of the transaction in which the changes were made. Typically, these events are used to
update resources outside the store.
.NET Events
You can subscribe to some events on shapes. For example, you can listen for mouse-clicks on a shape. You have
to write code that subscribes to the event for each object. This code can be written in an override of
InitializeInstanceResources().
Some events are generated on ShapeFields, which are used to draw decorators on a shape. For an example, see
How to: Intercept a Click on a Shape or Decorator.
These events usually do not occur inside a transaction. You should create a transaction if you want to make
changes in the store.
Navigate and Update a Model in Program Code
4/13/2022 • 14 minutes to read • Edit Online
You can write code to create and delete model elements, set their properties, and create and delete links
between elements. All changes must be made within a transaction. If the elements are viewed on a diagram, the
diagram will be "fixed up" automatically at the end of the transaction.
In addition, if you are writing the code in a different project from the one in which your DSL is defined, you
should import the assembly that is built by the Dsl project.
if (henry.Name.EndsWith("VIII")) ...
If in the DSL definition, a property's Kind is Calculated , you cannot set it. For more information, see Calculated
and Custom Storage Properties.
Relationships
Domain relationships that you define in the DSL definition become pairs of properties, one on the class at each
end of the relationship. The names of the properties appear in the DslDefinition diagram as labels on the roles at
each side of the relationship. Depending on the multiplicity of the role, the type of the property is either the class
at the other end of the relationship, or a collection of that class.
foreach (Person child in henry.Children) { ... }
The properties at opposite ends of a relationship are always reciprocal. When a link is created or deleted, the
role properties on both elements are updated. The following expression (which uses the extensions of
System.Linq ) is always true for the ParentsHaveChildren relationship in the example:
ElementLinks . A relationship is also represented by a model element called a link, which is an instance of the
domain relationship type. A link always has one source element and one target element. The source element and
the target element can be the same.
You can access a link and its properties:
ParentsHaveChildren link = ParentsHaveChildren.GetLink(henry, edward);
By default, no more than one instance of a relationship is allowed to link any pair of model elements. But if in
the DSL definition, the Allow Duplicates flag is true for the relationship, then there might be more than one
link, and you must use GetLinks :
foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinks(henry, edward)) { ... }
There are also other methods for accessing links. For example:
foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinksToChildren(henry)) { ... }
Hidden roles. If in the DSL definition, Is Proper ty Generated is false for a particular role, then no property
is generated that corresponds to that role. However, you can still access the links and traverse the links using the
methods of the relationship:
foreach (Person p in ParentsHaveChildren.GetChildren(henry)) { ... }
The most frequently used example is the PresentationViewsSubject relationship, which links a model element to
the shape that displays it on a diagram:
PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape
There are also methods for finding elements, such as the following:
store.ElementDirectory.FindElements(Person.DomainClassId);
store.ElementDirectory.GetElement(elementId);
You can get information about the classes, relationships, and other aspects of the DSL definition. For example:
DomainClassInfo personClass = henry.GetDomainClass();
DomainPropertyInfo birthProperty =
personClass.FindDomainProperty("BirthDate")
DomainRelationshipInfo relationship =
link.GetDomainRelationship();
You can make any number of changes inside one transaction. You can open new transactions inside an active
transaction.
To make your changes permanent, you should Commit the transaction before it is disposed. If an exception
occurs that is not caught inside the transaction, the Store will be reset to its state before the changes.
edward.Parents.Add(henry);
The last method is useful if you want to set properties on the relationship itself.
When you create an element in this way, a connector on the diagram is automatically created, but it has a
default shape, color, and other features. To control how the associated connector is created, see Creating
an Element and its Shape.
Deleting Elements
Delete an element by calling Delete() :
henry.Delete();
(This particular example would have no effect, because PropagatesDelete is false for the roles of the
ParentsHaveChildren relationship.)
In some cases, deletion is prevented by the existence of a lock, either on the element or on an element that
would be deleted by propagation. You can use element.CanDelete() to check whether the element can be
deleted.
edward.Parents.Remove(henry); // or:
These three methods all have the same effect. You only need to use one of them.
If the role has 0..1 or 1..1 multiplicity, you can set it to null , or to another value:
edward.FamilyTreeModel = null; // or:
edward.FamilyTreeModel = anotherFamilyTree;
DomainRoleInfo role =
link.GetDomainRelationship().DomainRoles[0];
link.MoveBefore(role, nextLink);
Locks
Your changes might be prevented by a lock. Locks can be set on individual elements, on partitions, and on the
store. If any of these levels has a lock that prevents the kind of change that you want to make, an exception
might be thrown when you attempt it. You can discover whether locks are set by using element.GetLocks(),
which is an extension method that is defined in the namespace Microsoft.VisualStudio.Modeling.Immutability.
For more information, see Defining a Locking Policy to Create Read-Only Segments.
Merge () can accept either a PresentationElement or a ModelElement . If you give it a PresentationElement , you
can also specify a position on the target diagram as a third parameter.
In your DSL Definition, each element that you specify creates a class that is derived from one of the following
standard classes.
K IN D O F EL EM EN T B A SE C L A SS
Shape NodeShape
Connector BinaryLinkShape
Diagram Diagram
An element on a diagram usually represents a model element. Typically (but not always), a NodeShape
represents a domain class instance, and a BinaryLinkShape represents a domain relationship instance. The
PresentationViewsSubject relationship links a node or link shape to the model element that it represents.
Every node or link shape belongs to one diagram. A binary link shape connects two node shapes.
Shapes can have child shapes in two sets. A shape in the NestedChildShapes set is confined to the bounding box
of its parent. A shape in the RelativeChildShapes list can appear outside or partly outside the bounds of the
parent - for example a label or a port. A diagram has no RelativeChildShapes and no Parent .
Navigating between shapes and elements
Domain model elements and shape elements are related by the PresentationViewsSubject relationship.
// using Microsoft.VisualStudio.Modeling;
// using Microsoft.VisualStudio.Modeling.Diagrams;
// using System.Linq;
Person henry = ...;
PersonShape henryShape =
PresentationViewsSubject.GetPresentation(henry)
.FirstOrDefault() as PersonShape;
This relationship also links the root of the model to the diagram:
FamilyTreeDiagram diagram =
PresentationViewsSubject.GetPresentation(familyTree)
.FirstOrDefault() as FamilyTreeDiagram;
diagram.ModelElement as FamilyTreeModel
connector.FromShape, connector.ToShape
Many shapes are composites; they are made up of a parent shape and one or more layers of children. Shapes
that are positioned relative to another shape are said to be its children. When the parent shape moves, the
children move with it.
Relative children can appear outside the bounding box of the parent shape. Nested children appear strictly
inside the bounds of the parent.
To obtain the top set of shapes on a diagram, use:
Diagram.NestedChildShapes
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
partial class MyDiagram
{
public override void OnDoubleClick(DiagramPointEventArgs e)
{
base.OnDoubleClick(e);
this.ElementOperations.MergeElementGroupPrototype(
this,
group.CreatePrototype(),
PointD.ToPointF(e.MousePosition));
t.Commit();
}
}
}
If you provide more than one shape, set their relative positions using the AbsoluteBounds .
You can also set the color and other exposed properties of connectors using this method.
Use Transactions
Shapes, connectors and diagrams are subtypes of ModelElement and live in the Store. You must therefore make
changes to them only inside a transaction. For more information, see How to: Use Transactions to Update the
Model.
See also
ModelElement
Validation in a Domain-Specific Language
Generating Code from a Domain-Specific Language
How to: Use Transactions to Update the Model
Integrating Models by using Visual Studio Modelbus
Responding to and Propagating Changes
How to: Use Transactions to Update the Model
4/13/2022 • 3 minutes to read • Edit Online
Transactions make sure that changes that were made to the store are treated as a group. Changes that are
grouped can be committed or rolled back as a single unit.
Whenever your program code modifies, adds, or deletes any element in the Store in Visual Studio Visualization
and Modeling SDK, it must do so inside a transaction. There must be an active instance of Transaction associated
with the Store when the change happens. This applies to all model elements, relationships, shapes, diagrams,
and their properties.
The transaction mechanism helps you avoid inconsistent states. If an error occurs during a transaction, all the
changes are rolled back. If the user performs an Undo command, each recent transaction is treated as a single
step. The user cannot undo parts of a recent change, unless you explicitly put them in separate transactions.
Opening a transaction
The most convenient method of managing a transaction is with a using statement enclosed in a try...catch
statement:
If an exception that prevents the final Commit() occurs during the changes, the Store will be reset to its previous
state. This helps you make sure that errors do not leave the model in an inconsistent state.
You can make any number of changes inside one transaction. You can open new transactions inside an active
transaction. The nested transactions must commit or roll back before the containing transaction ends. For more
information, see the example for the TransactionDepth property.
To make your changes permanent, you should Commit the transaction before it is disposed. If an exception
occurs that is not caught inside the transaction, the Store will be reset to its state before the changes.
this.Store.TransactionManager.CurrentTransaction.Rollback();
Transaction Context
Each transaction has a dictionary in which you can store any information you want:
store.TransactionManager
.CurrentTransaction.TopLevelTransaction
.Context.Add(aKey, aValue);
Transaction state
In some cases you need to avoid propagating a change if the change is caused by undoing or redoing a
transaction. This can happen, for example, if you write a property value handler that can update another value in
the Store. Because the undo operation resets all the values in the Store to their previous states, it is not
necessary to compute updated values. Use this code:
if (!this.Store.InUndoRedoOrRollback) {...}
Rules can fire when the store is initially being loaded from a file. To avoid responding to these changes, use:
if (!this.Store.InSerializationTransaction) {...}
Integrate models by using Visual Studio Modelbus
4/13/2022 • 16 minutes to read • Edit Online
Visual Studio ModelBus provides a method for creating links between models and from other tools into models.
For example, you could link domain-specific language (DSL) models and UML models. You can create an
integrated set of DSLs.
ModelBus lets you create a unique reference to a model or to a specific element inside a model. This reference
can be stored outside the model, for example, in an element in another model. When, on a later occasion, a tool
wants to obtain access to the element, the Model Bus infrastructure will load the appropriate model and return
the element. If you want, you can display the model to the user. If the file cannot be accessed in its previous
location, ModelBus will ask the user to find it. If the user finds the file, ModelBus will fix all the references to that
file.
NOTE
In the current Visual Studio implementation of ModelBus, the linked models must be items in the same Visual Studio
solution.
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
WARNING
If you have not selected a valid model or entity, the OK button will have no effect, even though it might appear
enabled.
6. If you specified a list of target types such as Company.FamilyTree.Person, then you must add an assembly
reference to your DSL project, referencing the DLL of the target DSL, for example
Company.FamilyTree.Dsl.dll
To test a Model Bus Reference
1. Build both the exposed and consuming DSLs.
2. Run one of the DSLs in experimental mode by pressing F5 or CTRL+F5.
3. In the Debugging project in the experimental instance of Visual Studio, add files that are instances of each
DSL.
NOTE
Visual Studio ModelBus can only resolve references to models that are items in the same Visual Studio solution.
For example, you cannot create a reference to a model file in another part of your file system.
4. Create some elements and links in the instance of the exposed DSL, and save it.
5. Open an instance of the consuming DSL, and select a model element that has a model bus reference
property.
6. In Properties window, double-click the model bus reference property. The picker dialog opens.
7. Click Browse and select the instance of the exposed DSL.
The picker will also let you choose an item in the model, if you specified the element-specific kind of
model bus reference.
NOTE
With the Visual Studio ModelBus, you can create references only to items in the same Visual Studio solution.
If you want to be able to use modelReference later, you can store it in a domain property that has the External
Type ModelBusReference :
To allow users to edit this domain property, use ModelReferenceEditor as the parameter in the Editor attribute.
For more information, see Allow the User to Edit a Reference.
To create a reference to an element
The adapter that you created for the model can be used to create and resolve references.
If you want to be able to use elementReference later, you can store it in a domain property that has the External
Type ModelBusReference . To allow users to edit it, use ModelElementReferenceEditor as the parameter in the
Editor attribute. For more information, see Allow the User to Edit a Reference.
Resolving references
If you have a ModelBusReference (MBR) you can obtain the model or the model element to which it refers. If the
element is presented on a diagram or other view, you can open the view and select the element.
You can create an adapter from an MBR. From the adapter, you can obtain the root of the model. You can also
resolve MBRs that refer to specific elements within the model.
For more information and a walkthrough, see Using Visual Studio ModelBus in a Text Template
Serializing a ModelBusReference
If you want to store a ModelBusReference (MBR) in the form of a string, you can serialize it:
A MBR that is serialized in this manner is independent of context. If you are using the simple file-based Model
Bus Adapter, the MBR contains an absolute file path. This is sufficient if the instance model files will never move.
However, the model files will typically be items in a Visual Studio project. Your users will expect to be able to
move the whole project to different parts of the file system. They will also expect to be able to keep the project
under source control and open it on different computers. Path names should therefore be serialized relative to
the location of the project that contains the files.
Serializing Relative to a Specified File Path
A ModelBusReference contains a ReferenceContext , which is a dictionary in which you can store information
such as the file path relative to which it should be serialized.
To serialize relative to a path:
elementReference.ReferenceContext.Add(
ModelBusReferencePropertySerializer.FilePathSaveContextKey,
currentProjectFilePath);
string serialized = modelBus.SerializeReference(elementReference);
When you deserialize an MBR, you must provide a ReferenceContext, which is then stored in the MBR object.
When you serialize an MBR, the stored ReferenceContext is used by the adapter to help generate the string. The
deserialized string does not contain all the information in the ReferenceContext. For example, in the simple file-
based adapter, the ReferenceContext contains a root file path, which is not stored in the serialized MBR string.
The MBR is deserialized in two stages:
ModelBusReferencePropertySerializer is the standard serializer that deals with the MBR header. It uses the
standard DSL SerializationContext property bag, which is stored in the ReferenceContext using the key
ModelBusReferencePropertySerializer.ModelBusLoadContextKey . In particular, the SerializationContext
should contain an instance of ModelBus .
Your ModelBus Adapter deals with the adapter-specific part of the MBR. It can use additional information
stored in the ReferenceContext of the MBR. The simple file-based adapter keeps root file paths using the
keys FilePathLoadContextKey and FilePathSaveContextKey .
An adapter reference in a model file is deserialized only when it is used.
To Create a Model
Creating, opening and editing a model
The following fragment is taken from the State Machine sample on the VMSDK website. It illustrates the use of
ModelBusReferences to create and open a model, and to obtain the diagram associated with the model.
In this sample, the name of the target DSL is StateMachine. Several names are derived from it, such as the name
of the model class and the name of the 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();
}
Validating references
The BrokenReferenceDetector tests all the domain properties in a Store that can hold ModelBusReferences. It
calls the action you that provide where any action is found. This is particularly useful for validation methods. The
following validation method tests the store on an attempt to save the model, and reports broken references in
the errors window:
[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 ths ReferenceState instance "
+ "named '{1}' contains reference value '{2}' which is invalid";
[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")]
When you right click the DSL Definition Diagram, click Enable ModelBus , and select Expose this DSL to the
ModelBus :
A new project ModelBusAdapter is added to the solution.
A reference to ModelBusAdapter is added to the DslPackage project. ModelBusAdapter has a reference to
the Dsl project.
In DslPackage\source.extention.tt , |ModelBusAdapter| is added as a MEF Component.
See also
How to: Open a Model from File in Program Code
How to: Add a Drag-and-Drop Handler
Using Visual Studio ModelBus in a Text Template
Extend your DSL by using MEF
4/13/2022 • 8 minutes to read • Edit Online
You can extend your domain-specific language (DSL) by using Managed Extensibility Framework (MEF). You or
other developers will be able to write extensions for the DSL without changing the DSL definition and program
code. Such extensions include menu commands, drag-and-drop handlers, and validation. Users will be able to
install your DSL, and then optionally install extensions for it.
In addition, when you enable MEF in your DSL, it can be easier for you to write some of the features of your DSL,
even if they are all built together with the DSL.
For more information about MEF, see Managed Extensibility Framework (MEF).
To enable your DSL to be extended by MEF
1. Create a new folder named MefExtension inside the DslPackage project. Add the following files to it:
File name: CommandExtensionVSCT.tt
IMPORTANT
Set the GUID in this file to be the same as the GUID CommandSetId that is defined in
DslPackage\GeneratedCode\Constants.tt
If you add this file, you must enable validation in your DSL by using at least one of the switches in
EditorValidation in DSL Explorer.
2. Create a new folder named MefExtension inside the Dsl project. Add the following files to it:
File name: DesignerExtensionMetaDataAttribute.tt
3. Add the following line to the existing file that is named DslPackage\Commands.vsct :
<Include href="MefExtension\CommandExtensionVSCT.vsct"/>
namespace MyMefExtension
{
// Defined in Dsl\MefExtension\DesignerExtensionMetaDataAttribute.cs:
[MyDslCommandExtension]
public class MyCommandClass : ICommandExtension
{
/// <summary>
/// Provides access to current document and selection.
/// </summary>
[Import]
IVsSelectionContext SelectionContext { get; set; }
/// <summary>
/// Called when the user selects this command.
/// </summary>
/// <param name="command"></param>
public void Execute(IMenuCommand command)
{
// Transaction is required if you want to update elements.
using (Transaction t = SelectionContext.CurrentStore
.TransactionManager.BeginTransaction("fix names"))
{
foreach (ExampleShape shape in SelectionContext.CurrentSelection)
{
ExampleElement element = shape.ModelElement as ExampleElement;
element.Name = element.Name + " !";
}
t.Commit();
}
}
/// <summary>
/// Called when the user right-clicks the diagram.
/// Determines whether the command should appear.
/// This method should set command.Enabled and command.Visible.
/// </summary>
/// <param name="command"></param>
public void QueryStatus(IMenuCommand command)
{
command.Enabled =
command.Visible = (SelectionContext.CurrentSelection.OfType<ExampleShape>().Count() > 0);
}
/// <summary>
/// Called when the user right-clicks the diagram.
/// Determines the text of the command in the menu.
/// </summary>
public string Text
{
get { return "My menu command"; }
}
}
}
Gesture Handlers
A gesture handler can deal with objects dragged onto the diagram from anywhere, inside or outside Visual
Studio. The following example lets the user drag files from Windows Explorer onto the diagram. It creates
elements that contain the file names.
You can write handlers to deal with drags from other DSL models and UML models. For more information, see
How to: Add a Drag-and-Drop Handler.
using System.ComponentModel.Composition;
using System.Linq;
using Company.MyDsl;
using Company.MyDsl.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling; // Transactions
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.ExtensionEnablement;
namespace MefExtension
{
[MyDslGestureExtension]
class MyGestureExtension : IGestureExtension
{
public void OnDoubleClick(ShapeElement targetElement, DiagramPointEventArgs diagramPointEventArgs)
{
System.Windows.Forms.MessageBox.Show("double click!");
}
/// <summary>
/// Called when the user drags anything over the diagram.
/// Return true if the dragged object can be dropped on the current target.
/// </summary>
/// <param name="targetMergeElement">The shape or diagram that the mouse is currently over</param>
/// <param name="diagramDragEventArgs">Data about the dragged element.</param>
/// <returns></returns>
public bool CanDragDrop(ShapeElement targetMergeElement, DiagramDragEventArgs diagramDragEventArgs)
{
// This handler only allows items to be dropped onto the diagram:
return targetMergeElement is MefDsl2Diagram &&
// And only accepts files dragged from Windows Explorer:
diagramDragEventArgs.Data.GetFormats().Contains("FileNameW");
}
/// <summary>
/// Called when the user drops an item onto the diagram.
/// </summary>
/// <param name="targetDropElement"></param>
/// <param name="diagramDragEventArgs"></param>
public void OnDragDrop(ShapeElement targetDropElement, DiagramDragEventArgs diagramDragEventArgs)
{
MefDsl2Diagram diagram = targetDropElement as MefDsl2Diagram;
if (diagram == null) return;
Validation constraints
Validation methods are marked by the ValidationExtension attribute that is generated by the DSL, and also by
ValidationMethodAttribute. The method can appear in any class that is not marked by an attribute.
For more information, see Validation in a Domain-Specific Language.
using Company.MyDsl;
using Company.MyDsl.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.Validation;
namespace MefExtension
{
class MyValidationExtension // Can be any class.
{
// SAMPLE VALIDATION METHOD.
// All validation methods have the following attributes.
/// <summary>
/// When validation is executed, this method is invoked
/// for every element in the model that is an instance
/// of the second parameter type.
/// </summary>
/// <param name="context">For reporting errors</param>
/// <param name="elementToValidate"></param>
private void ValidateClassNames
(ValidationContext context,
// Type determines to what elements this will be applied:
ExampleElement elementToValidate)
{
// Write code here to test values and links.
if (elementToValidate.Name.IndexOf(' ') >= 0)
{
// Log any unacceptable values:
context.LogError(
// Description:
"Name must not contain spaces"
// Error code unique to this type of error:
, "MyDsl001"
// Element to highlight when user double-clicks error:
, elementToValidate);
} } } }
See also
Shipping Visual Studio Extensions
Managed Extensibility Framework (MEF)
How to: Add a Drag-and-Drop Handler
Validation in a Domain-Specific Language
How to: Open a Model from File in Program Code
4/13/2022 • 2 minutes to read • Edit Online
Target framework
Set the Target framework of your application project to .NET Framework 4 or later.
1. Open the Visual Studio project for the application in which you want to read a DSL model.
2. In Solution Explorer , right-click the project and then click Proper ties .
3. In the project properties window, on the Application tab, set the Target framework field to .NET
Framework 4 (or later).
NOTE
The target framework should not be .NET Framework 4 Client Profile .
References
Add these references to your Visual Studio application project:
Microsoft.VisualStudio.Modeling.Sdk.11.0
If you do not see this under the .NET tab in the Add References dialog box, click the Browse tab and
navigate to
%Program Files%\Microsoft Visual Studio 2010 SDK\VisualStudioIntegration\Common\Assemblies\ .
Your DSL assembly, which you will find under the bin folder your DSL project. Its name is typically of the
form: YourCompany.YourProject .Dsl.dll .
using System;
using Microsoft.VisualStudio.Modeling;
using Company.FamilyTree; // Your DSL namespace
namespace StandaloneReadDslConsole
{ class Program
{ static void Main(string[] args)
{
// The path of a DSL model file:
string dslModel = @"C:\FamilyTrees\Tudor.ftree";
// Set up the Store to read your type of model:
Store store = new Store(
typeof(Company.FamilyTree.FamilyTreeDomainModel));
// The Model type generated by the DSL:
FamilyTreeModel familyTree;
// All Store changes must be in a Transaction:
using (Transaction t =
store.TransactionManager.BeginTransaction("Load model"))
{
familyTree =
FamilyTreeSerializationHelper.Instance.
LoadModel(store, dslModel, null, null, null);
t.Commit(); // Don't forget this!
}
// Now we can read the model:
foreach (Person p in familyTree.People)
{
Console.WriteLine(p.Name);
foreach (Person child in p.Children)
{
Console.WriteLine(" " + child.Name);
}
} } } }
Save to a file
The following addition to the previous code makes a change to the model and then saves it to a file.
using (Transaction t =
store.TransactionManager.BeginTransaction("update model"))
{
// Create a new model element:
Person p = new Person(store);
// Set its embedding relationship:
p.FamilyTreeModel = familyTree;
// - same as: familyTree.People.Add(p);
// Set its properties:
p.Name = "Edward VI";
t.Commit(); // Don't forget this!
}
// Save the model:
try
{
SerializationResult result = new SerializationResult();
FamilyTreeSerializationHelper.Instance
.SaveModel(result, familyTree, @"C:\FamilyTrees\Tudor-upd.ftree");
// Report any error:
if (result.Failed)
{
foreach (SerializationMessage message in result)
{
Console.WriteLine(message);
}
}
}
catch (System.IO.IOException ex)
{ ... }
How to: Set CLR Attributes on an Element
4/13/2022 • 2 minutes to read • Edit Online
Custom attributes are special attributes that can be added to domain elements, shapes, connectors, and
diagrams. You can add any attribute that inherits from the System.Attribute class.
To add a custom attribute
1. In the DSL Explorer , select the element to which you want to add a custom attribute.
2. In the Proper ties window, next to the Custom Attributes property, click the Browse (...) icon.
The Edit Attributes dialog box opens.
3. In the Name column, click <add attribute> and type the name of your attribute. Press ENTER.
4. The line under the attribute name shows parentheses. On this line type a parameter type for the attribute
(for example, string ), and then press ENTER.
5. In the Name Proper ty column, type an appropriate name, for example, MyString .
6. Click OK .
The Custom Attributes property now displays the attribute in the following format:
[ AttributeName ( ParameterName = Type )]
See also
Domain-Specific Language Tools Glossary
Defining a Locking Policy to Create Read-Only
Segments
4/13/2022 • 6 minutes to read • Edit Online
The Immutability API of the Visual Studio Visualization and Modeling SDK allows a program to lock part or all of
a domain-specific language (DSL) model so that it can be read but not changed. This read-only option could be
used, for example, so that a user can ask colleagues to annotate and review a DSL model but can disallow them
from changing the original.
In addition, as author of a DSL, you can define a locking policy. A locking policy defines which locks are
permitted, not permitted, or mandatory. For example, when you publish a DSL, you can encourage third-party
developers to extend it with new commands. But you could also use a locking policy to prevent them from
altering the read-only status of specified parts of the model.
NOTE
A locking policy can be circumvented by using reflection. It provides a clear boundary for third-party developers, but does
not provide strong security.
More information and samples are available at the Visual Studio Visualization and Modeling SDK website.
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Other lock values can be used to prevent changes in relationships, element creation, movement between
partitions, and re-ordering links in a role.
The locks apply both to user actions and to program code. If program code attempts to make a change, an
InvalidOperationException will be thrown. Locks are ignored in an Undo or Redo operation.
You can discover whether an element has a any lock in a given set by using IsLocked(Locks) and you can obtain
the current set of locks on an element by using GetLocks() .
You can set a lock without using a transaction. The lock database is not part of the store. If you set a lock in
response to a change of a value in the store, for example in OnValueChanged, you should allow changes that are
part of an Undo operation.
These methods are extension methods that are defined in the Microsoft.VisualStudio.Modeling.Immutability
namespace.
Locks on partitions and stores
Locks can also be applied to partitions and the store. A lock that is set on a partition applies to all the elements
in the partition. Therefore, for example, the following statement will prevent all the elements in a partition from
being deleted, irrespective of the states of their own locks. Nevertheless, other locks such as Locks.Property
could still be set on individual elements:
partition.SetLocks(Locks.Delete);
A lock that is set on the Store applies to all its elements, irrespective of the settings of that lock on the partitions
and the elements.
Using Locks
You could use locks to implement schemes such as the following examples:
Disallow changes to all elements and relationships except those that represent comments. This allows
users to annotate a model without changing it.
Disallow changes in the default partition, but allow changes in the diagram partition. The user can
rearrange the diagram, but cannot alter the underlying model.
Disallow changes to the Store except for a group of users who are registered in a separate database. For
other users, the diagram and model are read-only.
Disallow changes to the model if a Boolean property of the diagram is set to true. Provide a menu
command to change that property. This helps ensure users that they do not make changes accidentally.
Disallow addition and deletion of elements and relationships of particular classes, but allow property
changes. This provides users with a fixed form in which they can fill the properties.
Lock values
Locks can be set on a Store, Partition, or individual ModelElement. Locks is a Flags enumeration: you can
combine its values using '|'.
Locks of a ModelElement always include the Locks of its Partition.
Locks of a Partition always include the Locks of the Store.
You cannot set a lock on a partition or store and at the same time disable the lock on an individual
element.
VA L UE M EA N IN G IF ISLOCKED(VALUE) IS T RUE
None No restriction.
RolePlayer The set of links that are sourced at this element cannot be
changed. For example, new elements cannot be embedded
under this element. This does not affect links for which this
element is the target.
Locking Policies
As the author of a DSL, you can define a locking policy. A locking policy moderates the operation of SetLocks(),
so that you can prevent specific locks from being set or mandate that specific locks must be set. Typically, you
would use a locking policy to discourage users or developers from accidentally contravening the intended use of
a DSL, in the same manner that you can declare a variable private .
You can also use a locking policy to set locks on all elements dependent on the element's type. This is because
SetLocks(Locks.None) is always called when an element is first created or deserialized from file.
However, you cannot use a policy to vary the locks on an element during its life. To achieve that effect, you
should use calls to SetLocks() .
To define a locking policy, you have to:
Create a class that implements ILockingPolicy.
Add this class to the services that are available through the DocData of your DSL.
To define a locking policy
ILockingPolicy has the following definition:
These methods are called when a call is made to SetLocks() on a Store, Partition, or ModelElement. In each
method, you are provided with a proposed set of locks. You can return the proposed set, or you can add and
subtract locks.
For example:
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Immutability;
namespace Company.YourDsl.DslPackage // Change
{
public class MyLockingPolicy : ILockingPolicy
{
/// <summary>
/// Moderate SetLocks(this ModelElement target, Locks locks)
/// </summary>
/// <param name="element">target</param>
/// <param name="proposedLocks">locks</param>
/// <returns></returns>
public Locks RefineLocks(ModelElement element, Locks proposedLocks)
{
// In my policy, users can never delete an element,
// and other developers cannot easily change that:
return proposedLocks | Locks.Delete);
}
public Locks RefineLocks(Store store, Locks proposedLocks)
{
// Only one user can change this model:
return Environment.UserName == "aUser"
? proposedLocks : Locks.All;
}
To make sure that users can always delete elements, even if other code calls SetLocks(Lock.Delete):
/// <summary>
/// Called when a service is requested.
/// </summary>
/// <param name="serviceType">Service requested</param>
/// <returns>Service implementation</returns>
public override object GetService(System.Type serviceType)
{
if (serviceType == typeof(SLockingPolicy)
|| serviceType == typeof(ILockingPolicy))
{
if (myLockingPolicy == null)
{
myLockingPolicy = new MyLockingPolicy();
}
return myLockingPolicy;
}
// Request is for some other service.
return base.GetService(serviceType);
}
}
Add Extensions to DSL Definitions
4/13/2022 • 2 minutes to read • Edit Online
DSL Definition extension allows you to create a package of extensions to a domain-specific language (DSL). The
DSL extension, which is contained in a Visual Studio Integration Extension (VSIX), can be installed on a user's
computer in the same manner as a DSL. The additional features can be dynamically enabled and disabled at run
time. DSLs do not have to be explicitly designed for extension, and extensions can be designed later, or by third
parties, without altering the extended DSL.
DSL extensions can include the following features:
Properties for model and presentation elements
Decorators for shapes and connectors
Classes, relationships, shapes, and connectors
Validation constraints
Toolbox items and tabs
A user of an extended DSL can create and save a model that contains instances of the additional features. The
model can be read by other users who have installed the appropriate extension. Users who have not installed
the extension cannot use the additional features, but they can update and save a model without losing the
additional features.
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
See also
Related blog posts
Add a Tracking Property to a Domain-Specific
Language Definition
4/13/2022 • 17 minutes to read • Edit Online
Prerequisites
Before you can start this walkthrough, you must first install these components:
C O M P O N EN T L IN K
h. Allow the wizard to create a strong name key file for your assemblies.
i. Review the details of the solution, and then click Finish to create the DSL definition project.
Transform Templates
Now that you have defined the domain classes and properties for your DSL, you can verify that the DSL
definition can be transformed correctly to regenerate the code for your project.
1. On the Solution Explorer toolbar, click Transform All Templates .
2. The system regenerates the code for the solution, and saves DslDefinition.dsl. For information about the
XML format of definition files, see The DslDefinition.dsl File.
5. In the DslPackage project, also create a CustomCode folder, and add to it a Package.cs code file.
Add Helper Classes to Support Tracking Properties
To the HelperClasses.cs file, add the TrackingHelper and CriticalException classes as follows. You will
reference these classes later in this walkthrough.
1. Add the following code to the HelperClasses.cs file.
using System;
using System.Collections;
using System.Diagnostics;
using Microsoft.VisualStudio.Modeling;
namespace CompanyName.ProductName.TrackingPropertyDSL
{
internal static class TrackingHelper
{
/// <summary>Notify each model element in a collection that a tracked
/// property has changed.</summary>
/// <param name="store">The store for the model.</param>
/// <param name="collection">The collection of model elements that
/// contain the tracking property.</param>
/// <param name="propertyId">The ID of the tracking property.</param>
/// <param name="trackingPropertyId">The ID of the property that
/// indicates whether the tracking property is tracking.</param>
internal static void UpdateTrackingCollectionProperty(
Store store,
IEnumerable collection,
Guid propertyId,
Guid trackingPropertyId)
{
DomainPropertyInfo propInfo =
store.DomainDataDirectory.GetDomainProperty(propertyId);
DomainPropertyInfo trackingPropInfo =
store.DomainDataDirectory.GetDomainProperty(trackingPropertyId);
Debug.Assert(propInfo != null);
Debug.Assert(trackingPropInfo != null);
Debug.Assert(trackingPropInfo.PropertyType.Equals(typeof(bool)),
"Tracking property not specified as a boolean");
/// <summary>Helper class to flag critical exceptions from ones that are
/// safe to ignore.</summary>
internal static class CriticalException
{
/// <summary>Gets whether an exception is critical and can not be
/// ignored.</summary>
/// <param name="ex">The exception to check.</param>
/// <returns>True if the exception is critical.</returns>
internal static bool IsCriticalException(Exception ex)
{
if (ex is NullReferenceException
|| ex is StackOverflowException
|| ex is OutOfMemoryException
|| ex is System.Threading.ThreadAbortException)
return true;
if (ex.InnerException != null)
return IsCriticalException(ex.InnerException);
return false;
}
}
}
NOTE
The code that the DSL Tools generate for the custom type descriptor for ExampleModel calls GetCustomProperties ;
however, the DSL Tools do not generate code that implements the method.
Defining this method creates the tracking property descriptor for the Namespace tracking property. Also,
providing attributes for the tracking property enables the Proper ties window to display the property correctly.
To modify the type descriptor for the ExampleModel domain class
1. Add the following code to the TypeDescriptor.cs file.
using System;
using System.ComponentModel;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Design;
namespace CompanyName.ProductName.TrackingPropertyDSL
{
// To the custom type descriptor for the ExampleElement domain class, add
// the GetCustomProperties method.
public partial class ExampleElementTypeDescriptor
{
/// <summary>Returns the property descriptors for the described
/// ExampleElement domain class.</summary>
/// <remarks>This method adds the tracking property descriptor.
/// </remarks>
private PropertyDescriptorCollection GetCustomProperties(
Attribute[] attributes)
{
// Get the default property descriptors from the base class
PropertyDescriptorCollection propertyDescriptors =
base.GetProperties(attributes);
DomainPropertyInfo trackingProperty =
source.Store.DomainDataDirectory.GetDomainProperty(
ExampleElement.IsNamespaceTrackingDomainPropertyId);
propertyDescriptors.Add(new TrackingPropertyDescriptor(
source, domainProperty, trackingProperty, attr));
}
namespace CompanyName.ProductName.TrackingPropertyDSL
{
// Override the default Initialize method.
internal sealed partial class TrackingPropertyDSLPackage
{
protected override void Initialize()
{
// Add the custom type descriptor for the ExampleElement type.
TypeDescriptor.AddProvider(
new ExampleElementTypeDescriptionProvider(),
typeof(ExampleElement));
base.Initialize();
}
}
}
NOTE
The code that the DSL Tools generate for ExampleModel calls GetCustomElementsValue ; however, the DSL Tools do not
generate code that implements the method.
Defining the GetCustomElementsValuemethod provides the logic for the CustomElements calculated property of
ExampleModel . This method counts the number of ExampleElement domain classes that have a Namespace
tracking property that has a user-updated value, and returns a string that represents this count as a proportion
of the total elements in the model.
In addition, add an OnDefaultNamespaceChanged method to ExampleModel , and override the OnValueChanged
method of the DefaultNamespacePropertyHandler nested class of ExampleModel to call OnDefaultNamespaceChanged .
Because the DefaultNamespace property is used to calculate the Namespace tracking property, ExampleModel
must notify all ExampleElement domain classes that the value of DefaultNamespace has changed.
To modify the property handler for the tracked property
1. Add the following code to the ExampleModel.cs file.
using System.Linq;
namespace CompanyName.ProductName.TrackingPropertyDSL
{
public partial class ExampleModel
{
public string GetCustomElementsValue()
{
if (this.Elements.Count == 0) return "0/0";
if (!element.Store.InUndoRedoOrRollback)
{
element.OnDefaultNamespaceChanged(oldValue, newValue);
}
}
}
#endregion
}
}
NOTE
The code that the DSL Tools generate for ExampleModel calls the get and set methods; however, the DSL Tools do not
generate code that implements the methods.
using System;
using Microsoft.VisualStudio.Modeling;
namespace CompanyName.ProductName.TrackingPropertyDSL
{
// To the domain class that has the tracking property, add the caluclation
// for when the property is tracking.
public partial class ExampleElement
{
/// <summary>Calculates the actual value of the property when it is
/// tracking.</summary>
/// <returns>The value of the tracking property when it is
/// tracking.</returns>
/// <remarks>Making this method virtual allows child classes to modify
/// the calculation. This method does not need to perform validation, as
/// its caller handles validation prior to calling this method.
/// <para>In this case, the tracking value depends on the default namespace
/// property of the parent model.</para></remarks>
protected virtual string CalculateNamespace()
{
return this.ExampleModel.DefaultNamespace;
}
return namespaceStorage;
}
try
{
calculatedValue = element.CalculateNamespace();
}
catch (NullReferenceException)
{
}
catch (System.Exception e)
{
if (CriticalException.IsCriticalException(e))
{
throw;
}
}
if ((calculatedValue != null
&& object.Equals(element.Namespace, calculatedValue)))
{
element.isNamespaceTrackingPropertyStorage = true;
}
}
#endregion
}
}
NOTE
The code that the DSL Tools generate calls the OnPostLoadModel and OnPostLoadModelAndDiagram methods; however,
the DSL Tools do not generate code that implements these methods.
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.Modeling;
namespace CompanyName.ProductName.TrackingPropertyDSL
{
#region Helper classes for maintaining state while the store is serializing.
// Add the pre-reset and reset methods for the model element.
public partial class ExampleElement
{
/// <summary>Calls the pre-reset method on the associated property value
/// handler for each tracking property of this model element.</summary>
internal virtual void PreResetIsTrackingProperties()
{
ExampleElement.IsNamespaceTrackingPropertyHandler.Instance.PreResetValue(this);
}
#endregion
#endregion
}
Next steps
If you plan to use more than one tracking property, or implement tracking properties in more than one DSL, you
can create a text template to generate the common code for supporting each tracking property. For more
information about text templates, see Code Generation and T4 Text Templates.
See also
TrackingPropertyDescriptor
ElementTypeDescriptor
How to Define a Domain-Specific Language
How to: Create a Domain-Specific Language Solution
The DslDefinition.dsl File
4/13/2022 • 24 minutes to read • Edit Online
This topic describes the structure of the DslDefinition.dsl file in the Dsl project of a Domain-Specific Language
Tools solution, which defines a domain-specific language. The DslDefinition.dsl file describes the classes and
relationships of a domain-specific language, together with the diagram, shapes, connectors, serialization format,
and Toolbox of the domain-specific language and its editing tools. In a domain-specific language solution, the
code that defines those tools is generated according to the information in the DslDefinition.dsl file.
Generally, you use the Domain-Specific Language Designer to edit the DslDefinition.dsl file. However, its raw
form is XML, and you can open a DslDefinition.dsl file in an XML editor. You might find it useful to understand
what information the file contains and how it is organized for debugging and extension purposes.
Examples in this topic are taken from the Component Diagram solution template. To see an example, create a
domain-specific language solution that is based on the Component Models solution template. After you create
the solution, the DslDefinition.dsl file appears in the Domain-Specific Language Designer. Close the file, right-
click it in Solution Explorer , point to Open With , click XML Editor , and then click OK .
Usually, the namespace of the referenced item (in this example, the Library domain class) is the same as the
referencing item (in this case, the LibraryHasMembers domain relationship). In those cases, the moniker must
give only the name of the class. Otherwise, you should use the full form /Namespace/Name:
The moniker system requires that siblings in the XML tree have distinct names. For this reason, validation errors
occur if you try to save a domain-specific language definition that has, for example, two classes of the same
name. You should always correct such duplicate-name errors before you save the DslDefinition.dsl file so that
you can reload it correctly later.
Each type has its own type of moniker: DomainClassMoniker, DomainRelationshipMoniker, and so on.
Types
The Types section specifies all the types that the DslDefinition.dsl file contains as types of properties. These types
fall into two kinds: external types, such as System.String, and enumerated types.
External Types
The Component Diagram example lists a set of standard primitive types, although only some of them are used.
Each External Type definition consists of just a name and a namespace, such as String and System:
The full names of the types are used, instead of the equivalent compiler keywords such as "string".
External types are not restricted to standard library types.
Enumerations
A typical Enumeration specification resembles this example:
The IsFlags attribute controls whether the generated code is prefixed by the [Flags] Common Language
Runtime (CLR) attribute, which determines whether values of the enumeration can be combined bitwise. If this
attribute is set to true, you should specify power-of-two values for the literal values.
Classes
Most of the elements in any definition of a domain-specific language are either directly or indirectly instances of
DomainClass . Subclasses of DomainClass include DomainRelationship , Shape , Connector , and Diagram . The
Classes section of the DslDefinition.dsl file lists the domain classes.
Each class has a set of properties and might have a base class. In the Component Diagram example,
NamedElement is an abstract class that has a Name property, whose type is string:
NamedElement is the base of several of the other classes such as Component , which has its own properties in
addition to the Name property, which it inherited from NamedElement . The BaseClass child node contains a
moniker reference. Because the referenced class is in the same namespace, only its name is required in the
moniker:
<DomainClass Name="Component" Namespace="Fabrikam.CmptDsl5" DisplayName="Component">
<BaseClass>
<DomainClassMoniker Name="NamedElement" />
</BaseClass>
<Properties>
<DomainProperty Name="Kind" DisplayName="Kind" >
<Type>
<ExternalTypeMoniker Name="/System/String" />
</Type>
</DomainProperty>
</Properties>
Every domain class (including relationships, shapes, connectors, and diagrams) can have these attributes and
child nodes:
Id. This attribute is a GUID. If you do not provide a value in the file, the Domain-Specific Language
Designer will create a value. (In illustrations in this document, this attribute is usually omitted to save
space.)
Name and Namespace. These attributes specify the name and namespace of the class in the generated
code. Together they must be unique within the domain-specific language.
InheritanceModifier. This attribute is "abstract", "sealed", or none.
DisplayName. This attribute is the name that appears in the Proper ties window. The DisplayName
attribute can contain spaces and other punctuation.
GeneratesDoubleDerived. If this attribute is set to true, two classes are generated, and one is a
subclass of the other. All the generated methods are in the base, and the constructors are in the subclass.
By setting this attribute, you can override any generated method in custom code.
HasCustomConstructor . If this attribute is set to true, the constructor is omitted from the generated
code so that you can write your own version.
Attributes . This attribute contains the CLR Attributes of the generated class.
BaseClass . If you specify a base class, it must be of the same type. For example, a domain class must
have another domain class as its base, and a compartment shape must have a compartment shape. If you
do not specify a base class, the class in the generated code derives from a standard framework class. For
example, a domain class derives from ModelElement .
Proper ties . This attribute contains the properties that are maintained under transaction control and
persisted when the model is saved.
ElementMergeDirectives . Each element merge directive controls how a different instance of another
class is added to an instance of the parent class. You can find more detail about element merge directives
later in this topic.
A C# class is generated for each domain class that is listed in the Classes section. The C# classes are
generated in Dsl\GeneratedCode\DomainClasses.cs.
Properties
Each domain property has a name and a type. The name must be unique within the domain class and its
transitive bases.
The type must refer to one of those listed in the Types section. Generally, the moniker must include the
namespace.
<DomainProperty Name="Name" DisplayName="Name" DefaultValue="" Category="" IsElementName="true">
<Type>
<ExternalTypeMoniker Name="/System/String" />
</Type>
</DomainProperty>
DefaultValue . If you have specified this attribute, the value that you specified is assigned to this attribute
for new instances of this class. If IsElementName is set, the DefaultValue attribute specifies the initial part
of the new string.
Categor y is the header under which the property will appear in the Proper ties window.
Relationships
The Relationships section lists all the relationships in the domain-specific language. Every Domain Relationship
is binary and directed, linking members of a source class to members of a target class. The source and target
classes are typically domain classes, but relationships to other relationships are also permitted.
For example, the Connection relationship links members of the OutPort class to members of the InPort class.
Each link instance of the relationship connects an instance of an OutPort to an instance of an InPort. Because the
relationship is many-many, each OutPort can have many Connection links with sources on it, and each InPort
instance can have many Connection links that target it.
Source and target roles
Each relationship contains source and target roles that have the following attributes:
The RolePlayer attribute references the domain class of the linked instances: OutPort for the source,
InPort for the target.
The Multiplicity attribute has four possible values (ZeroMany, ZeroOne, One, and OneMany). This
attribute refers to the number of links of this relationship that can be associated with one role player.
The PropertyName attribute specifies the name that is used in the role playing class to access the objects
at the other end. This name is used in template or custom code to traverse the relationship. For example,
the PropertyName attribute of the source role is set to Targets . Therefore, the following code will work:
The role's Name is the name that is used within the Relationship class to refer to that end of a link. By
convention, a role name is always singular, because each link has only one instance at each end. The
following code would work:
By default, the IsPropertyGenerator attribute is set to true. If it is set to false, no property is created on
the Role Player class. (In that case, op.Targets , for example, would not work). However, it is still possible
to use custom code to traverse the relationship or obtain access to the links themselves if the custom
code uses the relationship explicitly:
Relationship attributes
In addition to the attributes and child nodes that are available to all classes, each relationship has these
attributes:
IsEmbedding . This Boolean attribute specifies whether the relationship is part of the embedding tree.
Every model must form a tree with its embedding relationships. Every domain class must therefore be
the target of at least one embedding relationship, unless it is the root of a model.
AllowsDuplicates . This Boolean attribute, which is false by default, applies only to relationships that
have a "many" multiplicity at both source and target. It determines whether language users may connect
a single pair of source and target elements by more than one link of the same relationship.
ComponentHasPorts.Component
In this example, InPort is a subclass of ComponentPort and has a relationship ComponentHasPorts. The
property is called Component.
When writing C# against this model, you can jump across a link in one step by using the property that the
relationship generates on each of the classes that it relates:
However, you must do both hops explicitly in Path Syntax. Because of this requirement, you can access the
intermediate link more easily. The following code completes the hop from the link to the Component:
ComponentHasPorts.Component / ! Component
(You can omit the relationship name where it is the same as in the previous segment.)
The class moniker that is under the Index node references the class of element that can be accepted. In this case,
ComponentPort is the abstract base class of InPort and OutPort. Therefore, either of those elements can be
accepted.
ComponentModel, the root class of the language, has element merge directives for components and comments.
The language user can drag items for those classes directly onto the diagram because the blank parts of the
diagram represent the root class. However, ComponentModel has no element merge directive for
ComponentPort. Therefore, the language user cannot drag InPorts or OutPorts directly onto the diagram.
The element merge directive determines what link or links are created so that the new element can integrate or
merge into the existing model. For a ComponentPort, an instance of ComponentHasPorts is created. The
DomainPath identifies both the relationship and the property of the parent class, Ports, to which the new
element will be added.
You can create more than one link on an element merge directive by including more than one link creation path.
One of the paths must be embedded.
You can use more than one segment in a link creation path. In this case, the last segment defines what link must
be created. The earlier segments navigate from the parent class to the object from which the new link should be
created.
For example, you can add this element merge directive to the Component class:
Language users can then drag a comment onto a component and have the new comment automatically created
with a link to the component.
The first link creation path navigates from the Component to the ComponentModel and then creates an instance of
the embedding relationship ComponentModelHasComments . The second link creation path creates a link of the
reference relationship CommentsReferenceComponents from the host Component to the new Comment. All
link creation paths must start with the host class and must end at a link that steps towards the newly
instantiated class.
XmlClassData
Each domain class (including relationships and other subtypes) can have extra information provided in an
XmlClassData node, which appears under the XmlSerializationBehavior section of the DslDefinition.dsl file. This
information specifically concerns how instances of the class are stored in serialized form when a model is saved
to a file.
Much of the generated code that XmlSerializationBehavior influences is in Dsl\GeneratedCode\Serializer.cs .
Each XmlClassData node includes these child nodes and attributes:
A moniker node, which references the class to which the data applies.
XmlProper tyData for each property that is defined on the class.
XmlRelationshipData for each relationship that is sourced at the class. (Relationships also have their
own XmlClassData nodes.)
TypeName string attribute, which determines the name of the serialization helper class in the generated
code.
ElementName string, which determines the XML tag of serialized instances of this class. By convention,
ElementName is usually the same as the class name except the first letter is lowercase. For example, a
sample model file starts with the following:
<componentModel ...
MonikerElementName in the user's serialized model files. This attribute introduces a moniker that
references this class.
MonikerAttributeName , which identifies the name of the XML attribute within a moniker. In this
fragment of a user's serialized file, the author of the domain-specific language defined
MonikerElementName as "inPortMoniker" and MonikerAttributeName as "path":
ConnectionBuilders
A connection builder is defined for each connection tool. Each connection builder consists of one or more
LinkConnectDirective elements, each of which contains one or more SourceDirective elements and one or more
TargetDirective elements. After clicking a connection tool, the user can start a connection from any shape
mapped to a model element that appears in the list of SourceDirective elements. The connection can then be
completed on a shape that is mapped to an element that appears in the list of TargetDirective elements. The
class of relationship instantiated depends on the LinkConnectDirective element designated by where the
connection was started.
XmlPropertyData
A DomainProper tyMoniker attribute identifies the property to which the data refers. This attribute must be a
property of the enclosing ClassData's class.
The XmlName attribute gives the corresponding attribute name as it should appear in the XML. By convention,
this string is the same as the property name except the first letter is lowercase.
By default, the Representation attribute is set to Attribute. If Representation is set to Element, a child node is
created in the XML. If Representation is set to Ignore, the property is not serialized.
The IsMonikerKey and IsMonikerQualifier attributes give a property a role in identifying instances of the
parent class. You can set IsMonikerKey to true for one property that is defined in or inherited by a class. This
attribute identifies an individual instance of the parent class. The property that you set to IsMonikerKey is
usually a name or other key identifier. For example, the Name string property is the moniker key for
NamedElement and its derived classes. When the user saves a model to file, this attribute must contain unique
values for each instance, among its siblings in the tree of embedding relationships.
In the serialized model file, the full moniker of an element is a path from the model root down the tree of
embedding relationships, quoting the moniker key at each point. For example, InPorts are embedded within
Components, which are in turn embedded in the model root. A valid moniker is therefore:
You can set the IsMonikerQualifier attribute for a string property and provide an additional way to construct
the full name of an element. For example, in the DslDefinition.dsl file, Namespace is a moniker qualifier.
XmlRelationshipData
Within a serialized model file, links (of both embedding and reference relationships) are represented by child
nodes of the source end of the relationship. For embedding relationships, the child node contains a subtree. For
reference relationships, the child node contains a moniker that references another part of the tree.
The XmlRelationshipData attribute in an XmlClassData attribute defines exactly how the child nodes are
nested within the source element. Every relationship that is a source on the Domain Class has one
XmlRelationshipData attribute.
The DomainRelationshipMoniker attribute identifies one of the relationships sourced on the class.
The RoleElementName attribute gives the XML tag name that encloses the child node in the serialized data.
For example, the DslDefinition.dsl file contains:
If the UseFullForm attribute is set to true, an extra layer of nesting is introduced. This layer represents the
relationship itself. The attribute must be set to true if the relationship has properties.
<XmlClassData ElementName="outPort">
<DomainClassMoniker Name="OutPort" />
<ElementData>
<XmlRelationshipData UseFullForm="true" RoleElementName="targets">
<DomainRelationshipMoniker Name="Connection" />
</XmlRelationshipData>
</ElementData>
</XmlClassData>
(The Connection Relationship has its own XML class data, which provides its element and attribute names.)
If the OmitElement attribute is set to true, the relationship role name is omitted, which abbreviates the
serialized file and is unambiguous if the two classes have no more than one relationship. For example:
<component name="Component3">
<!-- only one relationship could get here: -->
<outPort name="OutPort1">
<targets> ...
The XmlSerializationBehavior attribute is embedded under the Dsl attribute, but the OmitElement
attribute has been set on the embedding relationship. Therefore, no RoleElementName attribute intervenes. By
contrast, a ClassData attribute is the RoleElementName attribute of the embedding relationship between an
XmlSerializationBehavior attribute and an XmlClassData attribute.
Shape Maps
Shape maps determine how instances of a given domain class appear on the screen, represented by a shape.
Both shape and connector maps appear under the Diagram section of the DslDefinition.dsl file.
As in the following example, the ShapeMap elements have, at a minimum, the moniker of a domain class, the
moniker of a shape, and a ParentElementPath element:
<ShapeMap>
<DomainClassMoniker Name="InPort" />
<ParentElementPath>
<DomainPath>ComponentHasPorts.Component/!Component</DomainPath>
</ParentElementPath>
<PortMoniker Name="InPortShape" />
</ShapeMap>
The primary function of the ParentElementPath element is so that the same class of objects can appear as a
different shape in different contexts. For example, if an InPort could also be embedded in a comment, the
InPort could appear as a different shape for that purpose.
Secondly, the path determines how the shape relates to its parent. No embedding structure is defined between
the shapes in a DslDefinition.dsl file. You must infer the structure from the shape maps. The parent of a shape is
the shape that is mapped to the domain element that the parent element path identifies. In this case, the path
identifies the component to which the InPort belongs. In another shape map, the Component class is mapped
to ComponentShape. Therefore, the new InPort shape is made a child shape of its component's
ComponentShape .
If you attached the InPort shape to the diagram instead, the parent element path would have to take another
step, to the component model, which is mapped to the diagram:
ComponentHasPorts . Component / ! Component / ComponentModelHasComponents . ComponentModel / !
ComponentModel
The root of the model does not have a shape map. Instead, the root is referenced directly from the diagram,
which has a Class element:
Decorator Maps
A decorator map associates a property in the mapped class to a decorator on the shape. If the property is an
enumerated or Boolean type, its value can determine whether the decorator is visible. If the decorator is a text
decorator, the value of the property can appear, and the user can edit it.
Compartment Shape Maps
Compartment shape maps are subtypes of shape maps.
Connector Maps
The minimal connector map references a connector and a relationship:
<ConnectorMap>
<ConnectorMoniker Name="CommentLink" />
<DomainRelationshipMoniker Name="CommentsReferenceComponents" />
</ConnectorMap>
See also
Domain-Specific Language Tools Glossary
How to Define a Domain-Specific Language
Understanding Models, Classes and Relationships
Understanding the DSL Code
4/13/2022 • 13 minutes to read • Edit Online
A Domain-Specific Language (DSL) solution generates an API that you can use to read and update instances of
the DSL in Visual Studio. This API is defined in the code that is generated from the DSL definition. This topic
describes the generated API.
NOTE
The Component Diagrams DSL template is called Domain-Specific Language Designer .
Press F5 and experiment if you are not familiar with this solution template. Notice in particular that you create
ports by dragging a port tool onto a component, and that you can connect ports.
The schema for files that contains instances of your DSL. This file is copied to the compilation (bin ) directory.
When you install your DSL, you can copy this file to \Program Files\Microsoft Visual Studio
11.0\Xml\Schemas so that model files can be validated. For more information, see Deploying Domain-Specific
Language Solutions.
If you customize serialization by setting options in DSL Explorer, the schema will change accordingly. However, if
you write your own serialization code, this file might no longer represent the actual schema. For more
information, see Customizing File Storage and XML Serialization.
ConnectionBuilders.cs
A connection builder is a class that creates relationships. It is the code behind a connection tool. This file contains
a pair of classes for each connection tool. Their names are derived from the names of the domain relationship
and connection tool: RelationshipBuilder, and ConnectorToolConnectAction.
(In the component solution example, one of the connection builders is called ConnectionBuilder, This is a
coincidence, because the domain relationship is named Connection.)
The relationship is created in the Relationship Builder.Connect() method. The default version verifies that the
source and target model elements are acceptable, and then instantiates the relationship. For example:
CommentReferencesSubject(sourceAccepted, targetAccepted);
Each builder class is generated from a node in the Connection Builders section in DSL Explorer. One Connect
method can create relationships between one or more pairs of domain classes. Each pair is defined by a Link
Connect Directive, which you can find in DSL Explorer under the builder node.
For example, you could add to one connection builder Link Connect Directives for each of the three types of
relationship in the sample DSL. This would provide the user with a single connection tool. The type of
relationship instantiated would depend on the types of the source and target elements selected by the user. To
add Link Connect Directives, right-click a builder in DSL Explorer.
To write custom code that runs when a specific type of domain relationship is created, select the appropriate
Link Connect Directive under the builder node. In the Properties window, set Uses Custom Connect . Rebuild
the solution, and then supply code to correct the resulting errors.
To write custom code that runs whenever the user uses this connection tool, set the Is Custom property of the
connection builder. You can supply code that decides whether a source element is permitted, whether a specific
combination of source and target is permitted, and what updates should be made to the model when a
connection is made. For example, you could allow a connection only if it would not create a loop in the diagram.
Instead of a single relationship link, you could instantiate a more complex pattern of several inter-related
elements between the source and target.
Connectors.cs
Contains the classes for the connectors, which are the diagram elements that typically represent reference
relationships. Each class is generated from one connector in the DSL Definition. Every connector class is derived
from BinaryLinkShape
To make the color and some other style features variable at run time, right-click the class on the DSL Definition
diagram and point to Add Exposed .
To make additional style features variable at run time, see for example TextField and ShapeElement.
Diagram.cs
Contains the class that defines the diagram. It is derived from Diagram.
To make the color and some other style features variable at run time, right-click the class on the DSL Definition
diagram and point to Add Exposed .
In addition, this file contains the FixupDiagram rule, which responds when a new element is added to the model.
The rule adds a new shape and links the shape to the model element.
DirectiveProcessor.cs
This directive processor helps your users to write text templates that read an instance of your DSL. The directive
processor loads the assemblies (DLLs) for your DSL, and effectively inserts using statements for your
namespace. This allows the code in the text templates to use the classes and relationships that you have defined
in your DSL.
For more information, see Generating Code from a Domain-Specific Language and Creating Custom T4 Text
Template Directive Processors.
DomainClasses.cs
Implementations of domain classes that you have defined, including abstract classes and the model root class.
They are derived from ModelElement.
Each domain class contains:
A property definition and a nested handler class for each domain property. You can override
OnValueChanging() and OnValueChanged(). For more information, see Domain Property Value Change
Handlers.
In the example DSL, the Comment class contains a property Text and a handler class
TextPropertyHandler .
Accessor properties for the relationships in which this domain class participates. (There is no nested class
for role properties.)
In the example DSL, the Comment class has accessors that access its parent model through the embedding
relationship ComponentModelHasComments .
Constructors. If you want to override these, set Has Custom Constructor on the domain class.
Element Group Prototype (EGP) handler methods. These are necessary if the user can merge (add)
another element onto instances of this class. Typically the user does this by dragging from an element
tool or another shape, or by pasting.
In the example DSL, an Input Port or Output Port can be merged onto a Component. Also, Components
and Comments can be merged onto the model. The
The EGP handler methods in the Component class allow a Component to accept Ports, but not
Comments. The EGP handler in the root model class accepts Comments and Components, but not Ports.
DomainModel.cs
The class that represents the domain model. It is derived from DomainModel.
NOTE
This is not the same as the root class of the model.
Copy and Delete Closures define what other elements should be included when an element is copied or deleted.
You can control this behavior by setting the Propagates Copy and Propagates Delete properties of the roles
at each side of every relationship. If you want the values to be determined dynamically, you can write code to
override the methods of the Closure classes.
DomainModelResx.resx
This contains strings such as the descriptions of domain classes and properties, property names, toolbox labels,
standard error messages, and other strings that could be displayed to the user. It also contains tool icons and
images for image shapes.
This file is bound into the built assembly, and provides the default values of these resources. You can localize
your DSL by creating a satellite assembly that contains a localized version of the resources. That version will be
used when the DSL is installed in a culture matching the localized resources. For more information, see
Deploying Domain-Specific Language Solutions.
DomainRelationships.cs
Each link between two elements in a model is represented by an instance of a domain relationship class. All
relationship classes are derived from ElementLink, which in turn is derived from ModelElement. Because it is a
ModelElement, an instance of a relationship can have properties and can be the source or target of a
relationship.
HelpKeywordHelper.cs
Provides functions that are used when the user presses F1.
MultiplicityValidation.cs
In relationship roles where you specify a multiplicity of 1..1 or 1..*, the user should be warned that at least one
instance of the relationship is required. This file provides validation constraints that implement those warnings.
The 1..1 link to a embedding parent is not verified.
For these constraints to be executed, you must have set one of the Uses... options in the Editor\Validation
node in DSL Explorer. For more information, see Validation in a Domain-Specific Language.
PropertiesGrid.cs
This file contains code only if you have attached a Custom Type Descriptor to a domain property. For more
information, see Customizing the Properties Window.
SerializationHelper.cs
A validation method to ensure that no two elements are referenced by the same moniker. For more
information, see Customizing File Storage and XML Serialization.
SerializationHelper class, which provides functions that are used in common by the serialization classes.
Serializer.cs
A serializer class for each domain class, relationship, shape, connector, diagram, and model.
Many of the features of these classes can be controlled by the settings in DSL Explorer under Xml
Serialization Behavior .
Shapes.cs
A class for every shape class in the DSL Definition. Shapes are derived from NodeShape. For more
information, see Customizing File Storage and XML Serialization.
To override the generated methods with your own methods in a partial class, set Generates Double
Derived for the connector in the DSL Definition. To replace a constructor with your own code, set Has
Custom Constructor .
To make the color and some other style features variable at run time, right-click the class on the DSL
Definition diagram and point to Add Exposed .
To make additional style features variable at run time, see for example TextField and ShapeElement
ToolboxHelper.cs
Sets up the toolbox by installing element group prototypes into the element tools. Copies of these
prototypes are merged with the target elements when the user runs the tool.
You could override CreateElementPrototype() to define a toolbox item that creates a group of several
objects. For example, you could define an item to represent objects that have sub-components. After
changing the code, reset the experimental instance of Visual Studio to clear the toolbox cache.
The right-click menu commands that are visible on the diagram. You can adapt or add to this set. This file
contains the code for the commands. The location of the commands on menus is determined by the
Commands.vsct file. For more information, see Writing User Commands and Actions.
Constants.cs
GUIDs.
DocData.cs
YourDsl DocData manages loading and saving a model to file, and creates the Store instance.
For example, if you want to save your DSL in a database instead of a file, you could override Load and Save
methods.
DocView.cs
YourDsl DocView manages the window in which the diagram appears. For example, you could embed the
diagram inside a windows Form:
Add a User Control file to the DslPackage project. Add a Panel in which the diagram can be displayed. Add
buttons and other controls. In the code view of the form, add the following code, adjusting the names to your
DSL:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Shell;
namespace Company.EmbedInForm
{
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
/// <summary>
/// Return a User Control instead of the DSL window.
/// The user control will contain the DSL window.
/// </summary>
EditorFactory.cs
Instantiates DocData and DocView . It fulfills a standard interface that Visual Studio uses to open an editor when
your DSL package starts. It is referenced in the ProvideEditorFactory attribute in Package.cs
GeneratedVSCT.vsct
Locates the standard menu commands on menus, such as the diagram right-click (context) menu, the Edit
menu, and so on. The code for the commands is in CommandSet.cs. You can relocate or modify the standard
commands, and you can add your own commands. For more information, see Writing User Commands and
Actions.
ModelExplorer.cs
Defines the Model Explorer for your DSL. This is the tree view of the model that the user sees alongside the
diagram.
For example, you could override InsertTreeView() to change the order in which elements appear in the Model
Explorer.
If you want the selection in the model explorer to keep synchronized with the diagram selection, you could use
the following code:
if (package != null)
{
// find the model explorer window
T explorerWindow = package.GetToolWindow(typeof(T), true) as T;
if (explorerWindow != null)
{
// get the tree container
DslShell.ModelExplorerTreeContainer treeContainer =
explorerWindow.TreeContainer;
// find the tree node
DslShell.ExplorerTreeNode treeNode =
treeContainer.FindNodeForElement(modelElement);
// select the node
explorerWindow.TreeContainer.ObjectModelBrowser.SelectedNode = treeNode;
}
}
}
ModelExplorerToolWindow.cs
Defines the window in which the model explorer is displayed. Handles the selection of items in the explorer.
Package.cs
This file defines how the DSL integrates into Visual Studio. Attributes on the package class register the DSL as
the handler for files that have your file extension, define its toolbox, and define how to open a new window. The
Initialize() method is called one time when the first DSL is loaded into a Visual Studio instance.
Source.extension.vsixmanifest
WARNING
If you edit the .tt file to include resources such as icons or images, make sure that the resource is included in the VSIX
build. In Solution Explorer, select the file and make sure that the Include in VSIX property is True .
This file controls how the DSL is packaged into a Visual Studio Integration Extension (VSIX). For more
information, see Deploying Domain-Specific Language Solutions.
See also
How to Define a Domain-Specific Language
Understanding Models, Classes and Relationships
Customizing and Extending a Domain-Specific Language
Writing Code to Customize a Domain-Specific Language
Validation in a Domain-Specific Language
4/13/2022 • 10 minutes to read • Edit Online
As the author of a domain-specific language (DSL), you can define validation constraints to verify that the model
created by the user is meaningful. For example, if your DSL allows users to draw a family tree of people and
their ancestors, you could write a constraint that ensures that children have birth dates after their parents.
You can have the validation constraints execute when the model is saved, when it is opened, and when the user
explicitly runs the Validate menu command. You can also execute validation under program control. For
example, you could execute validation in response to a change in a property value or relationship.
Validation is particularly important if you are writing text templates or other tools that process your users'
models. Validation ensures that the models fulfill the preconditions assumed by those tools.
WARNING
You can also allow validation constraints to be defined in separate extensions to your DSL, along with extension menu
commands and gesture handlers. Users can choose to install these extensions in addition to your DSL. For more
information, see Extend your DSL by using MEF.
Running Validation
When a user is editing a model, that is, an instance of your domain-specific language, the following actions can
run validation:
Right-click the diagram and select Validate All .
Right-click the top node in the Explorer of your DSL and select Validate All
Save the model.
Open the model.
In addition, you can write program code that runs validation, for example, as part of a menu command or
in response to a change.
Any validation errors will appear in the Error List window. The user can double-click an error message to
select the model elements that are the cause of the error.
NOTE
Validation methods report errors, but do not change the model. If you want to adjust or prevent certain changes, see
Alternatives to Validation.
To define a validation constraint
1. Enable validation in the Editor\Validation node:
a. Open Dsl\DslDefinition.dsl .
b. In DSL Explorer, expand the Editor node and select Validation .
c. In the Properties window, set the Uses properties to true . It is most convenient to set all these
properties.
d. Click Transform All Templates in the Solution Explorer toolbar.
2. Write partial class definitions for one or more of your domain classes or domain relationships. Write
these definitions in a new code file in the Dsl project.
3. Prefix each class with this attribute:
[ValidationState(ValidationState.Enabled)]
By default, this attribute will also enable validation for derived classes. If you want to disable validation
for a specific derived class, you can use ValidationState.Disabled .
4. Add validation methods to the classes. Each validation method can have any name, but have one
parameter of type ValidationContext.
It must be prefixed with one or more ValidationMethod attributes:
In the LogError call, you can provide a list of model elements or relationship links that will be selected
when the user double-clicks the error message.
For information about how to read the model in program code, see Navigating and Updating a Model in
Program Code.
The example applies to the following domain model. The ParentsHaveChildren relationship has roles that
are named Child and Parent.
Validation Categories
In the ValidationMethodAttribute attribute, you specify when the validation method should be executed.
C AT EGO RY EXEC UT IO N
ValidationCategories When the file is saved. If there are validation errors, the user
will be given the option of canceling the save operation.
ValidationCategories When the file is saved. If there are errors from methods in
this category, the user is warned that it might not be
possible to re-open the file.
Aggregating validation constraints. To apply validation in a predictable order, define a single validation
method on an owner class, such the root element of your model. This technique also lets you aggregate multiple
error reports into a single message.
Drawbacks are that the combined method is less easy to manage, and that the constraints must all have the
same ValidationCategories . We therefore recommend that you keep each constraint in a separate method if
possible.
Passing values in the context cache. The context parameter has a dictionary into which you can place
arbitrary values. The dictionary persists for the life of the validation run. A particular validation method could,
for example, keep an error count in the context, and use it to avoid flooding the error window with repeated
messages. For example:
List<ParentsHaveChildren> erroneousLinks;
if (!context.TryGetCacheValue("erroneousLinks", out erroneousLinks))
erroneousLinks = new List<ParentsHaveChildren>();
erroneousLinks.Add(this);
context.SetCacheValue("erroneousLinks", erroneousLinks);
if (erroneousLinks.Count < 5) { context.LogError( ... ); }
Validation of Multiplicities
Validation methods for checking minimum multiplicity are automatically generated for your DSL. The code is
written to Dsl\Generated Code\MultiplicityValidation.cs . These methods take effect when you enable
validation in the Editor\Validation node in DSL Explorer.
If you set the multiplicity of a role of a domain relationship to be 1..* or 1..1, but the user does not create a link of
this relationship, a validation error message will appear.
For example, if your DSL has classes Person and Town, and a relationship PersonLivesInTown with a relationship
1..\ * at the Town role, then for each Person that has no Town, an error message will appear.
For more information, see How to: Add a Command to the Shortcut Menu.
You can also create a separate validation controller, and manage the errors yourself. For example:
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Modeling.Shell;
...
Store store = ...;
VsValidationController validator = new VsValidationController(s);
// Validate all elements in the Store:
if (!validator.Validate(store, ValidationCategories.Save))
{
// Deal with errors:
foreach (ValidationMessage message in validator.ValidationMessages) { ... }
}
The handlers are also called after Undo or Redo operations that affect the links or elements.
[ValidationMethod(CustomCategory = "PreconditionsForGeneratePartsList")]
[ValidationMethod(ValidationCategory.Menu)]
private void TestForCircularLinks(ValidationContext context)
{...}
NOTE
You can prefix a method with as many [ValidationMethod()] attributes as you want. You can add a method to both
custom and standard categories.
Alternatives to Validation
Validation constraints report errors, but do not change the model. If, instead, you want to prevent the model
becoming invalid, you can use other techniques.
However, these techniques are not recommended. It is usually better to let the user decide how to correct an
invalid model.
Adjust the change to restore the model to validity. For example, if the user sets a property above the
allowed maximum, you could reset the property to the maximum value. To do this, define a rule. For more
information, see Rules Propagate Changes Within the Model.
Roll back the transaction if an invalid change is attempted. You could also define a rule for this purpose,
but in some cases it is possible to override a property handler OnValueChanging() , or to override a method
such as OnDeleted(). To roll back a transaction, use
this.Store.TransactionManager.CurrentTransaction.Rollback(). For more information, see Domain Property
Value Change Handlers.
WARNING
Make sure that the user knows that the change has been adjusted or rolled back. For example, use
System.Windows.Forms.MessageBox.Show("message").
See also
Navigating and Updating a Model in Program Code
Event Handlers Propagate Changes Outside the Model
How to: Modify a Standard Menu Command in a
Domain-Specific Language
4/13/2022 • 4 minutes to read • Edit Online
You can modify the behavior of some of the standard commands that are defined automatically in your DSL. For
example, you could modify Cut so that it excludes sensitive information. To do this, you override methods in a
command set class. These classes are defined in the CommandSet.cs file, in the DslPackage project, and are
derived from CommandSet.
NOTE
If you want to create your own menu commands, see How to: Add a Command to the Shortcut Menu.
3. In each command set class, type " override " followed by a space. IntelliSense will show a list of the
methods that you can override. Each command has a pair of methods whose names begin "
ProcessOnStatus " and " ProcessOnMenu ".
4. Note which of the command set classes contains the command you want to modify.
5. Close the file without saving your edits.
NOTE
Ordinarily, you should not edit files that have been generated. Any edits will be lost the next time that the files are
generated.
2. In DslPackage , create a folder named Custom Code . In this folder, create a new class file named
CommandSet.cs .
3. In the new file, write a partial declaration that has the same namespace and name as the generated
partial class. For example:
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
namespace Company.Language1 /* Make sure this is correct */
{ internal partial class Language1CommandSet { ...
NOTE
If you used the class file template to create the new file, you must correct both the namespace and the class
name.
NOTE
This method does not affect whether the command is available through a keystroke. For example, disabling the Delete
menu item does not prevent the command from being invoked through the Delete key.
/// <summary>
/// Called when user right-clicks on the diagram or clicks the Edit menu.
/// </summary>
/// <param name="command">Set Visible and Enabled properties.</param>
protected override void ProcessOnStatusDeleteCommand (MenuCommand command)
{
// Default settings from the base method.
base.ProcessOnStatusDeleteCommand(command);
if (this.CurrentSelection.Count > 1)
{
// If user has selected more than one item, Delete is greyed out.
command.Enabled = false;
}
}
It is good practice to call the base method first, to deal with all the cases and settings with which you are not
concerned.
The ProcessOnStatus method should not create, delete, or update elements in the Store.
To change the behavior of the command
Override the ProcessOnMenu... method. The following example prevents the user from deleting more than one
element at a time, even by using the Delete key.
/// <summary>
/// Called when user presses Delete key
/// or clicks the Delete command on a menu.
/// </summary>
protected override void ProcessOnMenuDeleteCommand()
{
// Allow users to delete only one thing at a time.
if (this.CurrentSelection.Count <= 1)
{
base.ProcessOnMenuDeleteCommand();
}
}
If your code makes changes to the Store, such as creating, deleting or updating elements or links, you must do
so inside a transaction. For more information, see How to Create and Update model elements.
Write the code of the methods
The following fragments are frequently useful within these methods:
this.CurrentSelection . The shape that the user right-clicked is always included in this list of shapes and
connectors. If the user clicks on a blank part of the diagram, the Diagram is the only member of the list.
this.IsDiagramSelected() - true if the user clicked a blank part of the diagram.
this.IsCurrentDiagramEmpty()
See also
MenuCommand
Writing Code to Customise a Domain-Specific Language
How to: Add a Command to the Shortcut Menu
How VSPackages Add User Interface Elements
Visual Studio Command Table (.Vsct) Files
VSCT XML Schema Reference
VMSDK - Circuit Diagrams sample. Extensive DSL Customization
How to: Add a command to the shortcut menu
4/13/2022 • 11 minutes to read • Edit Online
You can add menu commands to your domain-specific language (DSL) so that your users can perform tasks that
are specific to your DSL. The commands appear on the context (shortcut) menu when users right-click on the
diagram. You can define a command so that it only appears in the menu in specific circumstances. For example,
you can make the command visible only when the user clicks specific types of element, or elements in specific
states.
In summary, the steps are performed in the DslPackage project as follows:
1. Declare the command in Commands.vsct
2. Update the package version number in Package.tt. You have to do this whenever you change
Commands.vsct
3. Write methods in the CommandSet class to make the command visible and to define what you want the
command to do.
NOTE
You can also modify the behavior of some existing commands such as Cut, Paste, Select All, and Print by overriding
methods in CommandSet.cs. For more information, see How to: Modify a Standard Menu Command.
NOTE
Each button or group is identified by a GUID and an integer ID. You can create several groups and buttons with
the same GUID. However, they must have different IDs. The GUID names and ID names are translated to actual
GUIDs and numeric IDs in the <Symbols> node.
3. Add a visibility constraint for the command so that it is loaded only in the context of your domain-specific
language. For more information, see VisibilityConstraints Element.
To do this, add the following elements in the CommandTable element after the Commands element.
<VisibilityConstraints>
<!-- Ensures the command is only loaded for this DSL -->
<VisibilityItem guid="guidCustomMenuCmdSet" id="cmdidMyContextMenuCommand"
context="guidEditor"/>
</VisibilityConstraints>
4. Define the names that you used for the GUIDs and IDs. To do this, add a Symbols element in the
CommandTable element after the Commands element.
<Symbols>
<!-- Substitute a unique GUID for the placeholder: -->
<GuidSymbol name="guidCustomMenuCmdSet"
value="{00000000-0000-0000-0000-000000000000}" >
<IDSymbol name="grpidMyMenuGroup" value="0x01001"/>
<IDSymbol name="cmdidMyContextMenuCommand" value="0x00001"/>
</GuidSymbol>
</Symbols>
5. Replace {000...000} with a GUID that identifies your groups and menu items. To obtain a new GUID, use
the Create GUID tool on the Tools menu.
NOTE
If you add more groups or menu items, you can use the same GUID. However, you must use new values for the
IDSymbols .
6. In the code you have copied from this procedure, replace each occurrence of the following strings with
your own strings:
grpidMyMenuGroup
cmdidMyContextMenuCommand
guidCustomMenuCmdSet
2. In DslPackage , create a folder that is named Custom Code . In this folder, create a new class file that is
named CommandSet.cs .
3. In the new file, write a partial declaration that has the same namespace and name as the generated
partial class. For example:
namespace Company.Language1 /* Make sure this is correct */
NOTE
If you used the class template to create the new file, you must correct both the namespace and the class name.
Your command set code will typically need to import the following namespaces:
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Shell;
Adjust the namespace and the class name to match those in the generated CommandSet.cs:
You must define two methods, one to determine when the command will be visible on the right-click (context)
menu, and the other to perform the command. These methods are not overrides; instead, you register them in a
list of commands.
Define when the command will be visible
For each command, define an OnStatus... method that determines whether the command will appear on the
menu, and whether it will be enabled or greyed out. Set the Visible and Enabled properties of the
MenuCommand , as shown in the following example. This method is called in order to construct the shortcut menu
every time that the user right-clicks the diagram, so it must work quickly.
In this example, the command is visible only when the user has selected a particular type of shape, and is
enabled only when at least one of the selected elements is in a particular state. The example is based on the
Class Diagram DSL template, and ClassShape and ModelClass are types that are defined in the DSL:
private void OnStatusMyContextMenuCommand(object sender, EventArgs e)
{
MenuCommand command = sender as MenuCommand;
command.Visible = command.Enabled = false;
foreach (object selectedObject in this.CurrentSelection)
{
ClassShape shape = selectedObject as ClassShape;
if (shape != null)
{
// Visibility depends on what is selected.
command.Visible = true;
ModelClass element = shape.ModelElement as ModelClass;
// Enabled depends on state of selection.
if (element != null && element.Comments.Count == 0)
{
command.Enabled = true;
return; // seen enough
} } } }
As a general guideline, make the Visible property depend on what is selected, and make the Enabled
property depend on the state of the selected elements.
An OnStatus method should not change the state of the Store.
Define what the command does
For each command, define an OnMenu... method that performs the required action when the user clicks the
menu command.
If you make changes to model elements, you must do so inside a transaction. For more information, see How to:
Modify a Standard Menu Command.
In this example, ClassShape , ModelClass , and Comment are types that are defined in the DSL, which is derived
from the Class Diagram DSL template.
private void OnMenuMyContextMenuCommand(object sender, EventArgs e)
{
MenuCommand command = sender as MenuCommand;
Store store = this.CurrentDocData.Store;
// Changes to elements and shapes must be performed in a Transaction.
using (Transaction transaction =
store.TransactionManager.BeginTransaction("My command"))
{
foreach (object selectedObject in this.CurrentSelection)
{
// ClassShape is defined in my DSL.
ClassShape shape = selectedObject as ClassShape;
if (shape != null)
{
// ModelClass is defined in my DSL.
ModelClass element = shape.ModelElement as ModelClass;
if (element != null)
{
// Do required action here - for example:
For more information about how to navigate from object to object in the model, and about how to create
objects and links, see How to: Modify a Standard Menu Command.
Register the command
Repeat in C# the declarations of the GUID and ID values that you made in the Symbols section of
CommandSet.vsct:
NOTE
If you change the Symbols section of the VSCT file, you must also change these declarations to match. You should also
increment the version number in Package.tt
Register your menu commands as part of this command set. GetMenuCommands() is called once when the
diagram is initialized:
protected override IList<MenuCommand> GetMenuCommands()
{
// Get the list of generated commands.
IList<MenuCommand> commands = base.GetMenuCommands();
// Add a custom command:
DynamicStatusMenuCommand myContextMenuCommand =
new DynamicStatusMenuCommand(
new EventHandler(OnStatusMyContextMenuCommand),
new EventHandler(OnMenuMyContextMenuCommand),
new CommandID(guidCustomMenuCmdSet, cmdidMyContextMenuCommand));
commands.Add(myContextMenuCommand);
// Add more commands here.
return commands;
}
Troubleshoot
Command does not appear in menu:
The command will appear only in debugging instances of Visual Studio, until you install the DSL package.
For more information, see Deploying Domain-Specific Language Solutions.
Make sure that your experimental sample has the correct file name extension for this DSL. To check the
file name extension, open DslDefinition.dsl in the main instance of Visual Studio. Then in DSL Explorer,
right-click the Editor node, and then click Properties. In the Properties window, examine the FileExtension
property.
Did you increment the package version number?
Set a breakpoint at the beginning of your OnStatus method. It should break when you right-click over any
part of the diagram.
OnStatus method is not called :
Make sure that the GUIDs and IDs in your CommandSet code match those in the Symbols section of
Commands.vsct.
In Commands.vsct, make sure that the GUID and ID in every Parent node identify the correct parent
Group.
In a Visual Studio command prompt, type devenv /rootsuffix exp /setup. Then restart the debugging
instance of Visual Studio.
Step through the OnStatus method to verify that command.Visible and command.Enabled are set to true.
Wrong menu text appears, or command appears in the wrong place :
Make sure that the combination of GUID and ID is unique to this command.
Make sure that you have uninstalled earlier versions of the package.
See also
Writing code to customize a domain-specific language
How to: Modify a standard menu command
Deploying domain-specific language solutions
Sample code: Circuit diagrams
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
How to: Add a Drag-and-Drop Handler
4/13/2022 • 14 minutes to read • Edit Online
You can add handlers for drag-and-drop events to your DSL, so that users can drag items onto your diagram
from other diagrams or from other parts of Visual Studio. You can also add handlers for events such as double-
clicks. Together, drag-and-drop and double-click handlers are known as gesture handlers.
This topic discusses drag-and-drop gestures that originate on other diagrams. For move and copy events within
a single diagram, consider the alternative of defining a subclass of ElementOperations . For more information,
see Customizing Copy Behavior. You might also be able to customize the DSL definition.
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using System.Linq;
In the new file, define a partial class for the shape or diagram class that should respond to the drag operation.
Override the following methods:
OnDragOver- This method is called when the mouse pointer enters the shape during a drag operation.
Your method should inspect the item that the user is dragging, and set the Effect property to indicate
whether the user can drop the item on this shape. The Effect property determines the appearance of the
cursor while it is over this shape, and also determines whether OnDragDrop() will be called when the user
releases the mouse button.
OnDragDrop - This method is called if the user releases the mouse button while the mouse pointer rests
over this shape or diagram, if OnDragOver(DiagramDragEventArgs e) previously set e.Effect to a value
other than None .
public override void OnDragDrop(DiagramDragEventArgs e)
{
if (!IsAcceptableDropItem(e))
{
base.OnDragDrop(e);
}
else
{ // Process the dragged item, for example merging a copy into the diagram
ProcessDragDropItem(e); // To be defined
}
}
OnDoubleClick - This method is called when the user double-clicks the shape or diagram.
For more information, see How to: Intercept a Click on a Shape or Decorator.
Define IsAcceptableDropItem(e) to determine whether the dragged item is acceptable, and
ProcessDragDropItem(e) to update your model when the item is dropped. These methods must first extract the
item from the event arguments. For information about how to do that, see How to get a reference to the
dragged item.
IDataObject Data - This property carries serialized versions of the source objects, usually in more than
one format. Its most useful functions are:
diagramEventArgs.Data.GetDataFormats() - Lists the formats in which you can decode the dragged
object. For example, if the user drags a file from the desktop, the available formats include the file
name (" FileNameW ").
diagramEventArgs.Data.GetData(format) - Decodes the dragged object in the specified format. Cast
the object to the appropriate type. For example:
string fileName = diagramEventArgs.Data.GetData("FileNameW") as string;
You can also transmit objects such as model bus references from the source in your own custom
format. For more information, see How to Send Model Bus References in a Drag and Drop.
ElementGroupPrototype Prototype - Use this property if you want users to drag items from a DSL or a
UML model. An element group prototype contains one or more objects, links, and their property values. It
is also used in paste operations and when you are adding an element from the toolbox. In a prototype,
objects and their types are identified by Guid. For example, this code allows the user to drag class
elements from a UML diagram or UML Model Explorer:
To accept UML shapes, determine the GUIDs of the UML shape classes by experiment. Remember that
there is usually more than one type of element on any diagram. Remember also that an object dragged
from a DSL or UML diagram is the shape, not the model element.
DiagramDragEventArgs also has properties that indicate the current mouse pointer position and whether the user
is pressing the CTRL, ALT, or SHIFT keys.
How to get the original of a dragged element
If the dragged item is a DSL element, you can open the source model and access the element.
The Data and Prototype properties of the event arguments contain only a reference to the dragged shape.
Usually, if you want to create an object in the target DSL that is derived from the prototype in some way, you
need to obtain access to the original, for example, reading the file contents, or navigating to the model element
represented by a shape. You can use Visual Studio Model Bus to help with this.
To prepare a DSL project for Model Bus
Make the source DSL accessible by Visual Studio Model Bus:
1. Open the DSL definition file of the source DSL in DSL Designer. Right-click the design surface and then
click Enable Modelbus . In the dialog box, choose one or both of the options. Click OK . A new project
"ModelBus" is added to the DSL solution.
2. Click Transform All Templates and rebuild the solution.
To send an object from a source DSL
1. In your ElementOperations subclass, override Copy() so that it encodes a Model Bus Reference (MBR)
into the IDataObject. This method will be called when the user starts to drag from the source diagram.
The encoded MBR will then be available in the IDataObject when the user drops in the target diagram.
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Shell;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Integration;
using Microsoft.VisualStudio.Modeling.Integration.Shell;
using System.Drawing; // PointF
using System.Collections.Generic; // ICollection
using System.Windows.Forms; // for IDataObject
...
public class MyElementOperations : DesignSurfaceElementOperations
{
public override void Copy(System.Windows.Forms.IDataObject data,
System.Collections.Generic.ICollection<ModelElement> elements, ClosureType closureType,
System.Drawing.PointF sourcePosition)
{
base.Copy(data, elements, closureType, sourcePosition);
data.SetData("ModelBusReference", elementReference);
}
...}
To receive a Model Bus Reference from a DSL in a target DSL or UML project
1. In the target DSL project, add project references to:
The source Dsl project.
The source ModelBus project.
2. In the gesture handler code file, add the following namespace references:
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.Integration;
using SourceDslNamespace;
using SourceDslNamespace.ModelBusAdapters;
3. The following sample illustrates how to get access to the source model element:
// But actually there might be several shapes - so get them from the prototype instead:
IElementDirectory remoteDirectory = adapter.Store.ElementDirectory;
foreach (Guid shapeGuid in prototype.SourceRootElementIds)
{
PresentationElement pe = remoteDirectory.FindElement(shapeGuid) as PresentationElement;
if (pe == null) continue;
SourceElement instance = pe.ModelElement as SourceElement;
if (instance == null) continue;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Design;
using Microsoft.VisualStudio.Modeling.Diagrams;
using System.Collections.Generic;
using System.Linq;
// This example is built on the "Class Diagrams" solution template of VMSDK (DSL Tools).
// You will need to change the following domain class names to your own:
// ClassShape = a compartment shape
// ClassModelElement = the domain class displayed using a ClassShape
// This code assumes that the embedding relationships displayed in the compartments
// This code assumes that the embedding relationships displayed in the compartments
// don't use inheritance (don't have base or derived domain relationships).
/// <summary>
/// Call back to the source shape to drop the dragged item.
/// </summary>
/// <param name="e"></param>
protected override void OnMouseUp(DiagramMouseEventArgs e)
{
base.OnMouseUp(e);
sourceShape.DoMouseUp(sourceChild, e);
this.Cancel(e.DiagramClientView);
e.Handled = true;
}
/// <summary>
/// Ideally, this shouldn't happen. This action should only be active
/// while the mouse is still pressed. However, it can happen if you
/// move the mouse rapidly out of the source shape, let go, and then
/// click somewhere else in the source shape. Yuk.
/// </summary>
/// <param name="e"></param>
protected override void OnMouseDown(DiagramMouseEventArgs e)
{
base.OnMouseDown(e);
this.Cancel(e.DiagramClientView);
e.Handled = false;
}
/// <summary>
/// Display an appropriate cursor while the drag is in progress:
/// Up-down arrow if we are inside the original compartment.
/// No entry if we are elsewhere.
/// </summary>
/// <param name="currentCursor"></param>
/// <param name="diagramClientView"></param>
/// <param name="mousePosition"></param>
/// <returns></returns>
public override System.Windows.Forms.Cursor GetCursor(System.Windows.Forms.Cursor currentCursor,
DiagramClientView diagramClientView, PointD mousePosition)
{
// If the cursor is inside the original compartment, show up-down cursor.
return sourceCompartmentBounds.Contains(mousePosition)
? System.Windows.Forms.Cursors.SizeNS // Up-down arrow.
: System.Windows.Forms.Cursors.No;
}
}
/// <summary>
/// Override some methods of the compartment shape.
/// *** GenerateDoubleDerived must be set for this shape in DslDefinition.dsl. ****
/// </summary>
public partial class ClassShape
{
/// <summary>
/// Model element that is being dragged.
/// </summary>
private static ClassModelElement dragStartElement = null;
/// <summary>
/// Absolute bounds of the compartment, used to set the cursor.
/// </summary>
private static RectangleD compartmentBounds;
/// <summary>
/// Attach mouse listeners to the compartments for the shape.
/// This is called once per compartment shape.
/// The base method creates the compartments for this shape.
/// </summary>
public override void EnsureCompartments()
{
base.EnsureCompartments();
foreach (Compartment compartment in this.NestedChildShapes.OfType<Compartment>())
{
compartment.MouseDown += new DiagramMouseEventHandler(compartment_MouseDown);
compartment.MouseUp += new DiagramMouseEventHandler(compartment_MouseUp);
compartment.MouseMove += new DiagramMouseEventHandler(compartment_MouseMove);
}
}
/// <summary>
/// Remember which item the mouse was dragged from.
/// We don't create an Action immediately, as this would inhibit the
/// inline text editing feature. Instead, we just remember the details
/// and will create an Action when/if the mouse moves off this list item.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void compartment_MouseDown(object sender, DiagramMouseEventArgs e)
{
dragStartElement = e.HitDiagramItem.RepresentedElements.OfType<ClassModelElement>().FirstOrDefault();
compartmentBounds = e.HitDiagramItem.Shape.AbsoluteBoundingBox;
}
/// <summary>
/// When the mouse moves away from the initial list item, but still inside the compartment,
/// create an Action to supervise the cursor and handle subsequent mouse events.
/// Transfer the details of the initial mouse position to the Action.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void compartment_MouseMove(object sender, DiagramMouseEventArgs e)
{
if (dragStartElement != null)
{
if (dragStartElement != e.HitDiagramItem.RepresentedElements.OfType<ClassModelElement>
().FirstOrDefault())
{
e.DiagramClientView.ActiveMouseAction = new CompartmentDragMouseAction(dragStartElement, this,
compartmentBounds);
dragStartElement = null;
}
}
}
/// <summary>
/// User has released the mouse button.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void compartment_MouseUp(object sender, DiagramMouseEventArgs e)
{
dragStartElement = null;
}
/// <summary>
/// Forget the source item if mouse up occurs outside the
/// compartment.
/// </summary>
/// <param name="e"></param>
public override void OnMouseUp(DiagramMouseEventArgs e)
{
base.OnMouseUp(e);
dragStartElement = null;
}
/// <summary>
/// Called by the Action when the user releases the mouse.
/// If we are still on the same compartment but in a different list item,
/// move the starting item to the position of the current one.
/// </summary>
/// <param name="dragFrom"></param>
/// <param name="e"></param>
public void DoMouseUp(ModelElement dragFrom, DiagramMouseEventArgs e)
{
// Original or "from" item:
ClassModelElement dragFromElement = dragFrom as ClassModelElement;
// Current or "to" item:
ClassModelElement dragToElement = e.HitDiagramItem.RepresentedElements.OfType<ClassModelElement>
().FirstOrDefault();
if (dragFromElement != null && dragToElement != null)
{
// Find the common parent model element, and the relationship links:
ElementLink parentToLink = GetEmbeddingLink(dragToElement);
ElementLink parentFromLink = GetEmbeddingLink(dragFromElement);
if (parentToLink != parentFromLink && parentFromLink != null && parentToLink != null)
{
// Get the static relationship and role (= end of relationship):
DomainRelationshipInfo relationshipFrom = parentFromLink.GetDomainRelationship();
DomainRoleInfo parentFromRole = relationshipFrom.DomainRoles[0];
// Get the node in which the element is embedded, usually the element displayed in the shape:
ModelElement parentFrom = parentFromLink.LinkedElements[0];
/// <summary>
/// Get the embedding link to this element.
/// Assumes there is no inheritance between embedding relationships.
/// (If there is, you need to make sure you've got the relationship
/// that is represented in the shape compartment.)
/// </summary>
/// <param name="child"></param>
/// <returns></returns>
ElementLink GetEmbeddingLink(ClassModelElement child)
{
foreach (DomainRoleInfo role in child.GetDomainClass().AllEmbeddedByDomainRoles)
{
foreach (ElementLink link in role.OppositeDomainRole.GetElementLinks(child))
{
// Just the assume the first embedding link is the only one.
// Not a valid assumption if one relationship is derived from another.
return link;
}
}
return null;
}
}
}
See also
Customizing Copy Behavior
Deploying Domain-Specific Language Solutions
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
How to: Intercept a Click on a Shape or Decorator
4/13/2022 • 9 minutes to read • Edit Online
The following procedures demonstrate how to intercept a click on a shape or an icon decorator. You can
intercept clicks, double-clicks, drags, and other gestures, and make the element respond.
NOTE
Set e.Handled to true , unless you want the event to be passed to the containing shape or diagram.
You should set Handled to true if you do not want the event to be passed to the containing shape.
4. Override the InitializeShapeFields method in your shape class by adding the following partial class
definition.
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Design;
using Microsoft.VisualStudio.Modeling.Diagrams;
using System.Collections.Generic;
using System.Linq;
// This example is built on the "Class Diagrams" solution template of VMSDK (DSL Tools).
// You will need to change the following domain class names to your own:
// ClassShape = a compartment shape
// ClassModelElement = the domain class displayed using a ClassShape
// This code assumes that the embedding relationships
// displayed in the compartments don't use inheritance
// (don't have base or derived domain relationships).
namespace Company.CompartmentDrag
{
/// <summary>
/// Manage the mouse while dragging a compartment item.
/// </summary>
public class CompartmentDragMouseAction : MouseAction
{
private ModelElement sourceChild;
private ModelElement sourceChild;
private ClassShape sourceShape;
private RectangleD sourceCompartmentBounds;
/// <summary>
/// Call back to the source shape to drop the dragged item.
/// </summary>
/// <param name="e"></param>
protected override void OnMouseUp(DiagramMouseEventArgs e)
{
base.OnMouseUp(e);
sourceShape.DoMouseUp(sourceChild, e);
this.Cancel(e.DiagramClientView);
e.Handled = true;
}
/// <summary>
/// Ideally, this shouldn't happen. This action should only be active
/// while the mouse is still pressed. However, it can happen if you
/// move the mouse rapidly out of the source shape, let go, and then
/// click somewhere else in the source shape.
/// </summary>
/// <param name="e"></param>
protected override void OnMouseDown(DiagramMouseEventArgs e)
{
base.OnMouseDown(e);
this.Cancel(e.DiagramClientView);
e.Handled = false;
}
/// <summary>
/// Display an appropriate cursor while the drag is in progress:
/// Up-down arrow if we are inside the original compartment.
/// No entry if we are elsewhere.
/// </summary>
/// <param name="currentCursor"></param>
/// <param name="diagramClientView"></param>
/// <param name="mousePosition"></param>
/// <returns></returns>
public override System.Windows.Forms.Cursor GetCursor(System.Windows.Forms.Cursor currentCursor,
DiagramClientView diagramClientView, PointD mousePosition)
{
// If the cursor is inside the original compartment, show up-down cursor.
return sourceCompartmentBounds.Contains(mousePosition)
? System.Windows.Forms.Cursors.SizeNS // Up-down arrow.
: System.Windows.Forms.Cursors.No;
}
}
/// <summary>
/// Override some methods of the compartment shape.
/// *** GenerateDoubleDerived must be set for this shape in DslDefinition.dsl. ****
/// </summary>
public partial class ClassShape
{
/// <summary>
/// Model element that is being dragged.
/// </summary>
private static ClassModelElement dragStartElement = null;
/// <summary>
/// Absolute bounds of the compartment, used to set the cursor.
/// </summary>
private static RectangleD compartmentBounds;
/// <summary>
/// Attach mouse listeners to the compartments for the shape.
/// This is called once per compartment shape.
/// The base method creates the compartments for this shape.
/// </summary>
public override void EnsureCompartments()
{
base.EnsureCompartments();
foreach (Compartment compartment in this.NestedChildShapes.OfType<Compartment>())
{
compartment.MouseDown += new DiagramMouseEventHandler(compartment_MouseDown);
compartment.MouseUp += new DiagramMouseEventHandler(compartment_MouseUp);
compartment.MouseMove += new DiagramMouseEventHandler(compartment_MouseMove);
}
}
/// <summary>
/// Remember which item the mouse was dragged from.
/// We don't create an Action immediately, as this would inhibit the
/// inline text editing feature. Instead, we just remember the details
/// and will create an Action when/if the mouse moves off this list item.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void compartment_MouseDown(object sender, DiagramMouseEventArgs e)
{
dragStartElement = e.HitDiagramItem.RepresentedElements
.OfType<ClassModelElement>().FirstOrDefault();
compartmentBounds = e.HitDiagramItem.Shape.AbsoluteBoundingBox;
}
/// <summary>
/// When the mouse moves away from the initial list item,
/// but still inside the compartment, create an Action
/// to supervise the cursor and handle subsequent mouse events.
/// Transfer the details of the initial mouse position to the Action.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void compartment_MouseMove(object sender, DiagramMouseEventArgs e)
{
if (dragStartElement != null)
{
if (dragStartElement != e.HitDiagramItem.RepresentedElements.OfType<ClassModelElement>
().FirstOrDefault())
{
e.DiagramClientView.ActiveMouseAction = new CompartmentDragMouseAction(dragStartElement, this,
compartmentBounds);
dragStartElement = null;
}
}
}
/// <summary>
/// User has released the mouse button.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void compartment_MouseUp(object sender, DiagramMouseEventArgs e)
{
dragStartElement = null;
}
/// <summary>
/// Forget the source item if mouse up occurs outside the
/// Forget the source item if mouse up occurs outside the
/// compartment.
/// </summary>
/// <param name="e"></param>
public override void OnMouseUp(DiagramMouseEventArgs e)
{
base.OnMouseUp(e);
dragStartElement = null;
}
/// <summary>
/// Called by the Action when the user releases the mouse.
/// If we are still on the same compartment but in a different list item,
/// move the starting item to the position of the current one.
/// </summary>
/// <param name="dragFrom"></param>
/// <param name="e"></param>
public void DoMouseUp(ModelElement dragFrom, DiagramMouseEventArgs e)
{
// Original or "from" item:
ClassModelElement dragFromElement = dragFrom as ClassModelElement;
// Current or "to" item:
ClassModelElement dragToElement = e.HitDiagramItem.RepresentedElements.OfType<ClassModelElement>
().FirstOrDefault();
if (dragFromElement != null && dragToElement != null)
{
// Find the common parent model element, and the relationship links:
ElementLink parentToLink = GetEmbeddingLink(dragToElement);
ElementLink parentFromLink = GetEmbeddingLink(dragFromElement);
if (parentToLink != parentFromLink && parentFromLink != null && parentToLink != null)
{
// Get the static relationship and role (= end of relationship):
DomainRelationshipInfo relationshipFrom = parentFromLink.GetDomainRelationship();
DomainRoleInfo parentFromRole = relationshipFrom.DomainRoles[0];
// Get the node in which the element is embedded, usually the element displayed in the shape:
ModelElement parentFrom = parentFromLink.LinkedElements[0];
/// <summary>
/// Get the embedding link to this element.
/// Assumes there is no inheritance between embedding relationships.
/// Assumes there is no inheritance between embedding relationships.
/// (If there is, you need to make sure you've got the relationship
/// that is represented in the shape compartment.)
/// </summary>
/// <param name="child"></param>
/// <returns></returns>
ElementLink GetEmbeddingLink(ClassModelElement child)
{
foreach (DomainRoleInfo role in child.GetDomainClass().AllEmbeddedByDomainRoles)
{
foreach (ElementLink link in role.OppositeDomainRole.GetElementLinks(child))
{
// Just the assume the first embedding link is the only one.
// Not a valid assumption if one relationship is derived from another.
return link;
}
}
return null;
}
}
}
See also
Responding to and Propagating Changes
Properties of Decorators
How to: Access and Constrain the Current Selection
4/13/2022 • 5 minutes to read • Edit Online
When you write a command or gesture handler for your domain-specific language, you can determine what
element the user right-clicked. You can also prevent some shapes or fields from being selected. For example, you
can arrange that when the user clicks an icon decorator, the shape that contains it is selected instead.
Constraining the selection in this manner reduces the number of handlers that you have to write. It also makes it
easier for the user, who can click anywhere in the shape without having to avoid the decorator.
M EM B ER DESC RIP T IO N
SingleDocumentSelection property Gets the primary element of the selection in the model
designer.
SingleSelection property Gets the primary element of the selection in the active
window.
2. The CurrentDocView property of the CommandSet class provides access to the DiagramDocView object
that represents the model designer window and provides additional access the selected elements in the
model designer.
3. In addition, the generated code defines an explorer tool window property and an explorer selection
property in the command set class for the domain-specific language.
The explorer tool window property returns an instance of the explorer tool window class for the
domain-specific language. The explorer tool window class derives from the
ModelExplorerToolWindow class and represents the model explorer for the domain-specific
language.
The ExplorerSelection property returns the selected element in the model explorer window for
the domain-specific language.
// using Microsoft.VisualStudio.Modeling.Shell;
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
namespace CompanyName.ProductName.GroupingDsl
{
public class CustomSelectionRules : DiagramSelectionRules
{
protected Diagram diagram;
protected IElementDirectory elementDirectory;
this.diagram = diagram;
this.elementDirectory = diagram.Store.ElementDirectory;
}
return true;
}
}
See also
CommandSet
ModelingPackage
DiagramDocView
ModelExplorerToolWindow
IMonitorSelectionService
DiagramSelectionRules
Diagram
Respond to and propagate changes
4/13/2022 • 3 minutes to read • Edit Online
When an element is created, deleted or updated, you can write code that propagates the change to other parts
of the model, or to external resources such as files, databases, or other components.
Reference
As a guideline, consider these techniques in the following order:
T EC H N IQ UE SC EN A RIO S F O R M O RE IN F O RM AT IO N
Define a Calculated domain property. A domain property whose value is Calculated and Custom Storage
calculated from other properties in the Properties
model. For example, a price that is the
sum of prices of related elements.
Define a Custom Storage domain A domain property stored in other Calculated and Custom Storage
property. parts of the model or externally. For Properties
example, you could parse an
expression string into a tree in the
model.
Override change handlers such as Keep different elements in sync, and Domain Property Value Change
OnValueChanging and OnDeleting keep external values in sync with the Handlers
model.
Rules You can define rules that are queued Rules Propagate Changes Within the
for execution just before the end of a Model
transaction in which a change has
happened. They are not executed on
Undo or Redo. Use them to keep one
part of the store in synch with another.
Store Events The modeling store provides Event Handlers Propagate Changes
notifications of events such as adding Outside the Model
or deleting an element or link, or
changing the value of a property. The
event is also executed on Undo and
Redo. Use store events to update
values that are not in the store.
T EC H N IQ UE SC EN A RIO S F O R M O RE IN F O RM AT IO N
.NET Events Shapes have event handlers that How to: Intercept a Click on a Shape or
respond to mouse clicks and other Decorator
gestures. You have to register for these
events for each object. Registration is
typically done in an override of
InitializeInstanceResources, and must
be done for each element.
Bounds Rules A bounds rule is used specifically to BoundsRules Constrain Shape Location
constrain the bounds of a shape. and Size
Selection rules Selection rules specifically constrain How to: Access and Constrain the
what the user can select. Current Selection
OnAssocatedPropertyChanged Indicate the model elements' states Updating Shapes and Connectors to
using features of shapes and Reflect the Model
connectors such as shadow,
arrowheads, color, and line widths and
style.
See also
How to: Intercept a Click on a Shape or Decorator
Writing Code to Customize a Domain-Specific Language
Calculated and Custom Storage Properties
4/13/2022 • 3 minutes to read • Edit Online
All domain properties in a domain-specific language (DSL) can be displayed to the user on the diagram and in
your language explorer, and can be accessed by program code. However, properties differ in the way that their
values are stored.
Standard (Default) A domain property that is saved in the store and serialized
to file.
Custom Storage A domain property that is not saved directly in the store,
but can be both get and set.
You have to provide the methods that get and set the value.
NOTE
This file is generated from DslDefinition.dsl. If you edit this file, your changes will be lost the next time that you
click Transform All Templates . Instead, add the required method in a separate file.
namespace Company.FamilyTree
{ public partial class Person
{ int GetAgeValue()
{ return System.DateTime.Today.Year - this.BirthYear; }
} }
8. If you set Kind to Custom Storage , you will also have to provide a Set method. For example:
Your code should not set values in the store when Store.InUndoRedoOrRollback is true. See Transactions
and Custom Setters.
9. Build and run the solution.
10. Test the property. Make sure that you try Undo and Redo .
For more information about transactions, see Navigating and Updating a Model in Program Code.
See also
Navigating and Updating a Model in Program Code
Properties of Domain Properties
How to Define a Domain-Specific Language
Domain property value change handlers
4/13/2022 • 5 minutes to read • Edit Online
In a Visual Studio domain-specific language, when the value of a domain property changes, the
OnValueChanging() and OnValueChanged() methods are invoked in the domain property handler. To respond to
the change, you can override these methods.
By contrast, if your property handler propagates changes outside the store, for example, to a file, database, or
non-store variables, then you should always make those changes so that the external values are updated when
the user invokes undo or redo.
Cancel a change
If you want to prevent a change, you can roll back the current transaction. For example, you might want to
ensure that a property remains within a specific range.
WARNING
If a rule makes changes to the store content, other rules and property handlers might be triggered. If a rule changes the
property that triggered it, it will be called again. You must make sure that your rule definitions do not result in endless
triggering.
using Microsoft.VisualStudio.Modeling;
...
// Change rule on the domain class Comment:
[RuleOn(typeof(Comment), FireTime = TimeToFire.TopLevelCommit)]
class MyCommentTrimRule : ChangeRule
{
public override void
ElementPropertyChanged(ElementPropertyChangedEventArgs e)
{
base.ElementPropertyChanged(e);
Comment comment = e.ModelElement as Comment;
Example
Description
The following example overrides the property handler of a domain property and notifies the user when a
property for the ExampleElement domain class has changed.
Code
using DslModeling = global::Microsoft.VisualStudio.Modeling;
using DslDesign = global::Microsoft.VisualStudio.Modeling.Design;
namespace msft.FieldChangeSample
{
public partial class ExampleElement
{
internal sealed partial class NamePropertyHandler
{
protected override void OnValueChanged(ExampleElement element,
string oldValue, string newValue)
{
if (!this.Store.InUndoRedoOrRollback)
{
// make in-store changes here...
}
// This part is called even in undo:
System.Windows.Forms.MessageBox.Show("Value Has Changed");
base.OnValueChanged(element, oldValue, newValue);
}
}
}
}
Rules Propagate Changes Within the Model
4/13/2022 • 8 minutes to read • Edit Online
You can create a store rule to propagate a change from one element to another in Visualization and Modeling
SDK (VMSDK). When a change occurs to any element in the Store, rules are scheduled to be executed, usually
when the outermost transaction is committed. There are different types of rules for different kinds of events,
such as adding an element, or deleting it. You can attach rules to specific types of elements, shapes, or diagrams.
Many built-in features are defined by rules: for example, rules ensure that a diagram is updated when the model
changes. You can customize your domain-specific language by adding your own rules.
Store rules are particularly useful for propagating changes inside the store - that is, changes to model elements,
relationships, shapes or connectors, and their domain properties. Rules do not run when the user invokes the
Undo or Redo commands. Instead, the transaction manager makes sure that the store contents are restored to
the correct state. If you want to propagate changes to resources outside the store, use Store Events. For more
information, see Event Handlers Propagate Changes Outside the Model.
For example, suppose that you want to specify that whenever the user (or your code) creates a new element of
type ExampleDomainClass, an additional element of another type is created in another part of the model. You
could write an AddRule and associate it with ExampleDomainClass. You would write code in the rule to create
the additional element.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.Modeling;
namespace ExampleNamespace
{
// Attribute associates the rule with a domain class:
[RuleOn(typeof(ExampleDomainClass), FireTime=TimeToFire.TopLevelCommit)]
// The rule is a class derived from one of the abstract rules:
class MyAddRule : AddRule
{
// Override the abstract method:
public override void ElementAdded(ElementAddedEventArgs e)
{
base.ElementAdded(e);
ExampleDomainClass element = e.ModelElement;
Store store = element.Store;
// Ignore this call if we're currently loading a model:
if (store.TransactionManager.CurrentTransaction.IsSerializing)
return;
NOTE
The code of a rule should change the state only of elements inside the Store; that is, the rule should change only model
elements, relationships, shapes, connectors, diagrams, or their properties. If you want to propagate changes to resources
outside the store, define Store Events. For more information, see Event Handlers Propagate Changes Outside the Model.
To define a rule
1. Define the rule as a class prefixed with the RuleOn attribute. The attribute associates the rule with one of
your domain classes, relationships, or diagram elements. The rule will be applied to every instance of this
class, which may be abstract.
2. Register the rule by adding it to the set returned by GetCustomDomainModelTypes() in your domain model
class.
3. Derive the rule class from one of the abstract Rule classes, and write the code of the execution method.
The following sections describe these steps in more detail.
To define a rule on a domain class
In a custom code file, define a class and prefix it with the RuleOnAttribute attribute:
[RuleOn(typeof(ExampleElement),
// Usual value - but required, because it is not the default:
FireTime = TimeToFire.TopLevelCommit)]
class MyRule ...
The subject type in the first parameter can be a domain class, domain relationship, shape, connector, or
diagram. Usually, you apply rules to domain classes and relationships.
The FireTime is usually TopLevelCommit . This ensures that the rule is executed only after all the primary
changes of the transaction have been made. The alternatives are Inline, which executes the rule soon after
the change; and LocalCommit, which executes the rule at the end of the current transaction (which might
not be the outermost). You can also set the priority of a rule to affect its ordering in the queue, but this is
an unreliable method of achieving the result you require.
You can specify an abstract class as the subject type.
The rule applies to all instances of the subject class.
The default value for FireTime is TimeToFire.TopLevelCommit. This causes the rule to be executed when
the outermost transaction is committed. An alternative is TimeToFire.Inline. This causes the rule to be
executed soon after the triggering event.
To register the rule
Add your rule class to the list of types returned by GetCustomDomainModelTypes in your domain model:
If you are not sure of the name of your domain model class, look inside the file
Dsl\GeneratedCode\DomainModel.cs
Write this code in a custom code file in your DSL project.
To write the code of the rule
Derive the rule class from one of the following base classes:
B A SE C L A SS T RIGGER
Each class has a method that you override. Type override in your class to discover it. The parameter of
this method identifies the element that is being changed.
Notice the following points about rules:
1. The set of changes in a transaction might trigger many rules. Usually, the rules are executed when the
outermost transaction is committed. They are executed in an unspecified order.
2. A rule is always executed inside a transaction. Therefore, you do not have to create a new transaction to
make changes.
3. Rules are not executed when a transaction is rolled back, or when the Undo or Redo operations are
performed. These operations reset all the content of the Store to its previous state. Therefore, if your rule
changes the state of anything outside the Store, it might not keep in synchronism with the Store content.
To update state outside the Store, it is better to use Events. For more information, see Event Handlers
Propagate Changes Outside the Model.
4. Some rules are executed when a model is loaded from file. To determine whether loading or saving is in
progress, use store.TransactionManager.CurrentTransaction.IsSerializing .
5. If the code of your rule creates more rule triggers, they will be added to the end of the firing list, and will
be executed before the transaction completes. DeletedRules are executed after all other rules. One rule
can run many times in a transaction, one time for each change.
6. To pass information to and from rules, you can store information in the TransactionContext . This is just a
dictionary that is maintained during the transaction. It is disposed when the transaction ends. The event
arguments in each rule provide access to it. Remember that rules are not executed in a predictable order.
7. Use rules after considering other alternatives. For example, if you want to update a property when a value
changes, consider using a calculated property. If you want to constrain the size or location of a shape, use
a BoundsRule . If you want to respond to a change in a property value, add an OnValueChanged handler to
the property. For more information, see Responding to and Propagating Changes.
Example
The following example updates a property when a domain relationship is instantiated to link two elements. The
rule will be triggered not only when the user creates a link on a diagram, but also if program code creates a link.
To test this example, create a DSL using the Task Flow solution template, and insert the following code in a file in
the Dsl project. Build and run the solution, and open the Sample file in the Debugging project. Draw a Comment
Link between a Comment shape and a flow element. The text in the comment changes to report on the most
recent element that you have connected it to.
In practice, you would usually write a DeleteRule for every AddRule.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;
namespace Company.TaskRuleExample
{
[RuleOn(typeof(CommentReferencesSubjects))]
public class RoleRule : AddRule
{
See also
Event Handlers Propagate Changes Outside the Model
Event Handlers Propagate Changes Outside the
Model
4/13/2022 • 5 minutes to read • Edit Online
In Visualization and Modeling SDK, you can define store event handlers to propagate changes to resources
outside the store, such as non-store variables, files, models in other stores, or other Visual Studio extensions.
Store event handlers are executed after the end of the transaction in which the triggering event occurred. They
are also executed in an Undo or Redo operation. Therefore, unlike store rules, store events are most useful for
updating values that are outside the store. Unlike .NET events, store event handlers are registered to listen to a
class: you do not have to register a separate handler for each instance. For more information about how to
choose between different ways to handle changes, see Responding to and Propagating Changes.
The graphical surface and other user interface controls are examples of external resources that can be handled
by store events.
To define a store event
1. Choose the type of event that you want to monitor. For a full list, look at the properties of
EventManagerDirectory. Each property corresponds to a type of event. The most frequently used event
types are:
ElementAdded - triggered when a model element, relationship link, shape or connector is created.
ElementPropertyChanged - triggered when the value of a Normal domain property is changed.
The event is triggered only if the new and old values are not equal. The event cannot be applied to
calculated and custom storage properties.
It cannot be applied to the role properties that correspond to relationship links. Instead, use
ElementAdded to monitor the domain relationship.
ElementDeleted - triggered after a model element, relationship, shape or connector has been
deleted. You can still access the property values of the element, but it will have no relationships to
other elements.
2. Add a partial class definition for YourDslDocData in a separate code file in the DslPackage project.
3. Write the code of the event as a method, as in the following example. It can be static , unless you want
to access DocData .
4. Override OnDocumentLoaded() to register the handler. If you have more than one handler, you can register
them all in the same place.
The location of the registration code is not critical. DocView.LoadView() is an alternative location.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;
namespace Company.MusicLib
{
partial class MusicLibDocData
{
// Register store events here or in DocView.LoadView().
protected override void OnDocumentLoaded()
{
base.OnDocumentLoaded(); // Don't forget this.
/*
// Alternatively, you can set one handler for
// all properties of a class.
// Your handler has to determine which property changed.
DomainClassInfo classInfo = this.Store.DomainDataDirectory
.FindDomainClass(typeof(Album));
this.Store.EventManagerDirectory
.ElementPropertyChanged.Add(classInfo,
new EventHandler<ElementPropertyChangedEventArgs>
(AlbumTitleAdjuster));
*/
return base.LoadView();
}
if (e.DomainProperty.Id == Album.TitleDomainPropertyId)
{
string newValue = (string)e.NewValue;
string lowerCase = newValue.ToLowerInvariant();
if (!newValue.Equals(lowerCase))
{
using (Transaction t = store.TransactionManager
.BeginTransaction("adjust album title"))
{
album.Title = lowerCase;
t.Commit();
} // Beware! This could trigger the event again.
}
}
// else other properties of this class.
}
ElementMoved A model element has been moved from one store partition
to another.
TransactionBeginning
TransactionCommitted
TransactionRolledBack
See also
Responding to and Propagating Changes
Sample code: Circuit Diagrams
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Update Shapes and Connectors to Reflect the
Model
4/13/2022 • 4 minutes to read • Edit Online
In a domain-specific language in Visual Studio, you can make the appearance of a shape reflect the state of the
underlying model.
The code examples in this topic should be added to a .cs file in your Dsl project. You need these directives in
each file:
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
If you want to make the property variable only under program control, and not by the user, select the new
domain property such as Fill Color in the DSL Definition diagram. Then, in the Properties window, set Is
Browsable to false or set Is UI Readonly to true .
This method can be used both for domain properties and non-store features, such as the size of the shape.
AssociateValueWith() should be called one time for each domain property that you want to register. After it has
been called, any changes to the specified property will call OnAssociatedPropertyChanged() in any shapes that
present the property's model element.
It is not necessary to call AssociateValueWith() for each instance. Although InitializeResources is an instance
method, it is invoked only one time for each shape class.
Generating Code from a Domain-Specific Language
4/13/2022 • 2 minutes to read • Edit Online
Microsoft Domain-Specific Language Tools provides a powerful way to generate code, documents, configuration
files, and other artifacts from data represented in models. Using Domain-Specific Language Tools, you can
create a set of classes that represent your data, and you can write your text templates in classes whose names
and properties reflect that data.
For example, Fabrikam has an XML file of customer names and e-mail addresses. Their developers create a
model in which Customer is a class, with properties name and e-mail. They write several text templates to
process the data, including this fragment which produces a table of all the customers as part of an HTML page:
<table>
<# foreach (Customer c in ContactList) { #>
<tr><td> <#= c.FullName #> </td>
<td> <#= c.EmailAddress #> </td> </tr>
<# } #> </table>
When the customer database is processed, the XML file is read into the model store. A directive processor,
created by using Domain-Specific Language Tools, makes the Customer class available to the code in the text
template. Many text templates can be run against the same store.
Text templates are essential to Domain-Specific Language Tools. They are used to generate the source code for
the elements of the domain model as well as for the VSPackage and the controls that are used to integrate the
tools with Visual Studio.
This section discusses some of the ways to create, modify, and debug text templates used in Domain-Specific
Language Tools.
In This Section
Accessing Models from Text Templates
Provides basic information about referring to domain-specific language in text templates.
Walkthrough: Debugging a Text Template that Accesses a Model
Describes how to do troubleshooting and debugging on a text template that refers to a domain-specific
language.
Walkthrough: Connecting a Host to a Generated Directive Processor
Describes how to connect a custom host to a generated directive processor.
The DslTextTransform Command
Describes the command file that executes the TextTransform executable on the command line for text templates
that reference domain-specific languages.
Reference
Writing a T4 Text Template
Provides the syntax of text template directives and control blocks.
Related Sections
Design-Time Code Generation by using T4 Text Templates
Explains the text template transformation process.
Code Generation in a Build Process
Read this topic if you are generating files from a DSL on a build server.
Access models from text templates
4/13/2022 • 7 minutes to read • Edit Online
By using text templates, you can create report files, source code files, and other text files that are based on
domain-specific language models. For basic information about text templates, see Code Generation and T4 Text
Templates. The text templates will work in the experimental mode when you are debugging your DSL, and will
also work on a computer on which you have deployed the DSL.
NOTE
When you create a DSL solution, sample text template *.tt files are generated in the debugging project. When you
change the names of the domain classes, these templates will no longer work. Nevertheless, they include the basic
directives that you need, and provide examples that you can update to match your DSL.
To debug the template, add debug="true" to the template directive. The template will open in another
instance of Visual Studio if an exception occurs. If you want to break into the debugger at a specific point
in the code, insert the statement System.Diagnostics.Debugger.Break();
For more information, see Debugging a T4 Text Template.
The name of the directive ( MyLanguage , in this example) is derived from the name of your DSL. It invokes a
directive processor that is generated as part of your DSL. You can find its source code in
Dsl\GeneratedCode\DirectiveProcessor.cs .
The DSL directive processor performs two principal tasks:
It effectively inserts assembly and import directives into the template that references your DSL. This lets
you use your domain classes in the template code.
It loads the file that you specify in the requires parameter, and sets a property in this that refers to the
root element of the loaded model.
Notice that:
1. The filename and validation parameters are separated with ";" and there must be no other separators
or spaces.
2. The list of validation categories determines which validation methods will be executed. Multiple
categories should be separated with "|" and there must be no other separators or spaces.
If an error is found, it will be reported in the errors window, and the result file will contain an error
message.
NOTE
This example code is for a language that is based on the Minimal Language solution template.
To access the models in your text template, you can now write code similar to the code in the following example.
<#
foreach (ExampleElement element in this.LibraryModel.Elements)
...
foreach (ExampleElement element in this.SchoolModel.Elements)
...
foreach (ExampleElement element in this.WorkModel.Elements)
...
#>
<#
For Each element As ExampleElement In Me.LibraryModel.Elements
...
For Each element As ExampleElement In Me.SchoolModel.Elements
...
For Each element As ExampleElement In Me.WorkModel.Elements
...
#>
LoopSplitter.tt invokes LoopTemplate.t4 , and then splits the resulting file into its segments. Notice that this
template does not have to be a modeling template, because it does not read the model.
<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="System.Runtime.Remoting.Messaging" #>
<#@ import namespace="System.IO" #>
<#
// Get the local path:
string itemTemplatePath = this.Host.ResolvePath("LoopTemplate.t4");
string dir = Path.GetDirectoryName(itemTemplatePath);
If you write text templates that read a model that contains Visual Studio ModelBus references, you might want to
resolve the references to access the target models. In that case, you have to adapt the text templates and the
referenced domain-specific languages (DSLs):
The DSL that is the target of the references must have a ModelBus Adapter that is configured for access
from text templates. If you also access the DSL from other code, the reconfigured adapter is required in
addition to the standard ModelBus Adapter.
The adapter manager must inherit from VsTextTemplatingModelingAdapterManager and must have the
attribute [HostSpecific(HostName)] .
The template must inherit from ModelBusEnabledTextTransformation.
NOTE
If you want to read DSL models that do not contain ModelBus references, you can use the directive processors that are
generated in your DSL projects. For more information, see Accessing Models from Text Templates.
For more information about text templates, see Design-Time Code Generation by using T4 Text Templates.
<MefComponent>|T4ModelBusAdapter|</MefComponent>
h. Transform All Templates and rebuild the solution. No build errors should occur.
3. In the new adapter project, add references to the following assemblies:
Microsoft.VisualStudio.TextTemplating.11.0
Microsoft.VisualStudio.TextTemplating.Modeling.11.0
4. In AdapterManager.tt:
Change the declaration of AdapterManagerBase so that it inherits from
VsTextTemplatingModelingAdapterManager.
public partial class <#= dslName =>AdapterManagerBase :
Microsoft.VisualStudio.TextTemplating.Modeling.VsTextTemplatingModelingAdapterManager { ...
Near the end of the file, replace the HostSpecific attribute before the AdapterManager class.
Remove the following line:
[DslIntegration::HostSpecific(DslIntegrationShell::VsModelingAdapterManager.HostName)]
This attribute filters the set of adapters that is available when a modelbus consumer searches for
an adapter.
5. Transform All Templates and rebuild the solution. No build errors should occur.
When this text template is executed, the SourceDsl directive loads the file Sample.source . The template can
access the elements of that model, starting from this.ModelRoot . The code can use the domain classes and
properties of that DSL.
In addition, the template can resolve ModelBus References. Where the references point to the Target model, the
assembly directives let the code use the domain classes and properties of that model's DSL.
If you do not use a directive that is generated by a DSL project, you should also include the following.
4. In each *.tt file in T4ModelBusAdapter, insert "T4" into the last part of the namespace, so that the line
resembles the following.
namespace <#= CodeGenerationUtilities.GetPackageNamespace(this.Dsl) #>.T4ModelBusAdapters
b. Near the end of the file, insert the following additional attribute in front of class AdapterManager.
[Microsoft.VisualStudio.Modeling.Integration.HostSpecific(HostName)]
/// <summary>
/// ModelBus modeling adapter manager for a <#= dslName #>Adapter model adapter
/// </summary>
[Mef::Export(typeof(DslIntegration::ModelBusAdapterManager))]
[Mef::ExportMetadata(DslIntegration::CompositionAttributes.AdapterIdKey,<#= dslName
#>Adapter.AdapterId)]
[DslIntegration::HostSpecific(DslIntegrationShell::VsModelingAdapterManager.HostName)]
[Microsoft.VisualStudio.Modeling.Integration.HostSpecific(HostName)]
public partial class <#= dslName #>AdapterManager : <#= dslName #>AdapterManagerBase
{
}
This enables you to create code that uses the other DSL. If you want to create references to several DSLs,
add them also.
3. In the DSL Definition diagram, right-click the diagram and then click Enable ModelBus . In the dialog
box, select Enable this DSL to Consume the ModelBus .
4. In the class ExampleElement , add a new domain property MBR , and in the Properties window, set its type
to ModelBusReference .
5. Right-click the domain property on the diagram and then click Edit ModelBusReference specific
proper ties . In the dialog box, select a model element .
Set the file dialog filter to the following.
Provider File|*.provide
The substring after "|" is a filter for the file selection dialog box. You could set it to allow any files by using
*.*
In the Model Element type list, enter the names of one ore more domain classes in the provider DSL
(for example, Company.MBProvider.Task). They can be abstract classes. If you leave the list blank, the user
can set the reference to any element.
6. Close the dialog and Transform All Templates .
You have created a DSL that can contain references to elements in another DSL.
Create a ModelBus reference to another file in the solution
1. In the MBConsumer solution, press CTRL+F5. An experimental instance of Visual Studio opens in the
MBConsumer\Debugging project.
2. Add a copy of Sample.provide to the MBConsumer\Debugging project. This is necessary because a
ModelBus reference must refer to a file in the same solution.
a. Right-click the Debugging project, point to Add , and then click Existing Item .
b. In the Add Item dialog, set the filter to All Files (*.*) .
c. Navigate to MBProvider\Debugging\Sample.provide and then click Add .
3. Open Sample.consume .
4. Click one example shape, and in the Properties window, click [...] in the MBR property. In the dialog box,
click Browse and select Sample.provide . In the elements window, expand the type Task and select one of
the elements.
5. Save the file. (Do not yet close the experimental instance of Visual Studio.)
You've created a model that contains a ModelBus reference to an element in another model.
Resolve a ModelBus Reference in a text template
1. In the experimental instance of Visual Studio, open a sample text template file. Set its content as follows.
<#@ template debug="true" hostspecific="true" language="C#"
inherits="Microsoft.VisualStudio.TextTemplating.Modeling.ModelBusEnabledTextTransformation" #>
<#@ MBConsumer processor="MBConsumerDirectiveProcessor" requires="fileName='Sample.consume'" #>
<#@ output extension=".txt" #>
<#@ assembly name = "Microsoft.VisualStudio.Modeling.Sdk.Integration.11.0" #>
<#@ assembly name = "Company.MBProvider.Dsl.dll" #>
<#@ import namespace="Microsoft.VisualStudio.Modeling.Integration" #>
<#@ import namespace="Company.MBProvider" #>
<#
// Property provided by the Consumer directive processor:
ExampleModel consumerModel = this.ExampleModel;
// Iterate through Consumer model, listing the elements:
foreach (ExampleElement element in consumerModel.Elements)
{
#>
<#= element.Name #>
<#
if (element.MBR != null)
using (ModelBusAdapter adapter = this.ModelBus.CreateAdapter(element.MBR))
{
// If we allowed multiple types or DSLs in the MBR, discover type here.
Task task = adapter.ResolveElementReference<Task>(element.MBR);
#>
<#= element.Name #> is linked to Task: <#= task==null ? "(null)" : task.Name #>
<#
}
}
#>
ExampleElement1
ExampleElement2
ExampleElement2 is linked to Task: Task2
See also
Integrating Models by using Visual Studio Modelbus
Code Generation and T4 Text Templates
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Walkthrough: Debugging a Text Template that
Accesses a Model
4/13/2022 • 4 minutes to read • Edit Online
When you modify or add text templates in a domain-specific language solution, you may get errors when the
engine transforms the template to source code or when it compiles the generated code. The following
walkthrough demonstrates some of the things you can do to debug a text template.
NOTE
For more information about text templates in general, see Code Generation and T4 Text Templates. For more information
about debugging text templates, see Walkthrough: Debugging a Text Template.
2. In Solution Explorer , right-click DebugTest.tt, and then click Run Custom Tool .
The Error List window displays this error:
The processor named 'DebuggingTestLanguageDirectiveProcessor' does not suppor t the
directive named 'modelRoot'. The transformation will not be run.
In this case, the directive call contains an incorrect directive name. You have specified modelRoot as the
directive name, but the correct directive name is DebuggingTestLanguage .
3. Double-click the error in the Error List window to jump to the code.
4. To correct the code, change the directive name to DebuggingTestLanguage .
The change is highlighted.
5. In Solution Explorer , right-click DebugTest.tt, and then click Run Custom Tool .
Now the system transforms the text template and generates the corresponding output file. You will not
see any errors in the Error List window.
To debug an incorrect property name
1. Replace the code in DebugTest.tt with the following code:
NOTE
The code contains an error. You are introducing the error in order to debug it.
2. In Solution Explorer , right-click DebugTest.tt, and then click Run Custom Tool .
The Error List window appears and displays one of these errors:
(C#)
Compiling transformation: Microsoft.VisualStudio.TextTemplating<GUID>.
GeneratedTextTransformation' does not contain a definition for 'ExampleModel'
(Visual Basic)
Compiling transformation: 'ExampleModel' is not a member of
'Microsoft.VisualStudio.TextTemplating<GUID>.GeneratedTextTransformation'.
In this case, the text template code contains an incorrect property name. You have specified ExampleModel
as the property name, but the correct property name is LibraryModel . You can find the correct property
name in the provides parameter, as shown in the following code:
<#@ DebuggingTestLanguage processor="DebuggingTestLanguageDirectiveProcessor"
requires="fileName='Sample.ddd'" provides="ExampleModel=LibraryModel" #>
3. Double-click the error in the Error List window to jump to the code.
4. To correct the code, change the property name to LibraryModel in the text template code.
The changes are highlighted.
5. In Solution Explorer , right-click DebugTest.tt, and then click Run Custom Tool .
Now the system transforms the text template and generates the corresponding output file. You will not
see any errors in the Error List window.
Walkthrough: Connect a Host to a Generated
Directive Processor
4/13/2022 • 7 minutes to read • Edit Online
You can write your own host that processes text templates. A basic custom host is demonstrated in Walkthrough:
Creating a Custom Text Template Host. You could extend that host to add functions such as generating multiple
output files.
In this walkthrough, you expand your custom host so that it supports text templates that call directive
processors. When you define a domain-specific language, it generates a directive processor for the domain
model. The directive processor makes it easier for users to write templates that access the model, reducing the
need to write assembly and import directives in the templates.
NOTE
This walkthrough builds on Walkthrough: Creating a Custom Text Template Host. Perform that walkthrough first.
Prerequisites
To define a DSL, you must have installed the following components:
C O M P O N EN T L IN K
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
In addition, you must have the custom text template transformation created in Walkthrough: Creating a Custom
Text Template Host.
IMPORTANT
This step generates the directive processor and adds the key for it in the registry.
using Microsoft.Win32;
Imports Microsoft.Win32
5. Locate the code for the property StandardAssemblyReferences , and replace it with the following code:
NOTE
In this step, you add references to the assemblies that are required by the generated directive processor that your
host will support.
typeof(Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation).Assembly.Location
};
}
}
6. Locate the code for the function ResolveDirectiveProcessor , and replace it with the following code:
IMPORTANT
This code contains hard-coded references to the name of the generated directive processor to which you want to
connect. You could easily make this more general, in which case it looks for all directive processors listed in the
registry and tries to find a match. In that case, the host would work with any generated directive processor.
//the engine calls this method based on the directives the user has
//specified it in the text template
//this method can be called 0, 1, or more times
//---------------------------------------------------------------------
public Type ResolveDirectiveProcessor(string processorName)
{
//check the processor name, and if it is the name of the processor the
//check the processor name, and if it is the name of the processor the
//host wants to support, return the type of the processor
//---------------------------------------------------------------------
if (string.Compare(processorName, "DSLMinimalTestDirectiveProcessor",
StringComparison.InvariantCultureIgnoreCase) == 0)
{
try
{
string keyName =
@"Software\Microsoft\VisualStudio\10.0Exp_Config\TextTemplating\DirectiveProcessors\DSLMinimalTestDir
ectiveProcessor";
using (RegistryKey specificKey = Registry.CurrentUser.OpenSubKey(keyName))
{
if (specificKey != null)
{
List<string> names = new List<String>(specificKey.GetValueNames());
string classValue = specificKey.GetValue("Class") as string;
if (!string.IsNullOrEmpty(classValue))
{
string loadValue = string.Empty;
System.Reflection.Assembly processorAssembly = null;
if (names.Contains("Assembly"))
{
loadValue = specificKey.GetValue("Assembly") as string;
if (!string.IsNullOrEmpty(loadValue))
{
//the assembly must be installed in the GAC
processorAssembly =
System.Reflection.Assembly.Load(loadValue);
}
}
else if (names.Contains("CodeBase"))
{
loadValue = specificKey.GetValue("CodeBase") as string;
if (!string.IsNullOrEmpty(loadValue))
{
//loading local assembly
processorAssembly =
System.Reflection.Assembly.LoadFrom(loadValue);
}
}
if (processorAssembly == null)
{
throw new Exception("Directive Processor not found");
}
Type processorType = processorAssembly.GetType(classValue);
if (processorType == null)
{
throw new Exception("Directive Processor not found");
}
return processorType;
}
}
}
}
catch (Exception e)
{
//if the directive processor can not be found, throw an error
throw new Exception("Directive Processor not found");
}
}
//if the directive processor is not one this host wants to support
throw new Exception("Directive Processor not supported");
}
NOTE
The programming language of the text template does not need to match that of the custom host.
<# //uncomment this line if you want to see the generated transformation class #>
<# //System.Diagnostics.Debugger.Break(); #>
<# //this code uses the results of the examplemodel directive #>
<#
foreach ( ExampleElement box in this.ExampleModel.Elements )
{
WriteLine("Box: {0}", box.Name);
WriteLine("");
}
#>
Text Template Host Test
<# 'this is the call to the examplemodel directive in the generated directive processor #>
<#@ DSLMinimalTest processor="DSLMinimalTestDirectiveProcessor" requires="fileName='<Your
Path>\Sample.min'" provides="ExampleModel=ExampleModel" #>
<# 'Uncomment this line to test that the host allows the engine to set the extension. #>
<# '@ output extension=".htm" #>
<# 'Uncomment this line if you want to see the generated transformation class. #>
<# 'System.Diagnostics.Debugger.Break() #>
<# 'this code uses the results of the examplemodel directive #>
<#
For Each box as ExampleElement In Me.ExampleModel.Elements
WriteLine("")
Next
#>
3. In the code, replace <YOUR PATH> with the path of the Sample.min file from the design-specific
language you created in the first procedure.
4. Save and close the file.
Test the custom host
1. Open a Command Prompt window.
2. Type the path of the executable file for the custom host, but do not press ENTER yet.
For example, type:
<YOUR PATH>CustomHost\bin\Debug\CustomHost.exe
NOTE
Instead of typing the address, you can browse to the file CustomHost.exe in Windows Explorer , and then drag
the file into the Command Prompt window.
3. Type a space.
4. Type the path of the text template file, and then press ENTER.
For example, type:
<YOUR PATH>TestTemplateWithDP.txt
NOTE
Instead of typing the address, you can browse to the file TestTemplateWithDP.txt in Windows Explorer , and then
drag the file into the Command Prompt window.
The custom host application runs and starts the text template transformation process.
5. In Windows Explorer , browse to the folder that contains the file TestTemplateWithDP.txt.
The folder also contains the file TestTemplateWithDP1.txt.
6. Open this file to see the results of the text template transformation.
The results of the generated text output appears and should look like this:
Box: ExampleElement1
Linked to: ExampleElement2
Box: ExampleElement2
Linked from: ExampleElement1
See also
Walkthrough: Creating a Custom Text Template Host
The DslTextTransform Command
4/13/2022 • 2 minutes to read • Edit Online
DslTextTransform.cmd is a script that calls TextTransform.exe and runs it with common options. You can use
DslTextTransformation.cmd to automate a nightly build of your Domain-Specific Language Tools projects. For
more information, see Generating Files with the TextTransform Utility.
DslTextTransform.cmd is located in the following directory:
<Visual Studio SDK Installation Path>\VisualStudioIntegration\Tools\Bin
You can specify the following arguments as input to DslTextTransform.cmd:
The output directory of the domain model project.
The output directory of the designer definition project.
The location of the text template file.
DslTextTransform.cmd processes the specified text template file using the default directive processors and
assemblies. If you create custom directive processors, you can create your own batch file that calls
TextTransform.exe. In this batch file, you can specify your assemblies and the associated custom directive
processors.
Customize File Storage and XML Serialization
4/13/2022 • 12 minutes to read • Edit Online
When the user saves an instance, or model, of a domain-specific language (DSL) in Visual Studio, an XML file is
created or updated. The file can be reloaded to recreate the model in the Store.
You can customize the serialization scheme by adjusting the settings under Xml Serialization Behavior in DSL
Explorer. There is a node under Xml Serialization Behavior for every domain class, property, and relationship.
The relationships are located under their source classes. There are also nodes corresponding to the shape,
connector, and diagram classes.
You can also write program code for more advanced customization.
NOTE
If you want to save the model in a particular format, but you do not need to reload it from that form, consider using text
templates to generate output from the model, instead of a custom serialization scheme. For more information, see
Generating Code from a Domain-Specific Language.
myDsl.myDsl
This model was saved and then re-opened in the XML text editor:
Understand Monikers
Monikers are used to represent cross-references between different parts of the model and diagram files. They
are also used in the .diagram file to refer to nodes in the model file. There are two forms of moniker:
Id monikers quote the GUID of the target element. For example:
Qualified key monikers identify the target element by the value of a designated domain property called
the moniker key. The moniker of the target element is prefixed by the moniker of its parent element in the
tree of embedding relationships.
The following examples are taken from a DSL in which there is a domain class named Album, which has
an embedding relationship to a domain class named Song:
Qualified key monikers will be used if the target class has a domain property for which the option Is
Moniker Key is set to true in Xml Serialization Behavior . In the example, this option is set for
domain properties named "Title" in the domain classes "Album" and "Song".
Qualified key monikers are easier to read than ID monikers. If you intend the XML of your model files to be read
by people, consider using qualified key monikers. However, it is possible for the user to set more than one
element to have the same moniker key. Duplicate keys could cause the file not to reload correctly. Therefore, if
you define a domain class that is referenced using qualified key monikers, you should consider ways of
preventing the user from saving a file that has duplicate monikers.
To set a domain class to be referenced by ID monikers
1. Make sure that Is Moniker Key is false for every domain property in the class and its base classes.
a. In DSL Explorer, expand Xml Serialization Behavior\Class Data\<the domain
class>\Element Data .
b. Verify that Is Moniker Key is false for every domain property.
c. If the domain class has a base class, repeat the procedure in that class.
2. Set Serialize Id = true for the domain class.
This property can be found under Xml Serialization Behavior .
To set a domain class to be referenced by qualified key monikers
Set Is Moniker Key for a domain property of an existing domain class. The type of the property must be
string .
There is an automatically generated validation method that checks for ambiguities. The method is in the
Load validation category. This makes sure that the user will be warned that it might not be possible to
re-open the file.
For more information, see Validation in a Domain-Specific Language.
Moniker Paths and Qualifiers
A qualified key moniker ends with the moniker key, and is prefixed with the moniker of its parent in the
embedding tree. For example, if the moniker of an Album is:
However, if Albums are referenced by ID instead, then the monikers would be as follows:
<albumMoniker Id="77472c3a-9bf9-4085-976a-d97a4745237c" />
<songMoniker title="/77472c3a-9bf9-4085-976a-d97a4745237c/Hot tea" />
Notice that because a GUID is unique, it is never prefixed by the moniker of its parent.
If you know that a particular domain property will always have a unique value within a model, you can set Is
Moniker Qualifier to true for that property. This will cause it to be used as a qualifier, without using the
moniker of the parent. For example, if you set both Is Moniker Qualifier and Is Moniker Key for the Title
domain property of the Album class, the model's name or identifier is not used in monikers for Album and its
embedded children:
<familyTreeModel ...>
<!-- The following node is omitted by using Omit Element: -->
<!-- <people> -->
<person name="Henry VIII" .../>
<person name="Elizabeth I" .../>
<!-- </people> -->
</familyTreeModel>
Set Use Full Form to embed the target nodes in nodes representing the relationship instances. This
option is set automatically when you add domain properties to a domain relationship.
<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>
To change the order in which attributes and relationships are serialized, right-click an item under Element
Data, and use the Move Up or Move Down menu commands.
Major customization using program code
You can replace parts or all of the serialization algorithms.
We recommend that you study the code in Dsl\Generated Code\Serializer.cs and SerializationHelper.cs .
To customize the serialization of a particular class
1. Set Is Custom in the node for that class under Xml Serialization Behavior .
2. Transform All Templates, build the solution, and investigate the resulting compilation errors. Comments
near each error explain what code you have to provide.
To provide your own serialization for the whole model
1. Override methods in Dsl\GeneratedCode\SerializationHelper.cs
Has Custom Element Schema If True, indicates that the domain class has a custom element
schema
Is Custom Set this to True if you want to write your own serialization
and deserialization code for this domain class.
Domain Class Domain class to which this class data node applies. Read-
only.
Element Name Xml node name for elements of this class. The default value
is a lower-case version of the domain class name.
Moniker Attribute Name Name of the attribute used in moniker elements to contain
the reference. If blank, the name of the key property or id is
used.
Moniker Element Name Name of the xml element used for monikers that refer to
elements of this class.
Moniker Type Name Name of the xsd type generated for monikers to elements of
this class. The XSD is in Dsl\Generated
Code\*Schema.xsd
Serialize Id If True, the element GUID is included in the file. This must be
true if there is no property that is marked Is Moniker Key
and the DSL defines reference relationships to this class.
Type Name Name of the xml type generated in the xsd from the
designated domain class.
Is Moniker Key If True, the property is used as the key for creating monikers
that reference instances of this domain class.
Is Moniker Qualifier If True, the property is used for creating the qualifier in
monikers. If false, and if SerializeId is not true for this domain
class, monikers are qualified by the moniker of the parent
element in the embedding tree.
Xml Name Name used for the xml attribute or element representing the
property. By default, this is a lower-case version of the
domain property name.
Has Custom Moniker Set this to true if you want to supply your own code for
generating and resolving monikers that traverse this
relationship.
Domain Relationship Specifies the relationship to which these options apply. Read-
only.
P RO P ERT Y DESC RIP T IO N
Omit Element If true, the XML node that corresponds to the source role is
omitted from the schema.
Role Element Name Specifies the name of the XML element that is derived from
the source role. The default value is the role property name.
See also
Navigating and Updating a Model in Program Code
Generating Code from a Domain-Specific Language
MSI and VSIX Deployment of a DSL
4/13/2022 • 4 minutes to read • Edit Online
You can install a domain-specific language on your own computer or on other computers. Visual Studio must
already be installed on the target computer.
M ET H O D B EN EF IT S
VSX (Visual Studio Extension) Very easy to deploy: Copy and execute the .vsix file from
the DslPackage project.
MSI (installer file) - Allows the user to open Visual Studio by double-clicking a
DSL file.
- Associates an icon with the DSL file type in the target
computer.
- Associates an XSD (XML schema) with the DSL file type.
This avoids warnings when the file is loaded into Visual
Studio.
<InstalledByMsi>true</InstalledByMsi>
2. Create or edit an icon that will represent your DSL in Windows Explorer. For example, edit
DslPackage\Resources\File.ico
3. Make sure that the following attributes of your DSL are correct:
In DSL Explorer click the root node, and in Properties window, review:
Description
Version
Click the Editor node and in the Properties window, click Icon . Set the value to reference an icon
file in DslPackage\Resources , such as File.ico
On the Build menu, open Configuration Manager , and select the configuration that you want to
build, such as Release or Debug .
4. Go to Visualization and Modeling SDK home page, and from the Downloads tab, download
CreateMsiSetupProject.tt .
5. Add CreateMsiSetupProject.tt to your Dsl project.
Visual Studio will create a file named CreateMsiSetupProject.vdproj .
6. In Windows Explorer, copy Dsl\*.vdproj to a new folder named Setup.
(If you want, you can now exclude CreateMsiSetupProject.tt from your Dsl project.)
7. In Solution Explorer , add Setup\*.vdproj as an existing project.
8. On the Project menu, click Project Dependencies .
In the Project Dependencies dialog box, select the setup project.
Select the box next to DslPackage .
9. Rebuild the solution.
10. In Windows Explorer, locate the built MSI file in your Setup project.
Copy the MSI file to a computer on which you want to install your DSL. Double-click the MSI file. The
installer runs.
11. In the target computer, create a new file that has the file extension of your DSL. Verify that:
In Windows Explorer list view, the file appears with the icon and description that you defined.
When you double-click the file, Visual Studio starts, and opens the DSL file in your DSL editor.
If you prefer, you can create the Setup project manually, instead of using the text template. For a
walkthrough that includes this procedure see Chapter 5 of the Visualization and Modeling SDK Lab.
To uninstall a DSL that was installed from an MSI
1. In Windows, open the Programs and Features control panel.
2. Uninstall the DSL.
3. Restart Visual Studio.
Multiple DSLs in One Solution
4/13/2022 • 2 minutes to read • Edit Online
You can package several DSLs as part of a single solution so that they are installed together.
You can use several techniques to integrate multiple DSLs. For more information, see Integrating Models by
using Visual Studio Modelbus and How to: Add a Drag-and-Drop Handler and Customizing Copy Behavior.
In each DslPackage*\source.extension.tt , update this line to the correct Dsl project name:
string dslProjectName = "Dsl2";
In the VSIX solution, add the Dsl* and DslPackage* projects. You might want to place each pair in
its own solution folder.
3. Combine the VSIX manifests of the DSLs:
a. Open YourVsixProject\source.extension.manifest .
b. For each DSL, choose Add Content and add:
Dsl* project as a MEF Component
DslPackage* project as a MEF Component
DslPackage* project as a VS Package
4. Build the solution.
The resulting VSIX will install both DSLs. You can test them by using F5, or deploy
YourVsixProject\bin\Debug\*.vsix .
See also
Integrating Models by using Visual Studio Modelbus
How to: Add a Drag-and-Drop Handler
Customizing Copy Behavior
VS Shell deployment
4/13/2022 • 2 minutes to read • Edit Online
An isolated shell lets you determine which Visual Studio functionality you need to interact with your domain-
specific language and how that solution should appear. For more information about the Visual Studio isolated
shell, see Customizing the Isolated Shell.
To set a Visual Studio Shell as the deployment target:
1. In the DslPackage project, open source.extension.tt .
2. Under <SupportedProducts> insert:
<IsolatedShell Version="1.0">MyIsolatedShell</IsolatedShell>
You can use Windows Forms to display the state of a domain-specific language (DSL) model, instead of using a
DSL diagram. This topic walks you through binding a Windows Form to a DSL by using the Visual Studio
Visualization and Modeling SDK.
The following image shows a Windows Form UI and the model explorer for a DSL instance:
NOTE
If you delete the root domain class and then create a new root, you will have to reset the Editor Root Class
property. In DSL Explorer , select Editor . Then in the Properties window, set Root Class to Farm .
4. Use the Named Domain Class tool to create the following domain classes:
Field - Give this an additional domain property named Size .
Animal - In the Properties window, set Inheritance Modifier to Abstract .
5. Use the Domain Class tool to create the following classes:
Sheep
Goat
6. Use the Inheritance tool to make Goat and Sheep inherit from Animal .
7. Use the Embedding tool to embed Field and Animal under Farm .
8. You might want to tidy the diagram. To reduce the number of duplicate elements, use the Bring Subtree
Here command on the shortcut menu of leaf elements.
9. Transform All Templates in the toolbar of Solution Explorer.
10. Build the Dsl project.
NOTE
At this stage, the other projects will not build without errors. However, we want to build the Dsl project so that its
assembly is available to the Data Source Wizard.
NOTE
An alternative step is to drag the Animals and Fields items from the Data Sources window onto the control. This
action automatically creates data grids and bindings between the grid view and the data source. However, this
binding does not work correctly for DSLs. Therefore it is better to create the data grids and bindings manually.
7. If the Toolbox does not contain the ModelingBindingSource tool, add it. On the shortcut menu of the
Data tab, choose Choose Items . In the Choose Toolbox Items dialog, select
ModelingBindingSource from the .NET Framework tab.
8. Using the Toolbox, create two instances of ModelingBindingSource , and name them AnimalBinding
and FieldBinding .
9. Set the DataSource property of each ModelingBindingSource to farmBindingSource .
Set the DataMember property to Animals or Fields .
10. Set the DataSource properties of AnimalGridView to AnimalBinding , and of FieldGridView to
FieldBinding .
using System.ComponentModel;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Design;
namespace Company.FarmApp
{
partial class FarmControl
{
public IContainer Components { get { return components; } }
2. In the DslPackage project, edit DslPackage\DocView.tt to update the following variable definition:
using Microsoft.VisualStudio.Modeling;
However, this code does not set a default name for the new item. It does not run any customized merge that you
might have defined in the Element Merge Directives of the DSL, and it does not run any custom merge code
that might have been defined.
Therefore we recommend that you use ElementOperations to create new elements. For more information, see
Customizing Element Creation and Movement.
See also
How to define a Domain-Specific Language
Write code to customize a Domain-Specific Language
Modeling SDK for Visual Studio - Domain-Specific Languages
Create a WPF-Based Domain-Specific Language
4/13/2022 • 2 minutes to read • Edit Online
You can create a domain-specific language that has a WPF designer instead of a graphical designer.
Information and samples about this feature can be found on the Visual Studio Visualization and Modeling Tools
website at http://go.microsoft.com/fwlink/?LinkId=186128
See also
How to Define a Domain-Specific Language
About Domain-Specific Languages
4/13/2022 • 6 minutes to read • Edit Online
Unlike a general-purpose language such as C# or UML, a domain-specific language (DSL) is designed to express
statements in a particular problem space, or domain.
Well-known DSLs include regular expressions and SQL. Each DSL is much better than a general-purpose
language for describing operations on text strings or a database, but much worse for describing ideas that are
outside its own scope. Individual industries also have their own DSLs. For example, in the telecommunications
industry, call description languages are widely used to specify the sequence of states in a telephone call, and in
the air travel industry a standard DSL is used to describe flight bookings.
Your business and your project also deal with special sets of concepts that could be described with a DSL. For
example, you could define a DSL for one of these applications:
Plan of navigation paths in a website.
Wiring diagrams for electronic components.
Networks of conveyor belts and baggage handling equipment for an airport.
When you design a DSL, you define a domain class for each of the important concepts in the domain, such as a
web page, lamp, or airport check-in desk. You define domain relationships such as hyperlink, wire, or a conveyor
belt to link the concepts together.
Users of your DSL create models. Models are instances of the DSL. For example, they describe a particular
website, or the wiring of a particular device, or the baggage handling system in a particular airport.
Your users can view a model as a diagram or as a Windows form. Models can also be viewed as XML, which is
how they are stored. When you define a DSL, you define how the instances of each domain class and
relationship appear on the user's screen. A typical DSL is displayed as a collection of icons or rectangles
connected by arrows.
The following figure shows a small model in a diagrammatic DSL:
Domain-Specific Development
Domain-specific development is the process of identifying the parts of your applications that can be modeled by
using a domain-specific language, and then constructing the language and deploying it to the application
developers. The developers use the domain-specific language to construct models that are specific to their
applications, use the models to generate source code, and then use the source code to develop the applications.
Domain-Specific Language Tools (DSL Tools), which are hosted in Visual Studio, let you design a domain-specific
language and then generate everything that users must have to create models that are based on the language.
The following tools are included in DSL Tools:
A project wizard that uses different solution templates to help you start developing your domain-specific
language.
A graphical designer for creating and editing your domain-specific language definition.
A validation engine that makes sure that the domain-specific language definition is well-formed, and
displays errors and warnings if there are problems.
A code generator that takes a domain-specific language definition as input and produces source code as
output.
See also
The Experimental Instance
Domain-Specific Language Tools Glossary
Overview of the Domain-Specific Language Tools
User Interface
4/13/2022 • 2 minutes to read • Edit Online
When you first open a Domain-Specific Language Tools (DSL Tools) solution in Visual Studio, the user interface
will resemble the following picture.
The following table explains how the parts of the UI are used.
EL EM EN T DEF IN IT IO N
The diagram has two sides. One side defines the types of the
elements in your models. The other side defines how your
models will appear on the screen.
Toolbox Drag tools from the toolbox to add domain classes and
shape types to the diagram. To add relationships, connectors
and shape maps, click the tool, then click the source node on
the diagram, and then the target node.
DSL Explorer DSL Explorer appears when a DSL definition is the active
window. It shows the DSL as a tree. DSL Explorer lets you
edit features of the model that are not displayed on the
diagram. For example, you can add toolbox items and switch
on the validation process by using the DSL Explorer .
DSL Details window The DSL Details window shows properties of the domain
model's elements that allow you to control how elements are
displayed, and how elements are copied and deleted.
T ERM DEF IN IT IO N
Domain Class Domain classes are the types of elements in your models.
To add a domain class, drag the domain class tool from the
Toolbox to the Classes and Relationships side of the
diagram.
Shapes and Connectors Shapes specify how model elements should be displayed on
a DSL diagram., Connectors specify lines on a DSL diagram
that can be used to display relationships.
See also
Overview of Domain-Specific Language Tools
Domain-Specific Language Tools Glossary
Customizing and Extending a Domain-Specific Language
Choosing a Domain-Specific Language Solution
Template
4/13/2022 • 2 minutes to read • Edit Online
To create a domain-specific language solution, choose one of the solution templates that are available in the
Domain-Specific Language Designer Wizard. By choosing the template that most closely resembles the
language that you want to create, you can minimize the modifications that you have to make to the starting
solution.
The following solution templates are available in the Domain-Specific Language Designer Wizard.
Task Flow Diagrams - Image and geometry shapes Use this solution template if your
- Swimlanes domain-specific language includes
workflows, states, or sequences. This
template creates a domain-specific
language that resembles UML activity
diagrams. The main entity is an activity,
and the main relationship is a
transition between activities. The
template includes several other
elements such as start state, final
state, and a synchronization bar.
T EM P L AT E F EAT URES DESC RIP T IO N
Minimal Language - One class and shape Use this solution template if your
- One relationship and connector domain-specific language does not
resemble the other templates. This
template creates a domain-specific
language that has two classes and one
relationship, which are represented in
the Toolbox as Box and Line . The
class and the relationship each have an
example string property.
Minimal WinForm Designer - A small model. Use this template if you want to build
- A Windows Form that displays the an application in which a DSL is bound
model. to a Windows Form, rather than a
graphical designer.
Minimal WPF Designer - A small model Use this template if you want to build
- A Windows Presentation Foundation an application in which a DSL is bound
user interface that displays the model to a WPF user interface, rather than a
graphical designer.
DSL Library - A minimal library Use this template if you want to build
a partial DSL definition that can be
imported into other DSL definitions.
See also
Overview of Domain-Specific Language Tools
How to: Create a Domain-Specific Language
Solution
4/13/2022 • 4 minutes to read • Edit Online
Prerequisites
Before you can start this procedure, install these components:
Visual Studio
Visual Studio SDK (installed as part of the Visual Studio extension development workload)
Modeling SDK (installed as a Visual Studio component)
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
NOTE
Preferably, the name that you choose for the project should be a valid Visual C# identifier because it might be
used to generate code.
NOTE
If you see a message that alerts you not to run text templates from untrusted sources, click OK . You can set this
message not to appear again.
See also
How to Define a Domain-Specific Language
Domain-Specific Language Tools Glossary
Working with the DSL Definition Diagram
4/13/2022 • 3 minutes to read • Edit Online
The diagram of a Domain-Specific Language Tools definition is an important tool for defining the domain-
specific language. You can add elements to your domain model and define relationships on the diagram, and
you can modify the layout of the diagram to make it more readable.
Copying elements
You can use copy, cut and paste on elements in the DSL definition diagram.
See also
Domain-Specific Language Tools Glossary
Working with the Domain-Specific Language
Explorer
4/13/2022 • 2 minutes to read • Edit Online
The DSL Explorer displays several kinds of information in a tree view. For more information about most of the
items in the DSL Explorer, see How to Define a Domain-Specific Language.
It has the following nodes:
Connection Builders . A list of the connection builders in the model.
Connectors . A list of the connector diagram elements in the model.
Diagram . A set of properties that define the diagram, and lists of connector maps and shape maps.
Domain Classes . A list of the domain classes in the model.
Domain Relationships . A list of the domain relationships in the model.
Domain Types . A list of the built-in and external types that are used in the model.
Editor . A list of Toolbox tabs and the items they contain, and validation settings for the model. For more
information about validation, see Validation in a Domain-Specific Language.
Explorer Behavior . Settings that specify the behavior of the explorer in the generated designer. For
more information about how to customize the explorer, see Customizing the Model Explorer.
Shapes . A list of the shape diagram elements in the model.
Xml Serialization Behavior . Serialization settings for the model. For more information about
serialization, see Customizing File Storage and XML Serialization.
See also
Domain-Specific Language Tools Glossary
How to: Change the namespace of a domain-
specific language
4/13/2022 • 2 minutes to read • Edit Online
You can change the namespace of a domain-specific language. Make the change in the DSL Explorer , in the
properties of the Dsl Package project, and in the assembly information.
9. If you have written any custom code, make sure to change the namespace and class references in the
code files.
10. Reset the Visual Studio Experimental instance.
a. Delete \Users\ {your name}\AppData\Local\Microsoft\VisualStudio\*Exp .
b. On the Windows Star t menu, choose All Programs > Microsoft Visual Studio 2010 SDK >
Tools > Reset the Experimental Instance .
11. On the Build menu, choose Rebuild Solution .
See also
Domain-specific language tools glossary
How to: Extend the Domain-Specific Language
Designer
4/13/2022 • 5 minutes to read • Edit Online
You can make extensions to the designer that you use to edit DSL Definitions. Types of extension that you can
make include adding menu commands, adding handlers for drag and double-click gestures, and rules that are
triggered when particular types of values or relationships change. The extensions can be packaged as a Visual
Studio Integration Extension (VSIX) and distributed to other users.
For sample code and more information about this feature, see the Visual Studio Visualization and Modeling SDK.
namespace Fabrikam.SimpleDslDesignerExtension
{
/// <summary>
/// Command extending the DslDesigner.
/// </summary>
[DslDefinitionModelCommandExtension]
public class MyDslDesignerCommand : ICommandExtension
{
/// <summary>
/// Selection Context for this command
/// </summary>
[Import]
IVsSelectionContext SelectionContext { get; set; }
/// <summary>
/// Is the command visible and active?
/// This is called when the user right-clicks.
/// </summary>
public void QueryStatus(IMenuCommand command)
{
command.Visible = true;
// Is there any selected DomainClasses in the Dsl explorer?
command.Enabled =
SelectionContext.AtLeastOneSelected<DomainClass>();
/// <summary>
/// Tells if the DslDesigner can consume the to-be-dropped information
/// </summary>
/// <param name="targetMergeElement">Shape on which we try to drop</param>
/// <param name="diagramDragEventArgs">Drop event</param>
/// <returns><c>true</c> if we can consume the to be dropped data, and <c>false</c> otherwise</returns>
public bool CanDragDrop(ShapeElement targetMergeElement,
DiagramDragEventArgs diagramDragEventArgs)
{
if (diagramDragEventArgs.Data.GetDataPresent(DataFormats.FileDrop))
{
diagramDragEventArgs.Effect = DragDropEffects.Copy;
return true;
}
return false;
}
/// <summary>
/// Processes the drop by displaying the dropped text
/// </summary>
/// <param name="targetMergeElement">Shape on which we dropped</param>
/// <param name="diagramDragEventArgs">Drop event</param>
public void OnDragDrop(ShapeElement targetDropElement, DiagramDragEventArgs diagramDragEventArgs)
{
if (diagramDragEventArgs.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] droppedFiles =
diagramDragEventArgs.Data.
GetData(DataFormats.FileDrop) as string[];
MessageBox.Show(string.Format("Dropped text {0}",
string.Join("\r\n", droppedFiles)), "Dropped Text");
}
}
}
The following code implements a simple model. Create a new GUID to replace the placeholder.
using System;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.DslDefinition;
namespace Fabrikam.SimpleDslDesignerExtension
{
/// <summary>
/// Simplest possible domain model
/// needed only for extension rules.
/// </summary>
[DomainObjectId(SimpleDomainModelExtension.DomainModelId)]
public class SimpleDomainModelExtension : DomainModel
{
// Id of this domain model extension
// Please replace this with a new GUID:
public const string DomainModelId =
"00000000-0000-0000-0000-000000000000";
/// <summary>
/// Constructor for the domain model extension
/// </summary>
/// <param name="store">Store in which the domain model will be loaded</param>
public SimpleDomainModelExtension(Store store)
: base(store, new Guid(SimpleDomainModelExtension.DomainModelId))
{
/// <summary>
/// Rules brought by this domain model extension
/// </summary>
/// <returns></returns>
protected override System.Type[] GetCustomDomainModelTypes()
{
return new Type[] {
typeof(DomainPropertyTypeChangedRule)
};
}
}
/// <summary>
/// Provider for the DomainModelExtension
/// </summary>
[Export(typeof(DomainModelExtensionProvider))]
[ProvidesExtensionToDomainModel(typeof(DslDefinitionModelDomainModel))]
public class SimpleDomainModelExtensionProvider
: DomainModelExtensionProvider
{
/// <summary>
/// Extension model type
/// </summary>
public override Type DomainModelType
{
get
{
return typeof(SimpleDomainModelExtension);
}
}
}
}
Supported Visual Studio Editions for Visualization &
Modeling SDK
4/13/2022 • 2 minutes to read • Edit Online
The following are lists of the Visual Studio editions that are supported with Domain-Specific Language Tools in
the authoring and deployment environments. For more information on these editions, see the Microsoft Visual
Studio Developer Center.
Authoring Edition
To define a DSL, you must have installed the following components:
P RO DUC T DO W N LO A D L IN K
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
Deployment Editions
Domain-Specific Language Tools supports the following configurations for deploying the domain-specific
languages that you build:
Visual Studio Enterprise
Visual Studio Professional
Visual Studio Shell (integrated mode) redistributable package redistributable package
Visual Studio Shell (isolated mode) redistributable package redistributable package
NOTE
To make a DSL able to run on a Shell product, you must set the Suppor ted VS Edition field in the Extension Manifest.
For more information, see Deploying Domain-Specific Language Solutions.
See also
Domain-Specific Language Tools Glossary
How to: Migrate a Domain-Specific Language to a
New Version
4/13/2022 • 2 minutes to read • Edit Online
You can migrate projects that define and use domain-specific language to Visual Studio 2010 from the version
of Domain-Specific Language Tools that was distributed with Visual Studio 2008.
A migration tool is provided as part of Visual Studio SDK. The tool converts Visual Studio projects and solutions
that use or define DSL Tools.
You must run the migration tool explicitly: it is not launched automatically when you open a solution in Visual
Studio. The tool and detailed guidance document can be found at this path:
%Program Files%\Microsoft Visual Studio 2010
SDK\VisualStudioIntegration\Tools\DSLTools\DslProjectsMigrationTool.exe
NOTE
The checkboxes that appear next to folder names have no effect. You must expand the folders to inspect
the projects and solutions.
4. Convert the projects.
a. Click Conver t .
Before each project file is converted, a copy of project.csproj is saved as project.vs2008.csproj
A copy of each solution.sln is saved as solution.vs2008.sln
b. Investigate any failed conversions that are reported.
Failures are reported in the text window. In addition, the tree view shows a red flag on each node
that has failed to convert. You can click the node to get more information about that failure.
5. Transform All Templates in solutions containing successfully converted projects.
a. Open the solution.
b. Click the Transform All Templates button in the header of Solution Explorer.
NOTE
You can make this step unnecessary. For more information, see How to Automate Transform All Templates.
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
See also
Related blog posts
API Reference for Modeling SDK for Visual Studio
4/13/2022 • 2 minutes to read • Edit Online
The Visual Studio Visualization and Modeling SDK provides the platform on which your domain-specific
languages (DSL) tools are built.
This section contains reference material for namespaces that have names that begin with
"Microsoft.VisualStudio.Modeling".
N A M ESPA C E C O N T EN T
Microsoft.VisualStudio.Modeling.DslDefinition.ExtensionEnabl Attributes that allow you to extend the DSL designer with
ement commands, gestures, and validation.
Microsoft.VisualStudio.Modeling.Integration.Picker The dialog box that lets users navigate to models and
elements to create Modelbus references.
Microsoft.VisualStudio.Modeling.Integration.Shell.Picker The Picker dialog box that lets users navigate to models and
elements to create Modelbus references.
N A M ESPA C E C O N T EN T
See also
Customizing T4 Text Transformation
Code Generation and T4 Text Templates
4/13/2022 • 3 minutes to read • Edit Online
In Visual Studio, a T4 text template is a mixture of text blocks and control logic that can generate a text file. The
control logic is written as fragments of program code in Visual C# or Visual Basic. In Visual Studio 2015 Update
2 and later, you can use C# version 6.0 features in T4 templates directives. The generated file can be text, such as
a web page, or a resource file, or program source code in any language.
For a domain-specific language(DSL), designed to express statements in a particular problem space, learn how
to Generate Code from a Domain-Specific Language
There are two kinds of T4 text templates: run time and design time
<html><body>
The date and time now is: <#= DateTime.Now #>
</body></html>
Notice that the template resembles the generated output. The similarity of the template to the resulting output
helps you avoid mistakes when you want to change it.
In addition, the template contains fragments of program code. You can use these fragments to repeat sections of
text, to make conditional sections, and to show data from your application.
To generate the output, your application calls a function that is generated by the template. For example:
Your application can run on a computer that doesn't have Visual Studio installed.
To create a run-time template, add a Preprocessed text template file to your project. You can also add a plain
text file and set its Custom Tool property to TextTemplatingFilePreprocessor .
For more information, see Run-Time Text Generation with T4 Text Templates. For more information about the
syntax of templates, see Writing a T4 Text Template.
The generated .cs file is in the following format based on the XML file values:
namespace Fabrikam.FirstJob
{
... // More code here.
}
As another example, the input could be a diagram of workflow in a business activity. When the users change
their business workflow, or when you start work with new users who have a different workflow, it's easy to
regenerate the code to fit the new model.
NOTE
The term model is sometimes used to describe data read by one or more templates. The model can be in any format, in
any kind of file or database. It does not have to be a UML model or a Domain-Specific Language model. 'Model' just
indicates that the data can be defined in terms of the business concepts, rather than resembling the code. Design-time
templates are quicker and more reliable when changing the configuration as the requirements change. Typically, input is
defined in terms of business requirements, as in the workflow example. Design-time templates are useful tools in an agile
development process.
To create a design-time template, add a Text Template file to your project. Alternatively, you can add a plain text
file and set its Custom Tool property to TextTemplatingFileGenerator .
For more information, see Design-Time Code Generation by using T4 Text Templates. For more information
about the syntax of templates, see Writing a T4 Text Template.
The text template transformation feature is named T4.
See also
Generate Code from a Domain-Specific Language
Design-Time Code Generation by using T4 Text
Templates
4/13/2022 • 11 minutes to read • Edit Online
Design-time T4 text templates let you generate program code and other files in your Visual Studio project.
Typically, you write the templates so that they vary the code that they generate according to data from a model.
A model is a file or database that contains key information about your application's requirements.
For example, you could have a model that defines a workflow, either as a table or a diagram. From the model,
you can generate the software that executes the workflow. When your users' requirements change, it is easy to
discuss the new workflow with the users. Regenerating the code from the workflow is more reliable than
updating the code by hand.
NOTE
A model is a data source that describes a particular aspect of an application. It can be any form, in any kind of file or
database. It does not have to be in any particular form, such as a UML model or Domain-Specific Language model. Typical
models are in the form of tables or XML files.
You are probably already familiar with code generation. When you define resources in a .resx file in your Visual
Studio solution, a set of classes and methods is generated automatically. The resources file makes it much easier
and more reliable to edit the resources than it would be if you had to edit the classes and methods. With text
templates, you can generate code in the same manner from a source of your own design.
A text template contains a mixture of the text that you want to generate, and program code that generates
variable parts of the text. The program code allows you to repeat or conditionally omit parts of the generated
text. The generated text can itself be program code that will form part of your application.
If you added the template to a Visual Basic project, the language attribute will be " VB ".
4. Add some text at the end of the file. For example:
Hello, world!
5. Save the file.
You might see a Security Warning message box that asks you to confirm that you want to run the
template. Click OK .
6. In Solution Explorer , expand the template file node and you will find a file that has the extension .txt .
The file contains the text generated from the template.
NOTE
If your project is a Visual Basic project, you must click Show All Files in order to see the output file.
2. Save the .tt file, and inspect the generated .txt file again. It lists the squares of the numbers from 0 to 10.
Notice that statements are enclosed within <#...#> , and single expressions within <#=...#> . For more
information, see Writing a T4 Text Template.
If you write the generating code in Visual Basic, the template directive should contain language="VB" .
"C#" is the default.
Set breakpoints in the template, in the same way that you would for ordinary code.
Choose Debug T4 Template from the shortcut menu of the text template file in Solution Explorer.
The template runs and stops at the breakpoints. You can examine variables and step through the code in
the usual way.
TIP
debug="true" makes the generated code map more accurately to the text template, by inserting more line numbering
directives into the generated code. If you leave it out, breakpoints might stop the run in the wrong state.
But you can leave the clause in the template directive even when you are not debugging. This causes only a very small
drop in performance.
3. Save the file and inspect the generated file, which now contains the following code:
class MyGeneratedClass {
private int P1 = 0;
private int P2 = 0;
private int P3 = 0;
}
The assembly directive makes the specified assembly available to your template code, in the same manner as
the References section of a Visual Studio project. You do not need to include a reference to System.dll, which is
referenced automatically. The import directive lets you use types without using their fully qualified names, in
the same manner as the using directive in an ordinary program file.
For example, after importing System.IO , you could write:
You can also use this.Host.TemplateFile , which identifies the name of the current template file.
The type of this.Host (in VB, Me.Host ) is Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost .
Getting data from Visual Studio
To use services provided in Visual Studio, set the hostSpecific attribute and load the EnvDTE assembly. Import
Microsoft.VisualStudio.TextTemplating , which contains the GetCOMService() extension method. You can then
use IServiceProvider.GetCOMService() to access DTE and other services. For example:
<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
EnvDTE.DTE dte = (EnvDTE.DTE) serviceProvider.GetCOMService(typeof(EnvDTE.DTE));
#>
TIP
A text template runs in its own app domain, and services are accessed by marshaling. In this circumstance,
GetCOMService() is more reliable than GetService().
NOTE
The Text Template Transformation SDK and the Visual Studio Modeling SDK are installed automatically when you install
specific features of Visual Studio. For more details, see this blog post.
<Import
Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\TextTemplating\Microsoft.TextTemplating.targe
ts" />
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
<!-- Other properties can be inserted here -->
</PropertyGroup>
<Import
Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v16.0\TextTemplating\Microsoft.TextTemplating.targe
ts" />
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
<!-- Other properties can be inserted here -->
</PropertyGroup>
Error reporting
To place error and warning messages in the Visual Studio error window, you can use these methods:
Error("An error message");
Warning("A warning message");
P RO P ERT Y SET T IN G
If you want to write the generating code of your template in Visual Basic, set the language attribute to
"VB" instead of "C#" .
Set the extension attribute to the file name extension for the type of file that you want to generate, for
example .cs , .resx , or .xml .
6. Save the file.
A subsidiary file is created, with the specified extension. Its properties are correct for the type of file. For
example, the Build Action property of a .cs file would be Compile .
Verify that the generated file contains the same content as the original file.
7. Identify a part of the file that you want to vary. For example, a part that appears only under certain
conditions, or a part that is repeated, or where the specific values vary. Insert generating code. Save the
file and verify that the subsidiary file is correctly generated. Repeat this step.
Next steps
N EXT ST EP TO P IC
Write and debug a more advanced text template, with code Writing a T4 Text Template
that uses auxiliary functions, included files, and external data.
Generate documents from templates at run time. Run-Time Text Generation with T4 Text Templates
Run text generation outside Visual Studio. Generating Files with the TextTransform Utility
Transform your data in the form of a domain-specific Generating Code from a Domain-Specific Language
language.
Write directive processors to transform your own data Customizing T4 Text Transformation
sources.
See also
Guidelines for Writing T4 Text Templates
Walkthrough: Generate Code by using Text
Templates
4/13/2022 • 12 minutes to read • Edit Online
Code generation allows you to produce program code that is strongly typed, and yet can be easily changed
when the source model changes. Contrast this with the alternative technique of writing a completely generic
program that accepts a configuration file, which is more flexible, but results in code that is neither so easy to
read and change, nor has such good performance. This walkthrough demonstrates this benefit.
NOTE
The application xsd.exe, which is included with Visual Studio, can generate strongly-typed classes from XML files. The
template shown here is provided as an example.
In the project that this walkthrough constructs, you can write code such as the following, and IntelliSense
prompts you with the correct attribute and child names as you type:
In the strongly typed version, a change to the XML schema results in changes to the classes. The compiler
highlights the parts of the application code that must be changed. In the untyped version that uses generic XML
code, there is no such support.
In this project, a single template file is used to generate the classes that make the typed version possible.
At this stage, this code will fail to compile. As you write the template, you will generate classes that allow it to
succeed.
A more comprehensive test could check the output of this test function against the known content of the
example XML file. But in this walkthrough, we will be satisfied when the test method compiles.
Add a text template file
Add a text template file, and set the output extension to .cs.
1. In Solution Explorer , right-click the project, click Add , and then click New Item .
2. In the Add New Item dialog box select Text Template from the Templates pane.
NOTE
Make sure that you add a Text Template, and not a Preprocessed Text Template.
3. In the file, in the template directive, change the hostspecific attribute to true .
This change will enable the template code to gain access to the Visual Studio services.
4. In the output directive, change the extension attribute to ".cs", so that the template generates a C# file. In a
Visual Basic project, you would change it to ".vb".
5. Save the file. At this stage, the text template file should contain these lines:
Notice that a .cs file appears in Solution Explorer as a subsidiary of the template file. You can see it by clicking
[+] next to the name of the template file. This file is generated from the template file whenever you save or
move the focus away from the template file. The generated file will be compiled as part of your project.
For convenience while you develop the template file, arrange the windows of the template file and the
generated file so that you can see them next to each other. This lets you see immediately the output of your
template. You will also notice that when your template generates invalid C# code, errors will appear in the error
message window.
Any edits you perform directly in the generated file will be lost whenever you save the template file. You should
therefore either avoid editing the generated file, or edit it only for short experiments. It is sometimes useful to
try a short fragment of code in the generated file, where IntelliSense is in operation, and then copy it to the
template file.
Develop the Text Template
Following the best advice on agile development, we will develop the template in small steps, clearing some of
the errors at each increment, until the test code compiles and runs correctly.
Prototype the code to be generated
The test code requires a class for each node in the file. Therefore, some of the compilation errors will go away if
you append these lines to the template, and then save it:
class Catalog {}
class Artist {}
class Song {}
This helps you see what is required, but the declarations should be generated from the node types in the sample
XML file. Delete these experimental lines from the template.
Generate application code from the model XML file
To read the XML file and generate class declarations, replace the template content with the following template
code:
Replace the file path with the correct path for your project.
Notice the code block delimiters <#...#> . These delimiters bracket a fragment of the program code that
generates the text. The expression block delimiters <#=...#> bracket an expression that can be evaluated to a
string.
When you are writing a template that generates source code for your application, you are dealing with two
separate program texts. The program inside the code block delimiters runs every time that you save the
template or move the focus to another window. The text that it generates, which appears outside the delimiters,
is copied to the generated file and becomes part of your application code.
The <#@assembly#> directive behaves like a reference, making the assembly available to the template code. The
list of assemblies seen by the template is separate from the list of References in the application project.
The <#@import#> directive acts like a using statement, allowing you to use the short names of classes in the
imported namespace.
Unfortunately, although this template generates code, it produces a class declaration for every node in the
example XML file, so that if there are several instances of the <song> node, several declarations of the class
song will appear.
Read the model file, then generate the code
Many text templates follow a pattern in which the first part of the template reads the source file, and the second
part generates the template. We need to read all of the example file to summarize the node types that it
contains, and then generate the class declarations. Another <#@import#> is needed so that we can use
Dictionary<>:
If you prefer class names to begin with an uppercase letter, you can replace the last part of the template with the
following template code:
At this stage, the generated .cs file contains the following declarations:
More details such as properties for the child nodes, attributes, and inner text can be added using the same
approach.
Access the Visual Studio API
Setting the hostspecific attribute of the <#@template#> directive allows the template to obtain access to the
Visual Studio API. The template can use this to obtain the location of the project files, to avoid using an absolute
file path in the template code.
Conclusion
This walkthrough demonstrates several techniques and benefits of code generation:
Code generation is the creation of part of the source code of your application from a model. The model
contains information in a form suited to the application domain, and may change over the lifetime of the
application.
Strong typing is one benefit of code generation. While the model represents information in a form more
suitable to the user, the generated code allows other parts of the application to deal with the information
using a set of types.
IntelliSense and the compiler help you create code that adheres to the schema of the model, both when
you write new code and when the schema is updated.
The addition of a single uncomplicated template file to a project can provide these benefits.
A text template can be developed and tested rapidly and incrementally.
In this walkthrough, the program code is actually generated from an instance of the model, a representative
example of the XML files that the application will process. In a more formal approach, the XML schema would be
the input to the template, in the form of an .xsd file or a domain-specific language definition. That approach
would make it easier for the template to determine characteristics such as the multiplicity of a relationship.
See also
Design-Time Code Generation by using T4 Text Templates
Writing a T4 Text Template
Invoke text transformation in the build process
4/13/2022 • 6 minutes to read • Edit Online
Text transformation can be invoked as part of the build process of a Visual Studio solution. There are build tasks
that are specialized for text transformation. The T4 build tasks run design-time text templates, and they also
compile run-time (preprocessed) text templates.
There are some differences in what the build tasks can do, depending on which build engine you use. When you
build the solution in Visual Studio, a text template can access the Visual Studio API (EnvDTE) if the
hostspecific="true" attribute is set. But that isn't true when you build the solution from the command line or
when you initiate a server build through Visual Studio. In those cases, the build is performed by MSBuild and a
different T4 host is used. This means that you can't access things like project file names in the same way when
you build a text template using MSBuild. However, you can pass environment information into text templates
and directive processors by using build parameters.
NOTE
The Text Template Transformation component is automatically installed as part of the Visual Studio extension
development workload. You can also install it from the Individual components tab of Visual Studio Installer, under the
SDKs, libraries, and frameworks category. Install the Modeling SDK component from the Individual components
tab.
If your build server runs on a computer that doesn't have Visual Studio installed, copy the following files to the
build computer from your development machine:
%ProgramFiles(x86)%\Microsoft Visual
Studio\2019\Community\MSBuild\Microsoft\VisualStudio\v16.0\TextTemplating
Microsoft.VisualStudio.TextTemplating.Sdk.Host.15.0.dll
Microsoft.TextTemplating.Build.Tasks.dll
Microsoft.TextTemplating.targets
%ProgramFiles(x86)%\Microsoft Visual
Studio\2019\Community\VSSDK\VisualStudioIntegration\Common\Assemblies\v4.0
Microsoft.VisualStudio.TextTemplating.15.0.dll
Microsoft.VisualStudio.TextTemplating.Interfaces.15.0.dll
Microsoft.VisualStudio.TextTemplating.VSHost.15.0.dll
%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Community\Common7\IDE\PublicAssemblies
Microsoft.VisualStudio.TextTemplating.Modeling.15.0.dll
TIP
If you get a MissingMethodException for a Microsoft.CodeAnalysis method when running TextTemplating build targets
on a build server, make sure the Roslyn assemblies are in a directory named Roslyn that's in the same directory as the
build executable (for example, msbuild.exe).
Edit the project file
Edit your project file to configure some of the features in MSBuild, for example, importing the text
transformation targets.
In Solution Explorer , choose Unload from the right-click menu of your project. That allows you to edit the
.csproj or .vbproj file in the XML editor. When you've finished editing, choose Reload .
- or -
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
<Import
Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v17.0\TextTemplating\Microsoft.TextTemplating.targe
ts" />
<Import
Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v16.0\TextTemplating\Microsoft.TextTemplating.targe
ts" />
<Import
Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\TextTemplating\Microsoft.TextTemplating.targe
ts" />
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
</PropertyGroup>
Overwrite files that are read-only, for example, because they are not checked out:
<PropertyGroup>
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
</PropertyGroup>
<PropertyGroup>
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>
By default, the T4 MSBuild task regenerates an output file if it's older than:
its template file
any files that are included
any files that have previously been read by the template or by a directive processor that it uses
This is a more powerful dependency test than is used by the Transform All Templates command in
Visual Studio, which only compares the dates of the template and output file.
To perform just the text transformations in your project, invoke the TransformAll task:
msbuild myProject.csproj /t:TransformAll
Source control
There is no specific built-in integration with a source control system. However, you can add your own extensions,
for example, to check out and check in a generated file. By default, the text transform task avoids overwriting a
file that is marked as read-only. When such a file is encountered, an error is logged in the Visual Studio Error
List, and the task fails.
To specify that read-only files should be overwritten, insert this property:
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
Unless you customize the postprocessing step, a warning will be logged in the Error List when a file is
overwritten.
<PropertyGroup>
<BeforeTransform>CustomPreTransform</BeforeTransform>
<AfterTransform>CustomPostTransform</AfterTransform>
</PropertyGroup>
<Target Name="CustomPreTransform">
<Message Text="In CustomPreTransform..." Importance="High" />
</Target>
<Target Name="CustomPostTransform">
<Message Text="In CustomPostTransform..." Importance="High" />
</Target>
<ItemGroup>
<None Include="MyTemplate.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<OutputFilePath>MyFolder</OutputFilePath>
<LastGenOutput>MyTemplate.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="MyTemplate.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<OutputFileName>MyOutputFileName.cs</OutputFileName>
<LastGenOutput>MyTemplate.cs</LastGenOutput>
</None>
</ItemGroup>
Specifying an OutputFileName or OutputFilePath isn't recommended if you are also transforming templates
inside Visual Studio using Transform All or running the single file generator. You'll end up with different file
paths depending on how you triggered the transformation. This can be confusing.
<ItemGroup>
<T4ReferencePath Include="$(VsIdePath)PublicAssemblies\" />
<!-- Add more T4ReferencePath items here -->
</ItemGroup>
To set the folders that will be searched for include files, provide a semicolon-separated list. Usually you add to
the existing folder list.
<PropertyGroup>
<IncludeFolders>
$(IncludeFolders);$(MSBuildProjectDirectory)\Include;AnotherFolder;And\Another</IncludeFolders>
</PropertyGroup>
In a text template, set hostspecific in the template directive. Use the parameter directive to get values:
NOTE
ResolveParameterValue gets data from T4ParameterValues only when you use MSBuild. When you transform the
template using Visual Studio, the parameters have default values.
<!-- Tell the MSBuild T4 task to make the property available: -->
<ItemGroup>
<T4ParameterValues Include="myLibFolder">
<Value>$(myLibFolder)</Value>
</T4ParameterValues>
</ItemGroup>
Now you can use your project property in assembly and include directives:
These directives get values from T4parameterValues both in MSBuild and in Visual Studio hosts.
Q&A
Why would I want to transform templates in the build ser ver? I already transformed templates in
Visual Studio before I checked in my code.
If you update an included file or another file read by the template, Visual Studio doesn't transform the file
automatically. Transforming templates as part of the build makes sure that everything's up to date.
What other options are there for transforming text templates?
The TextTransform utility can be used in command scripts. In most cases, it's easier to use MSBuild.
Invoke Text Transformation in a Visual Studio extension.
Design-time text templates are transformed by Visual Studio.
Run-time text templates are transformed at run time in your application.
See also
There's good guidance in the T4 MSbuild template at
%ProgramFiles(x86)%\Microsoft Visual
Studio\2017\Enterprise\msbuild\Microsoft\VisualStudio\v15.0\TextTemplating\Microsoft.TextTemplating.targets
Arbitrary Code
When you write a template, you can put any code within the <# #> tags. This allows arbitrary code to be
executed from within a text template.
Be sure you obtain templates from trusted sources. Make sure to warn the end-users of your application not to
execute templates that do not come from trusted sources.
TextTransform.exe is a command-line tool that you can use to transform a text template. When you call
TextTransform.exe, you specify the name of a text template file as an argument. TextTransform.exe calls the text
transformation engine and processes the text template. TextTransform.exe is usually called from scripts. However,
it is not usually required, because you can perform text transformation either in Visual Studio or in the build
process.
NOTE
If you want to perform text transformation as part of a build process, consider using the MSBuild text transformation
task. For more information, see Code Generation in a Build Process. In a machine on which Visual Studio is installed, you
can also write an application or Visual Studio Extension that can transform text templates. For more information, see
Processing Text Templates by using a Custom Host.
Syntax
TextTransform [<options>] <templateName>
Parameters
A RGUM EN T DESC RIP T IO N
templateName Identifies the name of the template file that you want to
transform.
O P T IO N DESC RIP T IO N
-out <filename> The file to which the output of the transform is written.
-P "%VSSHELLFOLDER%\Common7\IDE\PublicAssemblies"
-dp <processorName>!<className>! The name, full type name, and assembly of a directive
<assemblyName|codeBase> processor that can be used to process custom directives
within the text template.
Always type the '!' marks, even if you omit the optional
processor and directive names. For example:
-a !!param!value
-h Provides help.
Related topics
TA SK TO P IC
Generate files in a Visual Studio solution. Design-Time Code Generation by using T4 Text Templates
Write directive processors to transform your own data Customizing T4 Text Transformation
sources.
TA SK TO P IC
Write a text templating host that allows you to invoke text Processing Text Templates by using a Custom Host
templates from your own application.
Run-Time Text Generation with T4 Text Templates
4/13/2022 • 10 minutes to read • Edit Online
You can generate text strings in your application at run time by using Visual Studio runtime text templates. The
computer where the application executes does not have to have Visual Studio. Runtime templates are
sometimes called "preprocessed text templates" because at compile time, the template generates code that is
executed at run time.
Each template is a mixture of the text as it will appear in the generated string, and fragments of program code.
The program fragments supply values for the variable parts of the string, and also control conditional and
repeated parts.
For example, the following template could be used in an application that creates an HTML report.
Notice that the template is an HTML page in which the variable parts have been replaced with program code.
You could begin the design of such a page by writing a static prototype of the HTML page. You could then
replace the table and other variable parts with program code that generates the content that varies from one
occasion to the next.
Using a template in your application makes it is easier to see the final form of the output than you could in, for
example, a long series of write statements. Making changes to the form of the output is easier and more reliable.
NOTE
The template file name will be used as a class name in the generated code. Therefore, it should not have spaces or
punctuation.
4. Choose Add .
A new file is created that has extension .tt . Its Custom Tool property is set to
TextTemplatingFilePreprocessor . It contains the following lines:
NOTE
If the property is already set, make sure that it is TextTemplatingFilePreprocessor and not
TextTemplatingFileGenerator . This can happen if you include a file that already has the extension .tt .
3. Change the file name extension to .tt . Although this step is optional, it helps you avoid opening the file in
an incorrect editor.
4. Remove any spaces or punctuation from the main part of the file name. For example "My Web Page.tt"
would be incorrect, but "MyWebPage.tt" is correct. The file name will be used as a class name in the
generated code.
5. Insert the following line at the beginning of the file. If you are working in a Visual Basic project, replace
"C#" with "VB".
<#@ template language="C#" #>
<html><body>
<h1>Sales for January</h2>
<!-- table to be inserted here -->
This report is Company Confidential.
</body></html>
Embedded program code
You can insert program code between <# and #> . For example:
<table>
<# for (int i = 1; i <= 10; i++)
{ #>
<tr><td>Test name <#= i #> </td>
<td>Test value <#= i * i #> </td> </tr>
<# } #>
</table>
<table>
<#
For i As Integer = 1 To 10
#>
<tr><td>Test name <#= i #> </td>
<td>Test value <#= i*i #> </td></tr>
<#
Next
#>
</table>
Notice that statements are inserted between <# ... #> and expressions are inserted between <#= ... #> . For
more information, see Writing a T4 Text Template.
To place the generated class in a particular namespace, set the Custom Tool Namespace property of the text
template file.
Debugging Runtime Text Templates
Debug and test runtime text templates in the same way as ordinary code.
You can set a breakpoint in a text template. If you start the application in debugging mode from Visual Studio,
you can step through the code and evaluate watch expressions in the usual way.
Passing parameters in the constructor
Usually a template must import some data from other parts of the application. To make this easy, the code built
by the template is a partial class. You can create another part of the same class in another file in your project.
That file can include a constructor with parameters, properties and functions that can be accessed both by the
code that is embedded in the template, and by the rest of the application.
For example, you could create a separate file MyWebPageCode.cs :
<h2>Sales figures</h2>
<table>
<# foreach (MyDataItem item in m_data.Items)
// m_data is declared in MyWebPageCode.cs
{ #>
<tr><td> <#= item.Name #> </td>
<td> <#= item.Value #> </td></tr>
<# } // end of foreach
#>
</table>
Namespace My.Templates
Partial Public Class MyWebPage
Private m_data As MyData
Public Sub New(ByVal data As MyData)
m_data = data
End Sub
End Class
End Namespace
These directives must be placed at the beginning of the file, immediately after the <#@template directive.
Shared content
If you have text that is shared between several templates, you can place it in a separate file and include it in each
file in which it should appear:
The included content can contain any mixture of program code and plain text, and it can contain other include
directives and other directives.
The include directive can be used anywhere within the text of a template file or an included file.
Inheritance between Run-Time Text Templates
You can share content between run-time templates by writing a base class template, which can be abstract. Use
the inherits parameter of the <@#template#> directive to reference another runtime template class.
Inheritance pattern: Fragments in Base Methods
In the pattern used in the example that follows, notice the following points:
The base class SharedFragments defines methods within class feature blocks <#+ ... #> .
The base class contains no free text. Instead, all of its text blocks occur inside the class feature methods.
The derived class invokes the methods defined in SharedFragments .
The application calls the TextTransform() method of the derived class, but does not transform the base
class SharedFragments .
Both the base and derived classes are runtime text templates; that is, the Custom Tool property is set to
TextTemplatingFilePreprocessor .
SharedFragments.tt:
MyTextTemplate1.tt:
MyProgram.cs:
...
MyTextTemplate1 t1 = new MyTextTemplate1();
string result = t1.TransformText();
Console.WriteLine(result);
begin 1
Shared Text 2
end 1
DerivedTemplate1.tt:
#>
End material for DerivedTemplate1.
<#+
// Provide a fragment specific to this derived template:
Application code:
...
DerivedTemplate1 t1 = new DerivedTemplate1();
string result = t1.TransformText();
Console.WriteLine(result);
Resulting output:
See also
Code Generation and T4 Text Templates
Writing a T4 Text Template
T4 Toolbox
Writing a T4 Text Template
4/13/2022 • 8 minutes to read • Edit Online
A text template contains the text that will be generated from it. For example, a template that creates a web page
will contain "<html>..." and all the other standard parts of an HTML page. Inserted into the template are control
blocks, which are fragments of program code. Control blocks provide varying values and allow parts of the text
to be conditional and repeated.
This structure makes a template easy to develop, because you can start with a prototype of the generated file,
and incrementally insert control blocks that vary the result.
Text templates are composed of the following parts:
Directives - elements that control how the template is processed.
Text blocks - content that is copied directly to the output.
Control blocks - program code that inserts variable values into the text, and controls conditional or
repeated parts of the text.
To try the examples in this topic, copy them into a template file as described in Design-Time Code Generation by
using T4 Text Templates. After editing the template file, save it, and then inspect the output .txt file.
Directives
Text template directives provide general instructions to the text templating engine about how to generate the
transformation code and the output file.
For example, the following directive specifies that the output file should have a .txt extension:
Text blocks
A text block inserts text directly into the output file. There is no special formatting for text blocks. For example,
the following text template will produce a text file that contains the word "Hello":
Control blocks
Control blocks are sections of program code that are used to transform the templates. The default language is
C#, but to use Visual Basic, you can write this directive at the beginning of the file:
The language in which you write the code in the control blocks is unrelated to the language of the text that is
generated.
Standard control blocks
A standard control block is a section of program code that generates part of the output file.
You can mix any number of text blocks and standard control blocks in a template file. However, you cannot place
one control block inside another. Each standard control block is delimited by the symbols <# ... #> .
For example, the following control block and text block cause the output file to contain the line "0, 1, 2, 3, 4
Hello!":
<#
for(int i = 0; i < 4; i++)
{
Write(i + ", ");
}
Write("4");
#> Hello!
Instead of using explicit Write() statements, you can interleave text and code. The following example prints
"Hello!" four times:
<#
for(int i = 0; i < 4; i++)
{
#>
Hello!
<#
}
#>
You can insert a text block wherever a Write(); statement would be allowed in the code.
NOTE
When you embed a text block within a compound statement such as a loop or conditional, always use braces {...} to
contain the text block.
For example, the following control block causes the output file to contain "5":
<#= 2 + 3 #>
For example, the following template file declares and uses a method:
Class features must be placed at the end of the file in which they are written. However, you can <#@include#> a
file that contains a class feature, even if the include directive is followed by standard blocks and text.
For more information about control blocks, see Text Template Control Blocks.
Class feature blocks can contain text blocks
You can write a method that generates text. For example:
List of Squares:
<#
for(int i = 0; i < 4; i++)
{ WriteSquareLine(i); }
#>
End of list.
<#+ // Class feature block
private void WriteSquareLine(int i)
{
#>
The square of <#= i #> is <#= i*i #>.
<#+
}
#>
It is particularly useful to place a method that generates text in a separate file that can be included by more than
one template.
You should use absolute path names, or use standard macro names in the path name. For example:
You can use as many assembly and import directives as you want. You must place them before text and control
blocks.
For more information, see T4 Import Directive.
Including code and text
The include directive inserts text from another template file. For example, this directive inserts the content of
test.txt .
The included content is processed almost as if it were part of the including text template. However, you can
include a file that contains a class feature block <#+...#> even if the include directive is followed by ordinary
text and standard control blocks.
For more information, see T4 Include Directive.
Utility methods
There are several methods such as Write() that are always available to you in a control block. They include
methods for helping you indent the output, and for reporting errors.
You can also write your own set of utility methods.
For more information, see Text Template Utility Methods.
Load a file as a navigable model . A more powerful method is to read the data as a model, which your text
template code can navigate. For example, you can load an XML file and navigate it with XPath expressions. You
could also use xsd.exe to create a set of classes with which you can read the XML data.
Edit the model file in a diagram or form. Domain-Specific Language Tools provides tools that let you edit a
model as a diagram or Windows form. This makes it easier to discuss the model with users of the generated
application. Domain-Specific Language Tools also creates a set of strongly-typed classes that reflect the structure
of the model. For more information, see Generating Code from a Domain-Specific Language.
Relative file paths in design-time templates
In a design-time text template, if you want to reference a file in a location relative to the text template, use
this.Host.ResolvePath() . You must also set hostspecific="true" in the template directive:
You can also obtain other services that are provided by the host. For more information, see Accessing Visual
Studio or other Hosts from a Template.
Design-time Text Templates run in a separate AppDomain
You should be aware that a design-time text template runs in an AppDomain that is separate from the main
application. In most cases this is not important, but you might discover restrictions in certain complex cases. For
example, if you want to pass data in or out of the template from a separate service, then the service must
provide a serializable API.
(This isn't true of a run-time text template, which provides code that is compiled along with the rest of your
code.)
Editing Templates
Specialized text template editors can be downloaded from the Extension Manager Online Gallery. On the Tools
menu, click Extension Manager . Click Online Galler y , and then use the search tool.
Related topics
TA SK TO P IC
Generate files in a Visual Studio solution. Design-Time Code Generation by using T4 Text Templates
Run text generation outside Visual Studio. Generating Files with the TextTransform Utility
Transform your data in the form of a domain-specific Generating Code from a Domain-Specific Language
language.
Write directive processors to transform your own data Customizing T4 Text Transformation
sources.
T4 Text Template Directives
4/13/2022 • 2 minutes to read • Edit Online
All attribute values must be surrounded by double quotation marks. If the value itself contains quotation marks,
they must be escaped with the \ character.
Directives are typically the first elements in a template file or an included file. You should not place them inside a
code block <#...#> , nor after a class feature block <#+...#> .
T4 Template Directive
T4 Parameter Directive
T4 Output Directive
T4 Assembly Directive
T4 Import Directive
T4 Include Directive
T4 CleanUpBehavior directive
In addition, you can create your own directives. For more information, see Creating Custom T4 Text Template
Directive Processors. If you use the Visualization and Modeling SDK to create a domain-specific language (DSL),
a directive processor will be generated as part of your DSL.
T4 Template Directive
4/13/2022 • 6 minutes to read • Edit Online
A Visual Studio T4 text template usually starts with a template directive, which specifies how the template
should be processed. There should be no more than one template directive in a text template and any files that it
includes.
For a general overview of writing text templates, see Writing a T4 Text Template.
The template directive has several attributes that allow you to specify different aspects of the transformation.
All the attributes are optional.
compilerOptions attribute
Example:
compilerOptions="optimize+"
Valid values:
Any valid compiler options.
Ignored for run-time (preprocessed) templates.
These options are applied when the template has been converted into Visual C# or Visual Basic, and the
resulting code is compiled.
culture attribute
Example:
culture="de-CH"
Valid values:
"", the invariant culture, which is the default.
A culture expressed as a string in the form xx-XX. For example, en-US, ja-JP, de-CH, de-DE. For more information,
see System.Globalization.CultureInfo.
The culture attribute specifies the culture to use when an expression block is converted to text.
debug attribute
Example:
debug="true"
Valid values:
true
false (default)
If the debug attribute is true , the intermediate code file will contain information that enables the debugger to
identify more accurately the position in your template where a break or exception occurred.
For design-time templates the intermediate code file will be written to your %TEMP% directory.
To run a design-time template in the debugger, save the text template, then open the shortcut menu of the text
template in Solution Explorer, and choose Debug T4 Template .
hostspecific attribute
Example:
hostspecific="true"
Valid values:
true
false (default)
trueFromBase
If you set the value of this attribute to true , a property named Host is added to the class generated by your
text template. The property is a reference to the host of the transformation engine, and is declared as
ITextTemplatingEngineHost. If you have defined a custom host, you can cast it to the custom host type.
Because the type of this property depends on the type of host, it is only useful if you are writing a text template
that works only with a specific host. It's applicable to design-time templates, but not run-time templates.
When hostspecific is true and you are using Visual Studio, you can cast this.Host to IServiceProvider to
access Visual Studio features. You can also use Host.ResolvePath(filename) to obtain the absolute path of a file
in the project. For example:
<#
// Find a path within the current project:
string myFile = File.ReadAllText(this.Host.ResolvePath("MyFile.txt"));
#>
Content of myFile is:
<#= myFile #>
If you use the inherits and hostspecific attributes together, specify host="trueFromBase" in the derived class
and host="true" in the base class. This avoids a double definition of the Host property in the generated code.
language attribute
Example:
language="VB"
Valid values:
C# (default)
VB
The language attribute specifies the language (Visual Basic or Visual C#) to use for the source code in statement
and expression blocks. The intermediate code file from which the output is generated will use this language. This
language is not related to the language that your template generates, which can be any kind of text.
For example:
inherits attribute
You can specify that the program code of your template can inherit from another class, which can also be
generated from a text template.
Inheritance in a run-time (preprocessed) text template
You can use inheritance between run-time text templates to create a basic template that has several derived
variants. Run-time templates are those that have the Custom Tool property set to
TextTemplatingFilePreprocessor . A run-time template generates code that you can call in your application to
create the text defined in the template. For more information, see Run-Time Text Generation with T4 Text
Templates.
If you do not specify an inherits attribute, a base class and a derived class are generated from your text
template. When you specify an inherits attribute, only the derived class is generated. You can write a base
class by hand, but it must provide the methods that are used by the derived class.
More typically, you specify another preprocessed template as the base class. The base template provides
common blocks of text, which can be interleaved with text from the derived templates. You can use class feature
blocks <#+ ... #> to define methods that contain text fragments. For example, you can place the framework of
the output text in the base template, providing virtual methods that can be overridden in derived templates:
Run-time (preprocessed) text template BaseTemplate.tt:
This is the common header.
<#
SpecificFragment1();
#>
A common central text.
<#
SpecificFragment2();
#>
This is the common footer.
<#+
// Declare abstract methods
protected virtual void SpecificFragment1() { }
protected virtual void SpecificFragment2() { }
#>
Console.WriteLine(new DerivedTemplate().TransformText());
Resulting output:
You can build the base and derived classes in different projects. Remember to add the base project or assembly
to the derived project's references.
You can also use an ordinary hand-written class as the base class. The base class must provide the methods
used by the derived class.
WARNING
If you use the inherits and hostspecific attributes together, specify hostspecific="trueFromBase" in the derived
class and host="true" in the base class. This avoids a double definition of the Host property in the generated code.
linePragmas attribute
Example:
linePragmas="false"
Valid values:
true (default)
false
Setting this attribute to false removes the tags that identify your line numbers within the generated code. This
means that the compiler will report any errors by using line numbers of the generated code.This gives you more
debugging options, as you can choose to debug either the text template or the generated code.
This attribute can also help if you're finding the absolute filenames in pragmas are causing distracting merges
under source code control.
visibility attribute
Example:
visibility="internal"
Valid values:
public (default)
internal
In a runtime text template, this sets the visibility attribute of the generated class. By default, the class is part of
the public API of your code, but by setting visibility="internal" you can make sure that only your code can
use the text-generating class.
T4 Parameter Directive
4/13/2022 • 2 minutes to read • Edit Online
In a Visual Studio text template, the parameter directive declares properties in your template code that are
initialized from values passed in from the external context. You can set these values if you write code that
invokes text transformation.
The parameter directive declares properties in your template code that are initialized from values passed in
from the external context. You can set these values if you write code that invokes text transformation. The values
can be passed either in the Session dictionary, or in CallContext.
You can declare parameters of any remotable type. That is, the type must be declared with SerializableAttribute,
or it must derive from MarshalByRefObject. This allows parameter values to be passed into the AppDomain in
which the template is processed.
For example, you could write a text template with the following content:
In Visual Studio text templates, the output directive is used to define the file name extension and encoding of
the transformed file.
For example, if your Visual Studio project includes a template file named MyTemplate.tt which contains the
following directive:
<#@output extension=".cs"#>
There should be no more than one output directive in each text template.
extension attribute
Specifies the file name extension of the generated text output file.
The default value is .cs
Examples: <#@ output extension=".txt" #>
encoding attribute
Specifies the encoding to use when the output file is generated. For example:
<#@ output encoding="utf-8"#>
The default value is the encoding used by the text template file.
Acceptable Values: us-ascii
utf-16BE
utf-16
utf-8
utf-7
utf-32
0 (System default)
In general, you can use the WebName string or the CodePage number of any of the encodings returned by
System.Text.Encoding.GetEncodings.
T4 Assembly Directive
4/13/2022 • 2 minutes to read • Edit Online
In a Visual Studio design-time text template, the assembly directive loads an assembly so that your template
code can use its types. The effect is similar to adding an assembly reference in a Visual Studio project.
For a general overview of writing text templates, see Writing a T4 Text Template.
NOTE
You do not need the assembly directive in a run-time (preprocessed) text template. Instead, add the necessary
assemblies to the References of your Visual Studio project.
The assembly directive has no effect in a preprocessed text template. Instead, include the necessary references in
the References section of your Visual Studio project. For more information, see Run-Time Text Generation with
T4 Text Templates.
Standard Assemblies
The following assemblies are loaded automatically, so that you do not need to write assembly directives for
them:
Microsoft.VisualStudio.TextTemplating.1*.dll
System.dll
WindowsBase.dll
If you use a custom directive, the directive processor might load additional assemblies. For example, if
you write templates for a domain-specific language (DSL), you do not need to write assembly directives
for the following assemblies:
Microsoft.VisualStudio.Modeling.Sdk.1*.dll
Microsoft.VisualStudio.Modeling.Sdk.Diagrams.1*.dsl
Microsoft.VisualStudio.TextTemplating.Modeling.1*.dll
<!-- Tell the MSBuild T4 task to make the property available: -->
<ItemGroup>
<T4ParameterValues Include="myLibFolder">
<Value>$(myLibFolder)</Value>
</T4ParameterValues>
</ItemGroup>
Now you can use your project property in text templates, which transform correctly in both Visual Studio and
MSBuild:
See also
T4 Include Directive
T4 Import Directive
4/13/2022 • 2 minutes to read • Edit Online
In the code blocks of a Visual Studio T4 text template, the import directive allows you to refer to elements in
another namespace without providing a fully qualified name. It is the equivalent of using in C# or imports in
Visual Basic.
For a general overview of writing T4 text templates, see Writing a T4 Text Template.
In this example, template code can omit an explicit namespace for members of System.IO:
Standard Imports
The following namespace is imported automatically, so that you do not need to write an import directive for it:
System
In addition, if you use a custom directive, the directive processor might import some namespaces
automatically.
For example, if you write templates for a domain-specific language (DSL), you do not need to write
import directives for the following namespaces:
Microsoft.VisualStudio.Modeling
See also
T4 Assembly Directive
T4 Include Directive
4/13/2022 • 3 minutes to read • Edit Online
In a text template in Visual Studio, you can include text from another file by using an <#@include#> directive. You
can place include directives anywhere in a text template before the first class feature block <#+ ... #> . The
included files can also contain include directives, and other directives. This allows you to share template code
and boilerplate text between templates.
The name of an included file does not have to use the extension ".tt" .
You might want to use another extension such as ".t4" for included files. This is because, when you add
a .tt file to a project, Visual Studio automatically sets its Custom Tool property to
TextTemplatingFileGenerator . You do not usually want included files to be transformed individually.
On the other hand, you should be aware that in some cases, the file extension affects which additional
folders will be searched for include files. This might be important when you have an included file that
includes other files.
The included content is processed almost as if it were part of the including text template. However, you
can include a file that contains a class feature block <#+...#> even if the include directive is followed by
ordinary text and standard control blocks.
Use once="true" to ensure that a template is included only once, even if it's invoked from more than one
other include file.
This feature makes it easy to build up a library of reusable T4 snippets that you can include at will without
worrying that some other snippet has already included them. For example, suppose you have a library of
very fine-grained snippets that deal with template processing and C# generation. In turn, these are used
by some more task-specific utilities such as generating exceptions, which you can then use from any
more application-specific template. If you draw the dependency graph, you see that some snippets would
be included several times. But the once parameter prevents the subsequent inclusions.
MyTextTemplate.tt:
TextFile1.t4:
TextFile2.t4:
<!-- Tell the MSBuild T4 task to make the property available: -->
<ItemGroup>
<T4ParameterValues Include="myIncludeFolder">
<Value>$(myIncludeFolder)</Value>
</T4ParameterValues>
</ItemGroup>
Now you can use your project property in text templates, which transform correctly in both Visual Studio and
MSBuild:
To delete the appDomain after processing a text template, include the following line:
Text templates are processed in an appDomain that is separate from the host process. In most cases, when one
text template has been processed, the appdomain is used again to process the next template. But if you specify
CleanupBehavior, the appDomain is deleted and the next template will be processed in a new appDomain.
This slows text processing, but can be useful to make sure that resources are disposed.
This directive works only in the Visual Studio host.
Text Template Control Blocks
4/13/2022 • 4 minutes to read • Edit Online
Control blocks let you write code in your text template in order to vary the output. There are three kinds of
control blocks, which are distinguished by their opening brackets:
<# Standard control blocks #> can contain statements.
<#= Expression control blocks #> can contain expressions.
<#+ Class feature control blocks #> can contain methods, fields and properties.
<#
List<string> allAttributes = new List<string>();
XmlDocument xDoc = new XmlDocument();
xDoc.Load(@"E:\CSharp\Overview.xml");
XmlAttributeCollection attributes = xDoc.Attributes;
if (attributes.Count > 0)
{
foreach (XmlAttribute attr in attributes)
{
allAtributes.Add(attr.Name);
}
}
#>
You can embed plain text inside a compound statement such as if or for . For example, this fragment
generates an output line in each loop iteration:
<#
foreach (XmlAttribute attr in attributes)
{
#>
Found another one!
<#
allAtributes.Add(attr.Name);
}
#>
WARNING
Always use {...} to delimit nested statements that contain embedded plain text. The following example might not work
properly:
<# if (ShouldPrint) #> Some text. -- WRONG
<#
XmlDocument xDoc = new XmlDocument();
xDoc.Load(@"E:\CSharp\Overview.xml");
XmlAttributeCollection attributes = xDoc.Attributes;
if (attributes != null)
{
foreach (XmlAttribute attr in attributes)
{
#><#= attr.Name #><#
}
}
#>
<#+
private string FixAttributeName(string name)
{
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name);
}
#>
NOTE
A class feature control block must not be followed by standard control blocks in the same template file. However, this
restriction does not apply to the result of using <#@include#> directives. Each included file can have standard blocks
followed by class feature blocks.
You can create a function that generates output by embedding text and expression blocks inside a class feature
control block. For example:
<#+
private void OutputFixedAttributeName(string name)
{
#>
Attribute: <#= CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name) #>
<#+ // <<< Notice that this is also a class feature block.
}
#>
You could call this function from a standard block or from another class feature block:
<#
int x = 10;
while (x-- > 0)
{
#>
<#= x #>
<# } #>
Refactoring. In order to keep your text templates short and easy to understand, it is strongly
recommended that you avoid repetitive code either by factoring the reusable code into helper functions
in class feature blocks or by creating your own text template class that inherits from the
Microsoft.VisualStudio.TextTemplating.TextTransformation class.
Text Template Utility Methods
4/13/2022 • 2 minutes to read • Edit Online
There are several methods that are always available to you when you write code in a Visual Studio text template.
These methods are defined in TextTransformation.
TIP
You can also use other methods and services provided by the host environment in a regular (not preprocessed) text
template. For example, you can resolve file paths, log errors, and get services provided by Visual Studio and any loaded
packages. For more information, see Accessing Visual Studio from a Text Template.
Write methods
You can use the Write() and WriteLine() methods to append text inside a standard code block, instead of
using an expression code block. The following two code blocks are functionally equivalent.
Code block with an expression block
<#
int i = 10;
while (i-- > 0)
{ #>
<#= i #>
<# }
#>
<#
int i = 10;
while (i-- > 0)
{
WriteLine((i.ToString()));
}
#>
You may find it helpful to use one of these utility methods instead of an expression block inside a long code
block with nested control structures.
The Write() and WriteLine() methods have two overloads, one that takes a single string parameter and one
that takes a composite format string plus an array of objects to include in the string (like the
Console.WriteLine() method). The following two uses of WriteLine() are functionally equivalent:
<#
string msg = "Say: {0}, {1}, {2}";
string s1 = "hello";
string s2 = "goodbye";
string s3 = "farewell";
Indentation methods
You can use indentation methods to format the output of your text template. The TextTransformation class has a
CurrentIndent string property that shows the current indentation in the text template and an indentLengths
field that is a list of the indentations that have been added. You can add an indentation with the PushIndent()
method and subtract an indentation with the PopIndent() method. If you want to remove all indentations, use
the ClearIndent() method. The following code block shows the use of these methods:
<#
WriteLine(CurrentIndent + "Hello");
PushIndent(" ");
WriteLine(CurrentIndent + "Hello");
PushIndent(" ");
WriteLine(CurrentIndent + "Hello");
ClearIndent();
WriteLine(CurrentIndent + "Hello");
PushIndent(" ");
WriteLine(CurrentIndent + "Hello");
#>
Hello
Hello
Hello
Hello
Hello
<#
try
{
string str = null;
Write(str.Length.ToString());
}
catch (Exception e)
{
Error(e.Message);
}
#>
The type of this.Host depends on the type of host in which the template is executing. In a template that is
running in Visual Studio, you can cast this.Host to IServiceProvider to gain access to services such as the IDE.
For example:
Use the assembly directive to reference the assembly where the compiled class can be found.
Access Visual Studio or other hosts from a text
template
4/13/2022 • 2 minutes to read • Edit Online
In a text template, you can use methods and properties that are exposed by the host that executes the template.
Visual Studio is an example of a host.
NOTE
You can use host methods and properties in regular text templates, but not in preprocessed text templates.
You can use escape sequences in text templates to generate text template tags and (in C# code only) to escape
control characters and quotation marks.
To print open and close tags for a standard code block to the output file, escape the tags as follows:
You can do the same with other text template directive and code block tags.
If a text block includes strings used to escape text template tags, then you may use the following escape
sequences:
If a text template tag is preceded by an even number of escape (\) characters the template parser will
include half of the escape characters and include the sequence as a text template tag. For example, if there
are four escape characters in the text template, there will be two "\" characters in the generated file.
If the text template tag is preceded by an odd number of escape (\) characters, the template parser will
include half of the "\" characters plus the tag itself (<# or #>). The tag is not considered to be a text
template tag.
If an escape (\) character appears anywhere else in any sequence other than where it escapes a control
character or a quote (in C# only), the character will be output directly.
See also
How to: Generate Templates from Templates By Using Escape Sequences
How to: Generate Templates from Templates By
Using Escape Sequences
4/13/2022 • 2 minutes to read • Edit Online
You can create a text template that creates another text template as its generated text output. To do this, you
must use escape sequences to delineate the text template tags. If you do not use escape sequences, your
generated text template will have a pre-defined meaning. For more information about using escape sequences
in text templates, see Using Escape Sequences in Text Templates.
To generate a text template from within a text template
Use the backslash (\) as an escape character to produce the necessary markup tags within the text
template for directives, statements, expressions, and class features in a separate text template file.
Example
The following example uses escape characters to produce a text template from a text template. The output
directive sets the destination file type to the text template file type (.tt).
You can set breakpoints in text templates. To debug a design-time text template, save the text template file, and
then choose Debug T4 Template on the shortcut menu of the file in Solution Explorer. To debug a run-time text
template, simply debug the application to which it belongs.
To debug a text template, you should understand the steps of the template transformation process. Different
kinds of errors can occur within each step. The steps are as follows.
DESIGN - T IM E T EM P L AT E: W H EN IT RUN - T IM E T EM P L AT E: W H EN IT
ST EP H A P P EN S H A P P EN S
Code is generated from the text When you save the template or invoke When you save the template or invoke
template. text transformation. text transformation.
Generated code is compiled. Immediately after the previous step. Along with your application code.
Code runs. Immediately after the previous step. When your application runs and
invokes the template code.
Run-time errors in your template code.
In most cases, line numbers in the template code are given in the error report. When the error report refers to a
temporary filename, the usual cause is a mismatched bracket in the code of the text template.
You can set breakpoints in text templates and debug in the usual way.
Failed to load base class '{0}' from Occurs if you cannot find the base Be sure the specified class exists, and
which Transformation class inherits. class specified in the inherits that the assembly that it exists in is
parameter in a template directive. The specified in an assembly directive.
message provides the line number of
the template directive.
Failed to resolve include text for file:{0} Occurs when you cannot find an Be sure that the file path is relative to
included template. The message the original template path, or that the
provides the name of the requested file is in a location that is registered
include file. with the host, or that there is a full
path to the file.
ERRO R M ESSA GE DESC RIP T IO N SO L UT IO N
Errors were generated when initializing Occurs when the 'Initialize()' of the The code in the Initialize() function
the transformation object. The transformation class failed or returned comes from the base transformation
transformation will not be run. false. class specified in the <#@template#>
directive and from directive processors.
The error that caused initialize to fail
probably is on the error list.
Investigate why it failed. You can look
at the actual generated code for
Initialize() by following the procedures
to debug a template.
The assembly '{0}' for directive Occurs when the system does not Be sure that you only use trusted
processor '{1}' was not granted the grant FullTrust permissions to an assemblies on the local machine.
FullTrust permission set. Only trusted assembly containing a directive
assemblies are allowed to provide processor. The message provides the
directive processors. This directive name of the assembly and the name of
processor will not be loaded. the directive processor.
The path '{0}' must be either local to Occurs when a directive or assembly Be sure that the directory where the
this computer or part of your trusted directive references a file that is not on directive or assembly directives are
zone. your local machine or on your located is in your trusted zone. You can
network's trusted zone. add a network directory to your
trusted zone through Internet Explorer.
Multiple syntax errors such as "Invalid Too many closing braces in your Check the number of closing braces
token 'catch'" or "A namespace cannot template code. The compiler is and brackets inside code delimiters.
directly contain members" confusing it with the standard
generation code.
Loops or conditionals not compiled or In C#, always use braces to surround Add braces:
executed correctly. For example: text blocks that are embedded in <#if (i>10) { #> Number is: <#=
<#if (i>10)#> Number is: <#= i control statements. i #><# } #>
#> .
.
"Expression too complex" when Text block is too long. T4 converts text Break up the long text block with an
processing a design-time template or blocks to a string concatenation expression block such as:
compiling a runtime (preprocessed) expression, with one string literal for
template. each template line. Very long text <#= "" #>
blocks can overstep the compiler's size
Visual Studio stops working when limits.
attempting to inspect code generated
by a runtime template.
Loading the include file '{0}' returned a Occurs if an included text template file Either remove the include directive or
null or empty string. is blank. The message provides the file be sure the file has some content.
name of the included file.
WA RN IN G M ESSA GE DESC RIP T IO N SO L UT IO N
Compiling transformation: Prepends this string to all errors or If you have a problem finding the DLL,
warnings originating from the compiler you may need to provide either the full
when it compiles the transformation. path or a fully qualified strong name if
This string means that the compiler the DLL is in the GAC.
threw an error or warning.
The parameter '{0}' already exists in Occurs when a parameter is specified Remove the duplicate parameter
the directive. The duplicate parameter more than once in a directive. The specification.
will be ignored. message provides the name of the
parameter and the line number of the
directive.
There was an error loading the include Occurs when you cannot find a file Be sure the include file exists either in
file '{0}'. The include directive will be specified in an include directive. The the same directory as the original text
ignored. message provides the name of the file template file or in one of the include
and the line number of the directive. directories that are registered with the
host.
An invalid base class was specified for Occurs when the inherits Specify a class that derives from
the Transformation class. The base parameter in a template directive TextTransformation .
class must derive from specifies a class that does not inherit
Microsoft.VisualStudio.TextTemplating.T from TextTransformation . The
extTransformation. message provides the line number of
the template directive.
An invalid culture was specified in the Occurs when the culture parameter in Change the culture parameter to a
'template' directive. The culture must a template directive is specified valid culture in the "xx-XX" format.
be in the "xx-XX" format. The invariant incorrectly. The message provides the
culture will be used. line number of the template directive.
An invalid debug value '{0}' was Occurs when the debug parameter in Set the debug parameter to "true" or
specified in the template directive. The a template directive is specified "false".
debug value must be either "true" or incorrectly. The message provides the
"false". The default of "false" will be line number of the template directive.
used.
An invalid HostSpecific value '{0}' was Occurs when the host-specific Set the host-specific parameter to
specified in the template directive. The parameter in a template directive is "true" or "false".
HostSpecific value must be either specified incorrectly. The message
"true" or "false". The default of "false" provides the line number of the
will be used. template directive.
An invalid language '{0}' was specified Occurs when an unsupported Set the language parameter in the
in the 'template' directive. The language is specified in the template template directive to "C#" or"VB".
language must be either "C#" or "VB". directive. Only "C#" or "VB" are allowed
The default value of "C#" will be used. (case insensitive). The message
provides the line number of the
template directive.
Multiple output directives were found Occurs when multiple output Remove duplicate output directives.
in the template. All but the first one directives are specified in a template
will be ignored. file. The message provides the line
number of the duplicate output
directive.
WA RN IN G M ESSA GE DESC RIP T IO N SO L UT IO N
Multiple template directives were Occurs if you specify multiple Aggregate the different template
found in the template. All but the first template directives within a text directives into one template
one will be ignored. Multiple template file (including included files). directive.
parameters to the template directive The message provides the line number
should be specified within one of the duplicate template directive.
template directive.
No processor was specified for a Occurs if you specify a custom Provide a processor attribute with
directive named '{0}'. The directive will directive, but do not provide a the name of the directive
be ignored. processor attribute. The message processor for the directive.
provides the name of the directive and
the line number.
A processor named '{0}' could not be Occurs when the system cannot find Set the processor attribute in the
found for the directive named '{1}'. The the directive processor you directive to the name of the directive
directive will be ignored. specified within a custom directive. processor.
The message provides the directive
name, processor name, and the line
number of the directive.
A required parameter '{0}' for the Occurs when the system does not Provide the missing parameter.
directive '{1}' was not found. The provide a required directive parameter.
directive will be ignored. The message provides the name of the
missing parameter, the directive name,
and the line number.
The processor named '{0}' does not Occurs when a directive processor Correct the name of the directive.
support the directive named '{1}'. The does not support a directive. The
directive will be ignored. message provides the name and line
number of the offending directive
along with the name of the directive
processor.
The include directive for file '{0}' causes Displayed if circular include directives Do not specify circular include
an infinite loop. are specified (for example, file A directives.
includes file B, which includes file A).
An unexpected start or end tag was Displayed when you have an Either remove the mismatched start or
found within a block. Make sure that unexpected <# or #>. That is, if you end tag, or use an escape character.
you did not mis-type a start or end have a <# after another open tag that
tag, and that you don't have any has not been closed, or you have a #>
nested blocks in the template. when there is no unclosed open tag
before it. The message provides the
line number of the mismatched tag.
A directive was specified in the wrong Displayed by the parser if a directive is Be sure all directives are in the form
format. The directive will be ignored. not specified in the correct format. The <#@ name
Please specify the directive in the message provides the line number of [parametername="parametervalue"]*
#>
format the incorrect directive.
<#@ name
. For more information, see T4 Text
[parametername="parametervalue"]* Template Directives.
#>
WA RN IN G M ESSA GE DESC RIP T IO N SO L UT IO N
Failed to load Assembly '{0}' for Occurs when a directive processor Be sure the directive processor is
registered directive processor '{1}' could not be loaded by the host. The registered correctly and that the
message identifies the assembly assembly exists.
{2} provided for the directive processor
and the name of the directive
processor.
Failed to find type '{0}' in Assembly '{1}' Occurs when a directive processor type The vshost finds directive processor
for registered directive processor '{2}' could not be loaded from its assembly. information (name, assembly, and
The message provides the name of the type) in the registry. Be sure the
{3} type, assembly, and directive processor. directive processor is registered
correctly, and that the type exists in
the assembly.
There was a problem loading the Occurs when there is a problem You can specify assemblies to be
assembly '{0}' loading an assembly. The message loaded in <@#assembly#> directives,
provides the name of the assembly. and by directive processors. The error
message that follows this string should
provide more data on why the
assembly load failed.
There was a problem creating and Occurs when the system could not Be sure you use the correct directive
initializing the processor for a directive create or initialize a directive processor. processor, and that the directive
named '{1}'. The type of the processor The message provides the name and processor has a public default
is {0}. The directive will be ignored. line number of the directive and the constructor. Otherwise, use the debug
type of the processor. options to find out why the Initialize()
method of the directive processor is
failing. For more information, see
Troubleshooting Text Templates.
An Exception was thrown while Occurs when a directive processor Be sure that the parameters to the
processing a directive named '{0}'. throws an exception when processing directive processor are correct.
a directive.
The host threw an exception while Occurs when the host throws an Assembly references come from
trying to resolve the assembly exception when it tries to resolve an <@#assembly#> directives and from
reference '{0}'. assembly reference. The message directive processors. Be sure that the
provides the assembly reference string. 'name' parameter provided in the
assembly parameter is correct.
Attempt to specify unsupported {1} Occurs by the Be sure that the names in the
value '{0}' for directive {2} RequiresProvidesDirectiveProcessor (all name='value' pairs provided in the
our generated directive processors requires and provides parameters are
derive from it), when you supply an correct.
unsupported requires or provides
argument.
Guidelines for Writing T4 Text Templates
4/13/2022 • 7 minutes to read • Edit Online
These general guidelines might be helpful if you are generating program code or other application resources in
Visual Studio. They are not fixed rules.
MyProject
Custom Code
Class1.cs
Class2.cs
Generated Code
Class1.tt
Class1.cs
Class2.tt
Class2.cs
AnotherClass.cs
In MyRepor tText-Methods.cs :
private string ComputeTotal() { ... }
Team Members:
<# foreach (Person p in team.Members)
{ #>
<#= p.Name #>
<# } #>
See also
Design-Time Code Generation by using T4 Text Templates
Run-Time Text Generation with T4 Text Templates
How to ... with Text Templates
4/13/2022 • 5 minutes to read • Edit Online
Text templates in Visual Studio provide a useful way of generating text of any kind. You can use text templates to
generate text at run time as part of your application and at design time to generate some of your project code.
This topic summarizes the most frequently asked "How do I ...?" questions.
In this topic, multiple answers that are preceded by bullets are alternative suggestions.
For a general introduction to text templates, read Code Generation and T4 Text Templates.
How to ...
Generate part of my application code
I have a configuration or model in a file or a database. One or more parts of my code depend on that model.
Generate some of your code files from text templates. For more information, see Design-Time Code
Generation by using T4 Text Templates and What is the best way to start writing a template?.
Generate files at run time, passing data into the template
At run time, my application generates text files, such as reports, that contain a mixture of standard text and data.
I want to avoid writing hundreds of write statements.
Add a runtime text template to your project. This template creates a class in your code, which you can
instantiate and use to generate text. You can pass data to it in the constructor parameters. For more
information, see Run-Time Text Generation with T4 Text Templates.
If you want to generate from templates that are available only at run time, you can use standard text
templates. If you are writing a Visual Studio extension, you can invoke the text templating service. For
more information, see Invoking Text Transformation in a VS Extension. In other contexts, you can use the
text templating engine. For more information, see Microsoft.VisualStudio.TextTemplating.Engine.
Use the <#@parameter#> directive to pass parameters to these templates. For more information, see T4
Parameter Directive.
Read another project file from a template
To read a file from the same Visual Studio project as the template:
Insert hostSpecific="true" into the <#@template#> directive.
In your code, use this.Host.ResolvePath(filename) to obtain the full path of the file.
Invoke methods from a template
If the methods already exist, for example, in .NET classes:
Use the <#@assembly#> directive to load the assembly, and use <#@import#> to set the namespace
context. For more information, see T4 Import Directive.
If you frequently use the same set of assembly and import directives, consider writing a directive
processor. In each template, you can invoke the directive processor, which can load the assemblies and
the model files and set the namespace context. For more information, see Creating Custom T4 Text
Template Directive Processors.
If you are writing the methods yourself:
If you are writing a runtime text template, write a partial class definition that has the same name as your
runtime text template. Add the additional methods into this class.
Write a class feature control block <#+ ... #> in which you can declare methods, properties, and private
classes. When the text template is compiled, it is transformed to a class. The standard control blocks
<#...#> and text are transformed to a single method, and class feature blocks are inserted as separate
members. For more information, see Text Template Control Blocks.
Methods defined as class features can also include embedded text blocks.
Consider placing class features in a separate file which you can <#@include#> into one or more template
files.
Write the methods in a separate assembly (class library) and call them from your template. Use the
<#@assembly#> directive to load the assembly, and <#@import#> to set the namespace context. Note that
in order to rebuild the assembly while you are debugging it, you might have to stop and restart Visual
Studio. For more information, see T4 Text Template Directives.
Generate many files from one model schema
If you often generate files from models that have the same XML or database schema:
Consider writing a directive processor. This enables you to replace multiple assembly statements and import
statements in each template with a single custom directive. The directive processor can also load and parse
the model file. For more information, see Creating Custom T4 Text Template Directive Processors.
Generate files from a complex model
Consider creating a domain-specific language (DSL) to represent the model. This makes it much easier to
write the templates, because you use types and properties that reflect the names of the elements in your
model. You do not have to parse the file or navigate XML nodes. For example:
foreach (Book book in this.Library) { ... }
For more information, see Getting Started with Domain-Specific Languages and Generating Code from a
Domain-Specific Language.
Get data from Visual Studio
To use services provided in Visual Studio, by set the hostSpecific attribute and load the EnvDTE assembly. For
example:
Text templates are a feature of Visual Studio that allow you to generate program code or other text files through
a transformation process. Using Visual Studio SDK, you can extend the default template transformation process
by customizing the text template directive processor or the text template host.
In This Section
The Text Template Transformation Process Describes how text transformation works, and explains the role of the
template host and the directive processors.
Creating Custom T4 Text Template Directive Processors The directive processor deals with directives in your
template, such as <#@template#>. It runs during the compilation of the template, and can load assemblies and
other resources. It can also insert code that will load resources at run time. By defining your own directive
processor, you can reduce the complexity of your templates.
Invoking Text Transformation in a VS Extension If you are writing a Visual Studio extension such as a menu
command or event handler, your extension can use the Text Templating Service to transform any text template.
You can pass parameter data into the template by using the Session object, and get the values from within the
template by using the <#@parameter#> directive.
Processing Text Templates by using a Custom Host When the code of the text template executes, the host
provides access to external files and the state of the application. For example, the host that runs text
transformations in Visual Studio can provide access to Solution Explorer . It also displays errors in the error
message window. If you want to run text transformations in a different context, you can define your own host
that provides access to the services available in that context.
If you are writing a Visual Studio extension, consider using the existing text transformation service instead of
writing your own host. For more information, see Invoking Text Transformation in a VS Extension.
Reference
Write a T4 text template provides the syntax of text template directives and control blocks.
The Text Template Transformation Process
4/13/2022 • 3 minutes to read • Edit Online
The text template transformation process takes a text template file as the input and generates a new text file as
the output. For example, you can use text templates to generate Visual Basic or C# code, or you can generate an
HTML report.
Three components take part in this process: the engine, the host, and the directive processors. The engine
controls the process; it interacts with the host and the directive processor to produce the output file. The host
provides any interaction with the environment, such as locating files and assemblies. The directive processor
adds functionality, such as reading data from an XML file or a database.
The text template transformation process is performed in two steps. First, the engine creates a temporary class,
which is known as the generated transformation class. This class contains the code that is generated by the
directives and control blocks. After that, the engine compiles and executes the generated transformation class to
produce the output file.
Components
C O M P O N EN T DESC RIP T IO N C USTO M IZ A B L E ( Y ES/ N O )
Host The host is the interface between the Yes. You can write a custom host.
engine and the user environment.
Visual Studio is a host of the text
transformation process.
Directive Processors Directive processors are classes that Yes. You can write custom directive
handle directives in text templates. You processors
can use directives to provide data to a
text template from an input source.
The Engine
The engine receives the template as a string from the host, which handles all the files that are used in the
transformation process. The engine then asks the host to locate any custom directive processors and other
aspects of the environment. The engine then compiles and runs the generated transformation class. The engine
returns the generated text to the host, which normally saves the text to a file.
The Host
The host is responsible for anything that relates to the environment outside the transformation process,
including the following:
Locating text and binary files requested by the engine or a directive processor. The host can search
directories and the global assembly cache to locate assemblies. The host can locate custom directive
processor code for the engine. The host can also locate and read text files and return their contents as
strings.
Providing lists of standard assemblies and namespaces that are used by the engine to create the
generated transformation class.
Providing the application domain that is used when the engine compiles and executes the generated
transformation class. A separate application domain is used in order to protect the host application from
errors in the template code.
Writing the generated output file.
Setting the default extension for the generated output file.
Handling text template transformation errors. For example, the host can display the errors in the user
interface or write them to a file. (In Visual Studio, errors are displayed in the Error Message Window.)
Providing a required parameter value if a user has called a directive without providing a value. The
directive processor can specify the name of the directive and the parameter and ask the host to provide a
default value if it has one.
The standard directive processor converts this to a using statement in the generated transformation class. You
can then use the StringBuilder class in the rest of your template code without qualifying it as
System.Text.StringBuilder .
Create Custom T4 Text Template Directive
Processors
4/13/2022 • 3 minutes to read • Edit Online
The text template transformation process takes a text template file as the input and produces a text file as the
output. The text template transformation engine controls the process, and the engine interacts with a text
template transformation host and one or more text template directive processors to complete the process. For
more information, see The Text Template Transformation Process.
To create a custom directive processor, you create a class that inherits from either DirectiveProcessor or
RequiresProvidesDirectiveProcessor.
The difference between these two is that DirectiveProcessor implements the minimum interface that is
necessary to get parameters from the user and to generate the code that produces the template output file.
RequiresProvidesDirectiveProcessor implements the requires/provides design pattern.
RequiresProvidesDirectiveProcessor handles two special parameters, requires and provides . For example, a
custom directive processor might accept a file name from the user, open and read the file, and then store the text
of the file in a variable that is named fileText . A subclass of the RequiresProvidesDirectiveProcessor class
might take a file name from the user as the value of the requires parameter, and the name of the variable in
which to store the text as the value of the provides parameter. This processor would open and read the file and
then store the text of the file in the specified variable.
Before you call a custom directive processor from a text template in Visual Studio, you must register it.
For more information about how to add the registry key, see Deploying a Custom Directive Processor.
Custom Directives
A custom directive looks like this:
<#@ MyDirective Processor="MyDirectiveProcessor" parameter1="value1" ... #>
You can use a custom directive processor when you want to access external data or resources from a text
template.
Different text templates can share the functionality that a single directive processor provides, so directive
processors provide a way to factor code for reuse. The built-in include directive is similar, because you can use
it to factor out code and share it among different text templates. The difference is that any functionality that the
include directive provides is fixed and does not accept parameters. If you want to provide common
functionality to a text template and allow the template to pass parameters, you must create a custom directive
processor.
Some examples of custom directive processors could be:
A directive processor to return data from a database that accepts a user name and password as
parameters.
A directive processor to open and read a file that accepts the name of the file as a parameter.
Principal parts of a custom directive processor
To develop a directive processor, you must create a class that inherits from either DirectiveProcessor or
RequiresProvidesDirectiveProcessor.
The most important DirectiveProcessor methods that you must implement are as follows.
bool IsDirectiveSupported(string directiveName) - Return true if your directive processor can deal with
the named directive.
void ProcessDirective (string directiveName, IDictionary<string, string> arguments) - The template
engine calls this method for each occurrence of a directive in the template. Your processor should save
the results.
After all calls to ProcessDirective() the templating engine will call these methods:
string[] GetReferencesForProcessingRun() - Return the names of assemblies that the template code
requires.
string[] GetImportsForProcessingRun() - Return the namespaces that can be used in the template code.
string GetClassCodeForProcessingRun() - Return the code of methods, properties, and other declarations
that the template code can use. The easiest way to do this is to build a string containing the C# or Visual
Basic code. To make your directive processor capable of being called from a template that uses any CLR
language, you can construct the statements as a CodeDom tree and then return the result of serializing
the tree in the language used by the template.
For more information, see Walkthrough: Creating a Custom Directive Processor.
See also
Deploy a Custom Directive Processor explains how to register a custom directive processor.
Walkthrough: Create a Custom Directive Processor describes how to create a custom directive processor, how
to register and test the directive processor, and how to format the output file as HTML.
Deploying a Custom Directive Processor
4/13/2022 • 5 minutes to read • Edit Online
To use a custom directive processor in Visual Studio on any computer, you must register it by one of the
methods described in this topic.
The alternative methods are:
Visual Studio Extensions. This provides a way to install and uninstall the directive processor both on your
own computer and on other computers. Typically, you might package other features in the same VSIX.
VSPackage. If you are defining a VSPackage that contains other features in addition to the directive
processor, there is a convenient method of registering the directive processor.
Set a registry key. In this method, you add a registry entry for the directive processor.
You need to use one of these methods only if you want to transform your text template in Visual Studio or
MSBuild. If you use a custom host in your own application, your custom host is responsible for finding the
directive processors for each directive.
[$RootKey$\TextTemplating]
[$RootKey$\TextTemplating\DirectiveProcessors]
[$RootKey$\TextTemplating\DirectiveProcessors\ CustomDirectiveProcessorName]
@="Custom Directive Processor description"
"Class"="NamespaceName.ClassName"
"CodeBase"="$PackageFolder$\AssemblyName.dll"
Replace the following names with your own names: CustomDirectiveProcessorName , NamespaceName ,
ClassName , AssemblyName .
Your IsDirectiveSupported method must return true when it is passed the name of your
CustomDirective .
If you cannot see the extension in Extension Manager, but the system will not allow you to install it, delete
the extension from %localappdata%\Microsoft\VisualStudio\*.0\Extensions\ .
Open the .vsix file and inspect its contents. To open it, change the filename extension to .zip. Verify that it
contains the .dll, .pkgdef, and extension.vsixmanifest files. The extension.vsixmanifest file should contain
the appropriate list in the SupportedProducts node, and should also contain a VsPackage node under the
Content node:
<Content>
<VsPackage>CustomDirectiveProcessor.dll</VsPackage>
</Content>
NOTE
This attribute is placed on the package class, not the directive processor class.
The .pkgdef file will be generated when you build the project. When you install the VSPackage, the .pkgdef file
will register the directive processor.
Verify that the .pkgdef file appears in the build folder, which is usually bin\Debug or bin\Release. If it does not
appear, open the .csproj file in a text editor, and remove the following node:
<GeneratePkgDefFile>false</GeneratePkgDefFile> .
Incorrectly editing the registry can severely damage your system. Before making changes to the registry, be sure
to back up any valued data on the computer.
To register a directive processor by setting a registry key
1. Run regedit .
2. In regedit, navigate to
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectivePro
cessors
If you want to install the directive processor in the experimental version of Visual Studio, insert "Exp" after
"11.0".
3. Add a registry key that has the same name as the directive processor class.
In the registry tree, right-click the DirectiveProcessors node, point to New , and then click Key .
4. In the new node, add string values for Class and CodeBase or Assembly, according to the following tables.
a. Right-click the node that you created, point to New , and then click String Value .
b. Edit the name of the value.
c. Double-click the name and edit the data.
If the custom directive processor is not in the GAC, the registry subkeys should look like the following
table:
If the assembly is in the GAC, the registry subkeys should look like the following table:
See also
Creating Custom T4 Text Template Directive Processors
Walkthrough: Create a Custom Directive Processor
4/13/2022 • 20 minutes to read • Edit Online
Directive processors work by adding code to the generated transformation class. If you call a directive from a
text template, the rest of the code that you write in your text template can rely on the functionality that the
directive provides.
You can write your own custom directive processors. This enables you to customize your text templates. To
create a custom directive processor, you create a class that inherits from either DirectiveProcessor or
RequiresProvidesDirectiveProcessor.
Tasks that are illustrated in this walkthrough include the following:
Create a custom directive processor
Register the directive processor
Test the directive processor
The custom directive processor adds the variable and the property to the generated transformation class. The
directive that you write uses the System.CodeDom classes to create the code that the engine adds to the
generated transformation class. The System.CodeDom classes create code in either Visual C# or Visual Basic,
depending on the language specified in the language parameter of the template directive. The language of the
directive processor and the language of the text template that is accessing the directive processor do not have to
match.
The code that the directive creates looks like the following:
NOTE
If you want to install the directive processor on more than one computer, it is better to use a Visual Studio
Extension (VSIX) project and include a .pkgdef file in the extension. For more information, see Deploying a Custom
Directive Processor.
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TextTemplating;
namespace CustomDP
{
public class CustomDirectiveProcessor : DirectiveProcessor
{
// This buffer stores the code that is added to the
// generated transformation class after all the processing is done.
// ---------------------------------------------------------------------
private StringBuilder codeBuffer;
// This stores the full contents of the text template that is being processed.
// --------------------------------------------------------------------------
private String templateContents;
// These are the errors that occur during processing. The engine passes
// the errors to the host, and the host can decide how to display them,
// the errors to the host, and the host can decide how to display them,
// for example the host can display the errors in the UI
// or write them to a file.
// ---------------------------------------------------------------------
private CompilerErrorCollection errorsValue;
public new CompilerErrorCollection Errors
{
get { return errorsValue; }
}
if (string.IsNullOrEmpty(fileName))
{
throw new DirectiveProcessorException("Argument 'FileName' is null or empty.");
throw new DirectiveProcessorException("Argument 'FileName' is null or empty.");
}
// -------------------------------------------------------------------------
// The code that we are adding to the generated transformation class
// will call this method.
// -------------------------------------------------------------------------
public static class XmlReaderHelper
{
public static XmlDocument ReadXml(string fileName)
{
XmlDocument d = new XmlDocument();
Imports System
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.Collections.Generic
Imports System.Globalization
Imports System.IO
Imports System.Text
Imports System.Xml
Imports System.Xml.Serialization
Imports Microsoft.VisualStudio.TextTemplating
Namespace CustomDP
' This stores the full contents of the text template that is being processed.
' --------------------------------------------------------------------------
Private templateContents As String
' These are the errors that occur during processing. The engine passes
' the errors to the host, and the host can decide how to display them,
' for example the host can display the errors in the UI
' or write them to a file.
' ---------------------------------------------------------------------
Private errorsValue As CompilerErrorCollection
Public Shadows ReadOnly Property Errors() As CompilerErrorCollection
Get
Return errorsValue
End Get
End Property
' Each time this directive processor is called, it creates a new property.
' We count how many times we are called, and append "n" to each new
' property name. The property names are therefore unique.
' --------------------------------------------------------------------------
Private directiveCount As Integer = 0
' The engine has passed us the language of the text template
' we will use that language to generate code later.
' ----------------------------------------------------------
Me.codeDomProvider = languageProvider
Me.templateContents = templateContents
Me.errorsValue = errors
If String.Compare(directiveName, "SuperCoolDirective",
StringComparison.OrdinalIgnoreCase) = 0 Then
Return True
End If
Return False
End Function
If String.IsNullOrEmpty(fileName) Then
Throw New DirectiveProcessorException("Argument 'FileName' is null or empty.")
End If
End If
' Track how many times the processor has been called.
' -----------------------------------------------------------------
directiveCount += 1
End Sub
Me.codeDomProvider = Nothing
' --------------------------------------------------------------------------
' The code that we are adding to the generated transformation class
' will call this method.
' --------------------------------------------------------------------------
Public Class XmlReaderHelper
Try
d.Load(reader)
Catch e As System.Xml.XmlException
Return d
End Function
End Class
End Namespace
4. For Visual Basic only, open the Project menu, and click CustomDP Proper ties . On the Application tab,
in Root namespace , delete the default value, CustomDP .
5. On the File menu, click Save All .
6. On the Build menu, click Build Solution .
Build the Project
Build the project. On the Build menu, click Build Solution .
NOTE
If you want to install the directive processor on more than one computer, it is better to define a Visual Studio Extension
(VSIX) that includes a .pkgdef file along with your assembly. For more information, see Deploying a Custom Directive
Processor.
Keys for directive processors exist in the registry in the following location:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors
In this section, you add a key for your custom directive processor to the registry in the same location.
Cau t i on
Incorrectly editing the registry can severely damage your system. Before you make changes to the registry, back
up any valuable data that is on the computer.
To add a registry key for the directive processor
1. Run the regedit command by using the Start menu or the command line.
2. Browse to the location
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectivePro
cessors , and click the node.
On 64-bit systems, use
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\*.0\TextTemplati
ng\DirectiveProcessors
3. Add a new key named CustomDirectiveProcessor.
NOTE
This is the name that you will use in the Processor field of your custom directives. This name does not need to
match the name of the directive, the name of the directive processor class, or the directive processor namespace.
4. Add a new string value named Class that has a value CustomDP.CustomDirectiveProcessor for the name
of the new string.
5. Add a new string value named CodeBase that has a value equal to the path of the CustomDP.dll that you
created earlier in this walkthrough.
For example, the path might look like C:\UserFiles\CustomDP\bin\Debug\CustomDP.dll .
Your registry key should have the following values:
If you have put the assembly in the GAC, the values should look like the following:
<?xml version="1.0"?>
<doc>
<assembly>
<name>xmlsample</name>
</assembly>
<members>
<member name="T:SomeClass">
<summary>Class level summary documentation goes here.</summary>
<remarks>Longer comments can be associated with a type or member through the remarks
tag</remarks>
</member>
<member name="F:SomeClass.m_Name">
<summary>Store for the name property</summary>
</member>
<member name="M:SomeClass.#ctor">
<summary>The class constructor.</summary>
</member>
<member name="M:SomeClass.SomeMethod(System.String)">
<summary>Description for SomeMethod.</summary>
<param name="s">Parameter description for s goes here</param>
<seealso cref="T:System.String">You can use the cref attribute on any tag to reference a
type or member and the compiler will check that the reference exists.</seealso>
</member>
<member name="M:SomeClass.SomeOtherMethod">
<summary>Some other method.</summary>
<returns>Return results are described through the returns tag.</returns>
<seealso cref="M:SomeClass.SomeMethod(System.String)">Notice the use of the cref
attribute to reference a specific method</seealso>
</member>
<member name="M:SomeClass.Main(System.String[])">
<summary>The entry point for the application.</summary>
<param name="args">A list of command line arguments</param>
</member>
<member name="P:SomeClass.Name">
<summary>Name property</summary>
<value>A value tag is used to describe the property value</value>
</member>
</members>
</doc>
NOTE
Replace the string <YOUR PATH> with the path to the DocFile.xml file.
The language of the text template does not have to match the language of the directive processor.
<# // Uncomment this line if you want to see the generated transformation class. #>
<# // System.Diagnostics.Debugger.Break(); #>
<# // This will use the results of the directive processor. #>
<# // The directive processor has read the XML and stored it in Document0. #>
<#
XmlNode node = Document0.DocumentElement.SelectSingleNode("members");
<# // You can call the directive processor again and pass it a different file. #>
<# // @ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\<Your Second File>"
#>
<# // To use the results of the second directive call, use Document1. #>
<#
// XmlNode node2 = Document1.DocumentElement.SelectSingleNode("members");
// ...
#>
<#@ assembly name="System.Xml" #>
<#@ template debug="true" language="vb" #>
<#@ output extension=".txt" #>
<# ' This will call the custom directive processor. #>
<#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\DocFile.xml" #>
<# ' Uncomment this line if you want to see the generated transformation class. #>
<# ' System.Diagnostics.Debugger.Break() #>
<# ' This will use the results of the directive processor. #>
<# ' The directive processor has read the XML and stored it in Document0. #>
<#
Dim node as XmlNode = Document0.DocumentElement.SelectSingleNode("members")
WriteLine("")
Next
#>
<# ' You can call the directive processor again and pass it a different file. #>
<# ' @ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\DocFileTwo.xml" #>
<# ' To use the results of the second directive call, use Document1. #>
<#
' node = Document1.DocumentElement.SelectSingleNode("members")
' ...
#>
NOTE
In this example, the value of the Processor parameter is CustomDirectiveProcessor . The value of the
Processor parameter must match the name of the processor's registry key.
Name: F:SomeClass.m_Name
summary: Store for the name property
Name: M:SomeClass.#ctor
summary: The class constructor.
Name: M:SomeClass.SomeMethod(System.String)
summary: Description for SomeMethod.
param: Parameter description for s goes here
seealso: You can use the cref attribute on any tag to reference a type or member and the compiler
will check that the reference exists.
Name: M:SomeClass.SomeOtherMethod
summary: Some other method.
returns: Return results are described through the returns tag.
seealso: Notice the use of the cref attribute to reference a specific method
Name: M:SomeClass.Main(System.String[])
summary: The entry point for the application.
param: A list of command line arguments
Name: P:SomeClass.Name
summary: Name property
value: A value tag is used to describe the property value
NOTE
Additional open <# and close #> tags separate the statement code from the HTML tags.
<#@ assembly name="System.Xml" #>
<#@ template debug="true" #>
<#@ output extension=".htm" #>
<# // Uncomment this line if you want to see the generated transformation class #>
<# // System.Diagnostics.Debugger.Break(); #>
<html><body>
<# // This will use the results of the directive processor #>.
<# // The directive processor has read the XML and stored it in Document0#>.
<#
XmlNode node = Document0.DocumentElement.SelectSingleNode("members");
<# ' This will call the custom directive processor #>
<#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\DocFile.xml" #>
<# ' Uncomment this line if you want to see the generated transformation class #>
<# ' System.Diagnostics.Debugger.Break() #>
<html><body>
<# ' This will use the results of the directive processor #>.
<# ' The directive processor has read the XML and stored it in Document0#>.
<#
Dim node as XmlNode = Document0.DocumentElement.SelectSingleNode("members")
If you are writing a Visual Studio extension such as a menu command or domain-specific language, you can use
the text templating service to transform text templates. Get the STextTemplating service and cast it to
ITextTemplating.
For the type of a parameter, you must use a type that is serializable or that can be marshaled. That is, the type
must be declared with SerializableAttribute, or it must be derived from MarshalByRefObject. This restriction is
necessary because the text template is executed in a separate AppDomain. All built-in types such as
System.String and System.Int32 are serializable.
To pass parameter values, the calling code can place values either in the Session dictionary, or in the
CallContext.
The following example uses both methods to transform a short test template:
using Microsoft.VisualStudio.TextTemplating;
using Microsoft.VisualStudio.TextTemplating.VSHost;
...
// Get a service provider - how you do this depends on the context:
IServiceProvider serviceProvider = dte;
public void ErrorCallback(bool warning, string message, int line, int column)
{ errorMessages.Add(message); }
The code can be tested with a template file similar to the following:
The compiler warning will appear in the Visual Studio error window, and it will also generate a call to
ErrorCallback .
Reference parameters
You can pass values out of a text template by using a parameter class that is derived from MarshalByRefObject.
Related articles
To generate text from a preprocessed text template: Call the TransformText() method of the generated class. For
more information, see Run-Time Text Generation with T4 Text Templates.
To generate text outside a Visual Studio extension: Define a custom host. For more information, see Processing
Text Templates by using a Custom Host.
To generate source code that can later be compiled and executed: Call the PreprocessTemplate method of
ITextTemplating.
Process Text Templates by using a Custom Host
4/13/2022 • 2 minutes to read • Edit Online
The text template transformation process takes a text template file as the input and produces a text file as the
output. You can call the text transformation engine from a Visual Studio extension, or from a standalone
application running on a machine on which Visual Studio is installed. However, you must provide a text
templating host. This class connects the template to the environment, finding resources such as assemblies and
include files, and dealing with the output and error messages.
TIP
If you are writing a package or extension that will run within Visual Studio, consider using the text templating service,
instead of writing your own host. For more information, see Invoking Text Transformation in a VS Extension.
NOTE
We do not recommend using text template transformations in server applications. We do not recommend using text
template transformations except in a single thread. This is because the text templating Engine re-uses a single
AppDomain to translate, compile, and execute templates. The translated code is not designed to be thread-safe. The
Engine is designed to process files serially, as they are in a Visual Studio project at design time.
For run-time applications, consider using preprocessed text templates: see Run-Time Text Generation with T4 Text
Templates.
If your application uses a set of templates that are fixed at compile time, it is easier to use Preprocessed Text
Templates. You can also use that approach if your application will run on a machine on which Visual Studio is not
installed. For more information, see Run-Time Text Generation with T4 Text Templates.
using Microsoft.VisualStudio.TextTemplating;
...
Engine engine = new Engine();
string output = engine.ProcessTemplate(templateString, host);
Your application must find and provide the template, and must deal with the output.
In the host parameter, you must provide a class that implements ITextTemplatingEngineHost. This is called back
by the Engine.
The host must be able to log errors, resolve references to assembly and include files, provide an Application
Domain in which the template can execute, and call the appropriate processor for each directive.
Microsoft.VisualStudio.TextTemplating.Engine is defined in Microsoft.VisualStudio.TextTemplating.*.0.dll ,
and ITextTemplatingEngineHost is defined in Microsoft.VisualStudio.TextTemplating.Interfaces.*.0.dll .
In This Section
Walkthrough: Creating a Custom Text Template Host Shows you how to create a custom text template host that
makes the text template functionality available outside Visual Studio.
Reference
ITextTemplatingEngineHost
Related Sections
The Text Template Transformation Process describes how text transformation works, and which parts you can
customize.
Creating Custom T4 Text Template Directive Processors provides an overview of text template directive
processors.
Walkthrough: Create a Custom Text Template Host
4/13/2022 • 20 minutes to read • Edit Online
A text template host provides an environment that enables the text template transformation engine to run. The
host is responsible for managing the engine's interaction with the file system. The engine or directive processor
that needs a file or an assembly can request a resource from the host. The host can then search directories and
the global assembly cache to locate the requested resource. For more information, see The Text Template
Transformation Process.
You can write a custom host if you want to use the text template transformation functionality from outside
Visual Studio or if you want to integrate that functionality into custom tools. To create a custom host, you must
create a class that inherits from ITextTemplatingEngineHost. For the documentation of the individual methods,
see ITextTemplatingEngineHost.
WARNING
If you are writing a Visual Studio extension or package, consider using the text templating service instead of creating your
own host. For more information, see Invoking Text Transformation in a VS Extension.
Prerequisites
To complete this walkthrough, you must have the following:
Visual Studio 2010 or later
Visual Studio SDK
using System;
using System.IO;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
using Microsoft.VisualStudio.TextTemplating;
namespace CustomHost
{
//The text template transformation engine is responsible for running
//the transformation process.
//The host is responsible for all input and output, locating files,
//and anything else related to the external environment.
//-------------------------------------------------------------------------
class CustomCmdLineHost : ITextTemplatingEngineHost
{
//the path and file name of the text template that is being processed
//---------------------------------------------------------------------
internal string TemplateFileValue;
public string TemplateFile
{
get { return TemplateFileValue; }
}
//This will be the extension of the generated text output file.
//The host can provide a default by setting the value of the field here.
//The engine can change this value based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
private string fileExtensionValue = ".txt";
public string FileExtension
{
get { return fileExtensionValue; }
}
//This will be the encoding of the generated text output file.
//The host can provide a default by setting the value of the field here.
//The engine can change this value based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
private Encoding fileEncodingValue = Encoding.UTF8;
public Encoding FileEncoding
{
get { return fileEncodingValue; }
}
//These are the errors that occur when the engine processes a template.
//The engine passes the errors to the host when it is done processing,
//and the host can decide how to display them. For example, the host
//can display the errors in the UI or write them to a file.
//---------------------------------------------------------------------
private CompilerErrorCollection errorsValue;
public CompilerErrorCollection Errors
{
get { return errorsValue; }
}
//The host can provide standard assembly references.
//The engine will use these references when compiling and
//executing the generated transformation class.
//--------------------------------------------------------------
public IList<string> StandardAssemblyReferences
{
get
{
return new string[]
{
//If this host searches standard paths and the GAC,
//we can specify the assembly name like this.
//---------------------------------------------------------
//"System"
Namespace CustomHost
'The text template transformation engine is responsible for running
'the transformation process.
'The host is responsible for all input and output, locating files,
'and anything else related to the external environment.
'-------------------------------------------------------------------------
Public Class CustomCmdLineHost
Implements ITextTemplatingEngineHost
'the path and file name of the text template that is being processed
'---------------------------------------------------------------------
Friend TemplateFileValue As String
Public ReadOnly Property TemplateFile() As String Implements
ITextTemplatingEngineHost.TemplateFile
Get
Return TemplateFileValue
End Get
End Property
'This will be the extension of the generated text output file.
'The host can provide a default by setting the value of the field here.
'The engine can change this based on the optional output directive
'if the user specifies it in the text template.
'---------------------------------------------------------------------
Private fileExtensionValue As String = ".txt"
Public ReadOnly Property FileExtension() As String
Get
Return fileExtensionValue
End Get
End Property
'This will be the encoding of the generated text output file.
'The host can provide a default by setting the value of the field here.
'The engine can change this value based on the optional output directive
'if the user specifies it in the text template.
'---------------------------------------------------------------------
Private fileEncodingValue As Encoding = Encoding.UTF8
Public ReadOnly Property fileEncoding() As Encoding
Get
Return fileEncodingValue
End Get
End Property
'These are the errors that occur when the engine processes a template.
'The engine passes the errors to the host when it is done processing,
'and the host can decide how to display them. For example, the host
'can display the errors in the UI or write them to a file.
'---------------------------------------------------------------------
Private errorsValue As CompilerErrorCollection
Public ReadOnly Property Errors() As CompilerErrorCollection
Get
Return errorsValue
End Get
End Property
'The host can provide standard assembly references.
'The engine will use these references when compiling and
'executing the generated transformation class.
'--------------------------------------------------------------
Public ReadOnly Property StandardAssemblyReferences() As IList(Of String) Implements
ITextTemplatingEngineHost.StandardAssemblyReferences
Get
'If this host searches standard paths and the GAC,
'we can specify the assembly name like this.
'we can specify the assembly name like this.
'---------------------------------------------------------
'Return New String() {"System"}
'Because this host only resolves assemblies from the
'fully qualified path and name of the assembly,
'this is a quick way to get the code to give us the
'fully qualified path and name of the System assembly.
'---------------------------------------------------------
Return New String() {(New System.UriBuilder()).GetType().Assembly.Location}
End Get
End Property
'The host can provide standard imports or imports statements.
'The engine will add these statements to the generated
'transformation class.
'--------------------------------------------------------------
Public ReadOnly Property StandardImports() As IList(Of String) Implements
ITextTemplatingEngineHost.StandardImports
Get
Return New String() {"System"}
End Get
End Property
' Called by the Engine to enquire about
' the processing options you require.
' If you recognize that option, return an
' appropriate value.
' Otherwise, pass back NULL.
'--------------------------------------------------------------------
Public Function GetHostOption(ByVal optionName As String) As Object Implements
ITextTemplatingEngineHost.GetHostOption
Dim returnObject As Object
Select Case optionName
Case "CacheAssemblies"
returnObject = True
Case Else
returnObject = False
End Select
Return returnObject
End Function
'The engine calls this method based on the optional include directive
'if the user has specified it in the text template.
'This method can be called 0, 1, or more times.
'---------------------------------------------------------------------
'The included text is returned in the context parameter.
'If the host searches the registry for the location of include files
'or if the host searches multiple locations by default, the host can
'return the final path of the include file in the location parameter.
'---------------------------------------------------------------------
Public Function LoadIncludeText(ByVal requestFileName As String, ByRef content As String,
ByRef location As String) As Boolean Implements ITextTemplatingEngineHost.LoadIncludeText
content = System.String.Empty
location = System.String.Empty
'If the argument is the fully qualified path of an existing file,
'then we are done.
'----------------------------------------------------------------
If File.Exists(requestFileName) Then
content = File.ReadAllText(requestFileName)
Return True
'This can be customized to search specific paths for the file.
'This can be customized to accept paths to search as command line
'arguments.
'----------------------------------------------------------------
Else
Return False
End If
End Function
'The engine calls this method to resolve assembly references used in
'the generated transformation class project and for the optional
'assembly directive if the user has specified it in the text template.
'This method can be called 0, 1, or more times.
'---------------------------------------------------------------------
'---------------------------------------------------------------------
Public Function ResolveAssemblyReference(ByVal assemblyReference As String) As String
Implements ITextTemplatingEngineHost.ResolveAssemblyReference
'If the argument is the fully qualified path of an existing file,
'then we are done. (This does not do any work.)
'----------------------------------------------------------------
If File.Exists(assemblyReference) Then
Return assemblyReference
End If
'Maybe the assembly is in the same folder as the text template that
'called the directive.
'----------------------------------------------------------------
Dim candidate As String = Path.Combine(Path.GetDirectoryName(Me.TemplateFile),
assemblyReference)
If File.Exists(candidate) Then
Return candidate
End If
'This can be customized to search specific paths for the file,
'or to search the GAC.
'----------------------------------------------------------------
'This can be customized to accept paths to search as command line
'arguments.
'----------------------------------------------------------------
'If we cannot do better, return the original file name.
Return ""
End Function
'The engine calls this method based on the directives the user has
'specified in the text template.
'This method can be called 0, 1, or more times.
'---------------------------------------------------------------------
Public Function ResolveDirectiveProcessor(ByVal processorName As String) As System.Type
Implements ITextTemplatingEngineHost.ResolveDirectiveProcessor
'This host will not resolve any specific processors.
'Check the processor name, and if it is the name of a processor the
'host wants to support, return the type of the processor.
'---------------------------------------------------------------------
If String.Compare(processorName, "XYZ", StringComparison.InvariantCultureIgnoreCase) = 0
Then
'return typeof()
End If
'This can be customized to search specific paths for the file,
'or to search the GAC.
'If the directive processor cannot be found, throw an error.
Throw New Exception("Directive Processor not found")
End Function
'A directive processor can call this method if a file name does not
'have a path.
'The host can attempt to provide path information by searching
'specific paths for the file and returning the file and path if found.
'This method can be called 0, 1, or more times.
'---------------------------------------------------------------------
Public Function ResolvePath(ByVal fileName As String) As String Implements
ITextTemplatingEngineHost.ResolvePath
If fileName Is Nothing Then
Throw New ArgumentNullException("the file name cannot be null")
End If
'If the argument is the fully qualified path of an existing file,
'then we are done.
'----------------------------------------------------------------
If File.Exists(fileName) Then
Return fileName
End If
'Maybe the file is in the same folder as the text template that
'called the directive.
'----------------------------------------------------------------
Dim candidate As String = Path.Combine(Path.GetDirectoryName(Me.TemplateFile), fileName)
If File.Exists(candidate) Then
Return candidate
End If
'Look more places.
'Look more places.
'----------------------------------------------------------------
'More code can go here...
'If we cannot do better, return the original file name
Return fileName
End Function
'If a call to a directive in a text template does not provide a value
'for a required parameter, the directive processor can try to get it
'from the host by calling this method.
'This method can be called 0, 1, or more times.
'---------------------------------------------------------------------
Public Function ResolveParameterValue(ByVal directiveId As String, ByVal processorName As
String, ByVal parameterName As String) As String Implements
ITextTemplatingEngineHost.ResolveParameterValue
If directiveId Is Nothing Then
Throw New ArgumentNullException("the directiveId cannot be null")
End If
If processorName Is Nothing Then
Throw New ArgumentNullException("the processorName cannot be null")
End If
If parameterName Is Nothing Then
Throw New ArgumentNullException("the parameterName cannot be null")
End If
'Code to provide "hard-coded" parameter values goes here.
'This code depends on the directive processors this host will interact with.
'If we cannot do better, return the empty string.
Return String.Empty
End Function
'The engine calls this method to change the extension of the
'generated text output file based on the optional output directive
'if the user specifies it in the text template.
'---------------------------------------------------------------------
Public Sub SetFileExtension(ByVal extension As String) Implements
ITextTemplatingEngineHost.SetFileExtension
'The parameter extension has a '.' in front of it already.
'--------------------------------------------------------
fileExtensionValue = extension
End Sub
'The engine calls this method to change the encoding of the
'generated text output file based on the optional output directive
'if the user specifies it in the text template.
'---------------------------------------------------------------------
Public Sub SetOutputEncoding(ByVal encoding As System.Text.Encoding, ByVal
fromOutputDirective As Boolean) Implements ITextTemplatingEngineHost.SetOutputEncoding
fileEncodingValue = encoding
End Sub
'The engine calls this method when it is done processing a text
'template to pass any errors that occurred to the host.
'The host can decide how to display them.
'---------------------------------------------------------------------
Public Sub LogErrors(ByVal errors As System.CodeDom.Compiler.CompilerErrorCollection)
Implements ITextTemplatingEngineHost.LogErrors
errorsValue = errors
End Sub
'This is the application domain that is used to compile and run
'the generated transformation class to create the generated text output.
'----------------------------------------------------------------------
Public Function ProvideTemplatingAppDomain(ByVal content As String) As System.AppDomain
Implements ITextTemplatingEngineHost.ProvideTemplatingAppDomain
'This host will provide a new application domain each time the
'engine processes a text template.
'-------------------------------------------------------------
Return AppDomain.CreateDomain("Generation App Domain")
'This could be changed to return the current appdomain, but new
'assemblies are loaded into this AppDomain on a regular basis.
'If the AppDomain lasts too long, it will grow indefintely,
'which might be regarded as a leak.
'This could be customized to cache the application domain for
'a certain number of text template generations (for example, 10).
'This could be customized based on the contents of the text
'This could be customized based on the contents of the text
'template, which are provided as a parameter for that purpose.
End Function
End Class 'CustomCmdLineHost
'This will accept the path of a text template as an argument.
'It will create an instance of the custom host and an instance of the
'text templating transformation engine. It will also transform the
'template to create the generated text output file.
'-------------------------------------------------------------------------
Class Program
Shared Sub Main(ByVal args As String())
Try
ProcessTemplate(args)
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Sub
Shared Sub ProcessTemplate(ByVal args As String())
Dim templateFileName As String = ""
If args.Length = 0 Then
Throw New System.Exception("you must provide a text template file path")
End If
templateFileName = args(0)
If templateFileName Is Nothing Then
Throw New ArgumentNullException("the file name cannot be null")
End If
If Not File.Exists(templateFileName) Then
Throw New FileNotFoundException("the file cannot be found")
End If
Dim host As CustomCmdLineHost = New CustomCmdLineHost()
Dim engine As Engine = New Engine()
host.TemplateFileValue = templateFileName
'Read the text template.
Dim input As String = File.ReadAllText(templateFileName)
'Transform the text template.
Dim output As String = engine.ProcessTemplate(input, host)
Dim outputFileName As String = Path.GetFileNameWithoutExtension(templateFileName)
outputFileName = Path.Combine(Path.GetDirectoryName(templateFileName), outputFileName)
outputFileName = outputFileName & "1" & host.FileExtension
File.WriteAllText(outputFileName, output, host.fileEncoding)
Dim e As CompilerError
For Each e In host.Errors
Console.WriteLine(e.ToString())
Next
End Sub 'ProcessTemplate
End Class 'Program
End Namespace
4. For Visual Basic only, open the Project menu, and click CustomHost Proper ties . In the Star tup object
list, click CustomHost.Program .
5. On the File menu, click Save All .
6. On the Build menu, click Build Solution .
NOTE
The programming language of the text template does not have to match that of the custom host.
<# //Uncomment this line to test that the host allows the engine to set the extension. #>
<# //@ output extension=".htm" #>
<# //Uncomment this line if you want to debug the generated transformation class. #>
<# //System.Diagnostics.Debugger.Break(); #>
<# 'Uncomment this line to test that the host allows the engine to set the extension. #>
<# '@ output extension=".htm" #>
<# 'Uncomment this line if you want to debug the generated transformation class. #>
<# 'System.Diagnostics.Debugger.Break() #>
WriteLine("This is a test")
Next
#>
NOTE
Instead of typing the address, you can browse to the file CustomHost.exe in Windows Explorer and then drag
the file into the Command Prompt window.
3. Type a space.
4. Type the path of the text template file, and then press ENTER.
For example, type:
C:\<YOUR PATH>TestTemplate.tt
NOTE
Instead of typing the address, you can browse to the file TestTemplate.tt in Windows Explorer and then drag the
file into the Command Prompt window.
The custom host application runs and completes the text template transformation process.
5. In Windows Explorer , browse to the folder that contains the file TestTemplate.tt.
That folder also contains the file TestTemplate1.txt.
6. Open this file to see the results of the text template transformation.
The generated text output appears and looks like this:
This is a test
This is a test
This is a test
Next steps
In this walkthrough, you created a text template transformation host that supports the basic transformation
functionality. You can expand your host to support text templates that call custom or generated directive
processors. For more information, see Walkthrough: Connecting a Host to a Generated Directive Processor.
See also
ITextTemplatingEngineHost
API Reference for T4 Text Templates
4/13/2022 • 2 minutes to read • Edit Online
The Text Templating API lets you invoke and customize the transformation of text templates.
Namespaces
N A M ESPA C E P URP O SE