Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Passing a set of name/value pairs to a service method

0.00/5 (No votes)
4 Jan 2012 2  
Presenting some custom code that serializes and deserializes a dictionary of name/value pairs.

Introduction

Occasionally the need arises to pass a set of name/value pairs from a client application to a service method (and/or perhaps, in the other direction). The preferred in-memory repository for name/value pairs is the Dictionary<string, string> class. However, the generic dictionary classes are not serializable via standard mechanisms, so a little custom code must be written. There is a "natural" invertible mapping from Dictionary<string, string> to a CSV (comma-separated value) string consisting of name-value nodes. Code to serialize and deserialize a Dictionary<string, string> instance in such a way that it can be conveniently passed between a client and service is presented in this article.

Background

Readers may be interested in the specific challenge that led to the problem and solution presented here.

An application needs to send email messages to users. Message body templates, containing replaceable fields, are stored in a database. I wanted to write a single SendEmail service method that would send messages, merging data sent by the application into arbitrary field sets. The specific fields for which data would need to be supplied by the application would be dependent on the message context, more specifically the ID or name of the message to be sent. For example, one email message might require replacement values for a set 'A' of five fields, with another email message requiring replacement values for a set 'B' of seven fields. Perhaps sets 'A' and 'B' would have one or more fields in common, but that need not be the case.

C# provides a lot of flexibility in passing arguments to methods, but in this case we need to pass an arbitrary set of name/value (or key/value) pairs. How can we do that? Creating a dictionary representing the name/value pairs on the client side and passing that object to the service method would seem to be a reasonable and convenient approach. The only problem with that strategy is that dictionaries aren't serialized automatically.

Using the code

The solution that's presented here uses explicit method calls to serialize and deserialize the name/value dictionary. It's left as an exercise for the reader to make the processes implicit, if it seems necessary or desirable to do so.

Two methods are required - one for serialization and one for deserialization. I chose to implement them as extension methods on the Dictionary<string, string> and string classes.

/// <summary>
/// Converts the subject Dictionary into a comma-separated-value string.
/// </summary>
/// <param name="valueByKey">The subject Dictionary.</param>
/// <param name="separator">(Optional) non-default separator character.</param>
/// <param name="quote">(Optional) quote character in which the value
///       members of name/value pairs should be wrapped.</param>
/// <returns>A comma-separated-value string that corresponds to the subject Dictionary.</returns>
internal static string ToCsv(this Dictionary<string, string> valueByKey, 
                char separator = ',', char quote = '\x00')
{
    StringBuilder sb = new StringBuilder();
    foreach (string key in valueByKey.Keys)
    {
        if (sb.Length > 0)
        {
            sb.Append(separator);
        }
        if (quote == '\x00')
        {
            sb.AppendFormat("{0}={1}", key, valueByKey[key]);
        }
        else
        {
            sb.AppendFormat("{0}={1}{2}{3}", key, quote, valueByKey[key], quote);
        }
    }
    return sb.ToString();
}

The serialization method (above) maps the dictionary data into a CSV string. Notice that two special provisions exist, the use of which is optional:

  1. A character other than the default comma may be selected as the separator character. This feature makes it easy to deal with embedded commas in the data.
  2. A character may be specified with which to quote the value strings, which can serve as a disambiguating function, similar to the first provision, and can make the resulting CSV string more readable to humans.

I have encountered the need for both of the disambiguating features, on occasion.

/// <summary>
/// Parses a comma-separated-value string into a Dictionary<string, string>.
/// The separator defaults to comma but a different separator can be specified.
/// </summary>
/// <param name="csv">Comma-separated-value string to be parsed.</param>
/// <param name="separator">(Optional) non-default separator character.</param>
/// <param name="quote">(Optional) quote character
///   to be trimmed from the value members of name/value pairs.</param>
/// <returns>A Dictionary<string, string> that corresponds to the subject string.</returns>
internal static Dictionary<string, string> ToValueByKey(this string csv, 
                                           char separator = ',', char quote = '\x00')
{
    Dictionary<string, string> resultDictionary = new Dictionary<string, string>();
    string[] pairs = csv.Split(separator);
    foreach (string pair in pairs)
    {
        string[] nameValue = pair.Split('=');
        if (nameValue.Length == 2)
        {
            resultDictionary.Add(nameValue[0], nameValue[1].TrimmedOf(quote.ToString()));
        }
    }
    return resultDictionary;
}

/// <summary>
/// Trims the specified character(s) from both ends of the subject string.
/// </summary>
/// <param name="subject">The subject string.</param>
/// <param name="charsToTrim">Character(s) to trim from both ends of the subject string.</param>
/// <returns>The subject string, trimmed of the specified character(s).</returns>
internal static string TrimmedOf(this string subject, string charsToTrim)
{
    return subject.TrimmedOf(charsToTrim, charsToTrim);
}

/// <summary>
/// Trims the specified character(s) from the left end,
/// and a possibly different set of characters from the right end,
/// of the subject string.
/// </summary>
/// <param name="subject">The subject string.</param>
/// <param name="charsToTrimLeft">Character(s) to trim from the left end of the subject string.</param>
/// <param name="charsToTrimRight">Character(s) to trim from the right end of the subject string.</param>
/// <returns>The subject string, trimmed of the specified character(s).</returns>
internal static string TrimmedOf(this string subject, string charsToTrimLeft, string charsToTrimRight)
{
    string result = subject;
    int index = 0;
    while (index < subject.Length && charsToTrimLeft.Contains(subject[index].ToString()))
    {
        ++index;
    }
    int lastIndex = subject.Length - 1;
    while (lastIndex >= index && charsToTrimRight.Contains(subject[lastIndex].ToString()))
    {
        --lastIndex;
    }
    if (lastIndex < index)
    {
        result = string.Empty;
    }
    else
    {
        result = result.Substring(index, lastIndex - index + 1);
    }
    return result;
}

The deserialization method creates a dictionary, identical to the original, from the CSV string. Options exist, corresponding to those provided by the serialization method, for recognizing a non-default separator character and for removing a specified quote character from the value substrings.

I'm also providing two overloads of a TrimmedOf method that's used to remove the quotes, if that feature is required. Of course, TrimmedOf could be useful in other situations, as well - so feel free to add it to your general string manipulation toolbox.

It's worth noting in passing that TrimmedOf provides for the possibility that different left and right quote characters might be used ('[' and ']', for example), although the serialization/deserialization methods do not extend that potential to their clients. Furthermore, TrimmedOf is a more general method (like a sharp multi-functional knife that can be used to remove peeling and seeds as well as stems, to use a vegan analogy) that just happens to be convenient for lopping off quote characters.

Also, you can't use \x00 (or \0) as a quote character - but I don't expect to receive any complaints about that restriction. :)

This is hardly "rocket science". It's just the sort of relatively mundane custom code that hard-working programmers write very frequently to get things done.

Nevertheless, the code is useful (thoroughly tested, and in actual use), and I hope that someone else will use it and benefit from it. Perhaps having it pre-written will save someone a few minutes of coding.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here