I made a small custom configuration setting and I keep getting the error "The entry '' has already been added." when I try to use my custom collection. My code looks like this.
The issue comes from my tag.
I don't see what I am missing since I have the same thing implemented for and this one works perfectly.
My .NET version is 4.0 if that helps.
The app config section in question:
<WorkersCollectionSection>
<WorkersList>
<Worker name="Category" isEnabled="false"
assemblyNamespace="xxxxxx.xxxxxxxxxxxxxxx.Models.Category"
queueName="CategorQueue"
saveToFolder="false">
<HandlesList>
<Handle name="xxxxxxx" isEnabled="true"/>
<Handle name="yyyyyyy" isEnabled="true"/>
<Handle name="zzzzzzzzzz" isEnabled="true"/>
</HandlesList>
</Worker>
<WorkersList>
<WorkersCollectionSection>
The definition of the property:
[ConfigurationProperty("HandlesList")]
[ConfigurationCollection(typeof(WorkerCollection), AddItemName = "Handle")]
public HandleCollection HandleCollection
{
get { return (HandleCollection) base["HandlesList"]; }
}
The code for the tag :` public class HandleCollection : ConfigurationElementCollection, IEnumerable
{
public HandleCollection()
{
HandleElement handle = (HandleElement)CreateNewElement();
BaseAdd(handle);
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
}
protected override ConfigurationElement CreateNewElement()
{
return new HandleElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((HandleElement)element).Name;
}
public HandleElement this[int index]
{
get { return (HandleElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
public new HandleElement this[string Name]
{
get { return (HandleElement)BaseGet(Name); }
}
public int IndexOf(HandleElement handle)
{
return BaseIndexOf(handle);
}
public void Add(HandleElement url)
{
BaseAdd(url);
}
protected override void BaseAdd(ConfigurationElement element)
{
BaseAdd(element, false);
}
public void Remove(HandleElement handle)
{
if (BaseIndexOf(handle) >= 0)
BaseRemove(handle);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public void Remove(string name)
{
BaseRemove(name);
}
public void Clear()
{
BaseClear();
}
IEnumerator<ConfigurationElement> IEnumerable<ConfigurationElement>.GetEnumerator()
{
return (from i in Enumerable.Range(0, this.Count)
select this[i])
.GetEnumerator();
}
protected override string ElementName
{
get { return "Handle"; }
}
public static explicit operator HandleCollection(Dictionary<string, string> v)
{
throw new NotImplementedException();
}
public static explicit operator HandleCollection(ConfigurationSection v)
{
throw new NotImplementedException();
}
}`
Code for the handle elements inside the list:
public class HandleElement: ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
[StringValidator(InvalidCharacters = "~!##$%^&*()[]{}/;'\"|\\", MinLength = 0, MaxLength = 60)]
public string Name
{
get { return base["name"] as string; }
set { base["name"] = value; }
}
[ConfigurationProperty("isEnabled", IsRequired = true)]
public bool IsEnabled
{
get { return (bool)base["isEnabled"]; }
set { base["isEnabled"] = value; }
}
}
It seems that the issue was that I did not put a check in the collection adding method to check if the element I am adding has a different key than the empty one. I changed my collection constructor to :
public HandleCollection()
{
HandleElement handle = (HandleElement)CreateNewElement();
if (handle.Name != "")
BaseAdd(handle);
}
Now it seems to work fine.
Related
I need to implement automatic UI Tests for a Delphi Application with Visual Studio Coded UI Tests. I have already implemented the IAccessible Interface to my Delphi-Contols. It works fine and i get the AccessibleName from the Control.
Then i implemented an extension for visual studio. In this extension i have my own PropertyProvider-, ExtensionPackage- and WinControl-Class.
PropertyProvider:
namespace CUITExtension
{
public class AccessibleNamePropertyProvider : UITestPropertyProvider
{
private static Dictionary<string, UITestPropertyDescriptor> accessibleNamePropertyMap = null;
private static Dictionary<string, UITestPropertyDescriptor> AccessibleNamePropertyMap
{
get
{
if (accessibleNamePropertyMap == null)
{
UITestPropertyAttributes read = UITestPropertyAttributes.Readable
| UITestPropertyAttributes.DoNotGenerateProperties;
accessibleNamePropertyMap = new Dictionary<string, UITestPropertyDescriptor>
(StringComparer.OrdinalIgnoreCase);
accessibleNamePropertyMap.Add("AccessibleName", new UITestPropertyDescriptor(typeof(string), read));
}
return accessibleNamePropertyMap;
}
}
public override UITestPropertyDescriptor GetPropertyDescriptor(UITestControl uiTestControl, string propertyName)
{
return AccessibleNamePropertyMap[propertyName];
}
public override ICollection<string> GetPropertyNames(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
{
// the keys of the property map are the collection of property names
return AccessibleNamePropertyMap.Keys;
}
throw new NotSupportedException();
}
public override object GetPropertyValue(UITestControl uiTestControl, string propertyName)
{
if (String.Equals(propertyName, "AccessibleName", StringComparison.OrdinalIgnoreCase))
{
object[] native = uiTestControl.NativeElement as object[];
IAccessible acc = native[0] as IAccessible;
return acc.accName;
}
throw new NotSupportedException();
}
public override int GetControlSupportLevel(UITestControl uiTestControl)
{
if (string.Equals(uiTestControl.TechnologyName, "MSAA",
StringComparison.OrdinalIgnoreCase) &&
uiTestControl.ControlType.NameEquals("Custom"))
{
return (int)ControlSupport.ControlSpecificSupport;
}
// This is not my control, so return NoSupport
return (int)ControlSupport.NoSupport;
}
public override string[] GetPredefinedSearchProperties(Type specializedClass)
{
return null;
}
public override string GetPropertyForAction(UITestControl uiTestControl, UITestAction action)
{
return null;
}
public override string[] GetPropertyForControlState(UITestControl uiTestControl, ControlStates uiState, out bool[] stateValues)
{
stateValues = null;
return null;
}
public override Type GetPropertyNamesClassType(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
return typeof(AccessibleControl.PropertyNames);
return null;
}
public override Type GetSpecializedClass(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
return typeof(AccessibleControl);
return null;
}
public override void SetPropertyValue(UITestControl uiTestControl, string propertyName, object value)
{
return;
}
}
}
ExtensionPackage:
[assembly: Microsoft.VisualStudio.TestTools.UITest.Extension.UITestExtensionPackage(
"AccessibleNameExtensionPackage",
typeof(CUITExtension.AccessibleNameExtensionPackage))]
namespace CUITExtension
{
class AccessibleNameExtensionPackage : UITestExtensionPackage
{
public override string PackageDescription
{
get { return "Supports coded UI testing by using the AccessibleName"; }
}
public override string PackageName
{
get { return "AccessibleName Extension Package"; }
}
public override string PackageVendor
{
get { return "Microsoft (sample)"; }
}
public override Version PackageVersion
{
get { return new Version(1, 0); }
}
public override Version VSVersion
{
get { return new Version(14, 0); }
}
public override void Dispose() { }
public override object GetService(Type serviceType)
{
if (serviceType == typeof(UITestPropertyProvider))
{
if (propertyProvider == null)
{
propertyProvider = new AccessibleNamePropertyProvider();
}
return propertyProvider;
}
return null;
}
private UITestPropertyProvider propertyProvider = null;
}
}
WinControl:
namespace CUITExtension
{
public class AccessibleControl : WinControl
{
public AccessibleControl(UITestControl c) : base(c)
{
TechnologyName = "MSAA";
SearchProperties.Add(UITestControl.PropertyNames.ControlType, "Custom");
}
public virtual string AccessibleName
{
get
{
return (string)GetProperty("AccessibleName");
}
}
}
}
Now the Coded UI Test Builder is showing the AccessibleName and is also generating AccessibleName as a SearchProperty.
UIMap:
public AccessibleControl UIItemCustom
{
get
{
if ((this.mUIItemCustom == null))
{
this.mUIItemCustom = new AccessibleControl(this);
#region Search Criteria
this.mUIItemCustom.SearchProperties["AccessibleName"] = "UniqueName1";
this.mUIItemCustom.SearchProperties[WinControl.PropertyNames.ClassName] = "TEdit";
this.mUIItemCustom.WindowTitles.Add("Title");
#endregion
}
return this.mUIItemCustom;
}
}
*I have changed the Searchproperties here (only for the post, i didnt changed the generated code)
Now when I start the test, I get an exception that says that AccessibleName is not an valid searchproperty. I got this exception before, when i havent implemented the extension yet. But I thougth by implementing the propertyprovider AccessibleName should be a valid searchproperty now.
I tried to debug it, but it seems like by searching the Control it doesnt use the propertyprovider and i have no idea why?
I hope you can help me and if you need more information just ask.
Paul
I got the problem with the valid searchproperty to work.
I overrode the GetValidSearchProperties method from WinControl.
protected override Dictionary<string, bool> GetValidSearchProperties()
{
Dictionary<string, bool> searchProperties = base.GetValidSearchProperties();
if (!searchProperties.ContainsKey("AccessibleName"))
searchProperties.Add("AccessibleName", true);
return searchProperties;
}
I'm using a bindingsource to fill a form from Nhibernate list:
public class Customer{
public string Name { get; set;}
public IList<Order> Orders { get; set;}
}
bindingSourceCustomer.DataSource = session.Query<Customer>().ToList();
bindingSourceOrder.DataSource = bindingSourceCustomer;
bindingSourceOrder.DataMember = "Orders";
now when I call
bindingSourceOrder.AddNew();
an exception is thrown:
The value "System.Object" is not of type "Model.Order" and cannot be
used in this generic collection.
Now I changed the first line to:
bindingSourceCustomer.DataSource = session.Query<Customer>().Select(customer =>
{
customer.Orders = customer.Orders.ToList();
return customer;
})
.ToList();
it worked, the reason why, is because Nhibernate uses the PersistentBag as an implementation of IList, which apparently doesn't work well with binding source (As far as I see).
Any suggestion either how to make Nhibernate return List class, or how to solve the problem with binding source?
That's because the BindingSource is unable to discover the list type:
NHibernate's persistent bag has not the ITypedList interface nor the Indexer Property public.
You need to replace NHibernate CollectionTypeFactory with a custom one, before adding mappings.
I attach my implementation:
PersistentGenericBag:
public class EnhancedPersistentGenericBag<T> : PersistentGenericBag<T> , ITypedList
{
public EnhancedPersistentGenericBag(ISessionImplementor session, ICollection<T> coll) : base(session, coll) { }
public EnhancedPersistentGenericBag(ISessionImplementor session) : base(session) { }
public EnhancedPersistentGenericBag() { }
public new T this[int index]
{
get
{
return (T)base[index];
}
set
{
base[index] = value;
}
}
public string GetListName(PropertyDescriptor[] listAccessors) { return GetType().Name; }
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
return TypeDescriptor.GetProperties(typeof(T));
}
}
CollectionTypeFactory:
public class EnhancedCollectionTypeFactory : DefaultCollectionTypeFactory
{
public override CollectionType Bag<T>(string role, string propertyRef, bool embedded)
{
return new EnhancedGenericBagType<T>(role, propertyRef);
}
}
GenericBagType:
public class EnhancedGenericBagType<T> : BagType
{
public EnhancedGenericBagType(string role, string propertyRef) :
base(role, propertyRef, false) { }
public override IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister, object key)
{
return new EnhancedPersistentGenericBag<T>(session);
}
public override IPersistentCollection Wrap(ISessionImplementor session, object collection)
{
return new EnhancedPersistentGenericBag<T>(session, (ICollection<T>)collection);
}
public override Type ReturnedClass
{
get
{
return typeof(ICollection<T>);
}
}
protected override void Add(object collection, object element)
{
((ICollection<T>)collection).Add((T)element);
}
protected override void Clear(object collection)
{
((ICollection<T>)collection).Clear();
}
public override object Instantiate(int anticipatedSize)
{
if (anticipatedSize > 0)
return new List<T>(anticipatedSize + 1);
else
return new List<T>();
}
}
How to override default CollectionTypeFactory:
Configuration cfg = new Configuration();
cfg.CollectionTypeFactory<EnhancedCollectionTypeFactory>();
I am writing a custom configuration section using the .NET configuration api. I want to define a section that can have exactly one of two sub-elements,
e.g.
<MyCustomSection>
<myItems>
<myItemType name="1">
<firstSubTypeConfig />
</myItemType>
<myItemType name="2">
<secondSubTypeConfig />
</myItemType>
</myItems>
</MyCustomSection>
Currently, I have the sections defined like:
public class MyCustomSection : ConfigurationSection
{
public override bool IsReadOnly()
{
return false;
}
[ConfigurationProperty("myItems")]
public MyItemsCollection MyItems
{
get { return (MyItemsCollection)base["myItems"]; }
set { base["myItems"] = value; }
}
}
[ConfigurationCollection(typeof(MyItemType), AddItemName="myItemType",
CollectionType=ConfigurationElementCollectionType.BasicMap)]
public class MyItemsCollection : ConfigurationElementCollection
{
public override bool IsReadOnly()
{
return false;
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
protected override string ElementName
{
get { return "myItemType"; }
}
protected override ConfigurationElement CreateNewElement()
{
return new MyItemType();
}
protected override object GetElementKey(ConfigurationElement element)
{
return (element as MyItemType).Name;
}
}
public class MyItemType : ConfigurationElement
{
public override bool IsReadOnly()
{
return false;
}
[ConfigurationProperty("name", IsRequired=true)]
public string Name
{
get { return (string)base["name"]; }
set { base["name"] = value; }
}
[ConfigurationProperty("firstSubTypeConfig")]
public FirstSubTypeConfig FirstSubTypeConfig
{
get { return (FirstSubTypeConfig)base["firstSubTypeConfig"]; }
set { base["firstSubTypeConfig"] = value; }
}
[ConfigurationProperty("secondSubTypeConfig")]
public SecondSubTypeConfig SecondSubTypeConfig
{
get { return (SecondSubTypeConfig)base["secondSubTypeConfig"]; }
set { base["secondSubTypeConfig"] = value; }
}
}
public class FirstSubTypeConfig : ConfigurationElement
{
public override bool IsReadOnly()
{
return false;
}
}
public class SecondSubTypeConfig : ConfigurationElement
{
public override bool IsReadOnly()
{
return false;
}
}
The configuration is also modified and saved programmatically, and currently, saving the
configuration section will add the secondSubTypeConfig element even if I only specify the
firstSubTypeConfig element.
My first thought was to introduce a common base class for FirstSubTypeConfig and SecondSubTypeConfig, but I'm not clear on if or how the configuration api would handle that.
How do I setup mutually exclusive custom config elements?
I'm not sure this is the "correct" approach, but this works.
The code as written in the question will load the config file fine. You can check the ElementInformation.IsPresent property to validate that exactly one element is included.
e.g.
var custom = (MyCustomSection)ConfigurationManager.GetSection("MyCustomSection");
foreach (MyItemType itemType in custom.MyItems)
{
if (itemType.FirstSubTypeConfig.ElementInformation.IsPresent
&& itemType.SecondSubTypeConfig.ElementInformation.IsPresent)
{
throw new ConfigurationErrorsException("At most one of firstSubTypeConfig or secondSubTypeConfig can be specified in a myItemType element");
}
else if (!itemType.FirstSubTypeConfig.ElementInformation.IsPresent
&& !itemType.SecondSubTypeConfig.ElementInformation.Ispresent)
{
throw new ConfigurationErrorsException("Either a firstSubTypeConfig or a secondSubTypeConfig element must be specified in a myItemType element");
}
}
As for saving the config, it seems that checking for ElementInformation.IsPresent and explicitly setting it to null will prevent the element from being written to the config file. e.g.
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var custom = (MyCustomSection)config.GetSection("MyCustomSection");
//make modifications against the custom variable ...
foreach (MyItemType itemType in custom.MyItems)
{
if (!itemType.FirstSubTypeConfig.ElementInformation.IsPresent)
itemType.FirstSubTypeConfig = null;
if (!itemType.SecondSubTypeConfig.ElementInformation.IsPresent)
itemType.SecondSubTypeConfig = null;
}
config.Save();
I think that you should do that on the PostDeserialize method ihnerited from the ConfigurationElement class without doing that on your business logic.
For example:
protected override void PostDeserialize()
{
base.PostDeserialize();
if (FirstSubTypeConfig != null && SecondTypeCOnfig != null)
{
throw new ConfigurationErrorsException("Only an element is allowed.");
}
}
for configuration as following
<MyCollection default="one">
<entry name="one" ... other attrubutes />
... other entries
</MyCollection>
when implement a MyCollection, what should i do for the "default" attribute?
Let's suppose you have this .config file:
<configuration>
<configSections>
<section name="mySection" type="ConsoleApplication1.MySection, ConsoleApplication1" /> // update type & assembly names accordingly
</configSections>
<mySection>
<MyCollection default="one">
<entry name="one" />
<entry name="two" />
</MyCollection>
</mySection>
</configuration>
Then, with this code:
public class MySection : ConfigurationSection
{
[ConfigurationProperty("MyCollection", Options = ConfigurationPropertyOptions.IsRequired)]
public MyCollection MyCollection
{
get
{
return (MyCollection)this["MyCollection"];
}
}
}
[ConfigurationCollection(typeof(EntryElement), AddItemName = "entry", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class MyCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new EntryElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
if (element == null)
throw new ArgumentNullException("element");
return ((EntryElement)element).Name;
}
[ConfigurationProperty("default", IsRequired = false)]
public string Default
{
get
{
return (string)base["default"];
}
}
}
public class EntryElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name
{
get
{
return (string)base["name"];
}
}
}
you can read the configuration with the 'default' attribute, like this:
MySection section = (MySection)ConfigurationManager.GetSection("mySection");
Console.WriteLine(section.MyCollection.Default);
This will output "one"
I don't know if it's possible to have a default value in a ConfigurationElementCollection. (it doesn't seen to have any property for default value).
I guess you have to implement this by yourself. Look at the example below.
public class Repository : ConfigurationElement
{
[ConfigurationProperty("key", IsRequired = true)]
public string Key
{
get { return (string)this["key"]; }
}
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
}
}
public class RepositoryCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new Repository();
}
protected override object GetElementKey(ConfigurationElement element)
{
return (element as Repository).Key;
}
public Repository this[int index]
{
get { return base.BaseGet(index) as Repository; }
}
public new Repository this[string key]
{
get { return base.BaseGet(key) as Repository; }
}
}
public class MyConfig : ConfigurationSection
{
[ConfigurationProperty("currentRepository", IsRequired = true)]
private string InternalCurrentRepository
{
get { return (string)this["currentRepository"]; }
}
[ConfigurationProperty("repositories", IsRequired = true)]
private RepositoryCollection InternalRepositories
{
get { return this["repositories"] as RepositoryCollection; }
}
}
Here's the XML config:
<myConfig currentRepository="SQL2008">
<repositories>
<add key="SQL2008" value="abc"/>
<add key="Oracle" value="xyz"/>
</repositories>
</myConfig>
And then, at your code, you access the default item using the following:
MyConfig conf = (MyConfig)ConfigurationManager.GetSection("myConfig");
string myValue = conf.Repositories[conf.CurrentRepository].Value;
Of course, the MyConfig class can hide the details of accessing the Repositories and CurrentRepository properties. You can have a property called DefaultRepository (of type Repository) in MyConfig class to return this.
This may be a bit late but may be helpful to others.
It is possible but with some modification.
ConfigurationElementCollection inherits ConfigurationElement as such "this[string]" is available in ConfigurationElement.
Usually when ConfigurationElementCollection is inherited and implemented in another class, the "this[string]" is hidden with "new this[string]".
One way to get around it is to create another implementation of this[] such as "this[string, string]"
See example below.
public class CustomCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new CustomElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((CustomElement)element).Name;
}
public CustomElement this[int index]
{
get { return (CustomElement)base.BaseGet(index); }
set
{
if (BaseGet(index) != null)
BaseRemoveAt(index);
BaseAdd(index, value);
}
}
// ConfigurationElement this[string] now becomes hidden in child class
public new CustomElement this[string name]
{
get { return (CustomElement)BaseGet(name); }
}
// ConfigurationElement this[string] is now exposed
// however, a value must be entered in second argument for property to be access
// otherwise "this[string]" will be called and a CustomElement returned instead
public object this[string name, string str = null]
{
get { return base[name]; }
set { base[name] = value; }
}
}
If you want to genericize it, this should help:
using System.Configuration;
namespace Abcd
{
// Generic implementation of ConfigurationElementCollection.
[ConfigurationCollection(typeof(ConfigurationElement))]
public class ConfigurationElementCollection<T> : ConfigurationElementCollection
where T : ConfigurationElement, IConfigurationElement, new()
{
protected override ConfigurationElement CreateNewElement()
{
return new T();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((IConfigurationElement)element).GetElementKey();
}
public T this[int index]
{
get { return (T)BaseGet(index); }
}
public T GetElement(object key)
{
return (T)BaseGet(key);
}
}
}
Here's the interface referenced above:
namespace Abcd
{
public interface IConfigurationElement
{
object GetElementKey();
}
}
Example from MSDN on making a custom config section that should work as follows,
class RemoteServiceSection : ConfigurationSection
{
[ConfigurationProperty("remoteServices", IsDefaultCollection=false)]
[ConfigurationCollection(typeof(RemoteServiceCollection), AddItemName="addService", ClearItemsName="clearServices",
RemoveItemName="removeService")]
public RemoteServiceCollection Services
{
get
{
return this["remoteServices"] as RemoteServiceCollection;
}
}
}
class RemoteServiceCollection : ConfigurationElementCollection, IList<RemoteServiceElement>
{
public RemoteServiceCollection()
{
RemoteServiceElement element = (RemoteServiceElement)CreateNewElement();
Add(element);
}
public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.AddRemoveClearMap;
}
}
protected override ConfigurationElement CreateNewElement()
{
return new RemoteServiceElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((RemoteServiceElement)element).Hostname;
}
protected override string ElementName
{
get
{
return "remoteService";
}
}
public new IEnumerator<RemoteServiceElement> GetEnumerator()
{
foreach (RemoteServiceElement element in this)
{
yield return element;
}
}
public void Add(RemoteServiceElement element)
{
BaseAdd(element, true);
}
public void Clear()
{
BaseClear();
}
public bool Contains(RemoteServiceElement element)
{
return !(BaseIndexOf(element) < 0);
}
public void CopyTo(RemoteServiceElement[] array, int index)
{
base.CopyTo(array, index);
}
public bool Remove(RemoteServiceElement element)
{
BaseRemove(GetElementKey(element));
return true;
}
bool ICollection<RemoteServiceElement>.IsReadOnly
{
get { return IsReadOnly(); }
}
public int IndexOf(RemoteServiceElement element)
{
return BaseIndexOf(element);
}
public void Insert(int index, RemoteServiceElement element)
{
BaseAdd(index, element);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public RemoteServiceElement this[int index]
{
get
{
return (RemoteServiceElement)BaseGet(index);
}
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
}
class RemoteServiceElement : ConfigurationElement
{
public RemoteServiceElement() { }
public RemoteServiceElement(string ip, string port)
{
this.IpAddress = ip;
this.Port = port;
}
[ConfigurationProperty("hostname", IsKey = true, IsRequired = true)]
public string Hostname
{
get
{
return (string)this["hostname"];
}
set
{
this["hostname"] = value;
}
}
[ConfigurationProperty("ipAddress", IsRequired = true)]
public string IpAddress
{
get
{
return (string)this["ipAddress"];
}
set
{
this["ipAddress"] = value;
}
}
[ConfigurationProperty("port", IsRequired = true)]
public string Port
{
get
{
return (string)this["port"];
}
set
{
this["port"] = value;
}
}
}
}
I am getting the error that says 'Unrecognized element 'addService'. I think I've followed the MSDN article exactly. It can be found here - http://msdn.microsoft.com/en-us/library/system.configuration.configurationcollectionattribute.aspx
Thanks in advance for your help. This is what I wrote in app.config (with brackets of course that dont show up here?):
<remoteServices>
<addService hostname="xxxxxxx" ipAddress="xxx.x.xxx.xx" port="xxxx" >
</remoteServices>
Here is app.config as requested, x'ing out the specific names just for privacy purposes, they are just strings:
<configuration>
<configSections>
<section name="remoteServices" type="AqEntityTests.RemoteServiceSection,
AqEntityTests" allowLocation="true" allowDefinition="Everywhere"/>
</configSections>
<remoteServices>
<addService hostname="xxxxxx.xxxxxxx.com"
ipAddress="xxx.x.xxx.xx"
port="xx" />
</remoteServices>
For future generations:
Your config should look like this:
<configuration>
<configSections>
<section name="remoteServices" type="AqEntityTests.RemoteServiceSection,
AqEntityTests" allowLocation="true" allowDefinition="Everywhere"/>
</configSections>
<remoteServices>
<remoteServices>
<addService hostname="xxxxxx.xxxxxxx.com"
ipAddress="xxx.x.xxx.xx"
port="xx" />
</remoteServices>
</remoteServices>
</configuration>
Why?
You add to node:
<configSections>
custom section named:
name="remoteServices"
with type
type="AqEntityTests.RemoteServiceSection
and then in code, you add property to your custom section:
[ConfigurationProperty("remoteServices", IsDefaultCollection=false)]
Meaning you created node inside node with both having same name. Because of that you have received error "Unrecognized element 'addService'". Just compiler informing you that such element should not be in that node.
Two links for quick learning of custom configuration:
Custom Configuration Sections for Lazy Coders
How to create sections with collections
You might also look into using an unnamed default collection as I mention here
That allows you to add items in the manner you suggest.