seraphyの日記

日記というよりは過去を振り返るときのための単なる備忘録

C#でアプリケーション設定を取得・保存する、いくつかの方法

基本はapp.config(web.config)に集約される

DotNETではアプリケーション設定の類を扱うには、ざっくりと以下の方法があげられる。

  • app.config (ウェブアプリケーションの場合は、web.config)
    • appSettingsによる単純な key=value 形式の文字列データによる方法
    • アプリケーション固有のデータ構造をもつ独自のセクションの定義による方法
  • settingsファイル (*.settings)
  • resourceファイル (*.resx)
    • 埋め込みリソース、サテライトDLLとして
    • resxファイルとして (主にウェブアプリで)
app.config (web.config)

基本的には、アプリケーションの設定は、アプリケーション構成ファイル「app.config」に記述される。
(ウェブアプリの場合はweb.config)


アプリケーション構成ファイルは実行ファイルまたはウェブアプリケーションに対して1つだけ存在するファイルで、
実行時に動的に書きかえることはできないが、テキスト形式であるため、ユーザーが設定を書き換えることは容易であり、
その場所を探すのも容易である、という利点がある。


app.configは、標準で

  • 単純なkey=valueのリスト
  • データベースの接続文字列のリスト

による設定の記述がサポートされているが、
独自の構造を定義して複雑な設定も可能なように拡張性が備わっている。


また、*.configファイルが肥大化しすぎないように、各設定セクションごとに外部ファイルに分離する仕組みもある。

設定ファイル(*.settings)

settingsは、app.configの機能に、いくつかのコンパイル時サポートと、ランタイム側のサポートをつけたものといえる。

settingsは、app.configのkey=value形式の羅列と比較して、以下の特徴をもつ。

  • アプリケーション設定とユーザー固有設定とを分けて設定することができる
  • ユーザー固有の場合は実行時に設定値を変更し、記憶(保存)することができる。
  • データの型は単純な文字列以外に、数値指定や文字列コレクション等を指定することができる。
  • コードによるアクセッサを自動生成できる。これによりコードから型つきのプロパティとして各項目にアクセスできる。

※ ただし、web.configの場合はアプリケーション設定のみで、ユーザー固有設定の指定はできない。
※ Webアプリケーションの場合には「Profile」というユーザー固有の情報を保存する、別の仕組みが存在する。

リソースファイル (*.resx)

リソースファイルは設定というよりはリソースであるが、文字列定数のようなものを保持するには最適である。

  • コードによるアクセッサを自動生成できる。これによりコードから型つきのプロパティとして各項目にアクセスできる。
  • 実行時のカルチャ(ロケール)によって、リソースを自動的に切り替えられる。
  • リソースはexeまたはdllに埋め込むことも、resxファイルを残すことも可能。

アプリケーション構成ファイル(*.config)のappSettingsを使う方法

app.config(またはweb.config)には、標準サポートされている単純なkey=value形式の設定として「appSettings」セクションを使うことができる。

app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <!-- DotNET標準のappSettingsセクション、キーと値のペアのリスト -->
  <appSettings>
    <add key="key1" value="value1"/>
    <add key="key2" value="日本語もOK"/>
  </appSettings>
</configuration>

見ての通り、keyとvalueの文字列によるペアのリストである。

とくに個数制限はなく、いくつでも書くことができるので、このセクションは膨大な行になりえる。

これは、以下のようにしてアクセスできる。

app.configの場合のアクセス方法
    // 標準の「appSettings」へのアクセス方法
    foreach (string key in ConfigurationManager.AppSettings.AllKeys)
    {
        string value = ConfigurationManager.AppSettings[key];
        System.Diagnostics.Trace.WriteLine("appSettings: " + key + "=" + value);
    }
web.configの場合のアクセス方法
    // 標準の「appSettings」へのアクセス方法
    foreach (string key in WebConfigurationManager.AppSettings.AllKeys)
    {
        string value = WebConfigurationManager.AppSettings[key];
        System.Diagnostics.Trace.WriteLine("appSettings: " + key + "=" + value);
    }

appSettingsをグループに分割する。

app.configまたはweb.configで標準サポートされている「appSettings」セクションには、
特に制限なく、いくつでも設定値を定義できるが、あまりに増えすぎるとグループ分けしたりファイルとして独立させたくなるであろう。


その場合、独自のセクションを設けることで、appSettingsを分割することができる。

そして、app.configは各セクションごとに別ファイルに分割できるため、appSettingsのセクションを分割することで、ファイルも分割させることが可能になる。


※ 外部ファイルにする方法は、たとえば1つのソリューションに2個以上のexeファイルがあり、それらが共通の設定ファイルを参照する場合にも使える。


独自のセクションの定義はアプリケーション構成ファイルの「configSections/section」タグで設定する。

app.configの場合
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <!-- configSectionsの定義は、app.configの先頭に書く必要がある -->
  <configSections>
    <!-- 独自に指定したセクション、
         NameValueFileSectionHandlerを指定しているので、扱い方はappSettingsと同じ -->
    <section name="appSettings2"
             type="System.Configuration.NameValueFileSectionHandler"
             restartOnExternalChanges="false"/>
  </configSections>

  <!-- DotNET標準のappSettingsセクション、キーと値のペアのリスト -->
  <appSettings>
    <add key="key1" value="value1"/>
    <add key="key2" value="日本語もOK"/>
  </appSettings>

  <!-- 独自に指定したセクション、扱い方はappSettingsと同じ.
       configSource属性によって、どのセクションであっても外部ファイルに分割することできる.
       (実行ファイルのフォルダへのコピーを忘れずに!)
       appSettingsが大きくなった場合には、この方法でセクションとファイルを分割することができる -->
  <appSettings2 configSource="appSettings2.xml"/>
</configuration>

新しいセクション「appSettings2」を定義し、クラスを「NameValueFileSectionHandler」としている。

これは、標準のappSettingsと同じクラスであるため、アプリケーション側では同じような方法で設定値にアクセスできるようになる。


また、セクションの属性として「configSource="appSettings2.xml"」と指定しているため、そのセクションは外部ファイルから取り込まれることになる。


ファイルは以下のようにセクションの内容を記載する。

<?xml version="1.0" encoding="utf-8" ?>
<appSettings2>
  <add key="key3" value="value3"/>
  <add key="key4" value="value4"/>
</appSettings2>

この設定ファイル「appSettings2.xml」は、コンパイル時にexeファイルと同じ位置にコピーされるように設定しておく必要がある。
(*.configからの相対パスでアクセスされる為。)

これは以下のようにアクセスできる。

    // 独自に設定した「appSettings2」へのアクセス
    var appSettings2 = (NameValueCollection)
        ConfigurationManager.GetSection("appSettings2");
    foreach (string key in appSettings2)
    {
        string value = appSettings2[key];
        System.Diagnostics.Trace.WriteLine("appSettings2: " + key + "=" + value);
    }
web.configの場合

ウェブアプリケーションであっても、ほぼ同じである。

<configuration>
  <!-- configSectionsの定義は、app.configの先頭に書く必要がある -->
  <configSections>
    <!-- 独自に指定したセクション、
         NameValueFileSectionHandlerを指定しているので、扱い方はappSettingsと同じ -->
    <section name="appSettings2"
             type="System.Configuration.NameValueFileSectionHandler"
             restartOnExternalChanges="false"/>
  </configSections>
  
  <appSettings>
    <add key="key1" value="value1"/>
    <add key="key2" value="日本語もOK"/>
  </appSettings>

  <!-- 独自に指定したセクション、扱い方はappSettingsと同じ.
       configSource属性によって、どのセクションであっても外部ファイルに分割することできる.
       (実行ファイルのフォルダへのコピーを忘れずに!)
       appSettingsが大きくなった場合には、この方法でセクションとファイルを分割することができる -->
  <appSettings2 configSource="App_Data\appSettings2.xml"/>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.webServer>
     <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

ただし、今回は「configSource="App_Data\appSettings2.xml"」としているので、
App_Data下に「appSettings2.xml」を配置することにしている。


※ App_Data下であればブラウザから直接アクセスすることができないため、設定ファイルを外部に晒すことはない。
※ この場合、出力先ディレクトリにコピーする必要はない。


アクセス方法も、ほぼ同じである。

    // 独自に設定した「appSettings2」へのアクセス
    var appSettings2 = (NameValueCollection)
        WebConfigurationManager.GetSection("appSettings2");
    foreach (string key in appSettings2)
    {
        string value = appSettings2[key];
        System.Diagnostics.Trace.WriteLine("appSettings2: " + key + "=" + value);
    }

アプリケーション固有のデータ構造をもつ独自のセクションの定義による方法

app.config/web.configは、アプリケーション固有の独自のデータ構造による設定の記述が可能になっている。

「key = value」の形式では、うまく表現できないデータ構造の設定を簡潔に記述するには、独自のセクションを使うと良い。


独自のセクションを定義するには、まずSystem.Configuration.ConfigurationSectionを継承したクラスを作成する。


このクラスに対して、ConfigurationProperty属性をもつプロパティを定義することで、設定の属性や要素を定義する。


このプロパティの型によって、設定ファイル上の意味が異なる。

  • プロパティがint, stringなどの単一型を示す場合は、「属性」となる。
  • プロパティがConfigurationElement派生クラスである場合は、「要素」となる。
  • プロパティがConfigurationElementCollection派生クラスである場合は、「要素のコレクション」となる。
app.config/web.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <!-- configSectionsの定義は、app.configの先頭に書く必要がある -->
  <configSections>
    <!-- 独自に指定したカスタムセクション、
         独自のFooNetConfigHandlerクラスで設定内容にアクセスする -->
    <section name="serverConfig"
             type="AppConfigTest.FooNetConfigHandler, AppConfigTest"
             restartOnExternalChanges="false"/>
  </configSections>

  <!-- 独自のフォーマットで定義したカスタムセクション -->
  <serverConfig name="serverConf1">
    <defaultServer name="defaultServer1" address="1.2.3.4" port="80"/>
    <servers>
      <add name="host1" address="1.2.3.4" port="81"/>
      <add name="host2" address="1.2.3.5" port="82"/>
      <add name="host3" address="1.2.3.6" port="83"/>
    </servers>
  </serverConfig>
</configuration>

ここでは「serverConfig」という独自のセクションを「FooNetConfigHandler」というクラスで設定している。


このクラスと、それが内包する要素の定義は以下のとおり。

FooNetConfigHandler.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace AppConfigTest
{
    /// <summary>
    /// 独自に定義するセクション
    /// </summary>
    public class FooNetConfigHandler : ConfigurationSection
    {
        /// <summary>
        /// name属性
        /// </summary>
        [ConfigurationProperty("name", IsRequired = false)]
        public string Name
        {
            get
            {
                return (string)this["name"];
            }
            set
            {
                this["name"] = value;
            }
        }

        /// <summary>
        /// defaultServer要素
        /// </summary>
        [ConfigurationProperty("defaultServer", IsRequired = true)]
        public FooNetConfigItem DefaultServer
        {
            get
            {
                return (FooNetConfigItem)this["defaultServer"];
            }
        }

        /// <summary>
        /// servers要素下にhost要素のリストを設定する
        /// </summary>
        [ConfigurationProperty("servers", IsDefaultCollection = true)]
        public FooNetConfigItemCollection Servers
        {
            get
            {
                return (FooNetConfigItemCollection)this["servers"];
            }
        }

    }

    /// <summary>
    /// host要素のコレクションをまとめるservers要素の定義
    /// </summary>
    public class FooNetConfigItemCollection : ConfigurationElementCollection
    {
        /// <summary>
        /// すべてのキー名のコレクション
        /// </summary>
        public string[] AllKeys
        {
            get
            {
                return (from o in BaseGetAllKeys() select o.ToString()).ToArray();
            }
        }

        /// <summary>
        /// 指定されたキーに対応する要素の情報
        /// </summary>
        /// <param name="name">キー名</param>
        /// <returns>要素</returns>
        public new FooNetConfigItem this[string name]
        {
            get
            {
                return (FooNetConfigItem)BaseGet(name);
            }
        }


        /// <summary>
        /// 新しい ConfigurationElement を作成
        /// </summary>
        /// <returns></returns>
        protected override ConfigurationElement CreateNewElement()
        {
            return new FooNetConfigItem();
        }

        /// <summary>
        /// 指定した構成要素の要素キーを取得
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        protected override object GetElementKey(ConfigurationElement element)
        {
            FooNetConfigItem item = element as FooNetConfigItem;
            return item.Name;
        }
    }

    /// <summary>
    /// host要素の定義
    /// </summary>
    public class FooNetConfigItem : ConfigurationElement
    {
        /// <summary>
        /// name属性、必須、キーとして使用する.
        /// </summary>
        [ConfigurationProperty("name", IsRequired = true, IsKey = true)]
        public string Name
        {
            get
            {
                return (string)this["name"];
            }
            set
            {
                this["name"] = value;
            }
        }

        /// <summary>
        /// address属性、必須
        /// </summary>
        [ConfigurationProperty("address", IsRequired = true)]
        public string Address
        {
            get
            {
                return (string)this["address"];
            }
            set
            {
                this["address"] = value;
            }
        }

        /// <summary>
        /// port属性、必須
        /// </summary>
        [ConfigurationProperty("port", IsRequired = true)]
        public int Port
        {
            get
            {
                return (int)this["port"];
            }
            set
            {
                this["port"] = value;
            }
        }

        /// <summary>
        /// 診断用文字列を返す
        /// </summary>
        /// <returns>診断用文字列</returns>
        public override string ToString()
        {
            var buf = new StringBuilder();
            buf.Append("name").Append(Name);
            buf.Append(", address").Append(Address);
            buf.Append(", port").Append(Port);
            return buf.ToString();
        }
    }
}

