jwatts1980
jwatts1980

Reputation: 7356

Use ConfigurationSection / ConfigurationElementCollection to create a nested collection of items from web.config

I am trying to create a custom section group in my web.config. It is mostly working, but is failing out with the following error:

Unrecognized element 'add'. (web.config line 736) at System.Configuration.BaseConfigurationRecord.EvaluateOne(String[] keys, SectionInput input, Boolean isTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult)....

There may be other errors in the code, but this is the part I cannot get passed.

Here is my web.config XML:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="systemEmailsGroup">
      <section name="systemEmails" type="SystemEmailsConfiguration" />
    </sectionGroup>
  </configSections>
  <!-- lots of other stuff -->
  <systemEmailsGroup>
    <systemEmails>
      <emails>
        <email name="SystemEmail01" defaultAddress="[email protected]" defaultName="John Doe 01" />
        <email name="SystemEmail02" source="UserName" username="Username02" defaultAddress="[email protected]" defaultName="Jane Doe 02" />
        <email name="SystemEmail03" defaultAddress="[email protected]" defaultName="John Doe 03" />
        <email name="SystemEmail04" source="UserName" username="Username04" />
        <email name="SystemEmail05" source="RoleName" rolename="administrators" defaultEmailName="SystemEmail02" />
        <email name="SystemEmailGroup01" source="Group">
          <add name="SystemEmail06" defaultAddress="[email protected]" defaultName="Jane Doe 06" /> 
          <!-- ^^^ LINE 736 ^^^ -->
          <add name="SystemEmail07" defaultAddress="[email protected]" defaultName="John Doe 07" />
          <add name="SystemEmail08" defaultAddress="[email protected]" defaultName="Jane Doe 08" />
        </email>
      </emails>
    </systemEmails>
  </systemEmailsGroup>
</configuration>

Note that I marked the line 736 mentioned in the error.

My class structure is below. The first inherits ConfigurationSection, the second inherits ConfigurationElementCollection, and the last inherits ConfigurationElement. After that is an extension class for the SystemEmailsElement type. It's probably not relevant to the error, but I will include it just for completion sake.

public class SystemEmailsConfiguration : ConfigurationSection
{
    public static SystemEmailsConfiguration GetConfig()
    {
        return ConfigurationManager.GetSection("systemEmailsGroup/systemEmails") as SystemEmailsConfiguration;
    }

    [ConfigurationProperty("emails", IsDefaultCollection = false)]
    [ConfigurationCollection(typeof(SystemEmailsElementCollection), 
        AddItemName = "email", ClearItemsName = "clear", RemoveItemName = "remove")]
    public SystemEmailsElementCollection Emails
    {
        get
        {
            return base["emails"] as SystemEmailsElementCollection;
        }
    }

}

public enum SystemEmailsSource
{
    None = 0,
    UserName = 1,
    RoleName = 2,
    Group = 3
}

public class SystemEmailsElementCollection : ConfigurationElementCollection, IEnumerable<SystemEmailsElement>
{
    List<SystemEmailsElement> _elements = new List<SystemEmailsElement>();

    public SystemEmailsElement this[int index]
    {
        get
        {
            return _elements[index];
        }
        set
        {
            if (index >= 0 && index < _elements.Count)
            {
                _elements.RemoveAt(index);
            }
            _elements.Insert(index, value);
        }
    }

    public SystemEmailsElement this[string key]
    {
        get
        {
            return _elements.Where(e => e.Name == key).FirstOrDefault();
        }
        set
        {
            var el = _elements.Where(e => e.Name == key).FirstOrDefault();
            if (el != null)
            {
                int index = _elements.IndexOf(el);
                _elements.RemoveAt(index);
                _elements.Insert(index, value);
            }
            else
                _elements.Add(value);
        }
    }

    protected override ConfigurationElement CreateNewElement()
    {
        var el = new SystemEmailsElement();
        _elements.Add(el);
        return el;
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return _elements.Find(e => e.Equals(element)).Name;
    }

