2010-12-02 11 views
25

私はこのようConfigクラスを持っている:データベース内のXMLから構成セクションを読み取る方法は?

public class MyConfig : ConfigurationSection 
{ 
     [ConfigurationProperty("MyProperty", IsRequired = true)] 
     public string MyProperty 
     { 
      get { return (string)this["MyProperty"]; } 
      set { this["MyProperty"] = value; } 
     } 
} 

そして、我々はいくつかの変更を行っているし、今DBなどで設定ファイルを格納している。この

(MyConfig)ConfigurationManager.GetSection("myConfig") 

のような別のクラスでインスタンス化されていますこれは設定ファイルにあります。

下位互換性のためにMyConfigをConfigurationSectionとして維持したいのですが、DBから取得したXML文字列を使用してインスタンス化することもできます。

可能ですか?もしそうなら、どうですか? (上に例示したように動作するはずです)

+4

私は**これを解決するために**愛しています - 私はしばらくの間、多くの成功なしに、残念ながら研究してきました.... –

+1

私は私が一人ではないことを嬉しく思います。 –

+0

アプリケーションコードによって構成ファイルの場所を変更できないように思われるため、この回答はわかりません。しかし、私はこのシナリオで構成ファイルをオーバーロードせず、DBからデータを取得する独自の構成リーダーを作成します。 – akonsu

答えて

3

私の提案は、現在のMyConfigクラスを維持するが、コンストラクタ内のデータベースからXMLをロードし、次にMyConfigの各プロパティで、値を取得する場所を決定するロジックを置くことができます.configファイル)のいずれかの場所から設定をプルする必要がある場合は、値が空の場合は、それをフォールバックする必要があります。ただのMyConfigクラスにこれらのメンバーを追加します:

public class MyConfig : ConfigurationSection 
    { 
     private static MyConfig _current; 
     public static MyConfig Current 
     { 
      get 
      { 
       if (_current == null) 
       { 
        switch(ConfigurationStorageType) // where do you want read config from? 
        { 
         case ConfigFile: // from .config file 
          _current = ConfigurationManager.GetSection("MySectionName") as MyConfig; 
          break; 

         case ConfigDb: // from database 
         default: 
          using (Stream stream = GetMyStreamFromDb()) 
          { 
           using (XmlTextReader reader = new XmlTextReader(stream)) 
           { 
            _current = Get(reader); 
           } 
          } 
          break; 


        } 
       } 
       return _current; 
      } 
     } 

     public static MyConfig Get(XmlReader reader) 
     { 
      if (reader == null) 
       throw new ArgumentNullException("reader"); 

      MyConfig section = new MyConfig(); 
      section.DeserializeSection(reader); 
      return section; 
     } 
    } 

この方法では、あなたがのMyConfigクラスに変更することは何もありませんが、あなたはまだ変更する必要がある。ここ

public class MyConfig : ConfigurationSection 
{ 
    public MyConfig() 
    { 
     // throw some code in here to retrieve your XML from your database 
     // deserialize your XML and store it 
     _myProperty = "<deserialized value from db>"; 
    } 

    private string _myProperty = string.Empty; 

    [ConfigurationProperty("MyProperty", IsRequired = true)] 
    public string MyProperty 
    { 
     get 
     { 
      if (_myProperty != null && _myProperty.Length > 0) 
       return _myProperty; 
      else 
       return (string)this["MyProperty"]; 
     } 
     set { this["MyProperty"] = value; } 
    } 
} 
+0

これは私が取得したルートです –

3

これは難しい問題です。いくつかのリファクタリングが必要になるかもしれませんが、あなたは同じプロパティなどを公開することができます(ただし、あなたの設定を変更することはできません)。それは少しWTFですが、おそらく実行可能です。

本質的には、how to do this with LINQ to XML in this blog postと記載します。すべてのコードで、ConfigurationSectionに依存するクラスはありません。ブログの記事で説明した手法を使用して、それをバイパスし、インターフェイスを通じてPOCOを返します。これは私のコードをユニット単位でテストできるようにしました。インターフェイスにスタブを簡単に使用できるようになりました。

設定をDBに簡単に移動することもできます。設定インターフェースを実装している新しいクラスを作成し、IoC設定で切り替えるだけです。マイクロソフトでは、構成システムの柔軟性を考慮して設計していないため、独自のコードで使用する際に考慮する必要があります。

私が考えることができる唯一の他の方法は、ファイルにDB設定を書き込んだり、それを読み込んだりすることですが、それも変です!

14

は、私は通常、それを行う方法でありますあなたがに保存されている任意のSystem.Configuration.ConfigurationSectionを取得する必要がある場合は

string myProp = MyConfig.Current.MyProperty; 
5

:あなたの顧客は、この種のコードでそれにアクセスする方法データベースには、このような一般的なセクションリーダーを書いて検討することができる:

 
    
