Visual Studio Installer ProjectsでASP.NET Coreプロジェクトのインストーラーをなんとかして作る

## はじめに

Visual Studio Installer Projectsというのがある。要するにインストーラーを作れる機能である。

https://learn.microsoft.com/ja-jp/visualstudio/deployment/installer-projects-net-core?view=vs-2022

しかし↑にはこう書かれている。

>注意
>このワークフローは、ASP.NET Core アプリケーションには対応していません。Windows デスクトップ アプリケーションにのみ対応しています。

実際ASP.NET Coreプロジェクトのインストーラーをこの方法で作ってみるといろいろとおかしな事になる。インストールするとなぜか動かなかったり、さらには何もインストールしないインストーラーが作られたりする。
StackOverflowやGitHubやマイクロソフト コミュニティなどを追ったところ、同じ問題に悩む人々は見つかったが、対処法は見つからなかった。

その割にはMicrosoft公式のサンプルがあったりする。

https://github.com/microsoft/installer-project-samples

信じがたいことに、こいつは正常に動く。

## 対処法

結論から言うとこの2点である。
* 「項目の公開」の```PublishProfilePath```を指定する
* ```.csproj```と```.pubxml```を編集してなんとかする

### 対処法までの道のり

すでに動いている物があるのだから、これを見ながら問題を解決していけるはずである。

#### ```PublishProfilePath```を指定した場合の現象

公式サンプルでは指定されているので、とりあえず普通にやると作られる```FolderProfile.pubxml```でも指定してみる。
すると何もインストールしないインストーラーが作られてしまう。これではだめである。

というわけで、```PublishProfilePath```を外してみよう。

#### ```PublishProfilePath```を指定しない場合の現象

一見正常なインストーラーが作成されるように見えるが、次のような現象が発生し、実際には動作しない。
* ネイティブのライブラリが読み込まれず```DllNotFoundException```が発生する
* ```wwwroot```フォルダ等が含まれていない

結局だめである。

#### もう一度```PublishProfilePath```を指定する

改めて考えると、公式サンプルでは```PublishProfilePath```を指定していても正常なインストーラーが作られているという重要な事実がある。
すなわち、ここで指定している```.pubxml```に何かあるはずである。

https://github.com/microsoft/installer-project-samples/blob/main/NET_Core/WindowsService/NetCoreWinService/Properties/PublishProfiles/FrameworkDependent_Winx64.pubxml

公式サンプルの```.pubxml```(↑のファイル)を自分のプロジェクトに丸ごとコピーし、これを「項目の公開」の```PublishProfilePath```に指定したところ、とりあえず「何もしていないインストーラーが作られる」状態からは脱却した。

ここにもサンプルの```.pubxml```を貼り付けておく。
``` xml
﻿<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
  <PropertyGroup>
    <Configuration>Release</Configuration>
    <Platform>Any CPU</Platform>
    <PublishDir>bin\Release\net8.0\publish\Fd_Win64</PublishDir>
    <PublishProtocol>FileSystem</PublishProtocol>
    <_TargetId>Folder</_TargetId>
    <TargetFramework>net8.0</TargetFramework>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <SelfContained>false</SelfContained>
    <PublishSingleFile>false</PublishSingleFile>
    <PublishReadyToRun>false</PublishReadyToRun>
  </PropertyGroup>
</Project>
```

:::note
公式サンプル内の```.pubxml```に対し、普通に生成される```.pubxml```はいくつか項目が多い。おそらく特定の項目があると何もインストールしないインストーラーが作られてしまうのだろう。
:::

そして動作確認した結果はこうだ。

* ネイティブのライブラリは正常に読み込まれる
* ```wwwroot```フォルダ等が含まれていない
* Razor Pagesがいずれも表示されない

少なくとも重大な問題が一つ解決した。なお新たな問題も発生したが、これも最終的には解決した。

ちなみに、ネイティブのライブラリが読み込まれなかったのは```.deps.json```が含まれていなかったのが原因である。

#### ```wwwroot```フォルダ等が含まれない問題

正確には```wwwroot```フォルダの他、```appsettings.json```や```web.config```が含まれない。

https://stackoverflow.com/questions/46983930/what-content-items-does-enabledefaultcontentitems-enable

↑を読むと、どうやらASP.NET Coreプロジェクトでは、デフォルトでビルド時に含めるファイルがいろいろと設定されているようだ。

しかし今までの様々な現象を見るに、インストーラーを作る時はこれが正常に反映されず、様々なファイルが不足してしまうようである。
つまり、これらのファイルを含める設定を自前で行えばよい。
なお、先のページにも```.csproj```の断片があるが、これを丸ごと```.csproj```に書き足すだけではだめである。
↓のようなエラーが発生する。

https://learn.microsoft.com/ja-jp/dotnet/core/tools/sdk-errors/netsdk1022?f1url=%3FappId%3DDev17IDEF1%26l%3DJA-JP%26k%3Dk(NETSDK1022)%26rd%3Dtrue

対策は、デフォルトでビルド時にファイルを含める機能を無効にすることである。このようになる。
``` xml
<PropertyGroup>
    <EnableDefaultContentItems>false</EnableDefaultContentItems>
</PropertyGroup>
```
ならばいっそ、これを```true```にするだけで解決では？と思ってしまうが、残念ながらそうはいかなかった。というわけでまだ続きがある。


```wwwroot```が含まれない問題は↓を参考にして解消した。

https://stackoverflow.com/questions/75107251/net7-0-webapp-publishsinglefile-does-not-include-wwwroot/75110384#7511038


最終的に↓のようになった。これを```.csproj```内に書き足す。
``` xml
<PropertyGroup>
    <EnableDefaultContentItems>false</EnableDefaultContentItems>
</PropertyGroup>
<ItemGroup>
	<EmbeddedResource Include="wwwroot\**" CopyToOutputDirectory="PreserveNewest" />
	<Content Include="web.config" CopyToOutputDirectory="PreserveNewest" />
 
     <!--ルートディレクトリの.jsonだけ含めるようにする-->
	<Content Include="*.json" CopyToOutputDirectory="PreserveNewest" />
 
	<_ContentIncludedByDefault Include="@(Content)" />
 </ItemGroup>
```

#### Razor Pagesが含まれない問題

こちらは↓を参考にした。

https://github.com/dotnet/sdk/blob/26854d4fe75c45af1c17e696d98ca4c8b509f170/src/RazorSdk/Targets/Sdk.Razor.CurrentVersion.props

↓を```.csproj```内に書き足す。
``` xml
</ItemGroup>
	<Content Include="**\*.cshtml" ExcludeFromSingleFile="true" CopyToPublishDirectory="PreserveNewest" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);$(DefaultWebContentItemExcludes)" />
	<Content Include="**\*.razor" ExcludeFromSingleFile="true" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);$(DefaultWebContentItemExcludes)" />
	<None Remove="**\*.cshtml" />
	<None Remove="**\*.razor" />
</ItemGroup>
```

これでようやく正常に動作するようになった。

## Microsoftの公式サンプルはなぜ動くのか

```.pubxml```が予め細工されており、さらに申し訳程度のWeb APIを提供するだけのプログラムなので```wwwroot```や```appsettings.json```は必要ないしRazor Pagesも使っていないからである。

## ASP.NET Coreプロジェクトの```.exe```を直接起動すると```production```環境になる

インストーラーの問題とは直接関係ないがこれも記載しておく。
これは↓の通り、意図的な仕様である。

https://dblog.athome.co.jp/entry/2017/07/28/120000