M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

VSソリューションにDocFXのプロジェクトを追加する

.NETファミリー(.NET Framework、Mono、.NET Core)向けのドキュメント・ジェネレータの1つにDocFXがあります。このツール自体はVisual Studioやその他IDEからは独立したコマンドライン・ツールですが、Visual Studioソリューションの中でそのバイナリや設定ファイルを管理できると何かと便利そうです。というわけで、やってみました。

1. 下準備

まず今回の作業のためにサンプルのライブラリのソリューションを用意しました。もちろん実際にはここでドキュメント生成の対象とするソリューションをオープンするということになります:

f:id:m12i:20170708203415p:plain

あくまでサンプルなのでほとんど中身のないライブラリです。ダミーのクラス(public指定)とインターフェース(非public指定)だけです:

f:id:m12i:20170708203457p:plain

2. ドキュメント生成用のプロジェクトを追加

ソリューションに新しいプロジェクトを追加します。もっと良い方法があるのかもしれませんが、今回はクラスライブラリのプロジェクト・テンプレートを選択しました:

f:id:m12i:20170708203415p:plain

追加されたプロジェクトには自動でClass1.csなどのファイルが追加されますが、これらは不要なので削除します:

f:id:m12i:20170708203625p:plain

3. ドキュメント生成のための参照を追加

参照マネージャーを起動して(VS2017の場合、ソリューション・エクスプローラー内でドキュメント生成用プロジェクトの[参照]を右クリック→[参照の追加]をクリック)、ドキュメント生成の対象となるプロジェクトを選択します:

f:id:m12i:20170708204400p:plain

NuGetパッケージ・マネージャーを起動して(VS2017の場合、ソリューション・エクスプローラー内でドキュメント生成用プロジェクトの[参照]を右クリック→[NuGetパッケージの管理]をクリック)、"docfx.console"を検索してインストールします。インストールに際して提供者Microsoftのライセンスに同意を求められます:

f:id:m12i:20170708204543p:plain

4. DocFXの設定ファイルを編集しビルド

"docfx.console"のインストールが終わると一連の雛形ファイルがドキュメント生成用プロジェクトに追加されます。このなかの"docfx.json"をオープンして、コードを追加します:

f:id:m12i:20170708204736p:plain

JSONファイルのルート直下のプロパティ("metadata"とか"build"とか)はDocFXのコマンドライン・ツールを実行するときのサブコマンド名として認識されるもののようです。この中の"metadata"のセクションにある"src"プロパティ──"docfx metadata"コマンドで処理対象となるソースコードの定義情報──に, "src": "../(ドキュメント生成対象のプロジェクトのフォルダ名)"という一行を追加します。これでひとまず準備完了。

プロジェクトをビルドします(VS2017の場合、ソリューション・エクスプローラー内でドキュメント生成用プロジェクトを右クリック→[リビルド])。次のキャプチャ画像に示したような出力が行われます。すべて正常終了したことを確認します:

f:id:m12i:20170708205346p:plain

5. 生成されたドキュメントを確認

VS2017のソリューション・エクスプローラーには表示されませんが、プロジェクト・フォルダの直下に"_site"というフォルダがあり、そこに生成されたHTMLドキュメントが出力されています。"index.html"をIE Edgeで表示すると次のようにデフォルトのテンプレートでレンダリングされたドキュメントを閲覧できます:

f:id:m12i:20170708202426j:plain

"API Documentation"をクリックすると、ドキュメント生成対象のライブラリが公開しているメンバーの情報を閲覧できます:

f:id:m12i:20170708202510j:plain

なんとも残念なことにChromeブラウザで表示すると、XMLHttpRequestによるコンテンツのロードがクロス・オリジン・リクエストのエラーのため失敗しており、"toc.yml"で定義されている情報がロードできないために、各種ナビゲーションが無効になっています:

f:id:m12i:20170708202608j:plain

生成されたドキュメントを確認する方法としてはもう1つ、開発PC上でWebサーバを起動してそれ経由で配信されるコンテンツをWebブラウザで閲覧するというものがあります。この方法であれば上述のエラーは起きません:

> cd (プロジェクト・フォルダのパス)
> set PATH=..\packages\docfx.console.(NuGetでインストールしたバージョン)\tools;%PATH%
> docfx --serve

ショートファイル名で実行された場合でも*.exe.configを読み込む

前回の記事で紹介したように、Windows OSの環境で.NET Frameworkアプリケーションを実行するとき、エントリーポイントとなったアセンブリの名称がショートファイル名(8.3形式)で認識されるケースがあります。

