views:

33

answers:

1

hi,

I want to implement Craig Andera's custom XML configuration handler in a slightly different scenario. What I want to be able to do is to read in a list of arbitrary length of custom objects defined as:

public class TextFileInfo
{
    public string Name { get; set; }
    public string TextFilePath { get; set; }
    public string XmlFilePath { get; set; }
}

I managed to replicate Craig's solution for one custom object but what if I want several?

Craig's deserialization code is:

public class XmlSerializerSectionHandler : IConfigurationSectionHandler
{
    public object Create(object parent, object configContext, XmlNode section)
    {
        XPathNavigator nav = section.CreateNavigator();
        string typename = (string)nav.Evaluate("string(@type)");
        Type t = Type.GetType(typename);
        XmlSerializer ser = new XmlSerializer(t);
        return ser.Deserialize(new XmlNodeReader(section));
    }
}

I think I could do this if I could get

Type t = Type.GetType("System.Collections.Generic.List<TextFileInfo>")

to work but it throws

Could not load type 'System.Collections.Generic.List<Test1.TextFileInfo>' from assembly 'Test1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
+2  A: 

I don't think this would work in that scenario. Craig's solution works well for simple object graphs, but collections are a little trickier. Lists are serialised as arrays, so putting your example in, a serialised List is something like:

<ArrayOfTextFileInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlsns:xsd="http://www.w3.org/2001/XMLSchema&gt;
  <TextFileInfo>
    <Name>My Text File</Name>
    <TextFilePath>C:\MyTextFile.txt</TextFilePath>
    <XmlFilePath>C:\MyXmlFile.xml</XmlFilePath>
  </TextFileInfo>
</ArrayOfTextFileInfo>

Now, I'm guessing you could probably put that in a config, as long as the config section is named as "ArrayOfTextFileInfo". Not exactly that friendly. I think what you should probably do is use the standard configuration classes to build this:

public class TextFileConfigurationElement : ConfigurationElement
{
  [ConfigurationProperty("name", IsRequired = true, IsKey = true)]
  public string Name { 
    get { return (string)this["name"]; }
    set { this["name"] = value; }
  }

  [ConfigurationProperty("textFilePath")]
  public string TextFilePath {
    get { return (string)this["textFilePath"]; }
    set { this["textFilePath"] = value; }
  }

  [ConfigurationProperty("xmlFilePath")]
  public string XmlFilePath {
    get { return (string)this["xmlFilePath"]; }
    set { this["xmlFilePath"] = value; }
  }
}

[ConfigurationCollection(typeof(TextFileConfigurationElement))]
public class TextFileConfigurationElementCollection : ConfigurationElementCollection
{
  protected override void CreateNewElement() {
    return new TextFileConfigurationElement();
  }

  protected override object GetElementKey(ConfigurationElement element) {
    return ((TextFileConfigurationElement)element).Name;
  }
}

public class TextFilesConfigurationSection : ConfigurationSection
{
  [ConfigurationProperty("files")]
  public TextFileConfigurationElementCollection Files {
    get { return (TextFileConfigurationElementCollection)this["files"]; }
    set { this["files"] = value; }
  }

  public static TextFilesConfigurationSection GetInstance() {
    return ConfigurationManager.GetSection("textFiles") as TextFilesConfigurationSection;
  }
}

Once you've registered the config section:

<configSections>
  <add name="textFiles" type="...{type here}..." />
</configSections>

You can add in the configs:

<textFiles>
  <files>
    <add name="File01" textFilePath="C:\File01.txt" xmlTextFile="C:\File01.xml" />
  </files>
</textFiles>

Using that in code:

public List<TextFileInfo> GetFiles() {
  var list = new List<TextFileInfo>();

  var config = TextFileConfigurationSection.GetInstance();
  if (config == null)
    return list;

  foreach (TextFileConfigurationElement fileConfig in config.Files) {
    list.Add(new TextFileInfo 
                        {
                          Name = fileConfig.Name,
                          TextFilePath = fileConfig.TextFilePath,
                          XmlFilePath = fileConfig.XmlFilePath
                         });

  }

  return list;
}

Also, this:

Type t = Type.GetType("System.Collections.Generic.List<TextFileInfo>")

Won't work for a couple of reasons, you haven't fully qualified the TextFileInfo type (needs a namespace), and your definition of a generic type is wrong (I can see why you haven't specified it that way), it should look like:

Type t = Type.GetType("System.Collections.Generic.List`1[MyNamespace.TextFileInfo]");

Hope that helps!

Matthew Abbott