Visual Studio DSL 入门 13---结合T4生成代码
在前面的几节里,我们已经完成了一个简单的状态机设计器,通过这个状态机可以设计出一个状态流,但是如果只是这样,我们直接使用UML设计工具就行了,何必自己开发呢? 我们走的是模型驱动开发路线,呵呵,注意哥说的是开发,不是设计.这一节就和我们的开发联系起来,生成符合我们要求的代码.
结合vs.net dsl生成代码有以下几种方式:
直接硬编码,在代码里面利用模型拼接生成的代码,我记得activewriter就是这样做的生成nhibernate代码.
结合模板引擎,你可以使用xslt或者t4(text template transformation toolkit),或者是codesmith等.
在这里我们使用T4来生成,vs.net已经内置支持T4引擎(dsl和linq等都是使用t4来生成的), 即使这样,vs.net也没有内置对T4文件的编辑器,在开始下面之前,需要从这里下载免费的Community版本安装.
1.直接运行我们的项目,可以发现在Debugging项目下面有两个tt文件,这两个文件就是生成简单代码的一个例子,直接打开LanguageSmReport.tt
<#@Import Namespace="System" #>
<#@template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" language="C#" #>
<#@output extension=".txt" #>
<#@ LanguageSm processor="LanguageSmDirectiveProcessor" requires="fileName='Test.mydsl5'" #>
<#
foreach (State state in StateMachine.States) {
#>
<#=state.Name #>
<#
}
#>
2.运行自定义工具,生成的文件就是附属的txt文件:
Draft
NewOrder
Cancelled
Confirmed
3. 对应的我们的状态机是我建立的一个简单的订单状态流转:
4.回头过来再看一下这个t4模板文件,看起来其实很象aspx页面:
(1).通过Import引用需要的命名空间,事先所在的dll一定要添加到项目中.
(2).第二行指定模板继承自Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation,指定模板语言使用C#,注意,如果你需要使用framework 3.5,这里需要设置成C#3.5.
(3).通过output指令设置生成文件的后缀名和编码.
(4).声明我们的指令处理器以及需要加载的模型文件.
(5).模板的正文很容易理解,只需要记住它的几个控制块的类型.
<#….#>标准控制块,里面放控制语句,就是我们普通的C#或者VB代码组成的控制语句.
<#+..#>类特性控制块,里面可以添加方法,属性,域或者内嵌类,在这里一般放一些重用性高的代码.
<#=…#>表达式控制块,计算里面包含的表达式的值并输出.
5.但是这个T4文件又是怎么样的运行解析机制呢,其实它和我们的aspx页面很类似,我们来看一下它生成的转换类:
public class GeneratedTextTransformation : Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation {
public override string TransformText() {
try {
this.Write("\r\n");
this.Write("\r\n");
this.Write("\r\n");
this.Write("\r\n\r\n");
foreach (State state in StateMachine.States) {
this.Write("\r\n\t");
this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(
state.Name
));
this.Write("\r\n");
}
this.Write("\r\n");
} catch (System.Exception e) {
System.CodeDom.Compiler.CompilerError error = new System.CodeDom.Compiler.CompilerError();
error.ErrorText = e.ToString();
error.FileName = "template2.t4";
this.Errors.Add(error);
}
return this.GenerationEnvironment.ToString();
}
private Company.LanguageSm.StateMachine statemachineValue;
private Company.LanguageSm.StateMachine StateMachine {
get {
return this.statemachineValue;
}
}
protected override void Initialize() {
this.AddDomainModel(typeof(Microsoft.VisualStudio.Modeling.Diagrams.CoreDesignSurfaceDomainModel));
this.AddDomainModel(typeof(Company.LanguageSm.LanguageSmDomainModel));
base.Initialize();
if ((this.Errors.HasErrors == false)) {
Microsoft.VisualStudio.Modeling.Transaction statemachineTransaction = null;
try {
Microsoft.VisualStudio.Modeling.SerializationResult serializationResult = new Microsoft.VisualStudio.Modeling.SerializationResult();
statemachineTransaction = this.Store.TransactionManager.BeginTransaction("Load", true);
this.statemachineValue = Company.LanguageSm.LanguageSmSerializationHelper.Instance.LoadModel(serializationResult, this.Store, "Test.mydsl5", null, null);
if (serializationResult.Failed) {
throw new Microsoft.VisualStudio.Modeling.SerializationException(serializationResult);
}
statemachineTransaction.Commit();
} finally {
if ((statemachineTransaction != null)) {
statemachineTransaction.Dispose();
}
}
}
}
}
通过Write方法输出我的内容,然后其实是对于我们的模型的根域类属性,并重写Initialize()方法进行了初始化.
6.回过头来,我们要根据上面3中的订单状态图,生成我们的代码,最重要也是最基本的一点,在你打算用T4生成代码时,你一定要对你想生成的代码了如指掌.如果你连自己要什么都不知道,更不可能达到了. 我们以最基本的一个例子,虽然这样写代码可能并不合理,不过在这里我们为了使问题尽量简单化:
/// <summary>
/// 订单状态
/// </summary>
public enum OrderStateEnum
{
Draft,
NewOrder,
Cancelled,
Confirmed,
}
/// <summary>
/// 订单生成
/// </summary>
public partial class Order
{
public OrderStateEnum OrderState
{
get;
set;
}
public Order()
{
}
protected void SaveOrder(Order order)
{
if(order.OrderState == OrderStateEnum.Draft)
order.OrderState = OrderStateEnum.NewOrder;
}
}
(1).我们需要为我们的所有的状态生成到我们的枚举类型OrderStateEnum中(状态名就是枚举名,值不考虑).
(2).在我们的Order类中,有一个固定的OrderStateEnum类型的属性OrderState,表示当前订单的状态.
(3).需要为我们的每一个转移Transigion生成一个方法到我们的Order类中,方法名就是Transition的Event的值,方法体就是当订单的状态为Transigion的发起者Predecessor时,将订单的状态置为目标Successor.也就是说在SaveOrder内,判断如果订单的状态是Draft时,就把订单的状态置为NewOrder.
7.在明白了目标代码后,我们来写我们的T4文件,首先需要添加一个公共的方法来获取StateMachine里的所有的Transition.我们使用<#+#>来完成这个方法,注意这个方法需要放在整个模板文件的最下面.
<#+
System.Collections.Generic.IEnumerable<Transition> GetAllTransitions() {
foreach (State s in StateMachine.States)
foreach (Transition t in Transition.GetLinksToSuccessors(s))
yield return t;
}
#>
8.剩下的工作就更简单了,我们只需要遍历这些Transition,对于每个Transition,对于它的Predessor和Successor进行如上所说的判断和赋值即可,而对于固定的部分,我们只需要以文本的形式写出来就可以了:
/// <summary>
/// 订单状态
/// </summary>
public enum OrderStateEnum
{
<#
PushIndent(" ");
foreach (State state in StateMachine.States)
WriteLine(" {0},", state.Name);
PopIndent();
#>
}
/// <summary>
/// 订单生成
/// </summary>
public partial class Order
{
public OrderStateEnum OrderState
{
get;
set;
}
public Order()
{
}
public void Save(Order order){
// to save order
}
<#
foreach (Transition transition in GetAllTransitions()) {
#>
protected void <#=transition.Event#>
{
if(order.OrderState == OrderStateEnum.<#=transition.Predecessor.Name#>)
order.OrderState = OrderStateEnum.<#=transition.Successor.Name#>;
Save(order);
}
<#
}
#>
}
9.转换模板,就可以看到我们生成的代码了,虽然在这个例子中并没有显现出T4的强大,不过对于复杂的规范性的系统来说,能够通过Dsl进行设计,然后结合T4生成那些代码还是能够极大的提高生产率的.
/// <summary>
/// 订单状态
/// </summary>
public enum OrderStateEnum
{
Draft,
NewOrder,
Cancelled,
Confirmed,
}
/// <summary>
/// 订单生成
/// </summary>
public partial class Order
{
public OrderStateEnum OrderState
{
get;
set;
}
public Order()
{
}
public void Save(Order order){
// to save order
}
protected void SaveOrder(Order order)
{
if(order.OrderState == OrderStateEnum.Draft)
order.OrderState = OrderStateEnum.NewOrder;
Save(order);
}
protected void CancelOrder(Order order)
{
if(order.OrderState == OrderStateEnum.NewOrder)
order.OrderState = OrderStateEnum.Cancelled;
Save(order);
}
protected void ConfirmOrder(Order order)
{
if(order.OrderState == OrderStateEnum.NewOrder)
order.OrderState = OrderStateEnum.Confirmed;
Save(order);
}
}
10.需要注意的是,结合dsl和t4也不可能使你的一个项目不用手写代码了,它只能是能够生成你比较固定的代码部分,能够抽象出来的,目前t4还不能够解决生成代码中允许直接签入自定义代码,所以现在一般以文件为分隔,通过partial机制,由t4生成的cs文件中专门存储这些抽象出来的可生成部分,而这部分是不允许修改的,因为在你修改完模型后下次还会重新生成,而你需要扩展的部分,都以partial类的机制在另外一个类中.
代码下载
参考资源
1. Visual Stuido DSL 工具特定领域开发指南
2. DSL Tools Lab http://code.msdn.microsoft.com/DSLToolsLab 系列教程 [本系列的入门案例的主要参考]
作者:孤独侠客(似水流年)
出处:http://lonely7345.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
Visual Studio DSL 入门 13---结合T4生成代码相关推荐
- Visual Studio DSL 入门 9---创建状态机的图形符号
上一节我们已经创建状态机的元数据模型,在这一节,我们来完成我们状态机的图形符号,建立起状态机的测试运行环境. 打开dsl文件后,关注泳道的右侧,首先我们来完成域类的形状表示: 1 ...
- Visual Studio DSL 入门 1 --- 什么是特定领域开发和DSL
http://tech.ddvip.com/2010-02/1265268067144729.html 转载于:https://www.cnblogs.com/carl2380/archive/201 ...
- roslyn生成html,Visual Studio 2017 bin \ roslyn文件在生成期间被锁定
Visual Studio 2017 bin \ roslyn文件在生成期间被锁定 我正在运行VS2017版本26430.13,每次尝试构建Web项目时,都会收到错误,拒绝访问bin \ roslyn ...
- Visual Studio 开发入门
Visual Studio 开发入门 1. 版本 2. 高效性的常用功能 3. 安装Visual Studio IDE 4. 创建一个程序 5. 使用重构和IntelliSense 6. 调试代码 7 ...
- Visual Studio Code入门笔记
Visual Studio Code入门笔记 入门vscode半个月的小小白: 新的改变 可视化 入门vscode半个月的小小白: 作为一个入门vscode半个月的小小白,下面是一些自己的心路历程(血 ...
- Visual Studio Code 入门教程
一.入门教程 1.1 简介 Visual Studio Code ,简称 VSCode.它是一款由微软开发且跨平台的轻量级但功能强大的免费源代码编辑器.该软件支持语法高亮.代码自动补全.代码重构. ...
- 由于缺少调试目标“……”,Visual Studio无法开始调试。请生成项目并重试,或者相应地设置OutputPath和AssemblyName属性,使其指向目标程序集的正确位置...
使用VS2010时出现如下问题:由于缺少调试目标"--",Visual Studio无法开始调试.请生成项目并重试,或者相应地设置OutputPath和AssemblyName属性 ...
- visual studio 2008运行时 error PRJ0003 : 生成“rc.exe”出错
问题描述:visual studio 2008运行时 error PRJ0003 : 生成"rc.exe"出错 解决方法步骤如下 :1.运行vs2008安装程序,点击 ...
- Visual Studio使用技巧,创建自己的代码片段
1.代码片段的使用示例 在编写代码中常会使用代码片段来提高我们的编写代码的效率,如:在Visual Studio中编写一个 for(int i = 0; i < length;i++) { } ...
最新文章
- 传智播客JavaWeb day11--事务的概念、事务的ACID、数据库锁机制、
- 官宣!.NET官网发布中⽂版
- “开源”vs“商业”,差别到底有多大?这篇测试一目了然
- 如何在小数点前补0,new DecimalFormat(##0.00);
- Flutter实战一Flutter聊天应用(十六)
- 制作stick侧边栏导航效果
- python 字符串(二)
- java计算器自述文件_自述文件 - Unreal Engine
- 简述神经网络学习过程
- 60mph和kmh换算_mph和kmh换算(mph换算器)
- 岗位po是什么意思_通信公司中PO和PM分别是什么意思?
- CISP学习笔记2:风险管理1
- 计算机运维考核指标,信息中心考核指标库
- logit回归模型_详解 Logit/Probit 模型中的 completely determined 问题
- 20145312《信息安全系统设计基础》实验四 驱动程序设计
- CRC循环冗余校验---模2除法解析
- Python运用蒙特卡洛算法模拟植物生长
- echarts实现离线世界地图(国内)展示
- 视觉SLAM基础实现
- android 8 多媒体,1-4月中控多媒体搭载率接近8成,安卓+大屏正成为主流
热门文章
- kettle 连接 Oracle 异常
- 新安装XCode7/XCode8 模拟器无法运行报-unable to boot the simulator解决方法
- mybatis项目报错:java.sql.SQLException: ORA-00911: 无效字符 解决方法
- 在同一窗口和同一选项卡中打开URL
- 如何从JavaScript中的对象数组中获得不同的值?
- 具有左,中或右对齐项的Bootstrap NavBar
- 设置HttpClient的授权标头
- 有没有更好的方法在JavaScript中执行可选的函数参数? [重复]
- linux重启配置文件,rEFInd启动管理器配置文件详解
- python 代理服务器 身份验证_使用httplib2处理身份验证和代理服务器