Walkthrough: Adding Validation to a Domain Model

As the author of a domain-specific language, you can add constraints to your domain models. This enables you to check for semantic correctness beyond what you can express in the syntax of that language. Users of your domain-specific languages can validate the models they create from those languages. Also, they can display their results. For more information, see Validation Overview for Domain-Specific Languages.

To add validation to a domain model, you must do the following things:

  • Enable validation for the categories of events that should trigger it, by setting the properties of the Editor/Validation node in the DSL Definition.

  • Apply the ValidationState attribute to the classes of elements that you want to validate.

  • Add a validation method to each class of element that you want to validate. Validation methods are identified by the ValidationMethod attribute.

In this walkthrough, you continue from the steps conducted in Walkthrough: Customizing the Domain-Specific Language Definition by adding validation constraints to the FamilyTree solution.

Prerequisites

To complete this walkthrough, you must:

Enabling the Validation Framework

Before you add validation, you must set global switches within the language definition to specify when validation occurs. For this example, you enable validation when a user opens a file, saves a file, or uses the Validate menu command.

To enable the validation framework

  1. Open the solution that you customized in Walkthrough: Customizing the Domain-Specific Language Definition

  2. In Solution Explorer, open DslDefinition.dsl.

  3. In DSL Explorer, expand the Editor node, and then click Validation.

    If DSL Explorer does not appear, open the View menu, point to Other Windows, and click DSL Explorer.

  4. In the Properties window, set the Uses Menu, Uses Open, and Uses Save properties to True.

    If the Properties window is not showing, press F4.

  5. Save DslDefinition.dsl.

  6. Regenerate the code for the solution by clicking Transform All Templates on the Solution Explorer toolbar.

    Note

    You might see a message warning you not to run text templates from untrusted sources. If you do not want this message to appear again, select the Do not show this message again check box, and click OK. Otherwise, click OK.

Adding a Partial Class for Validation Code

When you transform all templates, the system generates the source code that defines your domain-specific language in the Dsl project. It also generates a class for each class and relationship that is defined in the DslDefinition.dsl file. To add validation constraints, you add methods to these classes. However, to avoid interfering with the generated text, you should write your validation methods in a separate file by using a partial class. When you write a large set of validations, you should create more than one file. You could write a separate file for each class. Another option is to put groups of related constraints in separate files, each of which might contain more than one partial class.

To add a partial class

  1. Add a new folder to the Dsl project and name it Validation.

  2. Add a new code file named BirthDates.cs to the Validation folder.

    Note

    In BirthDates.cs, add the following code:

    namespace Fabrikam.FamilyTree
    {
        using System;
        using System.Collections.Generic;
        using System.Text;
        using System.Collections.Specialized;
       using Microsoft.VisualStudio.Modeling.Validation;
        [ValidationState(ValidationState.Enabled)]
        public partial class Person
    {
        //   Validation methods for Person go here.
    }
    }
    
  3. To verify the name and the namespace of the generated class, open Dsl\GeneratedCode\DomainClasses.cs.

  4. Verify that the Person partial class in the code sample has the same name and namespace as the generated class.

The ValidationState attribute guarantees that validation methods in the class are called when validation occurs. By default, constraints apply to instances of subclasses, but you can set ValidationState.Disabled in specific subclasses if your domain requires it.

Writing a Validation Method

To define constraints, you must add a validation method to your partial class.

To add the validation method

  • In BirthDates.cs, add the following code as a method in the partial class Person:

    Note

    Replace "//… validation methods for Person go here" from the previous procedure.

    // This attribute identifies the method ValidateParentBirth 
    // to the validation framework.
    [ValidationMethod
     ( // These values select which events cause the method to be invoked.
          ValidationCategories.Open |
          ValidationCategories.Save |
          ValidationCategories.Menu
     )
    ]
      // This method is applied to each instance of the 
      // type in a model. 
      private void ValidateParentBirth(ValidationContext context)   
      {
        foreach (Person parent in this.Parents)
          {
            if (this.Birth <= parent.Birth)
            {
              context.LogError(
                 // Description
                           "Birth must be after Parent's birth",
                 // Unique code for this error
                           "FAB001FamilyParentBirthError", 
                  // Objects to select when user double-clicks error
                           this, 
                           parent);
            }
          }
      }
    

You should declare validation methods private or protected if they might be overridden in a subclass. This strategy avoids cluttering the public API of the domain model with validation methods.

The LogError and LogWarning methods put messages in the Error List window. The second parameter of these methods ("FAB001FamilyParentBirthError" in the example) should be a unique identifier for the error. The third and subsequent parameters are the elements that should be highlighted in the model designer when the user clicks the error message.

Testing the Constraint

Rebuild your project to test the validation code. If you have not made further changes to the language definition files, you do not need to transform all templates again.

To test the method

  1. On the Build menu, click Rebuild Solution.

  2. On the Debug menu, click Start Debugging, or press F5.

  3. Open Test.ftree.

    The validation error appears in the Error List window (because you specified that the file should be validated whenever it is opened).

  4. Add further classes and relationships with errors between the birth dates of the parents and the children.

  5. Right-click anywhere in the model designer, and click Validate All.

    The new validation errors appear in the Error List window.

  6. Save the file.

    You might see a message that indicates that there were validation errors. It will prompt you to either continue to save or not.

Debugging Constraints

You can make a constraint work only in debug builds by wrapping the validation method with #if DEBUG and #endif.

See Also

Tasks

Family Tree Sample (Domain-Specific Language Tools)

User Interface Process Sample (Domain-Specific Language Tools)

Class Diagram Sample (Domain-Specific Langauge Tools)

Concepts

Validation Overview for Domain-Specific Languages

Other Resources

Domain-Specific Language Tools Glossary