Visual Studio 插件的開發(轉)


起因

在做項目的時候,經常需要根據表結構create一些實體類,寫多了,實在是覺得無趣,於是就琢磨着做個代碼生成工具。當然現在有很多現成的,拿來用就好,可是總想自己弄個出來玩玩,一來是當初用DataSet,VS可以根據一個xsd文件生成那么多代碼,可以拖拖拽拽就搞定,一直沒鬧明白是怎么做的,不甘心,總想弄明白,二來,公司里,數據庫的腳本大多是根據一個xml配置文件生成的,這樣,我拿到這個xml生成代碼也是挺方便的。

學習

有了這個想法,可完全沒頭緒該怎么開發VS add-in, 無奈,只能仰仗Google大神了,“VS 插件”, “自動代碼生成 vs site:cnblogs.com” … 等等,一頓狂搜,了解到了相關的技術有:VS Add-in, CodeDom, T4, Template, Visual Designer, Domain-Specific Languages, Custom tools, etc.

VS Add-in: 可以使用任何.Net語言開發VS的插件,可以操作開發環境中的任何元素:解決方案,項目,文件,菜單,Solution Explorer, Output window…

CodeDom: 代碼文檔對象模型。類似Xml的Dom操作,CodeDom將代碼轉換成一種數據結構,通過操作這種數據結構可以實現生成代碼,修改代碼,動態編譯等等各種功能。

T4: Text Templating Transformation Toolkit, 通過模板加代碼的方式產生代碼,可以想象下aspx頁面,里面有html代碼,也有.Net代碼,通過轉換,最后呈現出html頁面。

Template: 模板,在添加新項的時候,在對話框中的那些就是模板。

Visual Designer: 像類型化DataSet,它有一個可視化的編輯界面,通過拖拽,點幾下鼠標等等操作就可以設計出我們想要的DataSet類型。

Domain-Specific Languages: 特定領域模型語言,通過它,我們可以設計出Visual Designer。

Custom Tools: 自定義工具,通過一個文件生成另一個文件。

實踐

CodeDom太復雜,用來生成代碼如果殺雞用牛刀,Visual Designer, Domain-Specific Languages,嗯,也用不到,暫時我不需要做個可視化的界面去設計我的代碼,等我哪天要實現個類似類型化DataSet那樣的插件的時候再考慮吧。去掉那些復雜的東西,我現在要用到的技術有:VS Add-in,用來實現VS的插件,使代碼生成工具集成進VS;T4, 用來產生代碼; Template,定義一個模板。

先說T4,看個簡單的列子:

首先創建一個項目,然后添加新項,選擇文本模板

image

文件里包含如下代碼

<#@ template debug="false" hostspecific="false" language="C#" #> 
<#@ output extension=".txt" #>

在文本模板中,<#@ #>中是指令,定義了模板的一些行為,如第二行,output extension=”.txt”,意思是說,該模板最后轉換生成的文件的后綴是.txt的。當然,我需要的代碼文件,所以將其改為.cs即可。將文本修改成下面這個樣子:

<#@ template debug="false" hostspecific="false" language="C#" #> 
<#@ output extension=".cs" #> 
using System;

namespace T4 

    public class T4Test 
    { 
    } 
}

保存后,可以看到Solution Explorer里面多了一個.cs文件

image

打開cs文件,可以看到里面包含如下代碼,跟我在模板文件里的內容一樣。

using System;

namespace T4 

    public class T4Test 
    { 
    } 
}

關於T4的語法,cnBlogs里有太多解釋,這里就不更進一步的描述了。

可是,這樣一個模板文件產生的代碼是固定的,而我需要的是輸入一段xml,根據xml來生成相應的代碼文件。要使模板文件知道xml里的內容,則需要將其內容作為參數傳遞到模板里,這個時候就需要用到參數指令:

<#@ parameter type="" name="" #>

然后再將之前的模板修改為:

<#@ template debug="false" hostspecific="false" language="C#" #> 
<#@ output extension=".cs" #> 
<#@ parameter type="System.String" name="className" #> 
using System;

namespace T4 

    public class <#= className #> 
    { 
    } 
}

如果這個時候,點擊保存,VS會提示你轉換失敗,因為輸入的參數為空。這個時候,需要修改模板文件的屬性,在自定義工具里,我們可以看到顯示的是TextTemplatingFileGenerator,它負責將模板文件轉換成目標文件。但是在這里,我需要的是將模板轉換成代碼生成類,將該屬性修改為:TextTemplatingFilePreprocessor。這個時候再保存,就會發現原來的cs文件里的內容變成了一段長的代碼:

namespace Test 

    using System; 
    
    
    #line 1 "E:\My Files\Documents\Visual Studio 2010\Projects\MStarGATDataEntityGenerator\NUTest.GAT.Common\TextTemplate1.tt" 
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "10.0.0.0")] 
    public partial class TextTemplate1 : TextTemplate1Base 
    {

… …

記得原來用VS2003的時候,還沒有T4技術,那個時候也寫了一個代碼生成工具,那個幸苦啊,一行行的WriteLine,又要控制輸出代碼的縮進,長長的一大段代碼。現在TextTemplatingFilePreprocessor工具將模板自動的生成了那個類,工作強度上輕松多了。在這個TextTemplate1類里有個TransformText的方法,調用它,就會根據模板輸出代碼。不過現在還不能直接去調用,否則還是會出現跟使用TextTemplatingFileGenerator工具時一樣的錯誤。因為,我們還是沒有設置className變量的值。可是找遍整個TextTemplate1類,既沒有在構造函數里發現參數可以設置,也沒發現公共屬性,也沒方法,只有一個私有的_classNameField變量。好在生成的TextTemplate1類是partial的,我們可以自己定義一個屬性去設置className,然后再調用TransformText方法。比如設置className為“Table”,我們將得到代碼:

using System;

namespace T4 

    public class Table 
    { 
    } 
}

