上一节我们为状态机设计器添加了一个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:
internal sealed partial class ConditionPropertyHandler : DomainPropertyValueHandler<Transition, string>
{
protected override void OnValueChanging(Transition element, string oldValue, string newValue)
{
if (!element.Store.InUndoRedoOrRollback)
{
if (!string.IsNullOrEmpty(newValue))
{
element.Label = ComputeSummary(newValue, element.Condition, element.Action);
}
}
base.OnValueChanging(element, oldValue, newValue);
}
}
介绍了硬约束后,我们来看一下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属性标记.
[ValidationState(ValidationState.Enabled)]
public partial class State
{
}
4. 我们在partial类中添加自定义的验证,其实也就是添加验证方法,在方法上打上ValidationMehtods标记
[ValidationState(ValidationState.Enabled)]
public partial class State
{
[ValidationMethod(ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu)]
private void ValidateAttributeNameAsValidIdentifier(ValidationContext context)
{
CSharpCodeProvider csharp = new CSharpCodeProvider();
if (string.IsNullOrEmpty(this.Name.Trim()))
context.LogError("State的名称不能为空", "StateMachine – State - 01", this);
else if (!csharp.IsValidIdentifier(Name))
{
context.LogError("State的名称不合法", "StateMachines – State - 02", this);
}
}
}
其中ValidationCategories是代表验证的调用时机, 设计器打开.保存模型文件还是通过右键菜单中的Validate. 参数ValidationContext包含验证的上下文信息,上面我们用来记录错误,信息提示等.
同样的方式我们添加对另外初始状态的验证,上面我们提示的信息是写死在程序里面的,当然可以实现从资源里获取.
5.我们要验证整个状态机只有一个初始状态怎么办,添加StateMachine的partial类,在这个类里面添加验证:
[ValidationState(ValidationState.Enabled)]
public partial class StateMachine
{
[ValidationMethod(ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu)]
private void ValidateStateMachineHasOneInitialState(ValidationContext context)
{
if(!States.Exists(p=>p.Kind == StateKind.Initial))
context.LogError("状态机至少应该有一个初始状态", "StateMachine - 01", this);
}
}
6.不过这还不够,我们还需要进行一下设置才能让验证生效,打开dsl文件,Dsl Explor中的Editor/Validation结点,设置Validation属性中的Use Menu,Use Open,Use Save为True:
7.重新转换所有的模板,我们来测试一下我们的验证,右键全部验证:
回过头来想一下,我们不允许结束状态上建立Transition的源,这应该是强制性的吧,既然这样,我们为什么还要在验证的时候才去验证,而允许用户有机会犯这样的错误呢?下面我们就来实现对这个的控制,使结束状态为源不能够建立Transition关系.
8.打开Dsl Explorer,结点Connection Builder/TransitionBuilder/Link Connect Directives/Transition,选中Transition后,打开Dsl Details窗口,我们设置域关系Transition的对于源角色State使用自定义接受(Custom accept).
9.转换所有模板,重新编译解决方案,你会发现有错误发生,是提示你必须要实现TransitionBuilder中的CanAcceptStateAsSource和CanAcceptStateAndStateAsSourceAndTarget方法,这是vs.net dsl设计自定义处理后通用的处理方法,你必须手动添加相应的方法后才能够编译通过. 在我们的CustomCode文件夹下面添加TransitionBuilder类,注意这是一个静态类,然后在这个静态类中添加这两个静态方法.
namespace Company.LanguageSm
{
public static partial class TransitionBuilder
{
private static bool CanAcceptStateAsSource(State state)
{
return ((state != null) && (state.Kind != StateKind.Final));
}
private static bool CanAcceptStateAndStateAsSourceAndTarget(State sourceState, State targetState)
{
return CanAcceptSource(sourceState);
}
}
}
10.再次运行我们的项目,你会发现,当选中Transition在一个结束状态上拖拽时,你会发现,显示圆形的不可用.
代码下载
参考资源
1. Visual Stuido DSL 工具特定领域开发指南
2. DSL Tools Lab http://code.msdn.microsoft.com/DSLToolsLab 系列教程 [本系列的入门案例的主要参考]
作者:孤独侠客(似水流年)
出处:http://lonely7345.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。