Using DSL in Visual Studio

Introduction


Before, I had never thought about developing tools that simplified development in Visual Studio, and more often I created various third-party utilities to help myself in development. But, as usual, a turning point has come.
Once the task was to develop a platform on the basis of which it would be necessary to develop specialized solutions.
I wanted to simplify the development of solutions on the platform as much as possible, while not limiting the possibility of flexible configuration.
There were two main directions for solving the problem:
  • Develop custom solutions tools
  • Create assistive tools embedded in the development environment


Own tools

Using this approach, it would be possible to do without coding in the future. This, undoubtedly, simplifies and speeds up the development of the solution, as well as reduces the possibility of errors (unless of course everything is done cleanly).
But the main difficulty that appears before the tool developer is to provide for as much as possible all the necessary features that may be required.
In each solution, non-trivial tasks arise, about which you could not think at the stage of creating tools. Also, when creating universal tools, there is an increase in their complexity, which affects the timing of creation, as well as the complexity of use.

Visual Studio Tools

When using auxiliary tools, you automate the necessary basic code as much as possible, and allow you to expand the functionality of the system.
But to use this approach, solution developers must have a higher professional level.

My choice

After weighing the pros and cons that I could come up with, I decided to opt for the second option. It was new to me and really fascinated.
Next, I will try to describe the basics of creating assistive tools for Visual Studio 2010 using DSL. Perhaps this will push you to further independent study of this area.

Preliminary


Before we begin, we need to download and install the following distributions:

Formulation of the problem


To begin with, we need to come up with an example on the basis of which it would be possible to state both the main possibilities and show how this mechanism can be effective for solving everyday tasks.
At first, this task seemed elementary, but when I fell into thought, I realized that everything was not so simple. After spending some time in meditation, I still came up with.
I propose creating a model to help develop client-server applications at the level of creating WCF services. Of course, we will not place a strong emphasis on the technical side of WCF, but take it as an object model.

Creating a project and debugging it


Having set ourselves the task, without any further thought, we will create our own project.
As always, getting started, we launch our beloved Visual Studio 2010.
We go into the project creation window and select the project type Domain-Specific Language Designer .


Further, the Wizard will offer us to choose the type of module to be created. Choose MinimalLanguage. In this type of project, the minimum number of pre-created elements, which will allow us to get rid of them faster.



Next, you need to set the extension for our file. With this extension, we will add our files to projects.
The last contribution is the total information on the project being created.
After we clicked Finish, our project with pre-created elements opens before us, this is done in order to show us an example.



You can immediately compile the project and try to test it.
For testing and debugging projects of this type, a debugging studio is used. If you click run, or run with debugging, then you will open an additional studio where a test copy of your project file will be created.
Try experimenting with this example.

We clean everything

Everything that appeared with us is not particularly useful to us, so we will delete everything that was added automatically.
Go to DSL Explorer and delete everything from there except the standard types in Domain Types.
The result should be the following:



Let's describe the main objects of Toolbox

Domain Class - data object.
Named Domain Class - A named data object that contains the Name property. Further, the system will interpret this property as the name of the object, and will automatically generate its unique name.
The Geometry Shape is the visual representation that is used to visualize the
Compartment Shape on the diagram of data objects - the same as the Geometry Shape, it only has a different kind of
Embedding Relationship - it connects objects with the membership relationship
Reference Relationship - it connects objects with the reference type to the
Inheritance object - the parent connection type, then there is inheritance of one object from another
connector- visualizer of connections
Diagram Element Map - used to link objects and links with their visual representation

Creating basic objects and their mappings

Before proceeding with the creation of our immediate facilities, it is necessary to create preparatory elements:
  • Designer is a designer to visualize our objects. In DSL Explorer , we will create an Editor object . It is necessary to set the extension for our project in it (This is the FileExtension property ), which let it be “wcfw”.
  • Diagram is the main visualization element that represents our objects. Let's call it WCFDiagram.
  • You need to create a root element ( Domain Class ), on which you will need to configure the previously created Editor (This is the Root Class property ) and Diagram (This is the Class Represented property ). Let's name our root element “WCFCollection”.
  • Explorer Behavior - something similar to DSL Explorer , which will be displayed when working with a file of our type.

This is the minimum list of what needs to be done so that our file is saved and the project can be compiled. But before compilation it is necessary to regenerate the code according to the changes made to the file. In order to do this, in the Solution Explorer, click the "Transform All Templates" button.



