はじめに
本稿では、C#を使用して柔軟な構成セクションハンドラを作成する方法を説明します。.NET開発者ならば、構成セクションハンドラを記述した経験が何度かあるでしょう。まず.NET構成システムの背景を簡単に紹介し、このシステムが有益である理由と、このシステムを拡張する方法について説明します。
構成システムの背景と概念
.NET構成システムの目的は単純で、構成情報を読み書きするための一貫した方法を開発者に提供することです。かつてのXML開発者は、一般的には初期設定ファイル(.ini)、データベース、または独自の構成形式を使用していました。現在の開発者は、構成インターフェイスを実装し、それに応じて構成ハンドラを記述するだけで済みます。
.NET構成システムには、.NET構成設定にプログラム的にアクセスするためのクラスがいくつか用意されています。System.Configuration.ConfigurationSettings
は、開発者が構成ファイルにアクセスするためのクラスです。このクラスのインターフェイスは非常に単純です。AppSettings
プロパティは<AppSettings>
構成セクションを公開するので、開発者はキー/値の形式に基づいて設定を取得することができます。このクラスには、GetConfig(sectionName)
というメソッドも含まれています。開発者はこのメソッドを使用して、特定のセクションの構成設定を要求できます。さらに、構成フレームワークを使用して、独自の構成セクションとハンドラを作成することもできます。構成設定は、System.Configuration.IConfigurationSectionHandler
インターフェイスを実装しているセクションハンドラによって作成されます。このインターフェイスはCreate
メソッドを公開しています。開発者はこのCreate
メソッドを利用して、特定セクションのXMLだけを設定オブジェクトに変換することができます。このメソッドにより、開発者は構成ファイルの所在、構成ファイル内での特定セクションの解析、ファイルのオープン/クローズといった詳細を気にせずに済みます。Create
メソッドから返されたオブジェクトは、適切な設定オブジェクトに変換またはキャストできます。
構成セクションハンドラの作成
開発を行っていると、実行時に構成システムから値を取得しなければならない場面にたびたび遭遇します。私の所属チームが最近作成したいくつかのWebアプリケーションは、すべてのページを1つのクラスから継承して作成するという方法をとっていました。このクラス内では、認証(authentication)、許可(authorization)、ページレイアウトを扱っていました。それぞれのアプリケーションの見た目はCSSで変えていましたが、使用しているHTMLコードは同じでした。我々はこのしくみを実現するために、スタイルシートを関連付けたオブジェクトをいくつか作成し、それを利用して、ページに組み込むスタイルシートを動的に設定していました。本稿ではこの例を使用して、構成ハンドラのさまざまな利用方法を具体的に説明していきたいと思います。
概要
基本ページクラスでは、ページに組み込むスタイルシートを構成ファイルで変更できるようにしたいと考えました。これを実現する手段として、StyleSheet
クラス、StyleSheetCollection
クラス、StyleSheetSetting
クラスを作成しました。StyleSheet
クラスには、Name
、Href
、Media
という3つの単純な文字列プロパティを用意しました。StyleSheetCollection
クラスはCollectionBase
から継承して作成し、複数のStyleSheet
オブジェクトを使い慣れた方法で格納できるようにしました。StyleSheetSetting
クラスには、StyleSheets
というStyleSheetCollection
型プロパティを用意しました。
StyleSheetSetting
クラスは、構成ファイル内のセクションを表すオブジェクト表現です。
セクションハンドラの実行
たいていの開発者は、構成セクションハンドラを特定の構成セクションごとに別々に記述しようと考えます(私もそうでした)。しかし、IConfigurationSectionHandler
インターフェイスの実装パターンを一度理解してしまえば、ハンドラの作成はお決まりの退屈な作業になります。ここでは、構成セクションハンドラの基本的な作成方法を具体的に示します。リスト1は、今回のサンプルの構成ファイルのソースコードです。簡潔にするために、これ以外の構成セクションは省略してあります。この構成ファイルでは、StyleSheetSettings_1
というセクションを作成し、それにBasicConfigurator
という型を割り当てています。これにより、System.Configuration.ConfigurationSettings.GetConfig("StyleSheetSettings_1")
が呼び出されたらいつでもBasicConfigurator.Create
メソッドを実行するよう.NET構成システムに指示しています。
<?xmlversion="1.0"encoding="utf-8"?> <configuration> <configSections> <sectionname="StyleSheetSettings_1" type="FifteenSeconds.Core.BasicConfigurator, FifteenSeconds.Core"/> </configSections> <StyleSheetSettings_1> <StyleSheets> <StyleSheetName="Page"Href="Styles/Page.css"Media="screen"/> <StyleSheetName="Custom"Href="Styles/Custom.css"Media="screen"/> <StyleSheetName="Print"Href="/Lib/Styles/Print.css"Media="print"/> </StyleSheets> </StyleSheetSettings_1> </configuration>
BasicConfiguration
ハンドラ内にCreate
メソッドを実装します。このメソッドのソースコードをリスト2に示します。このメソッドは、StyleSheetSettings_1
構成セクションの扱い方を.NET構成システムに教えるためのものです。まずStyleSheetSetting_1
オブジェクトを作成します。これが、このメソッドが返す設定オブジェクトになります。次にXmlNodeList
を作成し、XPath
クエリを使用してセクション内のすべての<StyleSheet>
ノードを選択します。このリスト内のすべてのノードを反復処理し、対応するStyleSheet
オブジェクトを作成します。このオブジェクトのプロパティを、セクションXML内の属性に基づいて割り当てます。その後、これらのStyleSheet
オブジェクトを、設定クラスのStyleSheetCollection
型のStyleSheets
プロパティに追加します。
この方法でも問題はありませんし、たいていの開発者はこの方法で満足するでしょう。この方法では、開発者がセクションごと、または独自に作成したオブジェクトモデルごとに新しい構成セクションハンドラを作成する必要があります。次の例では、どんな種類のオブジェクトモデルに対しても再使用できる構成セクションハンドラの記述方法を紹介します。
public object Create(object parent, object configContext, System.Xml.XmlNode section) { StyleSheetSettings_1 settings = null; if (section == null) { return settings; } settings = new StyleSheetSettings_1(); XmlNodeList styleSheets = section.SelectNodes(@"/StyleSheets/StyleSheet"); for (int i = 0; i < styleSheets.Count; i++) { XmlNode node = styleSheets.Item(i); StyleSheet styleSheet = new StyleSheet(); styleSheet.Name = node.Attributes["Name"].InnerText; styleSheet.Href = node.Attributes["Href"].InnerText; styleSheet.Media = node.Attributes["Media"].InnerText; settings.StyleSheets.Add(styleSheet); } return settings; }
XmlSerialzerConfigurationセクションハンドラ
我々はWebアプリケーションを開発する過程で、いくつかの構成セクションハンドラを記述しました。さらに、構成ファイル内の個々のセクションに対してハンドラを書かずに済むようにするために、これらのハンドラを抽象化する方法を研究しました。その中で偶然、Craig Anderaのすばらしい記事(リンク切れ)を見つけました。Craigの記事では、XmlSerialization
を使用してこの問題を解決するという方法が紹介されていました。リスト3は、その記事で扱っていた構成ファイルのコードの抜粋です。リスト4は、Craigが書いたソースコードです。
<configuration> <configSections> <section name="StyleSheetSettings_1" type="FifteenSeconds.Core.XmlConfigurator, FifteenSeconds.Core"/> </configSections> <StyleSheetSettings_1 configuratorType="FifteenSeconds.Core.StyleSheetSettings_1, FifteenSeconds.Core"> <StyleSheets> <StyleSheet Name="Page" Href="/Lib/Styles/Page.css" Media="screen"/> <StyleSheet Name="Custom" Href="/Lib/Styles/Custom.css" Media="screen"/> <StyleSheet Name="Print" Href="/Lib/Styles/Print.css" Media="print"/> </StyleSheets> </StyleSheetSettings_1>
public object Create(object parent, object configContext, System.Xml.XmlNode section) { Object settings = null; if (section == null) { return settings; } XPathNavigator navigator = section.CreateNavigator(); String typeName = (string)navigator.Evaluate("string(@configuratorType)"); Type sectionType = Type.GetType(typeName); XmlSerializer xs = new XmlSerializer(sectionType); XmlNodeReader reader = new XmlNodeReader(section); settings = xs.Deserialize(reader); return settings; }
構成ファイルのコードが前述の例とよく似ていることに気付くでしょう。唯一の違いはセクションにあります。この例では、configuratorType
という属性が追加されています。これにより、XmlConfigurator
は作成する設定オブジェクトの型を実行時に判断できるようになります。XmlConfigurator.Create
メソッドには、XMLを適切な設定オブジェクトにデシリアライズするコードが含まれています。このメソッドは、まずXmlNode
セクションからXPathNavigator
を作成します。次に、navigator
オブジェクトに対してEvaluate
メソッドを使用し、適切な型名を取得します。その後、Type.GetType
静的メソッドを使用して、その型への参照を作成します。次にXmlSerializer
を作成しますが、このとき、コンストラクタにセクションの設定の型を渡します。その後、セクションXMLから作成したXmlNodeReader
をXmlSerializer.Deserialize
メソッドに渡します。このメソッドは、作成しようとする設定オブジェクトに対応するオブジェクトを返します。Craigのコードには含まれていませんが、Craigの記事では、XMLのSerialization
属性を使用して設定オブジェクトのデシリアライズを制御するという手法についても触れられています。リスト5は、StyleSheet
クラスのソースコードです。話を簡単にするために、ここでは2つのプロパティだけを含めています。StyleSheet
クラスのプロパティに対応する属性を含んだStyleSheet
要素で書式設定されているXMLを使用するためには、これらのパブリックプロパティを属性で修飾する必要があります。
[XmlAttribute(DataType="string", AttributeName="Name")] public string Name { get{return _Name;} set{_Name = value;} } [XmlAttribute()] public string Href { get{return _Href;} set{_Href = value;} }
上記のコードでは、プロパティを修飾する2とおりの手法を紹介しています。1つ目の例では、属性の中でDataType
プロパティとAttributeName
プロパティを明示的に割り当てています。これらのプロパティを省略することもできます。その場合、XmlSerialization
は、これらのプロパティをシリアライズすべきものと同じ名前およびデータ型であると見なします。別の名前またはデータ型でプロパティをシリアライズしたい場合は、明示的に指定する方法が役立ちます。
この構成セクションハンドラはオブジェクトモデルの構造に関係なく再使用できるということを証明するために、別のクラスセットを作成してみました。この例では、Server
、Database
、Security
という3つの文字列プロパティを持つDataConnection
クラスを作成しました。パブリックプロパティステートメントではXmlAttribute
を省略しました。これにより、各プロパティを構成ファイル内の要素として入力することが必要になります。リスト6に、構成XMLのソースを示します。さらに、Connection
というDataConnection
型プロパティを持つDataAccessSettings
クラスを作成しました。
<DataAccessSettingstype="FifteenSeconds.Core.DataAccessSettings, FifteenSeconds.Core"> <Connection> <Server>(local);</Server> <Database>FifteenSeconds;</Database> <Security>Integrated Security=SSPI;</Security> </Connection> </DataAccessSettings>
これが、Connection
プロパティを持つ設定オブジェクトになります。
まとめ
本稿では、.NET構成システムの利便性を具体的に紹介しました。.NETのXmlSerialization
機能を利用すると、コードを効率的に再使用することができます。Craig Anderaも述べていたとおり、構成セクションハンドラを記述するのは一度きりにしたいものです。本稿のサンプルコードには、独立したクラスライブラリを持つコンソールアプリケーションのソースコードが含まれています。