Visual Studio DSL 入门 12---状态机设计器的规则(Rule)和验证(Validation)

    上一节我们为状态机设计器添加了一个Rule,主要用来处理当Transition的属性Label,Condition,Action,Event之间的任何一个值发生变化时,其余的属性值也要按照我们的规则来更新(我们的Label属性就是一个辅助的属性,用来更好的显示和编辑另外三个属性).我们可以看到vs.net dsl提供的Rule机制的强大,它主要提供了以下几个Rule:
     AddRule:  当ModelElement或者ElementLink添加时触发
     ChangeRule:  当一个元素或者关系的属性发生变化时触发
     DeleteingRule:  删除元素或关系时触发
     DeletedRule:   删除元素或关系后触发
     RolePlayerChangeRule: 当域关系的一端发生变化时
     RolePlayerPositionChangeRule: 对于多重的关系中的角色发生变化
     TransactionBeginningRule: 事务开始时触发
     TransactionCommitingRule  事务提交时触发
     TransactionRollingBackRule  事务回滚时触发
    另外应该注意的是,AddRule,ChangeRule,DeleteingRule…这些都是在元素添加,更改,删除同时触发,此时还在事务当中,也就是说,我们可以添加自己的规则,根据我们自定义的条件取消事务或做一些其它的处理。
    但是规则是强制性的,也就是说,在一个规则处理里面,我们如果限制一个属性值的类型必须是整型,否则就抛出异常,停止此事务的提交。这属于Vs.net Dsl提供的硬约束的一种实现,相反,还有软约束,那硬约束和软约束有什么不同呢?
     硬约束就是指从不让用户违反的约束,比如我们例子中的四个属性之间的这种关系,如果有些个案,就会导致我们的元数据混乱,生成代码就很麻烦.
     软约束是用户有时可以违反,有时又不能违反的约束,或者是说,即使用户违反了,我们也要保证元数据能够正常保存,正常提交。比如说我们的状态机中没有初始状态. 
   一个优秀的Dsl设计器应该是硬约束和软约束结合,软的不行来硬的!  当然,这里提到Rule只是硬约束的一种,比如我们还可以重载指定域属性值属性处理器内嵌类中的OnXXXChanged()方法,例如,我们添加一个partial类ConditionPropertyHandler:

隐藏行号 复制代码
  1. internal sealed partial class ConditionPropertyHandler : DomainPropertyValueHandler<Transition, string>
  2.    {
  3.        protected override void OnValueChanging(Transition element, string oldValue, string newValue)
  4.        {
  5.            if (!element.Store.InUndoRedoOrRollback)
  6.            {
  7.                if (!string.IsNullOrEmpty(newValue))
  8.                {
  9.                    element.Label = ComputeSummary(newValue, element.Condition, element.Action);
  10.                }
  11.            }
  12.            base.OnValueChanging(element, oldValue, newValue);
  13.        }
  14.    }

    介绍了硬约束后,我们来看一下vs.net  dsl的软约束的机制:
    和硬约束一样,软约束也是通过附加的C#类来完成,相比于定义特殊的规则或者是验证语言来说,这很方便,有了更大的灵活性。 我们来看一下validation机制,这就是vs.net dsl提供的软约束,它和rule最明显的区别就是,rule是被动触发的,当我们操作元数据,对模型进行操作时,触发了我们定义的Rule(规则),而validation(验证)一般是主动触发的,提供了上下文菜单,我们可以验证我们整个模型或者是单个元素。
    还是以我们的例子来介绍,为了状态机的合理性,我们需要遵守: 
    Name属性的值的有效性
    初始状态不能做为转移的目标,结束状态不能做为转移的开始.
    一个状态机应该有一个初始状态

    下面我们就一步一步添加这些Validation:
     1.首先在我们在CustomCode文件夹下面添加Validation文件夹,来存储我们的验证.
     2.新建一个partial类State,注意命名空间修改为CompanyName.LanguageSm,要保持和原来生成的State类一致.
     3.给类打上ValidationState属性标记.

隐藏行号 复制代码
  1. [ValidationState(ValidationState.Enabled)]
  2.   public partial class State
  3. {
  4.   }

      4. 我们在partial类中添加自定义的验证,其实也就是添加验证方法,在方法上打上ValidationMehtods标记

