引言
记得第一次做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, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton); //将对应于该命令的控件添加到“工具”菜单: if (command != null) { //command.AddControl(toolsPopup.CommandBar, 1); command.AddControl(projBar, 1); } } 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(1); if (item.Project != null) {//被选中的就是项目本生 project = item.Project; } else {//被选中的是项目下的子项 project = item.ProjectItem.ProjectItems.ContainingProject; } return project; } //获取选中文件全路径 private string GetSelecteditempath() { //从被选中对象中获取工程对象 EnvDTE.SelectedItem item = _applicationObject.SelectedItems.Item(1); 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(); } }
然后,启动,看看结果吧.
小结
回头看看,发现我还是在一本正经地胡说八道.示例比较简单,但是好歹也算是一个代码生成的示范了.事实上,我已经用这种方式编写了能生成完整增删改查模块代码的外接程序(生成代码后能马上编译启动那种).我想,在那些比较固定化的模块中,用代码生成是比较合适的,毕竟效率妥妥的.最后,应该有更好的方式实现代码生成的,大方的你可以指教一下我啊,拜谢.