DSL Tools #10 - Preventing the User from Adding Instances of a Domain Class
In the next few posts about the DSL tools, I'll be using the following domain model:
This model is relatively simple:
- One model can have multiple entities.
- One entity can have zero or more normal attributes.
- One entity can have zero or more calculated attributes.
The interesting thing in this model is that the user is not supposed to create entities or attributes (either normal or calculated).
These domain classes are created at runtime (by code) through a menu operation where the user selects a different model (designed with a different DSL) and the domain classes are read from that model and put in the model the user is working on (so he can change some properties relevant to this model).
Basically this a major requirement of the DSL I've been working on, and that made me find solutions for the problems I'll present in this and in the next few posts.
So, first, if the domain classes are created at runtime, how do I go and prevent the user from adding instances of these classes to the domain model (for example, through the model explorer)?
It's quite simple:
1. Prevent the User from Adding New Entities
Having all the extensibility points in place (see previous posts in this series), all I need to do is to create a partial class for the "container domain class" of Entity. That's ReportingModelRoot (see the GraphHasEntities relation).
On that class, I override the CanMerge method and check if I'm merging with an Entity domain class. If so, the method just has to return false.
1: using System;
2: using Microsoft.VisualStudio.Modeling;
3:
4: namespace MyDSL
5: {
6: /// <summary>
7: /// Provides custom behavior for the ModuleReferenceUsesEntities domain class.
8: /// </summary>
9: public partial class ReportingModelRoot
10: {
11: #region Public Methods
12:
13: /// <summary>
14: /// Returns a value indicating whether the source element represented by the
15: /// specified root ProtoElement can be added to this element.
16: /// </summary>
17: /// <param name="rootElement">The root ProtoElement representing a source element. This can be null,
18: /// in which case the ElementGroupPrototype does not contain an ProtoElements
19: /// and the code should inspect the ElementGroupPrototype context information.</param>
20: /// <param name="elementGroupPrototype">The ElementGroupPrototype that contains the root ProtoElement.</param>
21: /// <returns>
22: /// true if the source element represented by the ProtoElement can be added to this target element.
23: /// </returns>
24: protected override bool CanMerge(ProtoElementBase rootElement, ElementGroupPrototype elementGroupPrototype)
25: {
26: // The user cannot add entities (they're added by code)
27:
28: if (rootElement.DomainClassId.Equals(Entity.DomainClassId))
29: {
30: return false;
31: }
32:
33: // Default behavior
34:
35: return base.CanMerge(rootElement, elementGroupPrototype);
36: }
37:
38: #endregion
39: }
40: }
2. Prevent the User from Adding Attributes to an Entity
This is basically the same as in the previous case. The difference is just that the "container class" is now Entity:
1: using System;
2: using Microsoft.VisualStudio.Modeling;
3:
4: namespace MyDSL
5: {
6: /// <summary>
7: /// Provides custom behavior for the Entity domain class.
8: /// </summary>
9: public partial class Entity
10: {
11: #region Public Methods
12:
13: /// <summary>
14: /// Determines whether this instance can merge the specified root element.
15: /// </summary>
16: /// <param name="rootElement">The root element.</param>
17: /// <param name="elementGroupPrototype">The element group prototype.</param>
18: /// <returns>
19: /// <c>True</c> if this instance can merge the specified root element; otherwise, <c>false</c>.
20: /// </returns>
21: protected override bool CanMerge(ProtoElementBase rootElement, ElementGroupPrototype elementGroupPrototype)
22: {
23: // The user cannot add normal attributes (they're added by code)
24: // The user cannot add calculated attributes (they're added by code)
25:
26: if (rootElement.DomainClassId.Equals(NormalAttribute.DomainClassId) || rootElement.DomainClassId.Equals(CalculatedAttribute.DomainClassId))
27: {
28: return false;
29: }
30:
31: // Default behavior
32:
33: return base.CanMerge(rootElement, elementGroupPrototype);
34: }
35:
36: #endregion
37: }
38: }
One important thing to notice here is that this behavior is just UI related. It disables are commands in the designer (and the model explorer) that would allow the user to add instances of these domain classes. It doesn't prevent code from adding instances of these same classes. But that was just what I needed (since I have a procedure that does exactly that).