この場合、アプリケーション構成ファイル *.exe.config は読み込まれず、ConfigurationManager.AppSettingsConfigurationManager.ConnectionStringsが返すコレクションは要素を含まない空のコレクションになってしまいます。

問題への対策にはConfigurationManager.OpenMappedExeConfiguration(...)メソッドを利用します:

string mayShortName = Assembly.GetEntryAssembly().Location;
string longName = Path.GetFullPath(mayShortName);
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap
{
	ExeConfigFilename = longName  + ".config"
};
Configuration conf = ConfigurationManager.OpenMappedExeConfiguration
                    (fileMap, ConfigurationUserLevel.None);

このコードはPath.GetFullPath(...)メソッドがショートファイル名のパスをロングファイル名のパスに変換してくれるのを利用して、アプリケーション構成ファイルのパスを導出して読み込みを行います。

アプリケーション構成ファイルのロードには上位のファイルからの継承やローミングの有無など複雑な決まりごとがあるようで、それらすべてを考えた場合にこれが完全な対案となっているかどうかはわかりませんが・・・。

しかしAppSettingsプロパティなどが返すNameValueCollectionと異なり、このメソッドが返すのはConfigurationです。明らかに使い勝手が悪いです。ここからIDictionary<string, string>形式で設定情報を引き出すには次のようなコードを書くことになるでしょう:

IDictionary<string,string> appSettings = conf.AppSettings.Settings
                .Cast<KeyValueConfigurationElement>()
                .ToDictionary(e => e.Key, e => e.Value);
IDictionary<string,string> connStrings = conf.ConnectionStrings.ConnectionStrings
                .Cast<ConnectionStringSettings>()
                .ToDictionary(s => s.Name, s => s.ConnectionString);

やたらと手間がかかりますが、とにかくこれでアプリケーションがショートファイル名で実行された場合でも*.exe.configを読み込むことができます。

JP1/AJS2は基本的にショートファイル名で実行ファイルを取り扱う

先日の記事 JP1/AJSから実行すると*.exe.configが読み取れないケースがある で取り上げた事象の原因がわかりました。分かってみればなんのことはありません。「それは仕様です」ということでした。


JP1/AJS2はPCジョブの[詳細定義]ダイアログボックス*1の[実行ファイル名]欄に指定されたパスを基本的にショートファイル名(別名:8.3形式)に変換した上で実行するのです。必ずショートファイル名が利用されるというのではないのですが、ロングファイル名が利用されるケースのほうが少ないのです。

これについてはHITACHIが公開しているリファレンスのあるページに次のような記載がありました*2

ジョブの実行ファイルの名称は,通常,NTFS(NT File System)およびFAT(File Allocation Table)ボリューム上のファイル用に生成されたショートファイル名に変換されます。ジョブの実行ファイルをショートファイル名に変換しないでロングファイル名で実行したい場合は,次に示す手順でオプションを有効にしてください。

なお,キューレスジョブの場合は,ショートファイル名に変換しないでロングファイル名のまま実行されます。

このページでは「環境設定パラメーターIsExecFindExecutableに『1』を設定」することでこの挙動を抑制する方法が紹介されているのですが、それでもページ末尾には次のようにあります:

次の条件が重なるジョブを実行した場合は,環境設定パラメーターIsExecFindExecutableに「1」を設定していても,実行ファイルをショートファイル名に変換して実行します。キューレスジョブの場合でも,この条件によって実行ファイルをショートファイル名に変換して実行します。

  1. [実行ファイル名]に指定したファイル名が相対パス指定で,ファイル名の拡張子が「.exe」,「.bat」,「.cmd」,「.com」または「.pif」である
  2. 1のファイルパスをジョブ定義の[ワークパス]で指定している


ジョブをロングファイル名で実行したい場合は,次のどちらかの操作を行ってください。

  • [実行ファイル名]に絶対パスでファイル名を指定する
  • システム環境変数にファイルパスを指定し,[ワークパス]にファイルパスを指定しない

おまけに、これは厳密に言って「仕様」なのか不明ですが、[ワークパス]に何も入力していない状態 かつ [実行ファイル名]に絶対パスが指定されている場合でも、コマンドライン引数が[パラメーター]欄に独立して記述され、[実行ファイル名]には文字通り純粋な実行ファイル名が記述された状態である場合(≠実行ファイルの後ろに半角スペース区切りでコマンドライン引数が指定されている場合)、結局実行ファイル名はショートファイル名に変換されてしまうようなのです。

反対に[実行ファイル名]に「実行ファイルの絶対パス+半角スペース+コマンドライン引数」という記述をしていれば、実行ファイル名はロングファイル名として処理されました。