このようにして設定された独自のセクションにアクセスするには、以下のようにセクション名を指定してキャストするだけで良い。

使用例
    // 独自に定義したセクション
    var serverConfig = (FooNetConfigHandler)
        ConfigurationManager.GetSection("serverConfig");
    System.Diagnostics.Trace.WriteLine("serverConfig.Name=" + serverConfig.Name);
    System.Diagnostics.Trace.WriteLine("serverConfig.defaultServer=" + serverConfig.DefaultServer);
    foreach (string key in serverConfig.Servers.AllKeys)
    {
        var item = (FooNetConfigItem)serverConfig.Servers[key];
        System.Diagnostics.Trace.WriteLine(">" + key + "=" + item);
    }

扱い方はweb.configの場合もWebConfigurationManagerを使うだけで基本的には同じ扱いである。


複数の属性からなる定型的なデータが0個以上の不特定数あるような場合、key=valueで表現するには末尾に連番をつけるなど小賢しい技が必要となるが、
独自のセクションを作れば、アプリケーション的にもデータ構造的にアクセスしやすく、設定ファイルも見やすくなるというメリットがある。


実装方法こそ異なるが、XmlSerializerで独自構造のxml設定ファイルを読ませる方法と大差ない。

しかし、独自のxmlファイルと異なり、app.config/web.configにあることで、それが設定ファイルとして使われていることが一目瞭然である。


実装するのも、それほど手間ではなく、少々複雑な設定が必要であれば考慮に値すると思う。

(もちろん、独自のセクションであっても「configSource」属性による外部ファイル化は可能であるので、app.configファイル自体の肥大化は防げる。)

settingsを使う方法

settingsを使うことで、

  • 設定項目に型を指定してコンパイル時の型チェックが可能
    • int, string, decimal, double, DateTimeなどの型つきのプロパティにできる
    • StringCollectionを指定することで、文字列のコレクションも定義可能
  • ユーザーごとの設定の変更・保存が可能
    • 設定値の変更イベントをハンドリングできる
    • 設定値の保存イベントをハンドリングできる

といったことが利点がある。

デフォルトの「設定」の有効化

app.configの場合も、web.configの場合も、このデフォルトのsettingsを有効にするのはアプリケーションのプロパティから行える。

こうすると、「Settings.settings」というファイルが作成される。


個々の設定項目は、スコープとして「アプリケーション」か「ユーザー」を指定することができ、
ユーザーの場合は設定項目の書き換えと保存が可能になる。

(ただし、ウェブアプリケーションの場合には「アプリケーション」しか選択できない。)


アクセス修飾子には「internal」「public」と「なし」が選択できる。


internalもしくはpublicにした場合、この設定に対するアクセッサのコードが自動生成される。

