26/12/2007 20:26
El Bruno
[DSL] DSL Integration Service, integracion y referencias entre DSLs
Buenas
si llevas trabajando un tiempo utilizando las Domain Specific Language Tools, seguramente te encontrarás en una situación donde necesites compartir información entre diferentes modelos. Hace un tiempo, explique como "cargar" una instancia de un DSL utilizando el modelo de objetos que se genera con el mismo, pero ¿qué sucede cuando quieres tener una referencia a un modelo desde otro, o a determinadas partes del mismo?
Si partimos de la base que un DSL (por definición) debe estar enfocado a pequeños dominios de negocio, cuando pensamos en un concepto un poco más global, seguramente nos enfrentaremos a un escenario donde necesitemos integrar información de diferentes DSLs.
DSL Integration Service
La versión actual de Domain Specific Language Tools no permite realizar integración entre DSL diferentes. Sin embargo, podemos utilizar Visual Studio DSL Integration Service (DIS) para realizar esta tarea. DIS es una parte del Microsoft.VisualStudio.Modeling.Integration Package, cuyo objetivo y función principal es permitir la creación de referencias de elementos entre diferentes modelos DSL.
Lamentablemente, este paquete no está soportado por el equipo de desarrollo de VSX, y tampoco hay mucha información al respecto, pero jugando un poco y gracias a un poco de Reflector se pueden obtener resultados interesantes.
Tutorial - DIS Sample
Para demostrar la utilización del mismo, volveremos al ejemplo (que maduro post a post), donde tenemos un pequeño DSL que permite modelar proyectos y asignar usuarios al los mismos.
1. Agregar Referencias
Lo primero que debemos realizar es agregar una referencia a Microsoft.VisualStudio.Modeling.Integration en el proyecto Dsl y automáticamente se agregará en el proyecto DslPackage. Usualmente este assembly se encuentra ubicado en ..Program Files\Microsoft Visual Studio 8\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.Modeling.Integration.dll
2. El siguiente paso consiste en la publicación de nuestro modelo como un modelo "navegable". Debemos decorar nuestro proyecto con el el atributo RegisterAsExportable. Para esto modificamos el archivo Package.tt dentro del proyecto DslPackage y agregamos en la sección superior la declaración de la variable domainModelRoot (línea 5).
Además dentro del archivo Package.tt agregamos al final del mismo, la sección que decora el paquete utilizando el atributo RegisterAsExportable (líneas 29 a 36).
1 <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
2 <#@ include file="DslPackage\Package.tt" #>
3 <#
4 string directiveName = "Dsl15";
5 string domainModelRoot = "ExampleModel";
6 #>
7 //
8 // Package attributes which may need to change are placed on the partial class below, rather than in the main include file.
9 //
10 namespace <#= CodeGenerationUtilities.GetPackageNamespace(this.Dsl) #>
11 {
12 /// <summary>
13 /// Double-derived class to allow easier code customization.
14 /// </summary>
15 /// <remarks>
16 /// A package load key is required to allow this package to load when the Visual Studio SDK is not installed.
17 /// Package load keys may be obtained from http://msdn.microsoft.com/vstudio/extend.
18 /// Consult the Visual Studio SDK documentation for more information.
19 /// [VSShell::ProvideLoadKey("Standard", Constants.ProductVersion, Constants.ProductName, Constants.CompanyName, 1)]
20 /// </remarks>
21 [VSShell::ProvideMenuResource(1000, 1)]
22 [VSShell::ProvideToolboxItems(1)]
23 [VSTextTemplatingHost::ProvideDirectiveProcessor(typeof(global::<#= this.Dsl.Namespace #>.<#= directiveName #>DirectiveProcessor), global::<#= this.Dsl.Namespace #>.<#= directiveName #>DirectiveProcessor.<#= directiveName #>DirectiveProcessorName, "A directive processor that provides access to <#= directiveName #> files")]
24 [global::System.Runtime.InteropServices.Guid(Constants.<#= dslName #>PackageId)]
25 internal sealed partial class <#= dslName #>Package : <#= dslName #>PackageBase
26 {
27 }
28
29 [Microsoft.VisualStudio.Modeling.Integration.RegisterAsExportable(
30 typeof(<#= directiveName #>DomainModel),
31 typeof(<#= directiveName #>SerializationBehaviorMonikerResolver),
32 typeof(<#= domainModelRoot #>),
33 typeof(<#= directiveName #>SerializationBehavior))]
34 internal sealed partial class <#= dslName #>Package : <#= dslName #>PackageBase
35 {
36 }
37 }
3. En este momento modificaremos el modelo agregando una nueva propiedad al UserElement llamada ReferencedUserElement. El objetivo de esta nueva propiedad es poder realizar una referencia a otros UserElements del modelo actual o de otros modelos, permitiendo la selección de los mismos.
4. Para permitir la selección de un UserElement desde la propiedad ReferencedUserElement, debemos agregar los siguientes atributos a la propiedad Custom Attributes:
- Microsoft.VisualStudio.Modeling.Integration.TargetClass
- @"ElBruno.Dsl15\UserElement"
- System.ComponentModel.Editor
- typeof(Microsoft.VisualStudio.Modeling.Integration.Editors.ExportedClassEditor)
- typeof(System.Drawing.Design.UITypeEditor)
- System.ComponentModel.TypeConverter
- typeof(Microsoft.VisualStudio.Modeling.Integration.ExportedInstanceConverter)
- El 1er atributo Microsoft.VisualStudio.Modeling.Integration.TargetClass determina qué elementos del modelo se desea examinar.
- El 2do atributo System.ComponentModel.Editor determina qué editor se utilizará para seleccionar los valores de esta propiedad.
- El 3er atributo System.ComponentModel.TypeConverter especifica el tipo de converter utilizado para la conversión a String de la selección de un elemento de un modelo.
5. En este momento ya podemos transformar el código y compilar el proyecto y veremos que si lanzamos el proyecto de debugging veremos que los shapes de usuarios poseen una nueva propiedad que permite seleccionar un elemento de usuario.
DIS Does not support [Generate Double Derived = True]
Cuando seleccionamos la misma y presionamos F4 o hacemos click en el boton (...) veremos que nos encontramos con el siguiente error:
---------------------------
Microsoft Visual Studio
---------------------------
Unable to locate DomainObjectIdAttribute attribute in type 'ElBruno.Dsl15.UserShape'
---------------------------
OK
---------------------------
Y aquí el problema se complica bastante ya que DIS no soporta el trabajo con elementos que tengan la propiedad Generate Double Derived = True. En nuestro caso a esta propiedad la hemos utilizado bastante ya que con la misma, generamos las diferentes personalizaciones visuales en nuestro modelo (el fondo del DSL, los textos multilínea, etc.).
6. La solución para este caso es volver a False todos los elementos donde se haya cambiado la propiedad Generate Double Derived. Al momento de compilar nos encontraremos con varios errores, ya que como hemos generado código personalizado para la personalización de componentes visuales, este código no encuentra lugar para compilarse.
Solución temporal: comentar el código que dispara excepciones.
7. Compilar nuevamente la solución y lanzar el proyecto de Debug. Veremos que hemos perdido las personalizaciones visuales pero sin embargo si seleccionamos un elemento del tipo UserElement y editamos la propiedad ReferencedUserElement, veremos que podemos lanzar un editor para la selección de UserElement.
8. Lo interesante de la utilización del DIS, es que permite navegar y seleccionar los diferentes elementos que componen la solución. Como en la definición de la propiedad hemos restringido el acceso a solo aquellos que sean del tipo ElBruno.Dsl15, podremos navegar sobre todas las intancias de los mismos. Por ejemplo si agregamos un nuevo proyecto, y dentro del mismo agregamos un nuevo modelo con información de ejemplo, podremos ver que la selección de UserElement aparece este nuevo modelo.
Conclusión
Utilizando DIS podremos realizar referencias entre diferentes diseñadores creados utilizando Domain Specific Language Tools de una forma rápida y sencilla. Si bien este paquete no está soportado, su utilización es muy simple y bastante directa. Lamentablemente, DIS posee restricciones y una de ellas para mi no tiene justificación limita mucho la creación de los DSLs.
Todavía no he exprimido a fondo las posibilidades de este paquete, pero seguramente posee muchas otras virtudes y otras restricciones que iré explorando poco a poco (obviamente que también postearé al respecto).
Pueden descargar el proyecto de ejemplo desde http://geeks.ms/files/folders/elbruno/entry59709.aspx (thanks Rodrigo, upss perdon thanks Geeks.ms jejeje)
Solución al problema de la propiedad [Generate Double Derived = True]
He escrito cerca de 10 posts relacionados con las opciones que podemos utilizar para personalizar los modelos que creemos con las Domain Specific Language Tools. Una simple restricción no debería afectar tanto a la utilización de las mismas. Por ejemplo, ya que no podremos realizar un override del método InitilizeShapeFields para definir las propiedades de visualización, porque el mismo no está generado en 2 clases separadas, podremos utilizar otro override para realizar esta tarea.
Por ejemplo en el siguiente código correspondiente al Diagrama principal del DSL, hemos reemplazado el método InitilizeShapeFields por un nuevo metodo llamado SetDefinedStyle (línea 22) y luego llamando a este método desde la propiedad StyleSet (línea 11, a la que también hemos sobreescrito).
1 namespace ElBruno.Dsl15
2 {
3 public partial class Dsl15Diagram
4 {
5
6 /// <summary>
7 /// Gets an instance of the style set for the shape.
8 /// </summary>
9 /// <value></value>
10 /// <returns>An instance of the style set for the shape; otherwise, if there is no style set for the shape, the class style set. </returns>
11 public override StyleSet StyleSet
12 {
13 get
14 {
15 SetDefinedStyle(this.ShapeFields);
16 return base.StyleSet;
17 }
18 }
19
20 //protected override void InitializeShapeFields(IList<Microsoft.VisualStudio.Modeling.Diagrams.ShapeField> shapeFields)
21
22 private void SetDefinedStyle(IList<Microsoft.VisualStudio.Modeling.Diagrams.ShapeField> shapeFields)
23 {
24 base.InitializeShapeFields(shapeFields);
25
26 // add backgroung image to the designer
27 ImageField backgroundImage = new ImageField("background", Properties.Resources.fondo);
28 backgroundImage.DefaultFocusable = false;
29 backgroundImage.DefaultSelectable = false;
30 backgroundImage.DefaultVisibility = true;
31 shapeFields.Add(backgroundImage);
32
33 // define Background Image margins
34 backgroundImage.AnchoringBehavior.Clear();
35 backgroundImage.AnchoringBehavior.SetTopAnchor(AnchoringBehavior.Edge.Top, -1.5);
36 backgroundImage.AnchoringBehavior.SetLeftAnchor(AnchoringBehavior.Edge.Left, 0);
37 }
38 }
39 }
40
Aplicando este cambio ya podremos tener nuestro DSL con elementos multilínea, background personalizado y capacidades para la sección de elementos utilizando DIS. :D

Fuentes
Saludos @ Hospital
El Bruno
Crossposting from
ElBruno.com
Comparte este post: