VisualStudioのマルチプロジェクトなテンプレート作成方法


Azureのテンプレートプロジェクトを作成するときにマルチプロジェクトなテンプレートが必要だったのでやり方を記述しておきます。

ポイントは

* プロジェクトのGUIDの参照方法
* プロジェクト名の参照方法

です。

方法は、IWizardを実装してGUIDやプロジェクト名を渡すだけです。
だけなのですが、そこにはちょっとしたテクニックが必要です。

この記事では、Azureプロジェクトをテンプレートにしたいと思います。
以下のようなプロジェクト構成を複数作りたい場合を想定して、テンプレートで作成したいと思います。
```AppSample``` の部分はユーザが入力した値になります。

```
├── AppSample
│   ├── AppSample-Cloud
│   │   ├── AppSample-Cloud.ccproj
│   │   ├── AppSample-Cloud.ccproj.user
│   │   ├── AppSample-CodeContent
│   │   ├── ServiceConfiguration.Local.cscfg
│   │   ├── ServiceDefinition.csdef
│   ├── AppSample-Code
│   │   ├── AppSample-Code.csproj
│   │   ├── app.config
│   │   └── packages.config
│   │   ├── Dto
│   │   ├── Properties
│   │   ├── WorkerRole.cs
│   └── AppSample-Tests
│       ├── App.config
│       ├── AppSample-Tests.csproj
│       ├── Properties
│       ├── Sample.feature
│       ├── Sample.feature.cs
│       ├── SampleSteps.cs
│       └── packages.config
├── AppSample.sln
└── AppSample.v12.suo
```

```-Cloud``` はクラウドプロジェクト(Azure構成ファイルなど)、```-Code```はプログラムコードプロジェクト（WorkerRoleプロジェクト）、```-Tests```はテストプロジェクト(Specflowなど)が入ります。

# 準備

Visual Studio SDKをインストールします。
http://www.microsoft.com/en-us/download/details.aspx?id=40758

Azureプロジェクトなので、Azure SDKもインストールします。

# マルチプロジェクトテンプレート

以下のようなプロジェクト構成を作ります。

```
.
├── AppSample-Template
│   ├── ProjectTemplate-Cloud
│   │   ├── $safeprojectname$.ccproj
│   │   ├── Properties
│   │   ├── ServiceConfiguration.Local.cscf
│   │   ├── ServiceDefinition.csdefg
│   │   ├── diagnostics.wadcfgx
│   │   └── MyTemplate.vstemplate
│   ├── ProjectTemplate-Code
│   │   ├── $safeprojectname$.csproj
│   │   ├── AssemblyInfo.cs
│   │   ├── Dto
│   │   ├── Properties
│   │   ├── WorkerRole.cs
│   │   ├── app.config
│   │   ├── packages.config
│   │   └── MyTemplate.vstemplate
│   ├── ProjectTemplate-Tests
│   │   ├── $safeprojectname$.csproj
│   │   ├── App.config
│   │   ├── AssemblyInfo.cs
│   │   ├── Properties
│   │   ├── Sample.feature
│   │   ├── Sample.feature.cs
│   │   ├── SampleSteps.cs
│   │   ├── packages.config
│   │   └── MyTemplate.vstemplate
│   ├── __TemplateIcon.png
│   ├── AppSample-Template.csproj
│   └── AppSample-Template.vstemplate
└── AppSample-Template.sln

```


ProjectTemplate-Cloud、ProjectTemplate-Code、ProjectTemplate-Testsがテンプレート本体になります。
テンプレート本体に関しては後述します。

## VisualStudioでテンプレートプロジェクトを新規作成