それにより、コード上から型つきで設定にアクセスできるようになる。

独自のSettingsの追加

appSettingsが分割できるように、settingsも複数個のファイルに分けて定義することができる。

「新しい項目」の追加で「設定ファイル(*.settings)」から新しい設定ファイルを追加できる。

追加された*.settingsファイルは、Propertiesフォルダの下に移動するのが慣例のようである。
(そうすることで、Propertiesの名前空間の下に配置されるようになるため。)

ウェブアプリケーションの場合、VS2010の「新しい項目」のリストに「設定ファイル」が出てこない様子であるが、
生成済みのSettings.settingsをコピーしてリネームすれば、同様に追加することができる。

「設定」への型つきプロパティとしてのアクセス方法と、ユーザー別設定の書き換えと保存

設定は、型つきプロパティとしてアクセスでき、ユーザースコープの設定項目であれば変更と保存が可能になっている。

    // アプリケーション設定にアクセスする. (変更不可)
    System.Diagnostics.Trace.WriteLine("AppsDataInt=" + Properties.Settings.Default.AppsDataInt);
    System.Diagnostics.Trace.WriteLine("AppsData=" + Properties.Settings.Default.AppsData);

    // ユーザー別の設定にアクセスする.
    // ユーザー別設定が保存されていれば復元し、なければデフォルト値となる.
    System.Diagnostics.Trace.WriteLine("UsersDataInt=" + Properties.Settings.Default.UsersDataInt);
    System.Diagnostics.Trace.WriteLine("UsersData=" + Properties.Settings.Default.UsersData);

    // 別ファイルに分離している設定ファイル(Settings2)にアクセスする.
    System.Diagnostics.Trace.WriteLine("Setting2AppsData=" + Properties.Settings2.Default.Setting2AppsData);
    System.Diagnostics.Trace.WriteLine("Setting2UsersData=" + Properties.Settings2.Default.Setting2UsersData);
    foreach (string item in Properties.Settings2.Default.Setting2UserStringCollection)
    {
        System.Diagnostics.Trace.WriteLine("Setting2UserStringCollection=" + item);
    }

    // ユーザー別の設定は変更可能
    Properties.Settings.Default.UsersDataInt += 1;
    Properties.Settings.Default.UsersData += "☆";
    Properties.Settings2.Default.Setting2UsersData += "★";

    // ユーザー別設定を保存する.
    // 保存先はユーザープロファイル %LOCALAPPDATA%、または%APPDATA%の下の
    // 会社名\アプリ名\バージョン の下のフォルダに作成される.
    // Romaingフォルダに格納したい場合は設定プロパティの項目ごとのプロパティでRomaing=trueに設定する.
    Properties.Settings.Default.Save();
    System.Diagnostics.Trace.WriteLine("UsersDataInt(2)=" + Properties.Settings.Default.UsersDataInt);
    System.Diagnostics.Trace.WriteLine("UsersData(2)=" + Properties.Settings.Default.UsersData);

    // 明示的に保存しないと、変更された状態はアプリケーション終了で消失する. (C#の場合)
    //Settings2.Default.Save();
ユーザー別設定値の保存先

ユーザー別設定を保存すると、設定値の内容は以下のいずれかのフォルダ下に保存される。

  • %LOCALAPPDATA%\会社名\プロダクトのexe名をベースにしたもの\バージョン番号\user.config
  • %APPDATA%\会社名\プロダクトのexe名をベースにしたもの\バージョン番号\user.config

※ 会社名やプロダクトのexe名、バージョン番号は「アプリケーションプロパティ」の「アセンブリ情報」で設定するものである。


具体的には、以下のような場所に配置される。

C:\Users\seraphy\AppData\Roaming\seraphyware\AppConfigTest.exe_xxxx\1.0.0.0\user.config


Localに保存するか、Romingに保存するかは、設定項目ごとのプロパティによって指定する。

この「Romaing」を「true」とすることで、「%APPDATA%」(Romaingフォルダ)に配置できるようになる。

リソースファイルを使う (埋め込みリソースとして)

リソースファイルは設定ファイルというよりは定数や固定データをまとめたものといえるが、文字列定数などはリソースファイルで扱うことも十分可能である。

ただし、リソースファイルを文字列定数として扱う場合は、appSettingsの「key=value」と大差ない。

強いていえば、

  • *.configはアプリケーションごとに一つであるが、リソースはexeやdllを問わず、どこでも埋め込める
  • アクセッサのプロパティを自動生成できる 。(これは*.settingsでも可能)
  • とても長い文字列であっても扱いは容易である。
  • 実行時のカルチャー(ロケール)によって定数の中身を切り替えられる。

などといった特性がある。


リソースファイルを扱う方法のうち、埋め込みリソースにする方法は、前述の「Settings」による設定と良く似ている。

アプリケーションのアセンブリ情報で、ニュートラル言語を設定する。

アプリケーション設定を開く。

ここの「アセンブリ情報」を開く。

ここでニュートラル言語」を「なし」に設定する。

これは、言語や国を指定していないベースとなるリソースファイルの言語が特定のロケールに関連づけられていないことを表す。

リソースファイルを有効にする。

リソースファイルを有効にするには、アプリケーション設定のリソースタブを有効にする。

アクセス修飾子の指定は、リソースに対する型つきアクセッサを自動生成するためのもので、Settingsの場合と同じである。

ここで設定するリソースには言語や国の指定がないデフォルトのリソースである。

言語・国別のリソースファイルを追加する。

つぎに、このリソースファイルをコピーして、「Resources.ja-JP.resx」とリネームする。

ベース名とresx拡張子の間に「ja-JP」という言語と国の指定を挟んでいる。(Javaと異なりハイフンであることに注意。)


このようにすることで、実行時のカルチャ(ロケール)が日本の日本語である場合には、「Resources.ja-JP.resx」のものが使われるようになる。


アクセッサはデフォルトのリソースファイルですでに設定済みであるため、追加の言語リソース側では「なし」に設定しておく。

また、双方のリソースファイルともに、ビルドアクションは「埋め込まれたリソース」としておく。

リソースファイルにアクセスする。

リソースファイルを利用するには、以下のようにすれば良い。

    // ※ リソースにアクセスする
    // 日本語の場合は「*.ja-JP.resx」、それ以外をデフォルト「*.resx」のリソースから使う場合には、
    // ニュートラル言語を「なし」にすること。(ニュートラル言語 = デフォルトリソースの言語)

    for (int loop = 0; loop < 2; loop++)
    {
        System.Diagnostics.Trace.WriteLine("calture=" + System.Threading.Thread.CurrentThread.CurrentCulture);
        System.Diagnostics.Trace.WriteLine("ui-calture=" + System.Threading.Thread.CurrentThread.CurrentUICulture);

        System.Diagnostics.Trace.WriteLine(Properties.Resources.ResString1);
        System.Diagnostics.Trace.WriteLine(Properties.Resources.ResString2);

        // カレントスレッドのUIカルチャを英語(米国)に設定した場合
        System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
    }

ニュートラル言語が「なし」であるため、日本語環境では「ja-JP」のリソースファイルを探しに行くことになる。

今回は「ja-JP」以外のリソースファイルは用意していないため、台湾とか米国とかドイツで実行すると、デフォルトのリソースの「Resources.resx」が使われることになる。

(もし、ニュートラル言語が「日本語」であると、日本語環境の「ja-JP」の場合でResources.resxが適合するため、Resources.ja-JP.resxの方は見に行かないことに注意。)


なお、この方法では、リソースファイルはビルドされると独立したリソースだけが格納されたサテライトDLLとして生成されることになる。

リソースファイルの追加

リソースファイルはSettingsの場合と同様に、好きなだけ追加することができる。

追加方法も大して変わらないので省略。

埋め込まないリソースファイル (ウェブアプリケーションの場合)

ウェブアプリケーションの場合は国際化のためのフォルダとして

  • App_LocalResources (各aspxページごとのリソース)
  • App_GlobalResources (特定のaspxに関連づかないリソース)

の二つのフォルダが標準で用意されている。

ここにresxファイルを配置する。

言語・国別の指定方法は前述のものと同じである。


以下のフォルダ構造があるものと仮定すると、

