views:

428

answers:

2

I'd like to store a set of key/value pairs in the application settings of my ASP.NET web app, but I'm not finding a straightforward way to do that. For example, these two questions tell me that StringDictionary etc. won't serialize to XML and suggest that I'll have to roll my own implementation. But it seems like this should be easier to do; after all, web.config is XML and < applicationSettings> is essentially a collection of key/value pairs, so it feels like I'm missing something obvious. Given my specific case below, do I really have to roll my own serialization, or is there an easier workaround?

The web app in question is a basic contact form that sends email to different recipients based on the value of a parameter; e.g. http://www.examplesite.com/Contact.aspx?recipient=support would send email to [email protected].

The goal is to be able add or remove recipients (or change their addresses) by editing the web.config file so that I don't have to recompile and can easily maintain different configurations in test and production environments. For example:

// I can use something like this for the sender address
SmtpMsg.From = New MailAddress(My.Settings.EmailSender)

// And then just edit this part of web.config to use 
// different addresses in different environments.
<setting name="EmailSender" serializeAs="String">
 <value>[email protected]</value>
</setting>

// I want something like this for the recipients
SmtpMsg.To.Add(My.Settings.Recipients("support"))

// and presumably some sort of equivalent xml in web.config
// maybe something like this???
<recipients>
  <item name="support" serializeAs="String">
   <value>[email protected]</value>
  </item>
  <!-- add or remove item elements here -->
</recipients>

edit: replaced VB comments w/ C# comments because of the code-coloring

+2  A: 

You can do a couple things, though honestly I think this is easiest:

<appSettings>
    <add key="testValues" value="[email protected], [email protected], [email protected]" />
</appSettings>

Then you can get your object via:

String[] temp =
ConfigurationManager.AppSettings.GetValues("testValues").ToString().Split(',');

and then do a simple foreach statement to retrieve. You could even set this as a static object to be cached so it's a quicker retrieve. :)

Hope this helps,

JP

EDIT: An alternative scenario involves:

<appSettings file="test.config">
<!-- other settings to default to if test.config doesn't exist -->
</appSettings>

In this case, if you have a test.config file existing in your test environment, the AppSettings.GetValues() call will be made against that file. If the test.config file does not exist, the ConfigurationManager class will use the values within the appSettings node in your web.config file.

Jonathan
Maybe I'm misunderstanding, but that looks like it would give me multiple values for a single key; I'm looking for multiple keys, each with a single value.
Matt
Then if you want multiple keys with a single value, just use multiple add key lines and name them appropriately.From your explanation it seemed that you wanted multiple values per key. The overall idea is held that you can use a test.config file on your test environment and the default values (because test.config won't exist in production) will work when test.config does not exist.
Jonathan
OK, now I get it about the keys (should've thought that through before I commented); and the file attribute does seem handy for substituting test values.
Matt
+3  A: 

The simplist way would obviously be to just drop them in the app settings, but it wouldn't be very neat:

<appSettings>
  <add key="EmailSupport" value="[email protected]" />
  <add key="EmailSales" value="[email protected]" />
</appSettings>

Then in your code you're just doing something like:

if (!string.IsNullOrEmpty(Request["recipient"])) {
  string recipientEmail = 
         WebConfigurationManager.AppSettings["Email" + Request["recipient"]];
  // Send your email to recipientEmail
}

If you want to be a bit neater, you can create a custom Configuration Section like this (C# I'm afraid, but the docs have VB as well):

namespace EmailSystem {
  public class EmailRecipientsSection : ConfigurationSection {
    [ConfigurationProperty("emailSender", IsRequired = true, IsKey = false)]
    public string EmailSender {
        get { return (string)this["name"]; }
        set { this["name"] = value; }
    }

    [ConfigurationProperty("emailRecipients", IsDefaultCollection = true)]
    public EmailRecipientCollection EmailRecipients {
      get {
        var emailRecipientCollection = 
              (EmailRecipientCollection) base["emailRecipients"];
        return emailRecipientCollection;
      }
    }
  }

  public class EmailRecipientCollection : ConfigurationElementCollection {
    public EmailRecipientElement this[int index] {
      get { return (EmailRecipientElement) BaseGet(index); }
      set {
        if (BaseGet(index) != null) {
          BaseRemoveAt(index);
        }
        BaseAdd(index, value);
      }
    }

    public new EmailRecipientElement this[string name] {
      get { return (EmailRecipientElement) BaseGet(name); }
    }

    protected override ConfigurationElement CreateNewElement() {
      return new EmailRecipientElement();
    }

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

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

    [ConfigurationProperty("emailAddress", IsRequired = true)]
    public string EmailAddress {
      get { return (string) this["emailAddress"]; }
      set { this["emailAddress"] = value; }
    }
  }
}

Then in your web.config have something like this:

<configSections>
  [...]
  <section name="EmailSystem" type="EmailSystem, AssmeblyName" />
</configSections>

<EmailSystem emailSender="[email protected]">
  <emailRecipients>
    <clear />
    <add name="Support" emailAddress="[email protected]" />
    <add name="Sales" emailAddress="[email protected]" />
  </emailRecipients>
</EmailSystem>

Then you can call into this:

emailRecipient = Request["recipient"];

var emailSystem = ConfigurationManager.GetSection("EmailSystem")
                    as EmailRecipientsSection;

string recipientEmail = emailSystem.EmailRecipients[emailRecipient].emailAddress;

// send email to recipientEmail.
Zhaph - Ben Duguid
This is an almost perfect answer. It has a quick-and-easy solution that's good enough (i.e. just use <appSettings>) as well as a more thorough solution that addresses the root of my question (i.e. collections in web.config). Also great sample code. If I could vote this up again, I would. Thank you.
Matt
No probs - the comment is thanks enough :) - I guess if I'd ported the samples to VB, it would have been perfect ;)
Zhaph - Ben Duguid