All code is generated from * .tt files (which describe the T4 conversion) that are in the GeneratedCode folders in the Dsl and DslPackage projects. After the new objects have been generated, you can collect our projects and even start. The same debugging studio is launched, with the same project. Files that were related to the experimental project were added to the project, so you can delete all of them. Add your file type (just like any type), finding it by the name WCFWizard.

Creating an object describing the "service"

Let's start by creating a service object. To do this, you can drag the Named Domain Class element from the Toolbox and call it “WCFService”. We automatically got the Name property for the object, which has the Is Element Name flag , which says that this property is the name of the object.
The time has come to describe the basic properties of Domain Class and Named Domain Class objects.
Property NameDescription
Access modifierLevel of access to the generated class.
Base classBase class. We can select another created object here, and then in the generated code the current class will be inherited from the class specified by the base one.
Custom attributesThis is where additional attributes are hung on the object.
DescriptionDescription of the object.
Display nameThe display name of the object that the user will see.
Generates Double DerivedIf True, then two classes are generated (and usually one), one abstract, with the implementation of everything necessary, and the second generates a partial class that is empty inside. This allows you to further redefine virtual functions that are declared in base classes.
Has Custom ConstructorIf True, then you will need to define the necessary constructors in your extension class. This is convenient when you need to perform additional actions when creating an object.
Inheritance modifierAllows you to make your class abstract, or to prevent inheriting from it.
NameThe name of the object with this name a class will be generated.
NamespaceThe namespace for your class.

For now, leave all the default fields. And describe the additional properties of the Domain Property element
Property NameDescription
Getter access modifierRead access rights to the property.
Setter access modifierWrite access to the property.
Is UI Read OnlyShow property read-only.
Is browserWhether to display the property to the user.
Default valueThe default field values.
Element Name ProviderThe external type that will supply the values ​​for the property. The property must have a value of Is Element Name equal to True . Allows you to generate unique names by a specific algorithm.
Is element nameIs the property the name of the object. If True, then the initial value will be created unique.
Kind Type of field that affects its storage:
  • None - storage occurs in the generated code as a regular variable
  • Calculated - says that the field is calculated, and it is not stored in a variable. We will need to redefine the functions that are responsible for the calculation.
  • CustomStorage - custom storage. We will need to redefine the functions responsible for receiving and storing data properties

TypeType of property. A drop-down list is generated from the list of objects defined in Domain Types (can be found in DSL Explorer ). You can add your own objects.

Now we need to associate our WCFService with the WCFCollection parent relationship, which will say that the WCFService belongs to WCFCollection. For this, we extend the Embedding Relationship from WCFCollection to WCFService. As a result, we got something similar to the picture.



We have created a Domain Relationship object with the name "WCFCollectionHasWCFService". This object is responsible for the connection between the WCFCollection to the WCFService. All properties of this object are similar as Domain Class , and we describe additional properties separately.
Property NameDescription
Allows duplicatesIf this property is set to True, then it will be possible to create additional Embedding Relationships on the WCFService object.
Base relationshipA basic relationship whose properties we want to inherit.
Is embeddingShows that the connection is Embedding, or not.

To the right of Domain Relationship is “WCFService / 0 .. *” (it is of type Domain Role ), where WCFService is the name of the property of the WCFCollection object, which contains references to WCFService objects. 0 .. * is the type of connection that says that in WCFCollection there can be a number of WCFService from zero to infinity.

To the left of the Domain Relationship is “WCFCollection / 1..1” (it is of type Domain Role ), where WCFCollection is the name of the property of the WCFService object, which contains a reference to the WCFCollection object. 1..1 - a type of connection that says that WCFService must have exactly one (required) reference to the WCFCollection object.
Domain roledescribes the roles that exist in the bundle, as we can see from our example, each object (WCFService and WCFCollection) has its own role, which describes the behavior of the connection on each side.

We describe the main distinguishing features of a Domain Role object.
Property NameDescription
Collection typeAllows you to set the type of key for receiving objects. In this case, we will not generate a list, but a collection.
Is property browserDetermines the visibility of the property for the user.
Is property generatorDetermines whether a property is generated.
Property Custom AttributesAllows attaching attributes to a property.
Property Getter AccessRead access rights.
Property Setter AccessRights to access the property on the record.
Multiplicity Shows the number of created objects:
  • 0 .. * - from zero to infinity
  • 1..1 - in a single copy
  • 0..1 - from zero to one
  • 1 .. * - minimum from one to infinity