隐藏行号 复制代码
  1. [ValidationState(ValidationState.Enabled)]
    
  2.  public partial class State
    
  3. {
    
  4.      [ValidationMethod(ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu)]
    
  5.      private void ValidateAttributeNameAsValidIdentifier(ValidationContext context)
    
  6.      {
    
  7.          CSharpCodeProvider csharp = new CSharpCodeProvider();
    
  8.          if (string.IsNullOrEmpty(this.Name.Trim()))
    
  9.              context.LogError("State的名称不能为空", "StateMachine – State - 01", this);   
    
  10. 
    
  11.          else if (!csharp.IsValidIdentifier(Name))
    
  12.          {
    
  13.              context.LogError("State的名称不合法", "StateMachines – State - 02", this);
    
  14.          }
    
  15.      }
    
  16.  }
    

   其中ValidationCategories是代表验证的调用时机, 设计器打开.保存模型文件还是通过右键菜单中的Validate. 参数ValidationContext包含验证的上下文信息,上面我们用来记录错误,信息提示等.
   同样的方式我们添加对另外初始状态的验证,上面我们提示的信息是写死在程序里面的,当然可以实现从资源里获取.
     5.我们要验证整个状态机只有一个初始状态怎么办,添加StateMachine的partial类,在这个类里面添加验证:

隐藏行号 复制代码
  1. [ValidationState(ValidationState.Enabled)]
    
  2. public partial class StateMachine
    
  3. {
    
  4.     [ValidationMethod(ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu)]
    
  5.     private void ValidateStateMachineHasOneInitialState(ValidationContext context)
    
  6.     {
    
  7.        if(!States.Exists(p=>p.Kind == StateKind.Initial))
    
  8.             context.LogError("状态机至少应该有一个初始状态", "StateMachine - 01", this);
    
  9.     }
    
  10. }
    


     6.不过这还不够,我们还需要进行一下设置才能让验证生效,打开dsl文件,Dsl Explor中的Editor/Validation结点,设置Validation属性中的Use Menu,Use Open,Use Save为True:
   image 
   

 

    7.重新转换所有的模板,我们来测试一下我们的验证,右键全部验证:
   C8Q@_LN7M5R}3{])GYTL]3F

   回过头来想一下,我们不允许结束状态上建立Transition的源,这应该是强制性的吧,既然这样,我们为什么还要在验证的时候才去验证,而允许用户有机会犯这样的错误呢?下面我们就来实现对这个的控制,使结束状态为源不能够建立Transition关系.
   8.打开Dsl Explorer,结点Connection Builder/TransitionBuilder/Link Connect Directives/Transition,选中Transition后,打开Dsl Details窗口,我们设置域关系Transition的对于源角色State使用自定义接受(Custom accept).
@8U9QNZJ8W`00$KOCE@G}9W

    9.转换所有模板,重新编译解决方案,你会发现有错误发生,是提示你必须要实现TransitionBuilder中的CanAcceptStateAsSource和CanAcceptStateAndStateAsSourceAndTarget方法,这是vs.net dsl设计自定义处理后通用的处理方法,你必须手动添加相应的方法后才能够编译通过. 在我们的CustomCode文件夹下面添加TransitionBuilder类,注意这是一个静态类,然后在这个静态类中添加这两个静态方法.

隐藏行号 复制代码
  1. namespace Company.LanguageSm
    
  2. {
    
  3.     public static partial class TransitionBuilder
    
  4. {
    
  5.         private static bool CanAcceptStateAsSource(State state)
    
  6.         {
    
  7.             return ((state != null) && (state.Kind != StateKind.Final));
    
  8.         }
    
  9.         private static bool CanAcceptStateAndStateAsSourceAndTarget(State sourceState, State targetState)
    
  10.         {
    
  11.             return CanAcceptSource(sourceState);
    
  12.         }
    
  13.     }
    
  14. }
    

    10.再次运行我们的项目,你会发现,当选中Transition在一个结束状态上拖拽时,你会发现,显示圆形的不可用.

代码下载

参考资源
      1. Visual Stuido DSL 工具特定领域开发指南
      2. DSL Tools Lab     http://code.msdn.microsoft.com/DSLToolsLab  系列教程  [本系列的入门案例的主要参考]

作者:孤独侠客似水流年
出处:http://lonely7345.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

posted @ 2010-03-06 23:22 孤独侠客 阅读(...) 评论(...) 编辑 收藏