ClickOnce発行でライブラリ内フォルダーを同梱する方法|.NET Core 3.1 WPF(Prism)

Prismで構成した.NET Core 3.1 WPFアプリをClickOnceで発行すると、参照しているライブラリ(module)側のfolder\data.txtが成果物に入らず困ることがあります。本記事では、ファイル設定から「アプリケーション ファイル」でのInclude指定、旧形式プロジェクトが原因のケースと回避策まで、実務目線で整理します。

目次

やりたいこと:ライブラリ(module)配下のフォルダーをClickOnce発行物に同梱したい

今回の前提は、Prismのモジュール分割をしたWPFデスクトップアプリです。典型的には次のようなソリューション構成になります。

プロジェクト種別主なファイル目的
moduleクラスライブラリ(.dll)folder\data.txt / module.cs機能(画面・サービス・設定など)をモジュールとして提供
appWPFアプリ(.exe)app.cs / module参照エントリーポイント。ClickOnceで発行する対象

狙いはシンプルで、moduleプロジェクト側にあるフォルダーごと(例:folder\data.txt)を、ClickOnceで発行したappの成果物に含めたい、というものです。

結論:ClickOnceに同梱するには「出力にコピー」+「アプリケーション ファイルでInclude」が基本

ClickOnceの発行物にファイルを入れるには、次の2段階が必要です。

  • ビルド成果物として、appの出力フォルダーにファイルが存在する状態にする
  • ClickOnceが生成するマニフェストに、そのファイルを含める設定にする(「アプリケーション ファイル」でInclude)

特にライブラリ(module)側のコンテンツは、プロジェクト参照の仕方やプロジェクト形式によっては自動的にClickOnceに拾われないことがあります。以下で、確実に同梱するための手順と、表示されない場合の回避策までまとめます。

前提知識:ClickOnceは「アプリケーション ファイル」に載ったものを配布する

ClickOnceは、発行時に「アプリケーション マニフェスト」に配布対象を列挙し、そのリストに基づいてクライアントへ配布します。Visual Studioで操作する場合、その入口が「アプリケーション ファイル」画面です。ファイルを配布に載せるには、対象ファイルのビルドアクションがContentであることが前提になります(Content以外だと一覧に出ない/制御しづらいケースが多いです)。

また、.NET Core 3.1以降のClickOnceは、Visual Studio 2019以降では従来の「発行ウィザード」ではなく、発行プロファイル(.pubxml)を作る発行ツール(Publish tool)を中心に操作します。画面の呼び名や遷移はバージョンで多少違いますが、最終的にたどり着く先は「アプリケーション ファイル」で、ここで配布対象をInclude/Exclude/Data Fileなどに設定します。

手順:moduleプロジェクト側でdata.txtを「Content+常にコピー」にする

まずは、folder\data.txtがappの出力フォルダーへコピーされる状態を作ります。やることは、質問に書かれているとおりでOKです。

項目推奨設定理由
ビルド アクションContentClickOnceの「アプリケーション ファイル」に表示されやすくする(配布対象として扱いやすい)
出力ディレクトリにコピー常にコピー(Copy always)ビルドのたびに出力先へ確実に配置し、発行時に拾わせる

csprojで管理する場合は、次のようにします(すでに設定済みなら確認だけでOKです)。

<ItemGroup>
  <Content Include="folder\data.txt">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content>
</ItemGroup>

ここで大事なのは「moduleプロジェクトで設定したから安心」ではなく、最終的にapp(.exe)の出力フォルダーにも同じ相対パスで現れていることです。発行に進む前に、いったんReleaseでビルドして、次の場所にファイルが出ているか確認してください。

  • app\bin\Release\netcoreapp3.1\folder\data.txt

もしここに無ければ、ClickOnce以前の問題(コピー設定が効いていない/条件付きItemGroupになっている/ビルド構成が違うなど)なので、先にそこを直します。

手順:ClickOnce発行で「アプリケーション ファイル」に表示させ、Publish StatusをIncludeへ

次にappプロジェクトを発行します。Visual Studio 2019/2022の発行ツールでは、プロジェクトを右クリックして「発行」を選び、ClickOnceプロファイルを作成(または選択)して進めます。設定画面には「アプリケーション ファイル」へのリンク/ボタンがあります。

操作の流れは次のとおりです。

  1. ソリューション エクスプローラーで appプロジェクトを右クリック →「発行」
  2. 発行先(フォルダー/UNC/Webなど)を選び、特定のターゲットで ClickOnce を選択
  3. 設定画面で 「アプリケーション ファイル」 を開く
  4. ダイアログで 「すべてのファイルを表示(Show all files)」 をオンにする
  5. 一覧に出てきた folder\data.txt(または該当ファイル)を選び、Publish StatusをIncludeへ変更する
  6. OKで閉じて発行を実行する

ポイントは、「すべてのファイルを表示」をオンにしないと、意図したファイルが一覧に出てこないことがある点です。逆に言えば、出力フォルダーに存在するのに一覧に出ない場合は、ほぼここが原因です。

Include / Data File / Exclude の違いを押さえて事故を防ぐ

ClickOnceの「アプリケーション ファイル」では、ファイルごとに配布の扱いを変えられます。ここを理解しておくと、「入ったけど更新で消えた」「書き換えたら壊れた」などの事故を避けやすくなります。

Publish Status配置先のイメージ用途注意点
IncludeApplication Directory(アプリ本体の配置先)読み取り専用のテンプレート、同梱必須の静的ファイル更新のたびに新しいバージョンのフォルダーに展開されるため、アプリ配下を書き換える運用は避ける
Data FileData Directory(アプリ専用のデータ領域)アプリが管理するデータ(テキスト/XML/DBなど)バージョンごとにデータディレクトリが分かれ、更新時にコピー・置換のルールがある
Exclude配布しない開発用ファイル、不要なログ、同梱したくないファイル出力にあっても配布対象から外れる

folder\data.txtが「アプリの実行時に参照するだけの固定ファイル」なら、まずはIncludeで十分です。反対に、ユーザーが編集したりアプリが追記したりするなら、ClickOnceのデータ領域(Data Directory)か、%LOCALAPPDATA%配下などのユーザー領域へコピーして使う設計に寄せるのが安全です。

それでも「アプリケーション ファイル」に出てこない場合の原因を切り分ける

ここからが本題で、質問のケースのように「ライブラリ側のフォルダー/ファイルが一覧に出ない」ことがあります。原因は大きく3つに分けられます。

  • 出力フォルダーにコピーされていない(まずここを疑う)
  • ファイルのビルドアクションがContentになっていない(一覧に出る条件を満たしていない)
  • appプロジェクトの形式(旧形式/SDKスタイル)や参照関係の都合で、参照プロジェクトのContentが拾えない

チェック:ビルド アクションがContentになっているか

ClickOnceの「アプリケーション ファイル」にファイル名を表示させるには、対象ファイルのビルド アクションをContentに設定しておく必要がある、という注意書きがMicrosoftのドキュメントにもあります。Visual Studioのファイルプロパティで、まずここを再確認してください。

チェック:Release出力に本当に存在しているか

発行はRelease構成で行われることが多いです。Debugで動いているからといって、Releaseの出力にファイルが無いと発行物には入りません。次のように、発行に使う構成の出力フォルダーを必ず見ます。

  • app\bin\Release\netcoreapp3.1\folder\data.txt(例)

原因になりやすい:appが旧形式の「従来のWPFプロジェクト」だった

質問者のケースでは、module側が

<TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>

のようなマルチターゲット構成で、app側がSDKスタイルではない旧形式のWPFプロジェクトだったため、ClickOnceの「アプリケーション ファイル」画面で参照プロジェクト由来の項目がうまく扱えず、フォルダーが表示されない状態になっていました。

結論としては、次のどちらかで解決します。

  • 推奨:appをSDKスタイルのWPFプロジェクト(Microsoft.NET.Sdk.WindowsDesktop)へ移行する
  • 回避策:app側で「リンク(Link)」としてファイルを取り込み、appプロジェクトのContentとして扱う

推奨:appをSDKスタイル(Microsoft.NET.Sdk.WindowsDesktop)へ移行してClickOnceと相性を良くする

SDKスタイルに移行すると、ビルド・参照・発行のパイプラインが整理され、ClickOnceの発行設定も素直に効きやすくなります。特に.NET Core 3.1のClickOnceは発行ツール(Publish tool)を中心に扱う設計なので、SDKスタイルのプロジェクトのほうがトラブルが少ないです。

SDKスタイルのWPFプロジェクトは、csprojが次のような形になります(最小例)。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\module\module.csproj" />
  </ItemGroup>

</Project>

移行のやり方はプロジェクトの規模で最適解が変わりますが、現場で安全に進めやすいのは次の手順です。

  1. 同じターゲットフレームワークで新規にSDKスタイルのWPFプロジェクトを作る
  2. 既存appプロジェクトのXAML/C#、リソース、NuGet参照(Prismなど)を移植する
  3. moduleへの参照をProjectReferenceで張り直す
  4. ClickOnceの発行プロファイルを作成し、「アプリケーション ファイル」でdata.txtをIncludeする

「変換して壊れるのが怖い」という場合でも、新規作成→段階移植ならロールバックが容易で、移行中も旧プロジェクトを残せます。

回避策:appプロジェクトからファイルをリンクしてClickOnceに確実に載せる

どうしてもappをSDKスタイルへ移行できない(社内テンプレート制約、レガシー依存など)場合は、発想を変えてapp側にファイルを“取り込んだことにする”方法が効きます。

具体的には、app.csprojに「module配下のファイルをContentとしてIncludeし、出力へコピーする」設定を書きます。Visual Studio上ではappプロジェクトの配下に見せたいので、<Link>を使うのがコツです。

&lt;ItemGroup&gt;
  &lt;Content Include=&quot;..\module\folder\data.txt&quot;&gt;
    &lt;Link&gt;folder\data.txt&lt;/Link&gt;
    &lt;CopyToOutputDirectory&gt;Always&lt;/CopyToOutputDirectory&gt;
  &lt;/Content&gt;
&lt;/ItemGroup&gt;

フォルダー内の複数ファイルをまとめて扱うなら、ワイルドカードも使えます。

&lt;ItemGroup&gt;
  &lt;Content Include=&quot;..\module\folder\**\*.*&quot;&gt;
    &lt;Link&gt;folder\%(RecursiveDir)%(Filename)%(Extension)&lt;/Link&gt;
    &lt;CopyToOutputDirectory&gt;Always&lt;/CopyToOutputDirectory&gt;
  &lt;/Content&gt;
&lt;/ItemGroup&gt;

この方法なら、ClickOnceは「appプロジェクトのContent」として認識しやすく、アプリケーション ファイルに出てこない問題を実務的に回避できます。モジュールを複数のアプリから参照している場合でも、アプリごとに配布したいファイルだけを明示的に選べる点もメリットです。

実務で効く補足:発行後にファイルを読み込むパス設計

ClickOnce環境では、アプリ本体はユーザー配下のキャッシュ領域にバージョンごとに展開されます。したがって、「アプリのインストール先を固定してそこから読む」設計は避け、実行時のベースディレクトリから相対パスで読むのが安全です。

using System;
using System.IO;

var path = Path.Combine(AppContext.BaseDirectory, &quot;folder&quot;, &quot;data.txt&quot;);
// AppContext.BaseDirectory は実行中アプリの配置先(ClickOnceでも有効)
var text = File.ReadAllText(path);

なお、ClickOnceには「Data File」という扱いがあり、データ用ディレクトリ(Data Directory)へコピーして管理できます。Data Directoryは、アプリが管理するデータを置くための領域として説明されており、更新時には旧バージョンのData Directoryから新バージョンへデータファイルがコピーされるルールがあります(ハッシュが変わると置換され、旧版は.preフォルダーに退避されます)。

ただし重要な注意点として、.NET Core 3.1(および.NET 5/6)では、.NET Framework時代に使えたApplicationDeployment API(System.Deployment.Application)へプログラムからアクセスできません。Data Directoryを「APIで取得して使う」設計にしたい場合は、.NET 7以降で提供される仕組みの検討か、アプリ側で%LOCALAPPDATA%配下に自前の保存先を作る設計を選ぶのが現実的です。

たとえば、次のように初回起動時に同梱ファイルをユーザー領域へコピーし、その後は常にユーザー領域を参照するようにすると、ClickOnce更新で「書き換えたファイルが消える」問題を避けられます。

using System;
using System.IO;

string userDir = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    &quot;YourCompany&quot;,
    &quot;YourApp&quot;);

Directory.CreateDirectory(userDir);

string src = Path.Combine(AppContext.BaseDirectory, &quot;folder&quot;, &quot;data.txt&quot;);
string dst = Path.Combine(userDir, &quot;data.txt&quot;);

if (!File.Exists(dst))
{
    File.Copy(src, dst);
}

// 以後は dst を読み書きする

トラブルシュート:よくある症状と対処の早見表

症状ありがちな原因対処
ClickOnce発行物にfolder\data.txtが入らない出力にコピーされていない/Publish StatusがExcludemoduleのCopyToOutputDirectoryを確認し、アプリケーション ファイルでIncludeにする
アプリケーション ファイルの一覧にファイルが出ないビルド アクションがContentでない/「すべてのファイルを表示」がOFFファイルのビルド アクションをContentにし、Show all filesをONにする
ライブラリ側のファイルがどうしても出ないappが旧形式プロジェクトで参照プロジェクトのContentが拾えないappをSDKスタイルへ移行するか、app.csprojでLinkとして取り込む
更新後にファイルを書き換え内容ごと消えたIncludeしたファイルをアプリ配置先で直接編集しているユーザー領域へコピーして運用する/Data Fileの扱いを検討する

まとめ:最短で成功させるチェックリスト

  • module側の対象ファイルはContent+Copy alwaysにする
  • Releaseでビルドして、appの出力フォルダーにフォルダーごと存在することを確認する
  • 発行設定の「アプリケーション ファイル」でShow all filesをONにし、Publish StatusをIncludeにする
  • 一覧に出ない場合は、appが旧形式プロジェクトではないかを疑い、SDKスタイル移行かLink方式で回避する
  • 書き換える可能性があるデータは、アプリ配下ではなくユーザー領域で管理する

この流れを押さえておけば、Prismのモジュール分割構成でも、ClickOnceでライブラリ配下のフォルダーを安定して同梱できます。現場では「まず出力に出す→次にInclude」の二段階を意識するだけで、無駄なハマりをかなり減らせます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次