Propagates copyThe object that owns this role in the bundle will also be copied when copying the bundle.
Propagates DeleteThe object that owns this role in the bundle will also be deleted when the bundle is deleted.
Property nameThe name of the generated property.
Role playerThe type of object to which the role belongs.

Now save, click the “Transform All Templates” button, collect the solution and start. Now we can add objects of type WCFService through WCFWizard Explorer to WCFCollection and give them names.

Create a chart display

For our objects to begin to appear on the diagram, we need to create a visual representation for them. Toolbox elements are used for these purposes : Geometry Shape , Compartment Shape , Connector . The first two render objects of type Domain Class , and the last is used for Domain Relationship .
Add a Compartment Shape element and give it the name WCFServiceShape. This element has a number of properties responsible for its visual representation, but it is also possible to produce a more flexible visual representation through the extension of the generated class.
To associate objects with their representation, you must use the elementDiagram Element Map and stretch the connection between WCFService and WCFServiceShape.



Let's see what we can add to our Compartment Shape :
  • Compartment - Displays a collection of properties.
  • Domain Property - The property that will appear in Properties.
  • Expand Collapse Decorator - an element that allows you to collapse and expand the internal content.
  • Icon Decorator - image display.
  • Text Decorator - text output.

Add a Text Decorator and give it the name NameDecorator. Now we need to associate it with a specific property in WCFService, for this we use the DSL Details window . When you select our Diagram Element Map , information about the bundle will appear. Go to the Decorator Maps tab and activate our NameDecorator. The Path to display property element can be left empty, since we will use the WCFService property directly, but with this element we can set the path to the related object and then display its property. In the Display property element, select our name.



Now our WCFService has learned to display itself on a chart.

Putting an object on a Toolbox

You can add objects through WCFWizard Explorer, but it doesn’t look very nice. Let's try to add the ability to create an object through the Toolbox .
Let's go to DSL Explorer and create a new Toolbox Tab in Editor , which we will name WCFWizard Elements. This we created a group of elements for the Toolbox , in which we can create two types of elements: Connection Tool (for connections) and Element Tool (for objects).
Create an element of the Element Tool type , and give it the name WCFServiceTool, and take the icon from those that already exist in the resources. The most important thing is to set the Class property, which characterizes the object that should be created when dragging. Select WCFService.
Now the first object has appeared on our Toolbox , which you can safely drag onto the chart.

Creating an object describing a "function"

We perform similar actions, as was done above for WCFService, only with the name WCFFunction. And bind the WCFFunction to the WCFService (using Embedding Relationship ), and not to the WCFCollection. And we get something like the following.



Now we can add functions to the service, but they are not displayed on the diagram. Add a mapping to the bunch.
Drag an object of type Connector (you can adjust its external display in the properties) and associate it with the WCFServiceHasWCFFunction object using the Diagram Element Map .
Add an object that will describe the type of the object to set the parameters of the function and the return type.
Add a Domain Class Objectnamed WCFType, and create two text properties in it: Namespace and Type, to specify the namespace of the type and the type itself. Set the Inheritance Modifier property to abstract.
We also add a Domain Class object with the names WCFParamType (representing a set of function parameters), which will be inherited from WCFType (setting the Base Class parameter to WCFType).
For WCFParamType, add the Name property to identify the name of the parameter in the function and associate it with the WCFService as shown in the figure.



Now in the WCFFunctionShape object, add a Compartment (let's call it Params). Let's move to the DSL Details panel with the highlighted link between WCFFunction and WCFFunctionShape. Select the Compartment Maps tab and configure it as in the picture.



The Displayed elements collection path field indicates the path to the collection of elements that we want to display (using bundles), and then we specify the name of the property to display.
Now we have enough information to start generating simple code.
This article does not include code generation, but I will say that you can go in different ways, I used two:
  • Generate using T4.
  • Generate directly from code, when saving.

Build and Deploy a Package


It is time to clarify the issue of installing our extension in Visual Studio . When building the DSLPackage project , we get a file with the vsix extension , which is the installation package.
To deploy our project, just run the vsix file and go through the steps of the installer.
As a result, our extension appears in Visual Studio and can be viewed in the Extension Manager (located in the Tools menu ). Here you can also delete it.



Conclusion


I managed to cover only the smallest part of this tool, but I hope that this will encourage someone to do independent in-depth study.
If this topic will be interesting to the people, then I have plans to cover such issues as:
  • Using menu items.
  • Validation of data.
  • Code generation.

Also popular now: