【VS外接程序】利用T4模板生成模块代码
引言
记得第一次做asp.net mvc项目时,可以用model直接生成Html的增删改查页面, 没什么特殊要求都可以不用修改直接用了, 觉得很神奇,效率太高了.后来在做客户端开发时,发现很多模块都是增删改查,于是打算做个类似的代码生成插件.琢磨了几天,用了一个比较奇异的思路做了出来,估计和mvc的有大不同.下面,简略地分享一下,写得比较乱,将就一下哈.
总体思路
特性类->外接程序->T4模板->动态编译->生成文本文件->添加到当前项目
特性类
简单起见,构建了两个特性,分别用于Class,Property,然后编译,得到T4Attribute.ll.代码如下
namespace T4Attribute
{
[AttributeUsage(AttributeTargets.Class)]
public class T4ClassAttribute : Attribute
{
public string Author; //开发者名字 public T4ClassAttribute(string author)
{
this.Author = author;
}
} [AttributeUsage(AttributeTargets.Property)]
public class T4PropertyAttribute : Attribute
{
public string DisplayName; //打印的名字
public bool IsOutput; //是否打印 public T4PropertyAttribute(string DisplayName, bool IsOutput)
{
this.DisplayName = DisplayName;
this.IsOutput = IsOutput;
}
}
}
创建外接程序
在vs2012的新建项目-其他项目类型-扩展性,可以找外接程序的项目模板,选择后,有向导界面弹出来,注意下图的选项,勾上就行,其他的随意,记得添加刚才的T4Attribute.ll引用
T4模板
在外接程序的项目中,添加新项,找到运行时文本模板,创建即可,保存后会看到TT文件下生成了一个cs文件, 代码如下:
<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="T4Attribute.dll" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace= "T4Attribute" #>
<#@ parameter type="System.Object" name="model" #>
<#@ output extension=".cs" #>
<#
T4ClassAttribute ModelAttribute =(T4ClassAttribute)model.GetType().GetCustomAttributes(typeof(T4ClassAttribute), false).FirstOrDefault();
#> using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace <#= model.GetType().Namespace#>
{ /// <summary>
/// 创建人:<#= ModelAttribute.Author #>
/// </summary>
class Program
{
static void Main(string[] args)
{ <# foreach ( var item in model.GetType().GetProperties())
{
if(((T4PropertyAttribute)item.GetCustomAttributes(typeof(T4PropertyAttribute), false).FirstOrDefault()).IsOutput){
#>
Console.WriteLine("<#= ((T4PropertyAttribute)item.GetCustomAttributes(typeof(T4PropertyAttribute), false).FirstOrDefault()).DisplayName#>"); <# }} #> Console.ReadLine();
}
}
}
然后再添加一个类文件,它是刚才T4模板生成的部分类,作用是给模板类提供一个参数输入的构造函数,代码如下:
namespace MyCodeAddin.T4
{
public partial class ConsoleCode
{ public ConsoleCode(object model)
{
_modelField = model;
}
}
}
动态编译
动态编译的目的是将我们选中的model文件(.cs)实例化,传给T4模板类,生成最终cs文件,代码如下:
class DynamicCompiling
{
public static object Compo(string code)
{
string liststr = "";
// 1.CSharpCodePrivoder
CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); // 2.ICodeComplier
ICodeCompiler objICodeCompiler = objCSharpCodePrivoder.CreateCompiler(); // 3.CompilerParameters
CompilerParameters objCompilerParameters = new CompilerParameters();
objCompilerParameters.ReferencedAssemblies.Add("System.dll");
objCompilerParameters.ReferencedAssemblies.Add("System.Core.dll");
objCompilerParameters.ReferencedAssemblies.Add(Application.StartupPath + "\\T4Attribute.dll");
objCompilerParameters.GenerateExecutable = false;
objCompilerParameters.GenerateInMemory = true; // 4.CompilerResults
CompilerResults cr = objICodeCompiler.CompileAssemblyFromSource(objCompilerParameters, code); if (cr.Errors.HasErrors)
{
foreach (CompilerError err in cr.Errors)
{
liststr = liststr + err.ErrorText + "\r\n"; }
return liststr;
}
else
{
// 通过反射,调用objmodel的实例
Assembly objAssembly = cr.CompiledAssembly;
object objmodel = objAssembly.CreateInstance(objAssembly.GetTypes().FirstOrDefault().ToString());
return objmodel;
}
}
}
继续外接程序
接着,要在项目中的Connect.cs实现我们的代码生成.实现3个方法OnConnection,QueryStatus,Exec,代码如下
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
if (connectMode == ext_ConnectMode.ext_cm_UISetup)
{
object[] contextGUIDS = new object[] { };
Commands2 commands = (Commands2)_applicationObject.Commands; CommandBars cbs = (CommandBars)_applicationObject.CommandBars;
CommandBar projBar = cbs["Item"];
//如果希望添加多个由您的外接程序处理的命令,可以重复此 try/catch 块,
// 只需确保更新 QueryStatus/Exec 方法,使其包含新的命令名。
try
{
//将一个命令添加到 Commands 集合:
Command command = commands.AddNamedCommand2(_addInInstance, "MyCodeAddin", "MyCodeAddin", "Executes the command for MyCodeAddin", true, , ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton); //将对应于该命令的控件添加到“工具”菜单:
if (command != null)
{
//command.AddControl(toolsPopup.CommandBar, 1);
command.AddControl(projBar, );
}
}
catch (System.ArgumentException e)
{ MessageBox.Show(e.Message);
//如果出现此异常,原因很可能是由于具有该名称的命令
// 已存在。如果确实如此,则无需重新创建此命令,并且
// 可以放心忽略此异常。
}
}
} /// <summary>实现 IDTCommandTarget 接口的 QueryStatus 方法。此方法在更新该命令的可用性时调用</summary>
/// <param term='commandName'>要确定其状态的命令的名称。</param>
/// <param term='neededText'>该命令所需的文本。</param>
/// <param term='status'>该命令在用户界面中的状态。</param>
/// <param term='commandText'>neededText 参数所要求的文本。</param>
/// <seealso class='Exec' />
public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
{ if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
{
if (commandName == "MyCodeAddin.Connect.MyCodeAddin")
{
if (!GetSelecteditempath().ToLower().EndsWith(".cs"))
{
status = (vsCommandStatus)vsCommandStatus.vsCommandStatusInvisible; }
else
{
status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
}
return;
}
}
} /// <summary>实现 IDTCommandTarget 接口的 Exec 方法。此方法在调用该命令时调用。</summary>
/// <param term='commandName'>要执行的命令的名称。</param>
/// <param term='executeOption'>描述该命令应如何运行。</param>
/// <param term='varIn'>从调用方传递到命令处理程序的参数。</param>
/// <param term='varOut'>从命令处理程序传递到调用方的参数。</param>
/// <param term='handled'>通知调用方此命令是否已被处理。</param>
/// <seealso class='Exec' />
public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
handled = false;
if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if (commandName == "MyCodeAddin.Connect.MyCodeAddin")
{
if (!GetSelecteditempath().ToLower().EndsWith(".cs"))
{
return;
}
//读取选中类文件
FileStream fs = new FileStream(GetSelecteditempath(), FileMode.Open, FileAccess.Read);
StreamReader sr = new StreamReader(fs, Encoding.GetEncoding("GB2312"));
//将文件内容编译生成对象
string conString = sr.ReadToEnd();
object classobject = DynamicCompiling.Compo(conString); string aa = classobject.GetType().Namespace; if (classobject is string)
{
MessageBox.Show("动态编译失败:" + "\r\n" + classobject, "类文件编译错误!");
sr.Close();
fs.Close();
handled = true;
return;
} //创建代码文件,并添加到项目中
Createcode(classobject, GetSelectedProject(), GetSelectedProjectPath()); sr.Close();
fs.Close(); handled = true; }
}
} //创建viewmodel代码文件,并添加到项目
public static ProjectItem Createcode(object model, Project project, string path)
{
ConsoleCode consoleCode = new ConsoleCode(model);
string codetext = consoleCode.TransformText();
//如果不存在文件夹,则创建
string Projectpath = path;
if (!Directory.Exists(Projectpath))
{
Directory.CreateDirectory(Projectpath);
}
//将目标代码生成文件
string createpath = Projectpath + "Program.cs";
FileStream fr = new FileStream(createpath, FileMode.Create);
StreamWriter sw = new StreamWriter(fr);
sw.Write(codetext);
sw.Close();
fr.Close();
//添加文件到项目中
return project.ProjectItems.AddFromFile(createpath); } //获取选中所属项目
private Project GetSelectedProject()
{
Project project = null;
//从被选中对象中获取工程对象
EnvDTE.SelectedItem item = _applicationObject.SelectedItems.Item(); if (item.Project != null)
{//被选中的就是项目本生
project = item.Project;
}
else
{//被选中的是项目下的子项
project = item.ProjectItem.ProjectItems.ContainingProject;
}
return project;
}
//获取选中文件全路径
private string GetSelecteditempath()
{ //从被选中对象中获取工程对象
EnvDTE.SelectedItem item = _applicationObject.SelectedItems.Item(); string selectpath = item.ProjectItem.Properties.Item("FullPath").Value.ToString(); return selectpath;
} //获取选中文件所属项目的路径,不含文件名
private string GetSelectedProjectPath()
{
string path = "";
//获取被选中的工程
Project project = GetSelectedProject();
if (project != null)
{
//全名包括*.csproj这样的文件命
path = project.FullName;
}
//去掉工程的文件名 path = project.FullName.Replace(project.Name + ".csproj", ""); return path;
}
如何使用
将上面的工程编译后,在项目目录下得到MyCodeAddin.AddIn,MyCodeAddin.dll,T4Attribute.dll,我们将这三个文件放在我的文档\Visual Studio 2012\Addins下面,将T4Attribute.dll放在C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE下面,就可以打开vs2012了,没有意外的话会工具-外接程序中看到我们的插件.OK,让我们来测试一下吧,新建控制台项目,删掉Program.cs文件,添加Test.cs文件,代码如下
[T4Class( "Czl")]
class Test
{
[T4Property("名字",true)]
public string Name { get; set; }
[T4Property("部门", true)]
public string Dept { get; set; }
[T4Property("地址", false)]
public string Address { get; set; }
}
然后右键Test.cs文件,会看到一个按钮[MyCodeAddin],点击它后,会看到Program.cs已经自动添加到项目中了,代码如下
/// <summary>
/// 创建人:Czl
/// </summary>
class Program
{
static void Main(string[] args)
{ Console.WriteLine("名字"); Console.WriteLine("部门"); Console.ReadLine();
}
}
然后,启动,看看结果吧.
小结
回头看看,发现我还是在一本正经地胡说八道.示例比较简单,但是好歹也算是一个代码生成的示范了.事实上,我已经用这种方式编写了能生成完整增删改查模块代码的外接程序(生成代码后能马上编译启动那种).我想,在那些比较固定化的模块中,用代码生成是比较合适的,毕竟效率妥妥的.最后,应该有更好的方式实现代码生成的,大方的你可以指教一下我啊,拜谢.
【VS外接程序】利用T4模板生成模块代码的更多相关文章
- MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码
前言 经过前面EF的<第一篇>与<第二篇>,我们的数据层功能已经较为完善了,但有不少代码相似度较高,比如负责实体映射的 EntityConfiguration,负责仓储操作的I ...
- [转]MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码
本文转自:http://www.cnblogs.com/guomingfeng/p/mvc-ef-t4.html 〇.目录 一.前言 二.工具准备 三.T4代码生成预热 (一) 单文件生成:Hello ...
- 利用T4模板生成ASP.NET Core控制器的构造函数和参数
前言 在ASP.NET Core中引入了DI,并且通过构造函数注入参数,控制器中会大量使用DI注入各种的配置参数,如果配置注入的参数比较多,而且各个控制器需要的配置参数都基本一样的话,那么不断重复的复 ...
- EF Code First:使用T4模板生成相似代码
http://developer.51cto.com/art/201309/409948.htm
- FluentData-新型轻量级ORM 利用T4模板 批量生成多文件 实体和业务逻辑 代码
FluentData,它是一个轻量级框架,关注性能和易用性. 下载地址:FlunenData.Model 利用T4模板,[MultipleOutputHelper.ttinclude]批量生成多文件 ...
- 使用T4模板生成不同部署环境下的配置文件
在开发企业级应用的时候,通常会有不同的开发环境,比如有开发环境,测试环境,正式环境,生产环境等.在一份代码部署到不同环境的时候,不同环境的配置文件可能需要根据目标环境不同而不同.比如在开发环境中,数据 ...
- T4模板生成不同部署环境下的配置文件
使用T4模板生成不同部署环境下的配置文件 在开发企业级应用的时候,通常会有不同的开发环境,比如有开发环境,测试环境,正式环境,生产环境等.在一份代码部署到不同环境的时候,不同环境的配置文件可能需要根据 ...
- Visual Studio 2013 EF5实体数据模型 EDMX 使用 T4模板生成后使用 ObjectContext对象
Visual Studio 2013 EF5实体数据模型 EDMX 使用 T4模板生成后的继承对象为DbContext,以前的熟悉的ObjectContext对象不见了,当然使用ObjectConte ...
- 使用T4模板生成POCO类
为什么叫T4?因为简写为4个T. T4(Text Template Transformation Toolkit)是微软官方在VisualStudio 2008中开始使用的代码生成引擎.在 Visua ...
随机推荐
- 什么是 XML Schema(转)
什么是 XML Schema? XML Schema 的作用是定义 XML 文档的合法构建模块,类似 DTD. XML Schema 的作用: 定义可出现在文档中的元素 定义可出现在文档中的属性 定义 ...
- Python基础-高阶函数
1.高阶函数变量可以指向函数函数的参数可以接收变量一个函数可以接收另一个函数作为参数 def f(n): return n * n def fansik(a, b, func): return fun ...
- python常用模块——random模块
参考博客:http://www.360doc.com/content/14/0430/11/16044571_373443266.shtml 今天突然想起python该怎么生成随机数?查了一下,贴出实 ...
- CRC冗余校验码的介绍和实现
from:http://yoyo.play175.com/p/200.html 节选至百度百科: 首先,任何一个由二进制数位串组成的代码,都可以惟一地与一个只含有0和1两个系数的多项式建立一一对应的关 ...
- Android中的动画使用总结
android中动画可分为三种:帧动画,补间动画,和属性动画.其中属性动画是google推荐的,它可以实现前面两种动画的效果,运用起来更加灵活. 帧动画:顾名思义,就是一帧一帧的图片,快速播放形成的动 ...
- 学习OpenCV2——Mat之通道的理解
本文详细介绍了opencv中涉及通道的知识,包括图像类型转换,通道合成分解,图像的显示. 来源:http://blog.csdn.net/GDFSG/article/details/50927257 ...
- Yii2.0数据库查询实例(三)
常用查询: // WHERE admin_id >= 10 LIMIT 0,10 User::find()->])->offset()->limit()->all() / ...
- Windows读写文件的猫腻
这里主要涉及对于回车换行的讨论. 回车:\r 换行:\n Windows读写文件分为普通文件读写和二进制文件读写. 如果以二进制的方式读写文件(如rb, wb),将会完全的把文件内容读出来,不做任何处 ...
- python对象类型----数字&字符串
一数据类型: float: 1.3e-3 1.3*10的负三次方 print (1.3e-3) bin() #转换为二进进制 oct() #转换为8进制 hex()#转 ...
- 关于pycharm中的requirements.txt文件
作用:记录所有所依赖的第三方模块,方便迁移到不同的环境中后,防止缺少模块,或因为所依赖的第三方模块版本不同引起不必要的问题 生成命令:pip freeze > requirements.txt ...