好了,到此,關於T4要用到的知識就這些了。

下面就是VS Add-in了。前面說了,我想當在VS里添加了一個新文件,然后將一段xml復制到文件里,然后保存,最后就能得到代碼文件。前面T4已經可以幫助我實現生成代碼文件,現在我需要的是可以讓我在保存文件的時候將文件的內容傳遞給TextTemplate1類,然后調用TransformText方法,然后將得到的代碼內容保存到一個cs文件中,並添加到項目里。而這些工作就得依靠VS Add-in來幫我實現了。

首先,我們需要添加一個VS Add-in的項目

image

找到Connect.cs文件,在OnConnection方法中,添加以下代碼

DocumentEvents docMasterEvents = this.application.Events.get_DocumentEvents(null); 
docMasterEvents.DocumentSaved += new _dispDocumentEvents_DocumentSavedEventHandler( 
    docMasterEvents_DocumentSaved);

以及方法

void docMasterEvents_DocumentSaved(Document Document) 

    if (Document == null) return; 
    if(!Document.Name.ToLower().EndsWith(“.xml”)) return;

    TextTemplate1 gen = new TextTemplate1();

    gen.className = “Table”;

    string code = gen.TransformText();

    // todo: 將代碼寫入到文件里,並添加到項目中 
}

在上面的DocumentSaved方法里,如果發現文件后綴是xml的,就去調用模板。這里有個問題,那就是,會對所有的xml都去調用。可是在一個項目里總會有些其他的xml文件,這些文件是不需要轉換的。當然可以對文件內容進行判斷,再決定要不要調用模板,不過更簡單的方法是用一個特殊的文件名,而不是用.xml。比如在我的項目里,我使用.msgatde作為文件名后綴。講到這里,又該介紹下模板了,這里的模板是指添加新項時,那些可選擇的項目。

為什么要講模板呢,本來實現這個代碼自動生成的工具就是圖個方便,可如果每次都要自己將新建的xml文件的后綴改成我自己定義的那個后綴,多麻煩,說不定還很容易寫錯。所有為了更一步簡化操作,我打算定義一個模板,這樣在添加新項的時候,就自動生成一個.msgatde后綴的文件。

一個模板里,一般會至少包含3個文件:

image

第一個,圖標文件,在對話框里顯示用的。

第二個,模板文件,添加新項后,VS會根據這個文件產生新的文件添加到項目中。

第三個,模板描述文件。

現在來看下第三個文件的內容:

image

其中ProjectItem那一段即定義了模板的行為:通過模板里的DataEntity.msgatde文件產生目標文件$fileinputname$.msgatde。

在這些文件編輯好后,需要將其打包成一個zip文件,並將其放置到$my documents$\Visual Studio 2010\Templates\ItemTemplates\Visual C#目錄下。現在在選擇添加新項,即可看到我們定義的模板了。

image

ok,講完了怎么定義一個模板,現在該接着講VS Add-in了。在前面的DocumentSaved方法里還沒有實現如何將生成的代碼添加到項目里,現在接着說。

首先,需要將得到的代碼保存到文件里,這里就有一個問題了,如何知道路徑呢?在DocumentSaved方法中,有一個Document的參數,這個就是表示了當前的文檔,Document有個屬性ProjectItem,通過該屬性可以獲取到當前文件的路徑:

string fullname = Document.ProjectItem.Properties.Item(“FullPath”).Value.ToString();

知道了路徑,就容易多啦:

string codeFile = fullname.Repleace(“.msgatde”, “.cs”);

using(StreamWriter sw = new StreamWriter(codeFile))

{

    sw.Write(codeString);

}

現在文件是有了,該把它加到項目里了,很簡單:

Document.ProjectItem.ProjectItems.AddFromFile(codeFile);

說到這個方法,要順便說一句。都知道當我們添加了一個.aspx文件后,會自動產生一個.aspx.cs的文件,並且這個文件會存在於.aspx文件下,這是個很酷的特性,之前在搜索如何實現的時候,網上給出了大致有3種方法,如修改preject file的內容,添加dependupon元素,或者修改注冊表等方法,其實不用那么麻煩,通過上面的方法,.cs文件自動被添加到.msgatde文件下面了,效果如下:

image

最后講下部署插件,在插件的項目里,有個{prjoectname}.AddIn文件,將其copy到\Documents\Visual Studio 2010\Addins目錄下,然后將其用文本編輯軟件打開:

image

其中第十行是說明該插件的dll文件所在的路徑,將其修改為正確的路徑。當然,你也可以將文件也復制到該目錄下。

 

ok,目前關於如何創建一個代碼生成插件的部分就講完了。這里只是記錄了我在學習如何創建這個插件過程中遇到的難點,很多細節部分並沒有寫下來,還有很多東西需要完善,比如每次生成代碼,可以把生成的結果輸出到output window,如果出錯了,也把錯誤的原因output出來。

private void OutputToOPW(string format, params object[] objs) 

    OutputWindow ow = this.applicationObject.ToolWindows.OutputWindow; // applicationObject 是 一個DTE2對象 
    OutputWindowPane owp = null; 
    try 
    { 
        owp = ow.OutputWindowPanes.Item("MyAddin"); 
    } 
    catch 
    { 
        owp = ow.OutputWindowPanes.Add("MyAddin"); 
    } 
    owp.Activate(); 
    owp.OutputString(string.Format(format, objs)); 
}

最后,推薦一本書:《Practical.Code.Generation.in.NET》。多虧了這本書,才解決了我之前的很多困擾。

原文:http://www.cnblogs.com/FMax/archive/2011/07/09/2101699.html


免責聲明!

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



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