【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, 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();
        }
    }

然后,啟動,看看結果吧.

小結

     回頭看看,發現我還是在一本正經地胡說八道.示例比較簡單,但是好歹也算是一個代碼生成的示范了.事實上,我已經用這種方式編寫了能生成完整增刪改查模塊代碼的外接程序(生成代碼后能馬上編譯啟動那種).我想,在那些比較固定化的模塊中,用代碼生成是比較合適的,畢竟效率妥妥的.最后,應該有更好的方式實現代碼生成的,大方的你可以指教一下我啊,拜謝.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM