Overview of Connection Builders
To create a connection in a generated designer, you drag a Connection item from the Toolbox. Each Connection item invokes a connection builder, as defined in the definition of the domain-specific language. After you click the Connection item, you can drag from one object to another, under the control of the connection builder. For more information, see How to: Define Connection Builders and How to: Add a Connection Tool.
Connection builders appear in DSL Explorer as shown in the following illustration. Each connection builder has one or more link connect directives, which you can examine in the DSL Details window. Each link connect directive has one or more source roles and one or more target roles. For more information, see How to: Set Details on Link Connect Directives.
CommentReferencesSubjects Connection Builder
A simple connection builder, such as the CommentReferencesSubjectsBuilder in the Task Flow Diagram sample, creates links of only one relationship type. For more information, see Task Flow Diagram Sample. CommentReferencesSubjectsBuilder has one link connect directive, which is named CommentReferencesSubjects and which lists the Comment class as its source role player and the FlowElement class as its target. In the task flow sample, FlowElement is an abstract class that has multiple subclasses. By using this connection builder, you can connect an instance of the Comment class to any subclass of the FlowElement class. If you click this item but then try to connect other types, the connection builder will fail. The quoted classes are the domain classes, not shapes. However, you drag between shapes that present those classes.
The FlowBuilder connection builder is a more complex connection builder in the same task flow sample. By using this connection builder, you can create one of two relationships, depending on what types of objects you click. FlowBuilder has three link connect directives, each of which has a set of several source and target types. When you drag from a source object to a target, the types are matched up with the allowed combinations. If any match, the relationship for that link connect directive is created.
Custom Code for Connection Builders
In each link connect directive, the Source role directives tab defines from what types you can drag. Similarly, the Target role directives tab defines to what types you can drag. For each type, you can further specify whether to allow the connection (for that link connect directive) by setting the Custom Accept flag and then supplying the extra code.
You can also customize what occurs when the connection is made. For example, you can customize just the case where the drag occurs to or from a particular class, all the cases that one link connect directive governs, or the whole FlowBuilder connection builder. For each of these options, you can set custom flags at the appropriate level. When you transform all templates and try to build the solution, error messages direct you to comments that are in the generated code. These comments identify what you must supply.
In the Components Diagram sample, the connection builder for the Connection domain relationship is customized to restrict how connections are made between ports. For more information, see Components Diagram Sample.
As shown in the following illustration, in the Components Diagram sample, you can make connections only from OutPorts to InPorts, but you can nest components inside each other. Therefore, you might want to specify that a connection can come from a nested component to an OutPort:
Connection Coming in to an OutPort from a Nested Component
To specify such a connection, you set Uses Custom Accept on the InPort type as source role and the OutPort type as target role in the DSL Details window as shown in the following illustrations:
Link Connect Directive in DSL Explorer
Link Connect Directive in DSL Details Window
You must then provide methods in the ConnectionBuilder class:
public partial class ConnectionBuilder
{
/// <summary>
/// OK if this component has children
/// </summary>
private static bool CanAcceptInPortAsSource(InPort candidate)
{
return candidate.Component.Children.Count > 0;
}
/// <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…
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.