public class ConfigurationSectionReader where T : ConfigurationSection, new() 
{ 
    public T GetSection(string sectionXml) 
    { 
     T section = new T(); 
     using (StringReader stringReader = new StringReader(sectionXml)) 
     using (XmlReader reader = XmlReader.Create(stringReader, new XmlReaderSettings() { CloseInput = true })) 
     { 
      reader.Read(); 
      section.GetType().GetMethod("DeserializeElement", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(section, new object[] { reader, true }); 
     } 
     return section; 
    } 
} 
 

これはDeserializeElementメソッドをオーバーライドし、すべてのクラスのために動作します。例えば

 
    
var reader = new ConfigurationSectionReader(); 
var section = reader.GetSection(sectionXml); // where sectionXml is the XML string retrieved from the DB 
 
+1

これはプライベートメンバーに依存しているため悪いです。したがって、Monoや.NETの次のバージョンでは動作しないかもしれません。 – knocte

+0

これは恒久的なソリューションとして使用されている場合には理想的ではありませんが、 System.Configurationを使用すると、完全に適切なIMOです。物理的なファイルや環境の変換を必要としないリファクタリングを行うために、厄介なレガシーコードで同様の手法を使用しました。私がSystem.Configurationに依存することを避けるために、警告を与えてください。 – killthrush

3

むしろ古い質問が、ちょうどこれに対する解決策で遊ん:あなたはこのようなセクションを得ることができたより

 
    
protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey) 
{ 
    XmlDocument document = new XmlDocument(); 
    document.LoadXml(reader.ReadOuterXml()); 
    MyProperty = document.DocumentElement.HasAttribute("MyProperty") 
     ? document.DocumentElement.Attributes[ "MyProperty" ].Value 
     : string.Empty; 
} 
 

。これはSimon Mourierのアプローチと似ていますが(これはいくつかの点でよりうまくいく - よりハッキリではありませんが)、System.Configuration.ConfigurationManager.GetSection()を呼び出すコードは、静的メソッドを使用するように変更しなくても機能するため、全体的にコードの変更が少なくなります。

最初の基本的な注意点は、これがネストされたセクションで機能するかどうかはわかりませんが、私はほとんどそれがないと確信しています。ほとんどの主な注意点は、configセクションのクラスを変更する必要があるため、ソースを持つカスタムセクションでのみ使用できることです(変更が許可されています)。

第2およびMAIN CAVEAT私はちょうどこれを使って遊んでいるのですが、私はそれを開発で使っていないし、生産でもないし、単にこのような基本的な機能を自分のコードに塗りつぶすだけで、ノックオン効果が現れないかもしれない私の例。 自己責任において使用してください

EDITを(と言った、私が起こっている他のconfigセクションの負荷となるようUmbracoサイトでそれをテストしていて、彼らはまだすべての作業なので、私はそれがありませんすぐにひどい影響があると思います):これは元の質問ごとに3.5ではなく.NET 4です。その違いが違いを生むかどうかは分かりません。

コードは非常に単純ですが、DeserializeSectionをオーバーライドして、データベースから読み込むXMLリーダーを使用してください。

public class TestSettings : ConfigurationSection 
{ 
    protected override void DeserializeSection(System.Xml.XmlReader reader) 
    { 
     using (DbConnection conn = /* Get an open database connection from whatever provider you are using */) 
     { 
      DbCommand cmd = conn.CreateCommand(); 

      cmd.CommandText = "select ConfigFileContent from Configuration where ConfigFileName = @ConfigFileName"; 

      DbParameter p = cmd.CreateParameter(); 
      p.ParameterName = "@ConfigFileName"; 
      p.Value = "TestSettings.config"; 

      cmd.Parameters.Add(p); 

      String xml = (String)cmd.ExecuteScalar(); 

      using(System.IO.StringReader sr = new System.IO.StringReader(xml)) 
      using (System.Xml.XmlReader xr = System.Xml.XmlReader.Create(sr)) 
      { 
       base.DeserializeSection(xr); 
      }     
     }    
    } 

    // Below is all your normal existing section code 

    [ConfigurationProperty("General")] 
    public GeneralElement General { get { return (GeneralElement)base["General"]; } } 

    [ConfigurationProperty("UI")] 
    public UIElement UI { get { return (UIElement)base["UI"]; } } 

    ... 

    ... 
} 

私はあなたがweb.configが必要ですので、それを動作させるために、ASP.Netを使用していますが、その後ねえ、私はとにかく、接続文字列のどこかで必要とするか、または私がデータベースに接続するつもりはありませんすべて。

カスタムセクションは通常のように<configSections/>に定義する必要があります。この作業を行う上での鍵は、通常の設定の代わりに空の要素を置くことです。すなわち<TestSettings configSource="..."/>またはインライン設定の代わりに、単に<TestSettings/>

を入れ、構成マネージャは、その後、すべてのセクションをロードし、既存の<TestSettings/>要素を参照してください、そして、それをデシリアライズ、それはあなたのオーバーライドをヒットし、データベースからXMLをロードし、その時点でます代わりに。

:デシリアライズでは、文書全体ではなく、文書の断片(ノードが既にノードにあるときに呼び出されることが予想される)が必要であるため、セクションが別々のファイルに格納されている場合は、最初に<?xml ?>宣言、またはExpected to find an elementが得られます。

関連する問題