「新規作成」　→　「プロジェクト」　で、テンプレートの「機能拡張」→「C# Project Template」を選択します。（Visual Studio SDKを入れておくと表示されます。）
![project-template.png](https://qiita-image-store.s3.amazonaws.com/0/10494/1311563f-60ca-bc76-30eb-9f7e2917977a.png "project-template.png")

## マルチプロジェクト用のvstemplate

```AppSample-Template.vstemplate``` 以外のファイルはマルチプロジェクトなテンプレートのルートには必要ないので削除します。
\<VSTemplate\>のType属性を```ProjectGroup``` にするのがマルチプロジェクトにするポイントです。

```AppSample-Template.vstemplate
<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="2.0.0" Type="ProjectGroup" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
```

次に\<TemplateContent\>にテンプレート本体のvstemplateを記述します。
ここで記述した```$myprojectnameroot$```は、VSのテンプレートパラメータではありません。
後述するIWizardの実装で自分で設定するカスタムパラーメータになります。

```AppSample-Template.vstemplate
  <TemplateContent>
    <ProjectCollection>
      <ProjectTemplateLink ProjectName="$myprojectnameroot$-Cloud">
        ProjectTemplate-Cloud\MyTemplate.vstemplate
      </ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="$myprojectnameroot$-Code">
        ProjectTemplate-Code\MyTemplate.vstemplate
      </ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="$myprojectnameroot$-Tests">
        ProjectTemplate-Tests\MyTemplate.vstemplate
      </ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>
```

また、これから作るIWizardの設定も記述しておきます。
```PublicKeyToken```の値はCustomWizard.dllを作り、署名後に調べて記述します。
\<FullClassName\>は作成するIWizardの実装クラスを記述します。

```AppSample-Template.vstemplate
  <WizardExtension>
    <Assembly>CustomWizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=ここは実際の値を設定する</Assembly>
    <FullClassName>CustomWizard.CustomWizard</FullClassName>
  </WizardExtension>  
```

## IWizardの実装

ソリューションで右クリックして「追加」　→　「新しいプロジェクト」 → 「Visual C#」 → 「クラスライブラリ」で、今回は```CustomWizard```というプロジェクト名で作ります。
また、```CustomWizard.cs```クラスも作成します。

以下の参照を追加します。

* EnvDTE.dll
* Microsoft.VisualStudio.TemplateWizardInterface.dll
* System.Windows.Forms.dll

「参照設定」で「参照の追加」→「アセンブリ」で上記を検索して追加してください。

```class CustomWizard : IWizard```を実装していきますが、ポイントは以下の部分だけです。

```CustomWizard.cs
        public static Dictionary<string, string> globalDictionary;

        public void RunStarted(object automationObject,
            Dictionary<string, string> replacementsDictionary,
            WizardRunKind runKind, object[] customParams)
        {
            var userInput = replacementsDictionary["$safeprojectname$"];
            replacementsDictionary.Add("$myprojectnameroot$", userInput);

            Guid g = Guid.NewGuid();
            replacementsDictionary.Add("$codeguidroot$", g.ToString());

            globalDictionary = new Dictionary<string, string>();
            globalDictionary.Add("$myprojectnameroot$", replacementsDictionary["$myprojectnameroot$"]);
            globalDictionary.Add("$codeguidroot$", replacementsDictionary["$codeguidroot$"]);

        }
```

もう一つクラスを追加してから説明します。

```CutomWizardChild.cs
・・・
        public void RunStarted(object automationObject,Dictionary<string, string> replacementsDictionary,WizardRunKind runKind, object[] customParams)
        {
            replacementsDictionary.Add("$myprojectname$",CustomWizard.globalDictionary["$myprojectnameroot$"]);
            replacementsDictionary.Add("$codeguid$",CustomWizard.globalDictionary["$codeguidroot$"]);        
・・・
```

単純にIWizardを実装してカスタムパラメータを渡してもルートの```AppSample-Template.vstemplate```でしかカスタムパラメータの値を参照できません。
つまり、今回の場合は、```ProjectTemplate-Cloud```,```ProjectTemplate-Code```,```ProjectTemplate-Tests```でIWizardで設定した値が利用できないので、意味がありません。

それを解決するために上記のように二つのクラスを用意し、CutomWizardChildをそれぞれのプロジェクト(ProjectTemplate-Cloudなど)の```.vstemplate```に指定することで、ユーザが入力した値をテンプレートに反映させることができます。
（CutomWizardChildにはstatic変数経由で値を引き継いで設定しています。）

```ProjectTemplate-Cloud\ProjectTemplate-Cloud.vstemplateなど
  <WizardExtension>
    <Assembly>CustomWizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=ここは実際の値を設定する</Assembly>
    <FullClassName>CustomWizard.CustomWizardChild</FullClassName>
  </WizardExtension>  
```

前に「マルチプロジェクト用のvstemplate」で記述した```AppSample-Template.vstemplate```の```$myprojectnameroot$```は、このCustomWizardクラスで設定した値だったわけです。

そして、CutomWizardChildクラスで設定した値が重要になります。

実装プロジェクト(-Code)のguidを-Cloudや-Testsの参照で利用しています。
また、プロジェクト作成したときの入力値($myprojectname$)を利用して自身のプロジェクト名($safeprojectname$=$myprojectnameroot$-Cloudなど)以外を設定できるようにしています。

```ProjectTemplate-Code/$safeprojectname$.csproj
・・・
<ProjectGuid>{$codeguid$}</ProjectGuid>
・・・
```

```ProjectTemplate-Cloud/$safeprojectname$.ccproj
・・・
  <ItemGroup>
    <ProjectReference Include="..\$myprojectname$-Code\$myprojectname$-Code.csproj">
      <Name>$myprojectname$-Code</Name>
      <Project>$codeguid$</Project>
      <Private>True</Private>
      <RoleType>Worker</RoleType>
      <RoleName>$myprojectname$-Code</RoleName>
      <UpdateDiagnosticsConnectionStringOnPublish>True</UpdateDiagnosticsConnectionStringOnPublish>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <Folder Include="$myprojectname$-CodeContent\" />
  </ItemGroup>
  <ItemGroup>
    <DiagnosticsConfiguration Include="$myprojectname$-CodeContent\diagnostics.wadcfgx" />
  </ItemGroup>
・・・
```

```ProjectTemplate-Tests/$safeprojectname$.csproj
・・・
   <ProjectReference Include="..\$myprojectname$-Code\$myprojectname$-Code.csproj">
      <Project>{$codeguid$}</Project>
      <Name>$myprojectname$-Code</Name>
    </ProjectReference>    
・・・
```

## CustomWizardを署名する

[ここ](https://msdn.microsoft.com/ja-jp/library/xc31ft41.aspx)に書いてある内容と同じですが一応書いておきます。

IWizard を実装するアセンブリは、厳密な名前で署名して、グローバル アセンブリ キャッシュにインストールする必要があるそうです。

1.ソリューション エクスプローラーでCustomWizardプロジェクトで右クリックで開き、[プロパティ] を選択します。
2.[署名] タブを選択します。
3.[アセンブリの署名] ボックスを選択します。
4.[厳密な名前のキー ファイルを選択してください] ボックスで [<参照>] をクリックし、キー ファイルに移動します。 新しいキー ファイルを作成するには、[<新規作成>] を選択し、[厳密な名前キーの作成] ダイアログ ボックスでその名前を入力します。

## PublicKeyTokenの値を設定する

まずは、CustomWizardプロジェクトをbuildしときます。
```CustomWizard\bin\Debug\CustomWizard.dll```にできます。

```Sn.exe``` でdllのPublicKeyTokenを調べられます。
これはWindows SDKに含まれています。
自分の環境では以下にありました。
```C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\x64```

これをVisual StudioツールにあるVisual Studioコマンドプロンプトで実行します。
```C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts```

```
sn -T CustomWizard.dll
```

上記で取得した値をすべての```.vstemplate```の\<WizardExtension\>のPublicKeyTokenに設定します。

## テンプレートプロジェクト作成

あとはテンプレートを作成するだけです。
まずは、テンプレートの元になるプロジェクトを作成してビルドが通る状態にしておくと簡単です。

今回はC＃のテンプレートからAzureクラウドサービスを選択して、WorkerRoleを選択しておきます。

### プロジェクトのエクスポート

先ほど作成したビルドの通るプロジェクトをVisual Studioの機能でテンプレートにします。
「ファイル」　→　「プロジェクトのテンプレートを作成」で画像のウィザードが立ち上がるので、
「プロジェクト テンプレート」を選びます。
![template1.png](https://qiita-image-store.s3.amazonaws.com/0/10494/09c18721-22c0-acd9-cd62-3ce0ebff7563.png "template1.png")

「テンプレートを自動的にvisual studioにインポートする」は必要ないので外しときます。
![template2.png](https://qiita-image-store.s3.amazonaws.com/0/10494/c08d43bf-c346-2160-06f9-a50f21537f25.png "template2.png")

SampleApp-Cloud(Azureクラウドサービス)、SampleApp-Code(WorkerRoleProject)、SampleApp-Testsをそれぞれ別々にテンプレートを作成します。

完了すると出力フォルダが開くので、その中のzipを解凍します。

エクスポート機能を使わないで手動で作成する場合は、「新規作成」　→　「プロジェクト」　で、テンプレートの「機能拡張」→「C# Project Template」を選択します。（Visual Studio SDKを入れておくと表示されます。）
![project-template.png](https://qiita-image-store.s3.amazonaws.com/0/10494/1311563f-60ca-bc76-30eb-9f7e2917977a.png "project-template.png")

### テンプレートパラメーター

zipにあった```＜プロジェクト名＞.ccproj```(or ```.csproj```) ファイルを、テンプレート展開されたときのプロジェクト名にしたいです。
そのため、テンプレートパラメータ名を使って ```$safeprojectname$.ccproj``` に変更します。
ファイル内も変更します。

テンプレートパラメータについては以下を参照してください。
http://msdn.microsoft.com/ja-jp/library/eehb4faa.aspx

同じようにソースや構成ファイルなどにテンプレートパラメータを入れて、プロジェクト生成時に書き換えてほしい部分を記述しておきます。
（元々のプロジェクト名を$safeprojectname$に一括置換します。）

例えば、クラウドプロジェクトの場合は、以下のファイルを修正します。
```$safeprojectname$.ccproj```,```ServiceConfiguration.Cloud.cscfg```,```ServiceConfiguration.Local.cscfg```,```ServiceConfiguration.csdef```,```MyTemplate.vstemplate```

## $safeprojectname$.csprojの参照変更

プロジェクト構造を入れ子にしているので、dllの参照を変更します。
```..¥packages``` を```..¥..¥packages``` にします。

これは、例えば、プロジェクトテンプレートで作成した場合に以下のようなフォルダ構成になるからです。

```
.
├── AppProject ←　テンプレートで作成したプロジェクト
│   ├── AppProject-Cloud
│   └── AppProject-Code
├── ReferenceProject ←　これは元からある共通プロジェクト
└── packages　←　ここにある
```

## .vstemplateの修正

テンプレートでプロジェクトを生成させるときに```MyTemplate.vstemplate``` に記述した内容を見てプロジェクトを生成しているので、このファイルを修正していきます。

```ReplaceParameters``` 属性をtrueにしたファイルだけテンプレートパラメータが展開されます。

クラウドプロジェクト

```
  <TemplateContent>
    <Project TargetFileName="$safeprojectname$.ccproj" File="$safeprojectname$.ccproj" ReplaceParameters="true">
      <ProjectItem ReplaceParameters="true" TargetFileName="$safeprojectname$-Code">$safeprojectname$-Code</ProjectItem>
      <ProjectItem ReplaceParameters="false" TargetFileName="diagnostics.wadcfgx">diagnostics.wadcfgx</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="ServiceConfiguration.Cloud.cscfg">ServiceConfiguration.Cloud.cscfg</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="ServiceConfiguration.Local.cscfg">ServiceConfiguration.Local.cscfg</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="ServiceDefinition.csdef">ServiceDefinition.csdef</ProjectItem>
    </Project>
  </TemplateContent>

```

コードプロジェクト

```
  <TemplateContent>
    <Project TargetFileName="$safeprojectname$-Code.csproj" File="$safeprojectname$-Code.csproj" ReplaceParameters="true">
      <ProjectItem ReplaceParameters="true" TargetFileName="app.config">app.config</ProjectItem>
      <Folder Name="Dto" TargetFolderName="Dto">
        <ProjectItem ReplaceParameters="true" TargetFileName="SampleDto.cs">SampleDto.cs</ProjectItem>
      </Folder>
      <ProjectItem ReplaceParameters="true" TargetFileName="packages.config">packages.config</ProjectItem>
      <Folder Name="Properties" TargetFolderName="Properties">
        <ProjectItem ReplaceParameters="true" TargetFileName="AssemblyInfo.cs">AssemblyInfo.cs</ProjectItem>
      </Folder>
      <ProjectItem ReplaceParameters="true" TargetFileName="WorkerRole.cs">WorkerRole.cs</ProjectItem>
    </Project>
  </TemplateContent>
```

こうして作成したファイルは、ProjectTemplate-Cloud,ProjectTemplate-Code,ProjectTemplate-Testsに追加します。

## テンプレートルートのプロジェクトファイルを修正（.csproj）

今回の場合は```AppSample-Template.csproj``` です。

コンパイルエラーが出ているのは、ここを修正すると直ります。（エディタを開いている場合にエラーが出ていても、閉じたら消える場合は問題ありません。）
ポイントは、

* \<VSTemplate\>タグにtemplateファイルを指定すること
* \<None Inclue=""\>を利用すること

クラウドプロジェクト

```
  <ItemGroup>
    <None Include="diagnostics.wadcfgx" />
    <None Include="$safeprojectname$.ccproj" />
    <None Include="ServiceConfiguration.Cloud.cscfg" />
    <None Include="ServiceConfiguration.Local.cscfg" />
    <None Include="ServiceDefinition.csdef" />
  </ItemGroup>
  <ItemGroup>
    <VSTemplate Include="MyTemplate.vstemplate" />
  </ItemGroup>
```

コードプロジェクト

```
  <ItemGroup>
    <None Include="app.config" />
    <None Include="packages.config" />
    <None Include="Dto\SampleDto.cs" />
    <None Include="Properties\AssemblyInfo.cs" />
    <None Include="WorkerRole.cs" />
    <None Include="$safeprojectname$.csproj" />
  </ItemGroup>
  <ItemGroup>
    <VSTemplate Include="MyTemplate.vstemplate" />
  </ItemGroup>
```

\<None\>にするのは、コンパイル可能なC#のコードでもそうします。
これは、テンプレートパラメータを使ったり、依存DLLがなくてもビルドできるようにするためです。

ビルドして「文字$が予期されていません」などが出たら、自プロジェクトの```.csproj```で\<Compile\>になっているはずなので、\<None\>にします。

# インストーラー作成プロジェクトの作成

2010までは、```.vscontent``` ファイルを作って```.vsi```インストーラーを作っていましたが、2013で同じことをやろうとすると```%USERPROFILE%¥Documents\Visual Studio 2010\```の方に入れようとして2013では認識されませんでした。

2013以降は、vsixを作るようです。

テンプレート作成プロジェクトのソリューションで、
「新規作成」　→　「プロジェクト」　で、「Visual C#」の「VSIXプロジェクト」を選択して作成します。
![vsix-project.png](https://qiita-image-store.s3.amazonaws.com/0/10494/7ba39763-304e-a26a-783b-ddea5495569a.png "vsix-project.png")

以下の画像のように```source.extension.vsixmanifest``` ファイルを開いて、「Assets」を選択して、「Type」を「ProjectTemplate」、「Source」を「Current Project」、「Project」を作成したテンプレートプロジェクトにします。
CustomWizardプロジェクトも「Type」を「Assembly」にして追加します。

また「ProjectName」がツール名になるので最低限ここだけは適切な名前にしましょう。

![vsix.png](https://qiita-image-store.s3.amazonaws.com/0/10494/00af487f-17c8-655c-c34c-573207194469.png "vsix.png")



このプロジェクトをビルドするとbinに```.visx```インストーラーができます。

このファイルを配布して実行させれば、作ったテンプレートがインストールされます。
![installer.png](https://qiita-image-store.s3.amazonaws.com/0/10494/6828cea2-6fd8-9e62-a80f-7c7b6e7425c6.png "installer.png")


インストールされる場所は以下のランダムフォルダになります。

```
%USERPROFILE%\AppData\Local\Microsoft\VisualStudio\12.0\Extensions
```

ちなみにアンインストールは
ツール　→　拡張機能と更新プログラムで、該当のテンプレートを選択すれば削除できます。

![uninstall.png](https://qiita-image-store.s3.amazonaws.com/0/10494/9b07702b-f781-a237-a1d0-380076b369a6.png "uninstall.png")

# サンプルソース
githubに置いてあります。
参考にしてください。
https://github.com/ko2ic/Spike-VS-Multi-Project-Template