I've implemented a XmlReader class inheritor. My new class provides an xml data such as:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<child xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="" />
<child xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="" />
</root>
The problem is that one namespace declaration is duplicated multiple times. This namespace declarations is caused because when the reader reached "type" attribute its properties have the following values: LocalName = "type", Prefix = "xsi" and NamespaceURI = "http://www.w3.org/2001/XMLSchema-instance". I want to prevent this duplications by declaring napespace in "root" node. Is it possible?
Here is example of my code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.IO;
namespace Test
{
internal class Node
{
public string LocalName;
public string Prefix;
public string NamespaceURI;
public string Value;
public XmlNodeType NodeType;
}
public class MyXmlReader : XmlReader
{
private readonly Queue<Node> _data;
private string _localName;
private string _prefix;
private string _namespaceURI;
private string _value;
private XmlNodeType _nodeType;
private ReadState _readState;
public MyXmlReader()
{
_data = new Queue<Node>();
_data.Enqueue(new Node { LocalName = "root", NodeType = XmlNodeType.Element });
_data.Enqueue(new Node { LocalName = "child", NodeType = XmlNodeType.Element });
_data.Enqueue(new Node
{
LocalName = "type",
Prefix = "xsi",
NamespaceURI = "http://www.w3.org/2001/XMLSchema-instance",
NodeType = XmlNodeType.Attribute
});
_data.Enqueue(new Node { LocalName = "child", NodeType = XmlNodeType.Element });
_data.Enqueue(new Node
{
LocalName = "type",
Prefix = "xsi",
NamespaceURI = "http://www.w3.org/2001/XMLSchema-instance",
NodeType = XmlNodeType.Attribute
});
_data.Enqueue(new Node { NodeType = XmlNodeType.EndElement });
_readState = ReadState.Initial;
}
public override bool Read()
{
if (_data.Count > 0)
{
Node node = _data.Dequeue();
_localName = node.LocalName;
_prefix = node.Prefix;
_namespaceURI = node.NamespaceURI;
_value = node.Value;
_nodeType = node.NodeType;
return true;
}
return false;
}
public override void Close()
{
_readState = ReadState.Closed;
}
public override bool MoveToFirstAttribute()
{
return MoveToNextAttribute();
}
public override bool MoveToNextAttribute()
{
if (_data.Peek().NodeType == XmlNodeType.Attribute)
return Read();
return false;
}
public override bool MoveToElement()
{
_nodeType = XmlNodeType.Element;
_localName = "child";
return true;
}
public override bool ReadAttributeValue()
{
return false;
}
public override XmlNodeType NodeType
{
get { return _nodeType; }
}
public override string LocalName
{
get { return _localName; }
}
public override string NamespaceURI
{
get { return _namespaceURI; }
}
public override string Prefix
{
get { return _prefix; }
}
public override string Value
{
get { return _value; }
}
public override string BaseURI
{
get { return "Test"; }
}
public override bool IsEmptyElement
{
get
{
if (_localName == "root")
return false;
return true;
}
}
public override ReadState ReadState
{
get { return _readState; }
}
public override bool EOF
{
get { throw new NotImplementedException(); }
}
public override int Depth
{
get { throw new NotImplementedException(); }
}
public override string GetAttribute(string name)
{
throw new NotImplementedException();
}
public override string GetAttribute(string name, string namespaceURI)
{
throw new NotImplementedException();
}
public override string GetAttribute(int i)
{
throw new NotImplementedException();
}
public override bool MoveToAttribute(string name)
{
throw new NotImplementedException();
}
public override bool MoveToAttribute(string name, string ns)
{
throw new NotImplementedException();
}
public override string LookupNamespace(string prefix)
{
throw new NotImplementedException();
}
public override void ResolveEntity()
{
throw new NotImplementedException();
}
public override int AttributeCount
{
get { throw new NotImplementedException(); }
}
public override XmlNameTable NameTable
{
get { throw new NotImplementedException(); }
}
}
public class Program
{
public static void Main()
{
XmlDocument doc = new XmlDocument();
doc.Load(new MyXmlReader());
return;
}
}
}
I know some nasty and tricky approach: generate some fake attribute for root in xsi namespace. Then xml data will looks like
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:fake="fake">
But I also have XSD validation and such approach is not situable for me (there is no definition of "fake" attr)
Related
I have
properties that have ids and values and a name. Can I represent all those with a single class using XmlElement/XmlArray C# annotations? I would like to derive the xml element name from the class attribute name;
my class would look like:
public class Property {
public string name; //could be enum
public int id;
public string value;
}
e.g:
new Property("property1name",2,"testvalue");
new Property("property2name",10,"anothervalue");
I would like to have xml that looks like:
<property1name><id>2</id><value>testvalue</value></property1name>
<property2name><id>10</id><value>anothervalue</value></property2name>
instead of the usual
<property><name>property1name</name><id>2</id><value>testvalue</value></property>
<property><name>property2name</name><id>10</id><value>anothervalue</value></property>
In other words the xmlelement gets its name from attribute name of the class Property
Update
And here's a quick adaptation to handle your Property class. First, a List<T> subclass that implements IXmlSerializable:
public interface IHasElementName
{
string ElementName { get; set; }
}
public class XmlNamedElementList<T> : List<T>, IXmlSerializable where T : IHasXmlElementName
{
// https://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
// Any serializer created with the "XmlSerializer(typeof(T), rootAttribute)" must be cached
// to avoid resource & memory leaks.
class ValueSerializerCache
{
// By using a nested class with a static constructor, we defer generation of the XmlSerializer until it's actually required.
static ValueSerializerCache()
{
var rootAttribute = new XmlRootAttribute();
rootAttribute.ElementName = ValueTypeName;
rootAttribute.Namespace = ValueTypeNamespace;
serializer = new XmlSerializer(typeof(T), rootAttribute);
}
static readonly XmlSerializer serializer;
internal static XmlSerializer Serializer { get { return serializer; } }
}
static string ValueTypeName { get { return typeof(T).DefaultXmlElementName(); } }
static string ValueTypeNamespace { get { return typeof(T).DefaultXmlElementNamespace(); } }
#region IXmlSerializable Members
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
var typeName = ValueTypeName;
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var name = reader.Name;
using (var subReader = reader.ReadSubtree())
{
var doc = XDocument.Load(subReader);
if (doc != null && doc.Root != null)
{
doc.Root.Name = doc.Root.Name.Namespace + typeName;
using (var docReader = doc.CreateReader())
{
var obj = ValueSerializerCache.Serializer.Deserialize(docReader);
if (obj != null)
{
T value = (T)obj;
value.ElementName = XmlConvert.DecodeName(name);
Add(value);
}
}
}
}
// Move past the end of item element
reader.Read();
}
// Move past the end of the list element
reader.ReadEndElement();
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
foreach (var value in this)
{
XDocument doc = new XDocument();
using (var subWriter = doc.CreateWriter())
{
// write xml into the writer
ValueSerializerCache.Serializer.Serialize(subWriter, value);
}
if (doc.Root == null)
continue;
doc.Root.Name = doc.Root.Name.Namespace + XmlConvert.EncodeName(value.ElementName);
// Remove redundant namespaces.
foreach (var attr in doc.Root.Attributes().ToList())
{
if (!attr.IsNamespaceDeclaration || string.IsNullOrEmpty(attr.Value))
continue;
var prefix = writer.LookupPrefix(attr.Value);
if ((prefix == attr.Name.LocalName)
|| (prefix == string.Empty && attr.Name == "xmlns"))
attr.Remove();
}
doc.Root.WriteTo(writer);
}
}
#endregion
}
public static class XmlSerializationHelper
{
static Attribute GetCustomAttribute(MemberInfo element, Type attributeType)
{
return Attribute.GetCustomAttribute(element, attributeType);
}
static T GetCustomAttribute<T>(MemberInfo element) where T : Attribute
{
return (T)GetCustomAttribute(element, typeof(T));
}
public static string DefaultXmlElementName(this Type type)
{
var xmlType = GetCustomAttribute<XmlTypeAttribute>(type);
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
var xmlRoot = GetCustomAttribute<XmlRootAttribute>(type);
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
return xmlRoot.ElementName;
return type.Name;
}
public static string DefaultXmlElementNamespace(this Type type)
{
var xmlType = GetCustomAttribute<XmlTypeAttribute>(type);
if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
return xmlType.Namespace;
var xmlRoot = GetCustomAttribute<XmlRootAttribute>(type);
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.Namespace))
return xmlRoot.Namespace;
return string.Empty;
}
public static string GetXml<T>(this T obj)
{
return GetXml(obj, false);
}
public static string GetXml<T>(this T obj, bool omitNamespace)
{
return GetXml(obj, new XmlSerializer(obj.GetType()), omitNamespace);
}
public static string GetXml<T>(this T obj, XmlSerializer serializer)
{
return GetXml(obj, serializer, false);
}
public static string GetXml<T>(T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
{
ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
}
return GetXml(obj, serializer, ns);
}
public static string GetXml<T>(T obj, XmlSerializerNamespaces ns)
{
return GetXml(obj, new XmlSerializer(obj.GetType()), ns);
}
public static string GetXml<T>(T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
{
using (var textWriter = new StringWriter())
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true; // For cosmetic purposes.
settings.IndentChars = " "; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
if (ns != null)
serializer.Serialize(xmlWriter, obj, ns);
else
serializer.Serialize(xmlWriter, obj);
}
return textWriter.ToString();
}
}
}
And use it like:
public class Property : IHasElementName
{
public Property()
{
}
public Property(string name, int id, string value)
{
this.name = name;
this.id = id;
this.value = value;
}
[XmlIgnore]
public string name; //could be enum
public int id;
public string value;
#region IHasElementName Members
[XmlIgnore]
string IHasElementName.ElementName { get { return name; } set { name = value; } }
#endregion
}
public class RootObject
{
public RootObject()
{
this.Properties = new XmlNamedElementList<Property>();
}
public XmlNamedElementList<Property> Properties { get; set; }
}
public static class TestClass
{
public static void Test()
{
var root = new RootObject
{
// Characters " <> first" in the first element name are for testing purposes.
Properties = new XmlNamedElementList<Property> { new Property { id = 1, value = "1", name = "first" }, new Property("property1name", 2, "testvalue"), new Property("property2name", 10, "anothervalue") }
};
var xml = root.GetXml();
Debug.WriteLine(xml);
}
}
Which produces XML as follows:
<?xml version="1.0" encoding="utf-16"?>
<RootObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Properties>
<_x0020__x003C__x003E__x0020_first>
<id>1</id>
<value>1</value>
</_x0020__x003C__x003E__x0020_first>
<property1name>
<id>2</id>
<value>testvalue</value>
</property1name>
<property2name>
<id>10</id>
<value>anothervalue</value>
</property2name>
</Properties>
</RootObject>
Original Answer
As requested, here's an implementation of IXmlSerializable on a List<KeyValuePair<string, T>> in which the Key string becomes the element name in the collection.
What you would probably want to do is to adapt this to serialize a List<IHasElementName> where:
public interface IHasElementName
{
string ElementName { get; set; }
}
public class Property : IHasElementName
{
[XmlIgnore]
public string name; //could be enum
public int id;
public string value;
#region IHasElementName Members
[XmlIgnore]
string IHasElementName.ElementName
{
get
{
return name;
}
set
{
name = value;
}
}
#endregion
}
If the name is actually an Enum, you could return the enum string representation from HasElementName.ElementName.
The list looks like:
public class XmlKeyValueList<T> : List<KeyValuePair<string, T>>, IXmlSerializable
{
// TODO: validate that the "Key" string using XmlConvert.VerifyName.
// https://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
// Any serializer created with the "XmlSerializer(typeof(T), rootAttribute)" must be cached
// to avoid resource & memory leaks.
class ValueSerializerCache
{
// By using a nested class with a static constructor, we defer generation of the XmlSerializer until it's actually required.
static ValueSerializerCache()
{
var rootAttribute = new XmlRootAttribute();
rootAttribute.ElementName = ValueTypeName;
rootAttribute.Namespace = ValueTypeNamespace;
serializer = new XmlSerializer(typeof(T), rootAttribute);
}
static readonly XmlSerializer serializer;
internal static XmlSerializer Serializer { get { return serializer; } }
}
static string ValueTypeName { get { return typeof(T).DefaultXmlElementName(); } }
static string ValueTypeNamespace { get { return typeof(T).DefaultXmlElementNamespace(); } }
#region IXmlSerializable Members
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
var typeName = ValueTypeName;
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var name = reader.Name;
using (var subReader = reader.ReadSubtree())
{
var doc = XDocument.Load(subReader);
if (doc != null && doc.Root != null)
{
doc.Root.Name = typeName;
using (var docReader = doc.CreateReader())
{
var obj = ValueSerializerCache.Serializer.Deserialize(docReader);
if (obj != null)
{
Add(new KeyValuePair<string, T>(name, (T)obj));
}
}
}
}
// Move past the XmlNodeType.Element
if (reader.NodeType == XmlNodeType.EndElement)
reader.Read();
}
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
foreach (var pair in this)
{
XDocument doc = new XDocument();
using (var subWriter = doc.CreateWriter())
{
// write xml into the writer
ValueSerializerCache.Serializer.Serialize(subWriter, pair.Value);
}
if (doc.Root == null)
continue;
doc.Root.Name = pair.Key;
// Remove redundant namespaces.
foreach (var attr in doc.Root.Attributes().ToList())
{
if (!attr.IsNamespaceDeclaration || string.IsNullOrEmpty(attr.Value))
continue;
if (writer.LookupPrefix(attr.Value) == attr.Name.LocalName)
attr.Remove();
}
doc.Root.WriteTo(writer);
}
}
#endregion
}
public static class XmlSerializationHelper
{
public static string DefaultXmlElementName(this Type type)
{
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
return xmlRoot.ElementName;
return type.Name;
}
public static string DefaultXmlElementNamespace(this Type type)
{
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
return xmlType.Namespace;
var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.Namespace))
return xmlRoot.Namespace;
return string.Empty;
}
}
If UnitItem is changed into
public class UnitItem
{
public string AAA;
public string BBB;
}
then the XML will be
<Items>
<UnitItem>
<AAA>testa</AAA>
<BBB>testb</BBB>
</UnitItem>
</Items>
I'm creating a custom configuration section but I keep getting a Attribute Not Recognized error when trying to get the section.
I'm pretty sure it's some dumb typo - hopefully someone here can spot it.
Code:
<configSections>
<section name="ClientFilterSettings" type="ESPDB.Config.ClientFilterSettings, ESPDB"/>
</configSections>
<ClientFilterSettings>
<AllowedIPs>
<IPAddress IP="255.255.255.255"/>
</AllowedIPs>
</ClientFilterSettings>
Section:
namespace ESPDB.Config
{
public class ClientFilterSettings : ConfigurationSection
{
private static ClientFilterSettings _settings =
ConfigurationManager.GetSection(typeof(ClientFilterSettings).Name) as ClientFilterSettings
/*?? new ClientFilterSettings()*/;
private const string _allowedIPs = "AllowedIPs";
public static ClientFilterSettings Settings
{
get { return _settings; }
}
[ConfigurationProperty(_allowedIPs, IsRequired = true)]
[ConfigurationCollection(typeof(IPAddressCollection))]
public IPAddressCollection AllowedIPs
{
get { return (IPAddressCollection)this[_allowedIPs]; }
set { this[_allowedIPs] = value; }
}
}
}
Collection:
namespace ESPDB.Config
{
public class IPAddressCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new IPAddressCollection();
}
protected override object GetElementKey(ConfigurationElement element)
{
return (element as IPAddressElement).IP;
}
protected override string ElementName
{
get { return "IPAddress"; }
}
public IPAddressElement this[int index]
{
get
{
return base.BaseGet(index) as IPAddressElement;
}
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
public new IPAddressElement this[string responseString]
{
get { return (IPAddressElement)BaseGet(responseString); }
set
{
if (BaseGet(responseString) != null)
{
BaseRemoveAt(BaseIndexOf(BaseGet(responseString)));
}
BaseAdd(value);
}
}
}
}
Element
namespace ESPDB.Config
{
public class IPAddressElement : ConfigurationElement
{
private const string _ip = "IP";
[ConfigurationProperty(_ip, IsKey = true, IsRequired = true)]
public string IP
{
get { return this[_ip] as string; }
set { this[_ip] = value; }
}
}
}
There are multiple problems in your code.
1). You are recursively creating objects of ClientFilterSettings. Remove the following code, its not required.
private static ClientFilterSettings _settings =
ConfigurationManager.GetSection(typeof(ClientFilterSettings).Name) as ClientFilterSettings /*?? new ClientFilterSettings()*/;
public static ClientFilterSettings Settings
{
get { return _settings; }
}
2). Change the attribute from
[ConfigurationCollection(typeof(IPAddressCollection))]
TO
[ConfigurationCollection(typeof(IPAddressElement), AddItemName = "IPAddress", CollectionType = ConfigurationElementCollectionType.BasicMap)]
3). You are creating collection object within a collection. Modify the code below
return new IPAddressCollection();
WITH
return new IPAddressElement();
Would like to display extracted data from xml file on property grid. XML file looks something like this:
<?xml version="1.0" encoding="utf-8" ?>
<customer>
<cust id="1">
<id>120</id>
<name>hello</name>
</cust>
<cust id="2">
<name>hello12</name>
<id>12012</id>
</cust>
</customer>
Now I want to extract data from XML on a property grid with server id as category(i.e it has to display cust id="1" in one category and cust id="2" in second category)
Try this code sample:
[TypeConverter(typeof(CustomerObjectConverter))]
public class Customer
{
internal readonly int ServerId;
public int Id { get; set; }
public string Name { get; set; }
public Customer(int serverId)
{
ServerId = serverId;
}
public override string ToString()
{
return Id + ", " + Name;
}
}
public class CustomerCollection : CollectionBase, ICustomTypeDescriptor
{
public CustomerCollection()
{
}
public CustomerCollection(IEnumerable<Customer> collection)
{
foreach (var item in collection)
Add(item);
}
public void Add(Customer emp)
{
List.Add(emp);
}
public void Remove(Customer emp)
{
List.Remove(emp);
}
public Customer this[int index]
{
get { return (Customer)List[index]; }
}
public String GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public String GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
public PropertyDescriptorCollection GetProperties()
{
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);
for (int i = 0; i < List.Count; i++)
{
CustomersCollectionPropertyDescriptor pd = new CustomersCollectionPropertyDescriptor(this, i);
pds.Add(pd);
}
return pds;
}
}
internal class CustomersCollectionPropertyDescriptor : PropertyDescriptor
{
private readonly CustomerCollection collection;
private readonly int index = -1;
public CustomersCollectionPropertyDescriptor(CustomerCollection coll, int idx)
: base("#" + idx.ToString(), null)
{
collection = coll;
index = idx;
}
public override AttributeCollection Attributes
{
get { return new AttributeCollection(null); }
}
public override string Category
{
get { return "Server " + collection[index].ServerId; }
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return collection.GetType(); }
}
public override string DisplayName
{
get { return "Customer " + (index + 1); }
}
public override string Description
{
get { return "Customer"; }
}
public override object GetValue(object component)
{
return collection[index];
}
public override bool IsReadOnly
{
get { return true; }
}
public override string Name
{
get { return "#" + index.ToString(); }
}
public override Type PropertyType
{
get { return collection[index].GetType(); }
}
public override void ResetValue(object component) {}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override void SetValue(object component, object value)
{
}
}
internal class CustomerObjectConverter : ExpandableObjectConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return false;
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
return destinationType == typeof(string) && value is Customer
? value.ToString()
: base.ConvertTo(context, culture, value, destinationType);
}
}
Usage. Paste the following code into constructor of the form containing PropertyGrid with the name propertyGrid:
const string xmlString = "<customer><cust id=\"1\"><id>120</id><name>hello</name></cust><cust id=\"2\"><name>hello12</name><id>12012</id></cust></customer>";
XmlDocument xml = new XmlDocument();
xml.LoadXml(xmlString);
XmlNode root = xml["customer"];
CustomerCollection customers = new CustomerCollection();
foreach (XmlNode node in root.ChildNodes)
customers.Add(new Customer(int.Parse(node.Attributes["id"].Value))
{
Id = int.Parse(node["id"].InnerText),
Name = node["name"].InnerText
});
propertyGrid.SelectedObject = customers;
I'm having trouble getting a custom config section to work. It's some code I got from the web in an effort to try to understand this area a little better and enable me to get to where I want to ultimatly be, my own custom config section.
The error I get when I run the code in a console app is
'
Unrecognized attribute 'extension'. Note that attribute names are case-sensitive.'
The code in the main app to get things going is
var conf = ConfigurationManager.GetSection("uploadDirector");
and this is where the exception appears.
This is the config section I am hoping/trying to achieve
<AuthorisedClients>
<AuthorisedClient name="Client">
<Queue id="1" />
<Queue id="7" />
</AuthorisedClient>
<AuthorisedClient name="Client2">
<Queue id="3" />
<Queue id="4" />
</AuthorisedClient>
</AuthorisedClients>
Here's the code I have got from the web
.config file
<uploadDirector>
<filegroup name="documents" defaultDirectory="/documents/">
<clear/>
<add extension="pdf" mime="application/pdf" maxsize="100"/>
<add extension="doc" mime="application/word" maxsize="500"/>
</filegroup>
<filegroup name="images">
<clear/>
<add extension="gif" mime="image/gif" maxsize="100"/>
</filegroup>
</uploadDirector>
UploadDirectorConfigSection.cs
public class UploadDirectorConfigSection : ConfigurationSection {
private string _rootPath;
public UploadDirectorConfigSection() {
}
[ConfigurationProperty("rootpath", DefaultValue="/", IsRequired=false, IsKey=false)]
[StringValidator(InvalidCharacters=#"~!.##$%^&*()[]{};'\|\\")]
public string RootPath {
get { return _rootPath; }
set { _rootPath = value; }
}
[ConfigurationProperty("", IsRequired = true, IsKey = false, IsDefaultCollection = true)]
public FileGroupCollection FileGroups {
get { return (FileGroupCollection) base[""]; }
set { base[""] = value; }
}
}
FileGroupCollection.cs
public class FileGroupCollection : ConfigurationElementCollection {
protected override ConfigurationElement CreateNewElement() {
return new FileGroupElement();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((FileGroupElement) element).Name;
}
public override ConfigurationElementCollectionType CollectionType {
get {
return ConfigurationElementCollectionType.BasicMap;
}
}
protected override string ElementName {
get {
return "filegroup";
}
}
protected override bool IsElementName(string elementName) {
if (string.IsNullOrWhiteSpace(elementName) || elementName != "filegroup")
return false;
return true;
}
public FileGroupElement this[int index] {
get { return (FileGroupElement) BaseGet(index); }
set {
if(BaseGet(index) != null)
BaseRemoveAt(index);
BaseAdd(index, value);
}
}
}
FileGroupElement.cs
public class FileGroupElement : ConfigurationElement {
[ConfigurationProperty("name", IsKey=true, IsRequired = true)]
[StringValidator(InvalidCharacters = #" ~.!##$%^&*()[]{}/;'""|\")]
public string Name {
get { return (string) base["name"]; }
set { base["name"] = value; }
}
[ConfigurationProperty("defaultDirectory", DefaultValue = ".")]
public string DefaultDirectory {
get { return (string) base["Path"]; }
set { base["Path"] = value; }
}
[ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)]
public FileInfoCollection Files {
get { return (FileInfoCollection) base[""]; }
}
}
FileInfoCollection.cs
public class FileInfoCollection : ConfigurationElementCollection {
protected override ConfigurationElement CreateNewElement() {
return new FileInfoCollection();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((FileInfoElement) element).Extension;
}
}
FileInfoElement.cs
public class FileInfoElement : ConfigurationElement {
public FileInfoElement() {
Extension = "txt";
Mime = "text/plain";
MaxSize = 0;
}
[ConfigurationProperty("extension", IsKey = true, IsRequired = true)]
public string Extension {
get { return (string)base["extension"]; }
set { base["extension"] = value; }
}
[ConfigurationProperty("mime", DefaultValue = "text/plain")]
public string Mime {
get { return (string) base["mime"]; }
set { base["mime"] = value; }
}
[ConfigurationProperty("maxsize", DefaultValue = 0)]
public int MaxSize {
get { return (int) base["maxsize"]; }
set { base["maxsize"] = value; }
}
}
In your definition of FileInfoCollection in the method CreateNewElement you create FileInfoCollection which is wrong. Overridden CreateNewElement should return new collection element, not the new collection:
public class FileInfoCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new FileInfoElement();
}
protected override object GetElementKey (ConfigurationElement element)
{
return ((FileInfoElement)element).Extension;
}
}
Regarding your desired configuration, probably the simplest implementation will look like:
public class AuthorisedClientsSection : ConfigurationSection {
[ConfigurationProperty("", IsDefaultCollection = true)]
public AuthorisedClientElementCollection Elements {
get { return (AuthorisedClientElementCollection)base[""];}
}
}
public class AuthorisedClientElementCollection : ConfigurationElementCollection {
const string ELEMENT_NAME = "AuthorisedClient";
public override ConfigurationElementCollectionType CollectionType {
get { return ConfigurationElementCollectionType.BasicMap; }
}
protected override string ElementName {
get { return ELEMENT_NAME; }
}
protected override ConfigurationElement CreateNewElement() {
return new AuthorisedClientElement();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((AuthorisedClientElement)element).Name;
}
}
public class AuthorisedClientElement : ConfigurationElement {
const string NAME = "name";
[ConfigurationProperty(NAME, IsRequired = true)]
public string Name {
get { return (string)base[NAME]; }
}
[ConfigurationProperty("", IsDefaultCollection = true)]
public QueueElementCollection Elements {
get { return (QueueElementCollection)base[""]; }
}
}
public class QueueElementCollection : ConfigurationElementCollection {
const string ELEMENT_NAME = "Queue";
public override ConfigurationElementCollectionType CollectionType {
get { return ConfigurationElementCollectionType.BasicMap; }
}
protected override string ElementName {
get { return ELEMENT_NAME; }
}
protected override ConfigurationElement CreateNewElement() {
return new QueueElement();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((QueueElement)element).Id;
}
}
public class QueueElement : ConfigurationElement {
const string ID = "id";
[ConfigurationProperty(ID, IsRequired = true)]
public int Id {
get { return (int)base[ID]; }
}
}
And the test:
var authorisedClientsSection = ConfigurationManager.GetSection("AuthorisedClients")
as AuthorisedClientsSection;
foreach (AuthorisedClientElement client in authorisedClientsSection.Elements) {
Console.WriteLine("Client: {0}", client.Name);
foreach (QueueElement queue in client.Elements) {
Console.WriteLine("\tQueue: {0}", queue.Id);
}
}
I have defined the following class:
public class Root
{
public string Name;
public string XmlString;
}
and created an object:
Root t = new Root
{ Name = "Test",
XmlString = "<Foo>bar</Foo>"
};
When I use XmlSerializer class to serialize this object, it will return the xml:
<Root>
<Name>Test</Name>
<XmlString><Foo>bar</Foo></XmlString>
</Root>
How do I make it not encode my XmlString content so that I can get the serialized xml as
<XmlString><Foo>bar</Foo></XmlString>
Thanks,
Ian
You can limit the custom serialization to just the element that needs special attention like so.
public class Root
{
public string Name;
[XmlIgnore]
public string XmlString
{
get
{
if (SerializedXmlString == null)
return "";
return SerializedXmlString.Value;
}
set
{
if (SerializedXmlString == null)
SerializedXmlString = new RawString();
SerializedXmlString.Value = value;
}
}
[XmlElement("XmlString")]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public RawString SerializedXmlString;
}
public class RawString : IXmlSerializable
{
public string Value { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
this.Value = reader.ReadInnerXml();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteRaw(this.Value);
}
}
You can (ab)use the IXmlSerializable interface an XmlWriter.WriteRaw for that. But as garethm pointed out you then pretty much have to write your own serialization code.
using System;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace ConsoleApplicationCSharp
{
public class Root : IXmlSerializable
{
public string Name;
public string XmlString;
public Root() { }
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("Name", Name);
writer.WriteStartElement("XmlString");
writer.WriteRaw(XmlString);
writer.WriteFullEndElement();
}
public void ReadXml(System.Xml.XmlReader reader) { /* ... */ }
public XmlSchema GetSchema() { return (null); }
public static void Main(string[] args)
{
Root t = new Root
{
Name = "Test",
XmlString = "<Foo>bar</Foo>"
};
System.Xml.Serialization.XmlSerializer x = new XmlSerializer(typeof(Root));
x.Serialize(Console.Out, t);
return;
}
}
}
prints
<?xml version="1.0" encoding="ibm850"?>
<Root>
<Name>Test</Name>
<XmlString><Foo>bar</Foo></XmlString>
</Root>
try this:
public class Root
{
public string Name;
public XDocument XmlString;
}
Root t = new Root
{ Name = "Test",
XmlString = XDocument.Parse("<Foo>bar</Foo>")
};
I would be very surprised if this was possible. Suppose it was possible for you to do this - what would happen if you had malformed XML in the property - everything would just break.
I expect that you will either need to write your own serialization for this case, or make it so that the XmlString field is a structure that contains a foo field.