    public new IEnumerator<SystemEmailsElement> GetEnumerator()
    {
        return _elements.GetEnumerator();
    }
}


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

    [ConfigurationProperty("source", DefaultValue = SystemEmailsSource.None)]
    public SystemEmailsSource Source
    {
        get
        {
            return (this["source"] != null) ? (SystemEmailsSource)this["source"] : SystemEmailsSource.None;
        }
    }

    [ConfigurationProperty("defaultAddress")]
    public string DefaultAddress
    {
        get
        {
            return this["defaultAddress"] as string;
        }
    }

    [ConfigurationProperty("defaultName")]
    public string DefaultName
    {
        get
        {
            return this["defaultName"] as string;
        }
    }

    [ConfigurationProperty("defaultEmailName")]
    public string DefaultEmailName
    {
        get
        {
            return this["defaultEmailName"] as string;
        }
    }

    [ConfigurationProperty("username")]
    public string UserName
    {
        get
        {
            return this["username"] as string;
        }
    }

    [ConfigurationProperty("rolename")]
    public string RoleName
    {
        get
        {
            return this["rolename"] as string;
        }
    }

    [ConfigurationProperty("email")]
    [ConfigurationCollection(typeof(SystemEmailsElementCollection), 
        AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
    public SystemEmailsElementCollection Emails
    {
        get
        {
            return base["email"] as SystemEmailsElementCollection;
        }
    }
}


public static class SystemEmailsElementExtenstions
{
    /// <summary>
    /// Returns true if the SystemEmailsElement may contain a group of email addresses.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static bool IsGroup(this SystemEmailsElement value)
    {
        return (value.Source == SystemEmailsSource.Group || value.Source == SystemEmailsSource.RoleName);
    }

    /// <summary>
    /// Gets the email or emails result of this section as a MailAddressCollection.
    /// Returns an empty MailAddressCollection if not able to create any MailAddress objects.
    /// </summary>
    /// <returns></returns>
    public static MailAddressCollection GetEmails(this SystemEmailsElement value)
    {
        var emails = new MailAddressCollection();
        MailAddress eml = null;

        if (value.IsGroup())
        {
            switch (value.Source)
            {
                case SystemEmailsSource.RoleName:
                    if (value.RoleName != null)
                    {
                        foreach (UsersNames user in AppPublic.GetUsersByRole(value.RoleName))
                        {
                            eml = AppPublic.GetEmailByUserID(user.UserId);
                            if (eml != null)
                                emails.Add(eml);
                        }
                    }
                    break;

                default: //Group
                    foreach (var el in value.Emails)
                    {
                        foreach (MailAddress e in el.GetEmails())
                            emails.Add(e);
                    }
                    break;
            }
        }
        else
        {
            emails.Add(value.GetEmail());
        }

        if (emails.Count == 0)
        {
            eml = value.CreateEmailFromDefaults();
            if (eml != null)
                emails.Add(eml);
        }
        return emails;
    }

    /// <summary>
    /// Gets the email result of this section or the first item in a group as a MailAddress object.
    /// Returns null if not able to create the MailAddress.
    /// </summary>
    /// <returns></returns>
    public static MailAddress GetEmail(this SystemEmailsElement value)
    {
        if (value.IsGroup())
        {
            return value.GetEmails().FirstOrDefault();
        }
        else
        {
            MailAddress eml = null;

            if (value.Source == SystemEmailsSource.UserName)
            {
                if (value.UserName != null)
                    eml = AppPublic.GetEmailByUserName(value.UserName);
            }

            if (eml == null)
                eml = CreateEmailFromDefaults(value);

            return eml;
        }
    }

    /// <summary>
    /// Creates a mail address object using the default attribute values from the SystemEmailElement.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    private static MailAddress CreateEmailFromDefaults(this SystemEmailsElement value)
    {
        MailAddress eml = null;

        //DefaultEmailName takes precedence.
        if (value.DefaultEmailName != null)
        {
            var el = SystemEmailsConfiguration.GetConfig().Emails[value.DefaultEmailName];
            if (el != null)
                eml = el.GetEmail();
        }

        //Fall back to DefaultAddress.
        if (eml == null && value.DefaultAddress != null)
        {
            if (value.DefaultName == null)
                eml = new MailAddress(value.DefaultAddress);
            else
                eml = new MailAddress(value.DefaultAddress, value.DefaultName);
        }

        return eml;
    }
}

In an ideal world, the <add> nodes would be renamed to <email> nodes, and this code would be recursive and allow any (theoretical) number of node depths.

But if I could just get the first <add> level working that would great. I've been spinning my wheels on this for hours trying to find the right combination of attributes, signatures, XML, etc.

Thanks!!

Upvotes: 1

Views: 770

Answers (1)

Evk
Evk

Reputation: 101493

To fix your immediate error, you just need to add wrapping <email> tag to your <add/> elements. That is because your SystemEmailsElement contains Emails property which is collection with root tag defined as "email" (via [ConfigurationProperty("email")]). Like this:

<systemEmails>
  <emails>
    <email name="SystemEmail01" defaultAddress="[email protected]" defaultName="John Doe 01" />
    <email name="SystemEmail02" source="UserName" username="Username02" defaultAddress="[email protected]" defaultName="Jane Doe 02" />
    <email name="SystemEmail03" defaultAddress="[email protected]" defaultName="John Doe 03" />
    <email name="SystemEmail04" source="UserName" username="Username04" />
    <email name="SystemEmail05" source="RoleName" rolename="administrators" defaultEmailName="SystemEmail02" />
    <email name="SystemEmailGroup01" source="Group">
      <email>
        <add name="SystemEmail06" defaultAddress="[email protected]" defaultName="Jane Doe 06" />
        <!-- ^^^ LINE 736 ^^^ -->
        <add name="SystemEmail07" defaultAddress="[email protected]" defaultName="John Doe 07" />
        <add name="SystemEmail08" defaultAddress="[email protected]" defaultName="Jane Doe 08" />
      </email>
    </email>
  </emails>
</systemEmails>

Now, to make it work like in ideal world, you can utilize IsDefaultCollection property of ConfigurationProperty attribute like this:

[ConfigurationProperty("", IsDefaultCollection = true)]
[ConfigurationCollection(typeof (SystemEmailsElementCollection),
    AddItemName = "email")]
public SystemEmailsElementCollection Emails
{
    get { return base[""] as SystemEmailsElementCollection; }
}

This will allow you to create recursive email nodes the way you would expect to:

  <emails>
    <email name="SystemEmail01" defaultAddress="[email protected]" defaultName="John Doe 01" />
    <email name="SystemEmail02" source="UserName" username="Username02" defaultAddress="[email protected]" defaultName="Jane Doe 02" />
    <email name="SystemEmail03" defaultAddress="[email protected]" defaultName="John Doe 03" />
    <email name="SystemEmail04" source="UserName" username="Username04" />
    <email name="SystemEmail05" source="RoleName" rolename="administrators" defaultEmailName="SystemEmail02" />
    <email name="SystemEmailGroup01" source="Group">
      <email name="SystemEmail06" defaultAddress="[email protected]" defaultName="Jane Doe 06">
        <email name="SystemEmail04" source="UserName" username="Username04" />
        <email name="SystemEmail05" source="RoleName" rolename="administrators" defaultEmailName="SystemEmail02" />
      </email>
      <!-- ^^^ LINE 736 ^^^ -->
      <email name="SystemEmail07" defaultAddress="[email protected]" defaultName="John Doe 07" />
      <email name="SystemEmail08" defaultAddress="[email protected]" defaultName="Jane Doe 08" />
    </email>
  </emails>

Upvotes: 2

Related Questions