対象: root/programs 配下(CS / VB、net48 / net10.0)
配置: root/programs
本書は**「設定がどう読まれ、どう上書きされ、どこで踏むか」**を扱う。 **個々のキーが何を意味するかは書かない。**それは値の隣(各設定ファイルのコメント)にある。
設定ファイルは 86 個ある。全部に同じ説明を書くことはできないし、書けば必ずずれる。
| 書く場所 | 例 | |
|---|---|---|
| 仕組み(読み込み順・上書き・展開・落とし穴) | 本書だけ | 「環境変数は __ 区切りで上書きする」 |
| そのキーが何か(値の意味・既定値・選択肢) | 値の隣のコメント | 「FxSqlTraceLog : D層のパフォーマンス ログ出力の on / off」 |
| そのサンプル固有の事情 | そのサンプルの ANALYSIS.md / README.md |
「Legacy/* は相対パスで書く」 |
設定ファイルに仕組みを書き足したくなったら、本書に書いて、そこから参照する。 一部のファイルにだけ詳しい説明がある状態は、書かれていないファイルを「該当しない」と 誤読させる。(実際、86 個中 3 個にだけ詳細が書かれている状態になっていた)
| net48 | net10.0 | |
|---|---|---|
| デスクトップ・バッチ・CLI | app.config |
appsettings.json |
| Web | Web.config(+ app.config) |
appsettings.json(+ appsettings.{環境名}.json) |
net48 の Web では、Web.config が appSettings を別ファイルへ委譲していることがある。
<appSettings file="app.config"/>このときキーの実体は app.config 側にある。Web.config だけ見て「無い」と判断しない。
GetConfigParameter.InitConfiguration(configuration); // Startup のコンストラクタこれが無いと Fx* 系の設定がすべて null になる。 net48 は暗黙に読まれるので不要。
appSettings / connectionStrings というセクション名を、JSON でもそのまま使う。
{
"connectionStrings": { "ConnectionString_SQL": "..." },
"appSettings": { "FxXMLSPDefinition": "..." }
}ASP.NET Core の標準(ConnectionStrings / 任意のセクション)とは綴りが違う。
GetConfigParameter がこの名前で引くため、変えると読めなくなる。
appsettings.jsonはコメント付き JSON(JSONC)で書いてよい。Microsoft.Extensions.Configuration.Jsonは//を許容する。 ただし厳密な JSON パーサで読むと壊れるので、外部ツールに食わせるときは注意。
Host.CreateDefaultBuilder が次の順に読む。後が勝つ。
appsettings.json → appsettings.{環境名}.json → user-secrets(Development のみ)
→ 環境変数 → コマンドライン
{環境名} は ASPNETCORE_ENVIRONMENT の値。未指定なら Production。
appsettings.{環境名}.json は「キー単位」の上書きであって、丸ごと差し替えではない。
書いたキーだけが上書きされ、書いていないキーは appsettings.json の値が生きる(実測)。
| 実験 | 結果 |
|---|---|
appsettings.Container.json 無し(Production) |
200 |
有り + ASPNETCORE_ENVIRONMENT=Container |
500(1 キーだけ壊した値を書いた。読まれている) |
| 同じファイル + 環境名なし | 200(読まれない) |
環境名を変えると副作用がある。
Developmentにするとenv.IsDevelopment()が真になり、 開発者例外ページが出る(本番向けのUseExceptionHandler/UseHsts側に入らない)。 実例ではContainerという名前を使っている。
appSettings__FxXMLSPDefinition=/app/files/resource/Xml/SPDefinition.xml
connectionStrings__ConnectionString_SQL=Data Source=db;...
__ が :(セクション区切り)に読み替えられる。上の 3 節のとおり、環境変数は
appsettings.json にも appsettings.{環境名}.json にも勝つ。
Startup が受け取った IConfiguration をそのまま InitConfiguration へ渡しているため、
フレームワーク側から読む値にも効く。
Docker / Kubernetes の secrets も同じキー名で渡せる。
Microsoft.Extensions.Configuration.KeyPerFileを足し、builder.Configuration.AddKeyPerFile("/run/secrets", optional: true)とすると、 ファイル名がそのまま構成キーになる(__がセクション区切りとして効く)。Host.CreateDefaultBuilderは既定では読まないので、1 行足す必要がある。
混同しないこと。 こちらは ON のとき、接頭辞なしのキー名で環境変数を読む。
// Public/Util/GetConfigParameter.cs : CheckContainerization
if (containerization.ToUpper() == PubLiteral.ON)
{
return System.Environment.GetEnvironmentVariable(key); // 例: FxXMLSPDefinition
}| 読むもの | 有効化 | |
|---|---|---|
appSettings__ 方式 |
appSettings__FxXMLSPDefinition |
不要(既定で効く) |
FxContainerization |
FxXMLSPDefinition |
FxContainerization=ON |
net10.0 のコンテナで使っているのは前者。 FxContainerization は使っていない。
OS の機能ではない。Open棟梁 が自前で展開している。
// Public/IO/ResourceLoader.cs : ResolveFilePath
loadfilepath = StringVariableOperator.BuiltStringIntoEnvironmentVariable(loadfilepath);% で分割して Environment.GetEnvironmentVariable(名前, Process) を引くだけの実装で、
Windows の書式に依存しない。Linux コンテナでも効く。
"FxXMLSPDefinition": "%OT_RESOURCE_ROOT%/Xml/SPDefinition.xml"ResourceLoader を経由してファイルを開くパスは、すべて展開される
(ResolveFilePath / Exists / LoadAsString のいずれも ResolveFilePath を通る)。
- 定義 XML(
FxXML*Definition) - log4net / NLog の定義ファイルの場所(
FxLog4NetConfFile) - SQL ファイル(
SqlTextFilePath+ ファイル名。BaseDam.Load2→ResourceLoader.LoadAsString)
TransactionControl / BaseController / *NameService は、BuiltStringIntoEnvironmentVariable を
明示的に呼んでいる。
ResourceLoaderを経由しない読み方をしている箇所では効かない。 新しいパス系キーを足すときは、どちらの経路かを確かめること。
Open棟梁 が展開するのは設定ファイルの「場所」までで、中身はライブラリへそのまま渡す。 したがって、ログ定義の中の変数はログ ライブラリの書式で書く。
<!-- log4net : PatternString の %env{}。<param name="File"> ではなく型付きの <file> -->
<file type="log4net.Util.PatternString" value="%env{OT_LOG_ROOT}/ACCESS" /><!-- NLog : ${...} -->
<target xsi:type="File" name="ACCESS" fileName="${OT_LOG_ROOT}/ACCESS..." />appsettings*.json は、実行ファイルの場所ではなく「作業ディレクトリ」から読まれる。
作業ディレクトリが違うと、設定ファイルが一度も読まれないまま起動する。 それでも画面は出るので気付きにくい。起動ログで確かめられる。
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\OpenTouryo\root\programs ← 意図した場所か?
- コンテナでは
DockerfileにWORKDIR /appを置く dotnet <パス>\App.dllで起動するときは、出力フォルダを作業ディレクトリにする
実フォルダは Xml / Log / Sql / Test / X509。
XML や test と書いても Windows では通り、Linux では開けない。
サンプルは C:/root/files/resource/... を直書きしている(リポジトリを C:\root\ に
展開する前提)。別の場所に置くならビルドは通るが実行時に落ちる。
環境変数(4 節)か %変数%(5 節)で差し替える。
相対パスはまず作業ディレクトリ基準で探し、見つからなければ
AppContext.BaseDirectory(実行ファイルの場所)基準で再探索する
(ResolveFilePath)。CLI が任意の場所から起動される場合への対応。
Samples4NetCore/Legacy/* は、この仕組みに乗ってファイル名だけを書いている。
"FxXMLSPDefinition": "SPDefinition.xml"csproj の CopyToOutputDirectory で出力フォルダへ配る前提なので、
%変数% 化すると壊れる。
リバース プロキシで TLS を終端すると、アプリから見た接続は HTTP になる。
Request.IsHttps(net48 は IsSecureConnection)は false のままで、
ブラウザは HTTPS なのに Cookie に Secure が付かない(#549)。
対処は 2 通りある。
| 方針 | 手段(net10.0) | 手段(net48) |
|---|---|---|
| 検知(実態を知らせる) | UseForwardedHeaders |
IIS の URL Rewrite で HTTPS を立てる |
| 宣言(そう決め打つ) | CookieSecurePolicy=always |
forms の requireSSL="true" |
どちらも実測してある(X-Forwarded-Proto: https を付けた要求で Set-Cookie を見る)。
| 検知 | 宣言 | |
|---|---|---|
| net10.0 | 4 件中 3 件 | 4 件中 4 件 |
| net48 | 6 件中 2 件(=フレームワークが立てる SessionTimeOut の生成・削除の両方) |
— |
net10.0 の検知が 3 件止まりなのは、削除用 Cookie が
CookieExtensions.Delete(key)(オプション無し)を通るため。
net48 側は削除も newCookie.Secure を明示しているので、両方に付く。
net48 では
allowedServerVariablesへの登録も要る。Web.configからは設定できず(overrideModeDefault="Deny")、applicationHost.configに書く。忘れると要求ごとに HTTP 500.50 になる。
宣言の方が依存が少なく確実。 CookiePolicy はすべての Set-Cookie を後段で
上書きするため、個々の箇所が何を判断しているかに依存しない。
| 内容 | 欠けると | |
|---|---|---|
| ① 前段が伝播する | X-Forwarded-Proto: https を付ける |
何も起きない |
| ② アプリが受け取る設定になっている | UseForwardedHeaders |
付いていても捨てられる |
| ③ 付けられるのが前段だけ | ネットワークで閉じる/KnownProxies |
クライアントが詐称できる |
KnownIPNetworks/KnownProxiesを設定しないと黙って無視される。 既定ではループバックからの転送しか信用しない。 「on にしたのに直らない」の原因はほぼこれ。
分かれ目は「リクエストごとに変わるか」。
| 情報 | 性質 | 内部パラメタで矯正できるか |
|---|---|---|
| スキーム(HTTPS か) | 環境ごとに固定 | できる(CookieSecurePolicy / requireSSL) |
| 配置パス(サブパス) | 環境ごとに固定 | できる(app.UsePathBase) |
| 外部から見た URL | 環境ごとに固定 | できる(設定に絶対 URL を書く) |
| クライアント IP | リクエストごとに変わる | できない(伝播が必須) |
| クライアント証明書 | リクエストごとに変わる | できない(伝播が必須) |
クライアント IP は既に動く。 GetClientIpAddress と MyBaseAsyncApiController が
X-Forwarded-For をヘッダから直読みしており、UseForwardedHeaders に依存しない。
間違えるとはっきり壊れる。 本当に平文 HTTP の環境で always にすると、
Antiforgery の Cookie が返らずログインの POST が 400 になる(実測)。
ただしこれは利点でもある。
| 間違えたときの現れ方 | |
|---|---|
| 検知 | 静かに効かない。 画面は動き、Secure だけが付かない |
| 宣言 | はっきり壊れる。 すぐ気付いて直せる |
Request.IsSecureConnection はコードから変えられない(読み取り専用)。
IIS 側で HTTPS サーバ変数を立てる。
MVC でも Web Forms でも同じである。設定するのは IIS であって、アプリの実装ではない。
<system.webServer>
<rewrite>
<rules>
<rule name="ForwardedProto">
<match url=".*" />
<conditions>
<add input="{HTTP_X_FORWARDED_PROTO}" pattern="^https$" />
</conditions>
<serverVariables>
<set name="HTTPS" value="on" />
</serverVariables>
<action type="None" />
</rule>
</rules>
</rewrite>
</system.webServer>action type="None" は「書き換えも転送もしない」。サーバ変数を立てるためだけの規則である。
これを忘れると、すべての要求が HTTP 500.50 になる。
HTTP Error 500.50 - URL Rewrite Module Error.
The server variable "HTTPS" is not allowed to be set.
Add the server variable name to the allowed server variable list.
<rewrite>
<allowedServerVariables>
<add name="HTTPS" />
</allowedServerVariables>
</rewrite>Web.config からは設定できない。 allowedServerVariables は
overrideModeDefault="Deny" で、applicationHost.config にしか書けない。
アプリが勝手にサーバ変数を書き換えられないための関門である。
| 送ったヘッダ | secure が付いた Cookie |
|---|---|
X-Forwarded-Proto: https |
6 件中 2 件(SessionTimeOut の生成・削除の両方) |
| (ヘッダ無し) | 0 件 |
X-Forwarded-Proto: http |
0 件(条件の ^https$ が弾く) |
SessionTimeOut は FxCmnFunction が立てる Cookie なので、
IsSecureConnection が true になったことの直接の確認になる。
ARR は要らない。 ARR は IIS 自身をリバース プロキシにする機能で、 ここで要るのは URL Rewrite だけ。前段が既にある構成なら ARR は無関係。
IIS Express で試すときの注意。
/path:起動は%ProgramFiles%\IIS Express\AppServer\applicationhost.configを読む (%USERPROFILE%\Documents\IISExpress\config\...ではない)。/config:は/path:と併用できず、/site:が要る。
Web サーバを複数台にするなら要る。 セッションを StateServer や SQLServer へ 外出ししても、それだけでは足りない。
認証 Cookie(forms)と ViewState は machineKey で保護されている。
指定しないとサーバごとに別の鍵が自動生成されるため、台数を増やした途端、
片方が発行したものをもう片方が復号できず、
ログインし直しや「ビューステートの検証に失敗しました」になる。
<machineKey validationKey="<同じ値>" decryptionKey="<同じ値>"
validation="HMACSHA256" decryption="AES" />ViewState を使う Web Forms では、より当たりやすい。 netcore 側の対応物は DataProtection(10 節)。
サンプルはパスワードを直書きしている(すぐ動かせることを優先しているため)。 実際の環境では、次のいずれかで渡す。
| net48 | net10.0 | |
|---|---|---|
| 別ファイルへ逃がす | <connectionStrings configSource="..."/> |
appsettings.{環境名}.json |
| 暗号化する | aspnet_regiis -pef connectionStrings <パス> |
— |
| 環境変数 | — | connectionStrings__ConnectionString_SQL=... |
| 開発機のみ | — | dotnet user-secrets(Development のみ読まれる) |
| コンテナ | — | secrets + AddKeyPerFile(4-1 節) |
| そもそも持たない | Integrated Security=SSPI |
同左 |
証明書は「パスワードを無くす」選択肢がある。 PFX + パスワードではなく PEM + 秘密鍵ファイルにすると、管理対象が 1 つ減る。
dotnet dev-certs https --format Pem -ep ./https/aspnetapp.pem -np
| net48 | net10.0 | |
|---|---|---|
| ファイル | app.config / Web.config |
appsettings.json |
| 環境別 | (なし。configSource で分ける) |
appsettings.{環境名}.json |
| 初期化 | 暗黙 | InitConfiguration() が必須 |
| 環境変数での上書き | (標準では無い) | __ 区切り |
Cookie の Secure を必ず立てる |
requireSSL="true" |
CookieSecurePolicy=always |
| 転送ヘッダの取り込み | IIS の URL Rewrite | UseForwardedHeaders |
| 鍵の共有(複数台) | machineKey |
DataProtection(DataProtectionKeyPath) |
| セッションの外出し | sessionState mode="StateServer" ほか |
AddDistributedSqlServerCache ほか |
Samples の設定ファイルは CS / VB でほぼ同じだが、完全には同じでない。
「同じはずだから」とコピーすると、VB 固有の記述が消える。(実際に消した。#549 の作業中)
対応する設定ファイル 25 組を、XML の要素・属性で比較した (コメント・空白・改行・BOM の差は無視)。
| 組数 | |
|---|---|
| 完全一致 | 8 |
| 差分あり | 17 |
組数は必ず見ること。 対応づけは「CS からの相対パスを VB に当てる」方式なので、 階層が一致しないプロジェクトは 1 組も対象に入らない。 「差分 0 件」と「対象に入っていない」は、出力の上では区別がつかない。
差分の種類は次のとおり(1 組が複数に該当することがある)。
| 種類 | 組数 | 判断 |
|---|---|---|
startup / supportedRuntime(VB のみ) |
10 | 違ってよい(VS が VB プロジェクトに自動で入れる) |
runtime/assemblyBinding の bindingRedirect |
8 | 違ってよい(NuGet が生成。参照するパッケージの版で変わる) |
packages.config の構成 |
2 | 揃える(下記) |
ClientSettingsProvider.ServiceUri(VB のみ) |
1 | 違ってよい(VS のテンプレート由来) |
SqlTextFilePath の値 |
1 | 違わないといけない(下記) |
compilation/assemblies(VB のみ) |
1 | 違ってよい(VB は明示参照が要ることがある) |
接続文字列の末尾の ; |
1 | 要確認(ConnectionString_MCN。CS だけ ; が付く) |
| 要素の順序だけ | 1 | 無視してよい(MVC_Sample/app.config) |
Samples/2CS_sample/GenDaoAndBatUpd_sample/app.config
CS : <add key="SqlTextFilePath" value="GenDaoAndBatUpd_sample.Dao" />
VB : <add key="SqlTextFilePath" value="GenDaoAndBatUpd_sample" />
同じにすると壊れる。 埋め込みリソースの名前の作られ方が、言語で違うため。
| マニフェスト名 | |
|---|---|
| C# | <RootNamespace>.<フォルダ>.<ファイル名> |
| VB | <RootNamespace>.<ファイル名>(フォルダ名が入らない) |
RootNamespace はどちらも GenDaoAndBatUpd_sample、EmbeddedResource Include="Dao\..." も
同じである。違うのはコンパイラの規則の方なので、設定値で吸収するしかない。
SqlTextFilePathは「埋め込みリソースなら名前空間、通常のファイルならフォルダのパス」 という二役を持つ(MyBaseDao.SetSqlByFile2)。5 節・7-3 節も参照。
拡張子が .config なので元から対象に入っている。$allowed には入れていない。
見るのは、陳腐化した宣言(packages.config にしか出てこないパッケージ)と
targetFramework の残留(プロジェクトの TargetFrameworkVersion と食い違う宣言)である。
どちらもビルドは通る。 だから検査でしか見つからない。
packages.configの見方(サテライト・コンテンツ・メタ パッケージの区別)はBUILDING.md11 節。「参照が無い=不要」ではない。
丸ごとコピーしない。 上のとおり、丸ごと同じにしてよいファイルは 25 組中 8 組しかない。
差分の行数を必ず見る。 コメントを 16 行足したはずが +23/−53 になっていたことで、
実際に事故に気付けた。
git diff --numstat
値が変わっていないことは、機械的に確かめられる。
CompareConfig.ps1 が、XML の要素・属性だけを取り出して突き合わせる。
コメント・空白・改行・BOM の差は落ちるので、値の差だけが残る。
cd root\programs
.\CompareConfig.ps1 # 一覧
.\CompareConfig.ps1 -Detail -Only "WebApp_sample" # 内訳
.\CompareConfig.ps1 -Check # 合否(想定外の差分があれば 1)-Check は「差分の有無」ではなく「想定外の差分の有無」を見る。
上の表で「違ってよい」とした種類(startup / runtime / compilation/assemblies /
SqlTextFilePath / ClientSettingsProvider.*)は、当てはまれば想定内として扱う。
================ 判定 ================
**想定外の差分 2 件**
Samples\WebApp_sample\WebForms_Sample\WebForms_Sample\Web.config
/configuration/connectionStrings/... [connectionString=...Database=test2;...]
許容する種類を増やすときは、なぜ違ってよいのかをこの節にも書くこと。
スクリプトの $allowed だけに足すと、理由が残らない。
そして、足す前に「なぜ違うのか」を履歴で確かめること。
「差がある」ことと「違ってよい」ことは別である。
理由が「片側だけ作業が済んでいない」なら、許容ではなく、作業を終わらせるのが答えになる。
$allowed に入れてしまうと、やり残しが「仕様」として固定される。
compilation/assemblies の欠落と接続文字列の ; は、この方法で見つけた。
CI は代わりにならない。 VB の疎通テストは 6 対象しか動かさないため、差分のあるファイルの半分以上は 一度も実行されない。 実行される分でも
compilation/assembliesの欠落は0_RunAll.ps1 -Lang Bothを通過した(実測)。 CI は「壊れていないこと」の証拠にはなるが、「意図どおり」の証拠にはならない。
| 見たいもの | 場所 |
|---|---|
| net10.0 の Web(本番向けの切り替えを含む) | CS/Samples4NetCore/Backend/MVC_Sample/MVC_Sample/appsettings.json |
環境変数・%変数%・環境別ファイルの実運用 |
CS/Samples4NetCore/Docker/(対象ごとに分かれる。MVC_Sample / ASPNETWebService) |
net48 の Web(machineKey / 転送ヘッダの注記) |
CS/Samples/WebApp_sample/MVC_Sample/MVC_Sample/Web.config |
| 相対パスで自己完結させる書き方 | CS/Samples4NetCore/Legacy/Bat_sample/*/appsettings.json |
| 領域ごとの事情 | 各 ANALYSIS.md(Frameworks / Samples / Samples4NetCore) |