VisualStudioで項目テンプレートを作成する+プロジェクトのプロパティを使う

# この記事でやること

項目テンプレートの中でcsprojファイルにあるプロパティを使用する方法として↓を順にやる。

- 項目テンプレートを作成する
- VSIXを作成する
- ウィザードを作成する
- 項目テンプレートを修正する

# 前提

必要なものは2つ。

- [Visual Studio 2017](https://www.visualstudio.com/)
- Visual Studio SDK

## Visual Studio SDK

VisualStudioの拡張機能を作成するためのSDK (以下、VSSDK)。テンプレートだけでなく、Visual Studio のメニューを増やしたりすることもできる。
Visual Studio の Installer から選択してインストールしておく。

![vssdk_installer.png](https://qiita-image-store.s3.amazonaws.com/0/106819/c4b5d8f1-842a-8898-7479-89dbfb74114f.png)

個別のコンポーネントからも可。

![vssdk_install.png](https://qiita-image-store.s3.amazonaws.com/0/106819/907eb0ea-ea76-230f-f624-32873c67813a.png)

VSSDKの使い道は↓が参考になる。
http://tech.tanaka733.net/entry/how-to-find-how-to-develop-vs-sdk

# 項目テンプレートとVSIX

テンプレートを作成し、拡張機能として「ツール」メニューからインストールできるようにする。

## 項目テンプレートの作成

C#プロジェクトから、Extensibilityのカテゴリに「C# Item Template」があるので選んでプロジェクトを作成する。

![itemtemplate_proj.png](https://qiita-image-store.s3.amazonaws.com/0/106819/48cab94c-8333-e224-40bf-72f9d46cd8c2.png)

### AppliesToをわすれない

↓のようにプロジェクトが生成される。C#のプロジェクト内で項目を追加できるようにしたい場合は、\<AppliesTo\>タグを追加してCSharpに指定する。最初の生成時にはないので注意。

![itemtemplate1_app.png](https://qiita-image-store.s3.amazonaws.com/0/106819/9c647069-001d-99a8-ab25-0b0f5ae12fa6.png)

\<ProjectItem\>として最初はClass.csが生成される。ReplaceParameter=trueとなっているように、[パラメータの置換](https://msdn.microsoft.com/ja-jp/library/ms185311.aspx)ができる。
[使えるパラメータ名](https://msdn.microsoft.com/ja-jp/library/eehb4faa.aspx)には\$itemname\$などあるが、項目テンプレートでは\$projectname\$など使えないものもある。
使えないので仕方なく以降でウィザードをつくる。

[カスタムパラメータ](https://msdn.microsoft.com/ja-jp/library/eehb4faa.aspx#Anchor_2)を使うと自分で置換する組をつくれる。

### ProjectItemはコンテンツにする

Class.csをProjectItemにしたいときは、ビルドアクションを「コンパイル」から「コンテンツ」にする。(xamlなどでも同じ)

![err.png](https://qiita-image-store.s3.amazonaws.com/0/106819/f9e29fe4-4ddf-c6ac-d01e-2619d1c4f7cc.png)

忘れると、\$\$がついたパラメータのままコンパイルされてエラーがたくさん。

## VSIXの作成

ただテンプレートを作るだけだとzipを適当な場所に置くことになるが、VSIXをつくっておくと、拡張機能としてインストールとか更新ができるので便利。

ここでは、さっき作成した項目テンプレートのソリューションに新しいプロジェクトを追加する。

![add_vsix.png](https://qiita-image-store.s3.amazonaws.com/0/106819/0c218553-df2b-077e-8dc1-451f5abeb665.png)

VSIX Projectを追加したら↓みたいになる。

![added_vsixproj.png](https://qiita-image-store.s3.amazonaws.com/0/106819/c5ba90ba-e99d-a2b1-0fe4-e5858f856fda.png)

### VSIX Projectを作ったら、テンプレートを追加

生成された「source.extension.vsixmanifest」を選んで↓の1~6の順でItemTemplate1を選ぶとVSIXにテンプレートを追加する。

![select_itemtemplate.png](https://qiita-image-store.s3.amazonaws.com/0/106819/e3604699-c29b-e1e0-e891-9c5b26e28a31.png)

VSIXをスタートアップ設定してF5でデバッグ実行すると、実験用のVSが立ち上がって拡張機能が追加されているので、項目テンプレートを試しに使ってみることも可。

# プロジェクトファイルの情報を使うために

そのままだと使えるパラメータが少ないので、ウィザードを経由して使えるパラメータを増やす。

## ウィザードの作成

https://msdn.microsoft.com/ja-jp/library/ms185301.aspx

### テンプレートウィザードはdllを作る

ウィザードはクラスライブラリ(.dll)として作成する。パラメータをユーザが指定するようにしたければ、画面を作ってもよい。(が、パラメータを使いたいだけなので今回はパス。)

![add_dll.png](https://qiita-image-store.s3.amazonaws.com/0/106819/14fe483d-d7f4-e11d-25e3-97da512c3874.png)

### IWizardを継承したエントリクラスを作る

IWizardを使うために、クラスライブラリにNuGetでVSSDK.TemplateWizardInterfaceを追加する。参照を右クリック、NuGetパッケージの管理から↓の画面に。

![add_nuget.png](https://qiita-image-store.s3.amazonaws.com/0/106819/8a53945c-6ac1-d342-fdb0-9e4f6cc841b5.png)

「Microsoft.VisualStudio.TemplateWizard.IWizard」を継承したクラスをつくる。

```csharp
using EnvDTE;
using Microsoft.VisualStudio.TemplateWizard;

namespace ClassLibrary1
{
    public class Class1 : Microsoft.VisualStudio.TemplateWizard.IWizard
    {
        public void BeforeOpeningFile(ProjectItem projectItem) { }

        public void ProjectFinishedGenerating(Project project) { }

        public void ProjectItemFinishedGenerating(ProjectItem projectItem) { }

        public void RunFinished() { }

        public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
        {
            // ここに処理とか画面出したりとかを書く
        }

        public bool ShouldAddProjectItem(string filePath) => true; // 無条件でProjectItemを生成する
    }
}
```

### プロジェクトのプロパティをとってくる

automationObjectを使って選択中のプロジェクトを探してプロパティを使えるようにするコードを書く。

```csharp
public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
{
    // 選択中のプロジェクトはActiveSolutionProjectsの配列の先頭にある
    var dte = (EnvDTE._DTE)automationObject;
    var array = dte.ActiveSolutionProjects as Array;
    var project = array.GetValue(0) as EnvDTE.Project;

    // プロジェクトのプロパティをテンプレートで使えるように、置換リストに追加する
    foreach(Property prop in project.Properties)
    {
        try
        {
            replacementsDictionary.Add($"${prop.Name}$", prop.Value.ToString());
        }
        catch (Exception) { }
    }
}
```

カスタムテンプレートの辞書に追加したので、項目テンプレートから各パラメータを使用可能になる。


### ウィザードを使うにはアセンブリに署名が必要

クラスライブラリのプロパティから署名を作成します。

![sign.png](https://qiita-image-store.s3.amazonaws.com/0/106819/14616e8a-294b-ce9e-8be1-173fa0f1f1ff.png)

key.snkが生成されます。ここから、アセンブリの読み込みに必要なパブリックトークンを確認する。(ここ面倒)

参考：「メモ: dllファイルのPublicKeyTokenの確認方法」
 http://d.hatena.ne.jp/torazuka/20131121/sn


開発者用コマンドプロンプトを開き、sn.exe -T \<dllのパス\>を使う。

![devcmd.png](https://qiita-image-store.s3.amazonaws.com/0/106819/ccc9f34f-d8ac-1be3-5ce9-bfea95125217.png)

↓のようにトークンが表示される。あとで使うのでメモしておく。

```text
Microsoft(R) .NET Framework Strong Name Utility バージョン 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.

公開キー トークン 7e8b2aa4e85d22d3
```

### ウィザードを作成したら、VSIXに追加

ウィザードが完成したら、今度はAssemblyを選んでVSIXのAssetsに追加する。dllがVSIX内に配置されるようになる。

![add_assembly.png](https://qiita-image-store.s3.amazonaws.com/0/106819/9920c261-1797-20aa-37d5-83173ef2d4fb.png)


## 項目テンプレートからウィザードが動くようにする

ItemTemplate1.vstemplateを開いてWizardExtensionを追加する。

```xml
<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" Type="Item" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" xmlns:sdk="http://schemas.microsoft.com/developer/vstemplate-sdkextension/2010">
  <TemplateData>
    <Name>ItemTemplate1</Name>
    ～省略～
  </TemplateData>
  <TemplateContent>
    ～省略～
    <ProjectItem ReplaceParameters="true">Class.cs</ProjectItem>
  </TemplateContent>
  <WizardExtension>
    <!-- アセンブリ名とバージョン、言語、確認したトークンを入れる -->
    <Assembly>ClassLibrary1, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=7e8b2aa4e85d22d3</Assembly> 
    <!-- IWizardを継承したエントリクラスを名前空間込みで入れる -->
    <FullClassName>ClassLibrary1.Class1</FullClassName>
  </WizardExtension>
</VSTemplate>
```