WebConfigTest
│  Default.aspx
│  Default.aspx.cs
│
├─App_GlobalResources
│      Sample.Designer.cs
│      Sample.resx
│
└─App_LocalResources
        Default.aspx.ja-JP.resx
        Default.aspx.resx

このフォルダにリソースをおいた場合、aspx側ファイルからはasp.net式を通じてリソースアクセスができる。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebConfigTest.Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Default.aspx</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        ローカルリソースのアクセス: <asp:Label id="label2" runat="server" text="<%$ Resources: ResString1 %>" /><br />
        グローバルのソースへのアクセス: <asp:Label id="label1" runat="server" text="<%$ Resources: Sample, ResSample1 %>" /><br />
    </div>
    </form>
</body>
</html>

プログラム Default.aspx.csから、これらにアクセスするには以下のように行う。

    // ローカルリソースへのアクセス(ページ内からのアクセスの場合)
    var resString1 = GetLocalResourceObject("ResString1") as string;
    System.Diagnostics.Trace.WriteLine(resString1);

    // ローカルリソースへのアクセス2 (別クラス等からページの指定でのアクセス)
    var resString1b = HttpContext.GetLocalResourceObject("~/Default.aspx", "ResString1") as string;
    System.Diagnostics.Trace.WriteLine(resString1b);

    // グローバルリソースへのアクセス
    // (ページ内であればHttpContextは指定不要)
    var sampleString1 = HttpContext.GetGlobalResourceObject("Sample", "ResSample1") as string;
    System.Diagnostics.Trace.WriteLine(sampleString1);

    // グローバルリソースへのアクセス方法2
    var sampleString1b = Resources.Sample.ResSample1;
    System.Diagnostics.Trace.WriteLine(sampleString1b);

グローバルリソースは暗黙でアクセッサが生成されるので、Resource名前空間下でプロパティとしてアクセス可能である。

(※ ローカルリソースもアクセッサを有効にすることで、型つきプロパティとしてアクセス可能にできる。)

ウェブアプリケーション以外のresxファイルを直接扱う方法

埋め込みリソースではなく、resxファイルを単体で扱うことも可能である。


その場合、そのresxファイルのビルドアクションは「コンテンツ」として、「出力ディレクトリにコピー」をtrueとする。


これによりコンパイル時にresxファイルはexeやdllに埋め込まれずにexe/dllと同じフォルダにコピーされることになる。


これをプログラムから扱うには以下のように行うことができる。

    // ※ リソースファイルにアクセスする. (resxファイル名指定)

    // 特定のクラスのあるdllやexe等の位置から求める場合
    string resxDirUri = Path.GetDirectoryName(Assembly.GetAssembly(typeof(Program)).CodeBase);
    Uri uri = new Uri(resxDirUri);
    string resxDir = uri.LocalPath;

    // 単に実行ファイルの存在するディレクトリから求めたい場合は以下でも可
    // string resxDir = Path.GetDirectoryName(Application.ExecutablePath);

    // resxへのパスを作成する
    string resxPath = Path.Combine(resxDir, "Resources2.resx");

    // resxファイルを読み取る
    var resxReader = new ResXResourceReader(resxPath);

    // resxファイルの中身を列挙する.
    foreach (DictionaryEntry d in resxReader)
    {
        Console.WriteLine(d.Key + "=" + d.Value);
    }

ただし、この方法ではresxファイル名を直接指定するため、カルチャによるリソースの切り替えのようなことはできない。

コンパイル後もresxファイルを直接編集できるというメリットはあるが、他の方法と比べて格段に良いとは言い難く、
あえて、これを選択する必要はないと思われる。

結論

結局のところ、アプリケーション構成ファイル(*.config)のappSettingsの単純なkey=value形式の設定が、原始的ではあるが、もっとも汎用的である。

(この方法は、はじめ小さな設定で初めて、のちのちに肥大化したとしても、それを分割するのも、そう面倒なことでもない。)


DLLのような独立したアセンブリに入れるライブラリのようなもので、それ自身しか使わない定数のようなものはリソースに埋め込むのが良いだろう。


それ以外の方法は、スコープや用途・役割にあわせて適宜選ぶことになると思われる。

以上、メモ終了。

関連情報

設定ファイルの仕組み

アプリケーションの各種設定ファイルの格納先ディレクトリを取得する方法