Helo
Can anybody explain me how to get configuration element from .config file.
I know how to handle attributes but not elements. As example, I want to parse following:
<MySection enabled="true">
<header><![CDATA[ <div> .... </div> ]]></header>
<title> .... </title>
</MySection>
My c# code looks like this so far:
public class MyConfiguration : ConfigurationSection
{
[ConfigurationProperty("enabled", DefaultValue = "true")]
public bool Enabled
{
get { return this["enabled"].ToString().ToLower() == "true" ? true : false; }
}
[ConfigurationProperty("header")]
public string header
{
???
}
}
It works with attributes, how do I do with elements (header property in above code) ?
There is another approach for doing the same thing.
We could create an element by overriding DeserializeElement method to get string value:
public class EmailTextElement : ConfigurationElement {
public string Value { get; private set; }
protected override void DeserializeElement(XmlReader reader, bool s) {
Value = reader.ReadElementContentAs(typeof(string), null) as string;
}
}
Here's a pretty good custom config section designer tool you can use (and it's free):
Configuration Section Designer
EDIT:
I was looking into MSDN and it seems that custom config sections can't do what you want, ie. getting the config value from an element. Custom config elements can contain other config elements, but the config values always come from attributes.
Maybe you can put your html snippets into other files and refer to them from the config, like this.
<MySection enabled="true">
<header filename="myheader.txt" />
<title filename="mytitle.txt" />
</MySection>
Inherit the ConfigurationElement class and override its deserialize method. Use the new class to represent elements with text content.
http://www.codeproject.com/KB/XML/ConfigurationTextElement.aspx
Working with your example, you are going to override the Deserialization of "header" in the ConfigurationElement to get the CDATA value.
<MySection enabled="true">
<header name="foo"><![CDATA[ <div> .... </div> ]]></header>
<title> .... </title>
</MySection>
public sealed class HeaderSection: ConfigurationElement {
private string __Name, __CDATA;
[ConfigurationProperty("name", IsRequired = true)]
public string Name {
get {
return this.__Name;
}
set {
this.__Name = value;
}
}
[ConfigurationProperty("value", IsRequired = true)]
public string Value {
get {
return this.__CDATA;
}
set {
this.__CDATA = value;
}
}
protected override void DeserializeElement(System.Xml.XmlReader reader, bool s) {
this.Name = reader.GetAttribute("name").Trim();
string cdata = reader.ReadElementContentAs(typeof(string), null) as string;
this.Value = cdata.Trim();
}
}
You can use the ConfigurationManager.GetSection("SectionName") method for getting the configuration section in the config files.
I finally found one way to do it.
There is IConfigurationSectionHandler interface that allows for things I want. It requires the one to write the method
public object Create(object parent, object configContext, XmlNode section)
After it, u parse section on your own so I was able to fetch XmlElement's without a problem:
header = s["header"] != null ? s["header"].InnerText : String.Empty;
title = s["title"] != null ? s["title"].InnerText : String.Empty;
The down side of this is that interface is outdated but MSDN states that it will not be removed from future versions of the frameworks as it is used internally.
You can create a class that inherits from System.Configuration.ConfigurationElement that represents an element in your configuration section.
There's a simple example in the MSDN documentation for ConfigurationElement.
According to MSDN, in .NET 4 there's a new CurrentConfiguration property which gives you a reference to the top-level Configuration instance that represents the configuration hierarchy that the current ConfigurationElement instance belongs to.
Related
I want a configuration section that looks like this:
<MailMessage>
<from value="me#you.com" />
<subject value ="Subject goes here" />
<body value="Hello. You've got mail!" />
</MailMessage>
And I have implemented the classes like in the second answer of this link:
How to implement a ConfigurationSection with a ConfigurationElementCollection
Now for me the elements of MailMessage section are not a collection but this should not be a problem but I receive the error when I try to access the property:
Unrecognized element 'from'
I get the section with the code:
private static MailMessageSection emailSection = ConfigurationManager.GetSection("MailMessage") as MailMessageSection;
Here is the implementation of the elements:
public class MailMessageSection : ConfigurationSection
{
[ConfigurationProperty("from")]
public FromElement From
{
get { return base["from"] as FromElement; }
}
[ConfigurationProperty("subject")]
public SubjectElement Subject
{
get { return base["subject"] as SubjectElement; }
}
[ConfigurationProperty("body")]
public BodyElement Body
{
get { return base["body"] as BodyElement; }
}
}
public class FromElement : ConfigurationElement
{
[ConfigurationProperty("value")]
public string From
{
get { return base["value"] as string; }
}
}
public class SubjectElement : ConfigurationElement
{
[ConfigurationProperty("value")]
public string Subject
{
get { return base["value"] as string; }
}
}
public class BodyElement : ConfigurationElement
{
[ConfigurationProperty("value")]
public string Body
{
get { return base["value"] as string; }
}
}
Any ideas what could be wrong? Thanks for your time!
Looking for error is serializable classes can be frustrating. I suggest you to use the auto generate functionalities in VisualStudio. Here is how you do it (very simple):
1. Copy the XML example (to the clipboard)
2. Create new class for the XML ("MailMessageSection" in your case)
3. In VS go to Edit > Paste Special > Paste XML As Classes
I know this is not exactly the reason why the from is not working, but using auto generated code is much better practice then write it on your own.
Hope it helps...
I have an XML with an optional tag as follows:
<Config>
<CheckForCompleteTransform>true</CheckForCompleteTransform>
<!-- more tags -->
</Config>
And the class-definition:
public class config {
[System.Xml.Serialization.XmlElement("CheckForCompleteTransform")]
public bool? CheckForCompleteTransform { get; set; }
}
This works if I either set the tag to what I provided within my example-XML above or I omit it completely. But what if I provide the tag as <MyTag/>? If this notation is used I want the serializer to set the corresponding property within my class to true, but I awlays get a
System.FormatException: the string literal '' is not valid for type
Boolean
Any ideas on how to achieve this?
Check this here
u can use [XmlElement("CheckForCompleteTransform", IsNullable=true)] CheckForCompleteTransform property in your class
public class config
{
[XmlElement("CheckForCompleteTransform", IsNullable = true)]
public bool? CheckForCompleteTransform { get; set; }
}
and add xsi:nil="true" attribute to CheckForCompleteTransform tag like this
<CheckForCompleteTransform xsi:nil="true" />
I haven't verified this myself, but have you tried adding a
[XmlElement(IsNullable = true)]
attribute to the CheckForCompleteTransform ?
EDIT:
Ok, how about together with
DefaultValueAttribute(true);
Im serializing my class to XML. I have an issue with the root element of one of my classes is NOT being named properly.
The complete XML structure should look like the following.
<Workflow>
<Name>My Workflow</Name>
<Description />
<Modules>
<Module Name="Intro" MenuText="IntroText" />
</Modules>
</Workflow>
However Im getting this result
<Workflow>
<Name>My Workflow</Name>
<Description />
<Modules>
<WorkflowModule Name="Intro" MenuText="IntroText" />
</Modules>
</Workflow>
I want the element "WorkflowModule" to be called "Module" however the problem is that I already have another class called Module. So to get around this problem I called it a WorkflowModule and put a class XmlRoot() declartion like so;
[XmlRoot("Module")]
public class WorkflowModule
{...}
But when I serialize the Workflow class it still comes up with WorkflowModule.
Here are my 2 classes classes;
[XmlRoot("Workflow")]
public class Workflow
{
private string _name;
private string _description;
private List<WorkflowModule> _modules = new List<WorkflowModule>();
[XmlElement("Name")]
public String Name
{
get { }
set { }
}
[XmlElement("Description")]
public String Description
{
get { }
set { }
}
[XmlArrayItem(typeof(WorkflowModule))]
public List<WorkflowModule> Modules
{
get { }
set { }
}
}
[XmlRoot("Module")]
public class WorkflowModule
{
private string _name;
private string _menu_text;
public WorkflowModule()
{
}
[XmlAttribute("Name")]
public String Name
{
get { }
set { }
}
[XmlAttribute("MenuText")]
public String MenuText
{
get { }
set { }
}
}
}
Set the element name within XmlArrayItem attrubute:
[XmlArrayItem(typeof(WorkflowModule), ElementName = "Module")]
There are many ways to control it as defined in this duplicate post How do I Set XmlArrayItem Element name for a List<Custom> implementation?
These attribute control serialize from the this object's perspective as it transverses nested objects
[XmlArray("RootArrayElementNameGoesHere")]
[XmlArrayItem(typeof(Workflow), ElementName="ArrayItemElementNameGoesHere")]
public List<WorkflowModule> Modules
This attribute redefining the element name but can be overwritten with the local [XmlArrayItem] or [XmlElement] attributes to provides local override from the owning objects serialization
[XmlType(TypeName = "UseThisElementNameInsteadOfClassName")]
public class WorkflowModule
This attribute is only honored when its the direct object being serialized
[XmlRoot("UseThisElementNameWhenItIsTheRoot")]
public class WorkflowModule
Consider the following configuration group in a .NET .config file.
<MySettingsGroup enabled="true">
<MySettingsSection enabled="true">
</MySettingsSection>
</MySettingsGroup>
The supporting classes are:
public class MySettingsConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("enabled", DefaultValue = true, IsRequired = false)]
public bool Enabled
{
get
{
// works fine
return Convert.ToBoolean(this["enabled"]);
}
}
public class MySettingsConfigurationGroup : ConfigurationSectionGroup
{
[ConfigurationProperty("enabled", DefaultValue = true, IsRequired = false)]
public bool Enabled
{
get
{
// the way to retrieve attributes in sections is not supported by groups
// return Convert.ToBoolean(this["enabled"]);
return true;
}
}
How can the Enabled property on the MySettingsConfigurationGroup be implemented?
I don't think section groups were designed to be customized in the way you're attempting. A better solution would be to simply define your own configuration section that itself contains other configurations and omit the use of a section group altogether. Then, you'd get the full flexibility that configuration sections offer.
below is my class thanks to the article found at:
URL: Derik Whittaker
My Code:
public class FavsSection : ConfigurationSection
{
public override bool IsReadOnly()
{
return base.IsReadOnly();
}
public FavsSection() // default Constructor.
{ }
[ConfigurationProperty("Items", IsRequired=true)]
public FavouritesCollection FavsItems
{
get
{
return (FavouritesCollection)(base ["Items"]);
}
}
}
[ConfigurationCollection(typeof(FavouriteElement))]
public class FavouritesCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new FavouriteElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((FavouriteElement)(element)).ItemType;
}
public FavouriteElement this[int idx]
{
get
{
return (FavouriteElement)BaseGet(idx);
}
}
public override bool IsReadOnly()
{
return base.IsReadOnly();
}
}
public class FavouriteElement : ConfigurationElement
{
[ConfigurationProperty("id", DefaultValue = "", IsKey = true, IsRequired = true)]
public string ID
{
get
{
return ((string)(base["id"]));
}
set
{
base["id"] = value;
}
}
[ConfigurationProperty("path", DefaultValue = "", IsKey = false, IsRequired = false)]
public string Path
{
get
{
return ((string)(base["path"]));
}
set
{
base["path"] = value;
}
}
}
My config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="FavouritesMenu" type="MyFileExplorer.FavsSection, MyFileExplorer" />
</configSections>
<FavouritesMenu>
<Items>
<add id="1" path="c:\foo" />
<add id="2" path="C:\foo1" />
</Items>
</FavouritesMenu>
</configuration>
As you can see I am trying to write data into my custom section called 'Favourites Menu'. I think I have got the basic gist of the idea but I don;t see how to make my next step ... something got do with the 'IsReadOnly' method? Can someone please help me fill in the blanks? Feel free to rename things to make it easier to read? I thought I would make a half decent effort before I asked for help ...
RESEARCH: StackOverFlow - SAME QUESTION!
---------- Got lost on Pike65's comment ... cannot write to collection because it is set to read only.
I presume the collection needs setting to IsReadOnly false and some helper methods are needed to add data into the collection? This part is all alittle hazy to me ...
Thanks for reading,
Ibrar
I have been doing some basic testing and to 'NO' surprise ... the above actually works. You just need to make sure that when you want to pass data to your config section, then by default they are read only. So you will need to override the 'isReadOnly()' method inorder for the above code to work.
So the solution is that the above peice of code, does work ... you just need to override an extra method to allow you to access the collection responsible for holding your element data and manipulate its contents via the properties you define in the class that extends or inherits from the Configuration Element class.
UPDATE:
The above code sample I pasted in allows you to edit what already exists in the config file inside your custom section. In order to add a new item for example like the following:
FavsSection favconfig = (FavsSection)config.GetSection("FavouritesMenu");
ToolStripMenuItem menu = (ToolStripMenuItem)returnMenuComponents("favouritesToolStripMenuItem", form);
ToolStripItemCollection items = menu.DropDownItems;
for (int i = 0; i < items.Count; i++)
{
//favconfig.FavsItems[i].ID = i.ToString();
//favconfig.FavsItems[i].Path = items[i].Text;
favconfig.FavsItems[i] = new FavouriteElement()
{
ID = i.ToString(),
Path = items[i].Text
};
}
As you can see above, I am physically adding a new 'FavouriteElement' object into the collection returned by the property 'favconfig.FavItems'. In order to to do this, one property needs extending to support this.
public FavouriteElement this[int idx]
{
get
{
return (FavouriteElement)BaseGet(idx);
}
set
{
base.BaseAdd(value);
}
}
This indexer or paramterful property as 'Jeffrey Richter' calls them needs to have it's 'Set' accessor implemented as shown above in the code snippet. I have pasted it in here as it did not take long to figure out and most of the code is changed using a template I have used from Derik Whittaker's Article. Hopefully this will alow other coders to implement something similar.
Another solution would be to simply rather than 'getting' the collection all the time that 'lassoes' together all my 'FavouriteElements', you could implement the 'set' accessor for the related property. I have not tested this but I might be worth trying out.