C# Equivalent of PHP http_build_query - c#

I need to pass some data to a PHP page on a server from my C# client using HttpWebRequest. The expected data according to the documentation is an array of arrays, something like this:
$postData = array(
'label1' => 'myLabel',
'label2' => array(
'label2_1' => 3
'label2_2' => array(
'label2_2_1' => 3
)
)
);
The structure above is just an example. It can be very complicated and the structure itself is not constant.
In PHP there is a function named http_build_query which serializes these PHP nested arrays to a simple string, which can be sent as the data of a HTTP POST request. The problem is I need to call this PHP page from my C# application. I would like to represent these nested arrays either as nested Dictionary<string, object>s, or anonymous types.
How can I do that? What rules does http_build_query follow to produce its output string?
There is a very similar question Converting PHP array of arrays to C#, which does not solve my problem, unfortunately. The accepted answer recommends a solution for a fixed structure, the second one does not work at all.

Well, there doesn't seem to be anything built-in to .NET to allow you to do this. However, if you want to re-implement the PHP behavior in .NET, you can either white-box implement it by taking a look at the PHP source code, or black-box implement it by reading the PHP documentation of http_build_query and testing the function out on various inputs.
I took a black-box approach and created the following class:
/// <summary>
/// Helps up build a query string by converting an object into a set of named-values and making a
/// query string out of it.
/// </summary>
public class QueryStringBuilder
{
private readonly List<KeyValuePair<string, object>> _keyValuePairs
= new List<KeyValuePair<string, object>>();
/// <summary> Builds the query string from the given instance. </summary>
public static string BuildQueryString(object queryData, string argSeperator = "&")
{
var encoder = new QueryStringBuilder();
encoder.AddEntry(null, queryData, allowObjects: true);
return encoder.GetUriString(argSeperator);
}
/// <summary>
/// Convert the key-value pairs that we've collected into an actual query string.
/// </summary>
private string GetUriString(string argSeperator)
{
return String.Join(argSeperator,
_keyValuePairs.Select(kvp =>
{
var key = Uri.EscapeDataString(kvp.Key);
var value = Uri.EscapeDataString(kvp.Value.ToString());
return $"{key}={value}";
}));
}
/// <summary> Adds a single entry to the collection. </summary>
/// <param name="prefix"> The prefix to use when generating the key of the entry. Can be null. </param>
/// <param name="instance"> The instance to add.
///
/// - If the instance is a dictionary, the entries determine the key and values.
/// - If the instance is a collection, the keys will be the index of the entries, and the value
/// will be each item in the collection.
/// - If allowObjects is true, then the object's properties' names will be the keys, and the
/// values of the properties will be the values.
/// - Otherwise the instance is added with the given prefix to the collection of items. </param>
/// <param name="allowObjects"> true to add the properties of the given instance (if the object is
/// not a collection or dictionary), false to add the object as a key-value pair. </param>
private void AddEntry(string prefix, object instance, bool allowObjects)
{
var dictionary = instance as IDictionary;
var collection = instance as ICollection;
if (dictionary != null)
{
Add(prefix, GetDictionaryAdapter(dictionary));
}
else if (collection != null)
{
Add(prefix, GetArrayAdapter(collection));
}
else if (allowObjects)
{
Add(prefix, GetObjectAdapter(instance));
}
else
{
_keyValuePairs.Add(new KeyValuePair<string, object>(prefix, instance));
}
}
/// <summary> Adds the given collection of entries. </summary>
private void Add(string prefix, IEnumerable<Entry> datas)
{
foreach (var item in datas)
{
var newPrefix = String.IsNullOrEmpty(prefix)
? item.Key
: $"{prefix}[{item.Key}]";
AddEntry(newPrefix, item.Value, allowObjects: false);
}
}
private struct Entry
{
public string Key;
public object Value;
}
/// <summary>
/// Returns a collection of entries that represent the properties on the object.
/// </summary>
private IEnumerable<Entry> GetObjectAdapter(object data)
{
var properties = data.GetType().GetProperties();
foreach (var property in properties)
{
yield return new Entry()
{
Key = property.Name,
Value = property.GetValue(data)
};
}
}
/// <summary>
/// Returns a collection of entries that represent items in the collection.
/// </summary>
private IEnumerable<Entry> GetArrayAdapter(ICollection collection)
{
int i = 0;
foreach (var item in collection)
{
yield return new Entry()
{
Key = i.ToString(),
Value = item,
};
i++;
}
}
/// <summary>
/// Returns a collection of entries that represent items in the dictionary.
/// </summary>
private IEnumerable<Entry> GetDictionaryAdapter(IDictionary collection)
{
foreach (DictionaryEntry item in collection)
{
yield return new Entry()
{
Key = item.Key.ToString(),
Value = item.Value,
};
}
}
}
The code is pretty self-explanatory, but it accepts a dictionary, an array, or an object. If it's an top-level object, it serialized the properties. If it's an array, each element is serialized with the appropriate array index. If it's a dictionary, the key/values are serialized. Arrays and Dictionary-values that contain other arrays or dictionaries are flattened, similar to PHPs behavior.
For example, the following:
QueryStringBuilder.BuildQueryString(new
{
Age = 19,
Name = "John&Doe",
Values = new object[]
{
1,
2,
new Dictionary<string, string>()
{
{ "key1", "value1" },
{ "key2", "value2" },
}
},
});
// 0=1&1=2&2%5B0%5D=one&2%5B1%5D=two&2%5B2%5D=three&3%5Bkey1%5D=value1&3%5Bkey2%5D=value2
QueryStringBuilder.BuildQueryString(new object[]
{
1,
2,
new object[] { "one", "two", "three" },
new Dictionary<string, string>()
{
{ "key1", "value1" },
{ "key2", "value2" },
}
}
);
Generates:
Age=19&Name=John%26Doe&Values%5B0%5D=1&Values%5B1%5D=2&Values%5B2%5D%5Bkey1%5D=value1&Values%5B2%5D%5Bkey2%5D=value2
which is:
Age=19&Name=John%26Doe&Values[0]=1&Values[1]=2&Values[2][key1]=value1&Values[2][key2]=value2
Age=19
Name=John&Doe
Values[0]=1
Values[1]=2
Values[2][key1]=value1
Values[2][key2]=value2

Using NameValueCollection you could do this:
private string ToQueryString(NameValueCollection queryData)
{
var array = (from key in queryData.AllKeys
from value in queryData.GetValues(key)
select string.Format(CultureInfo.InvariantCulture, "{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value)))
.ToArray();
return "?" + string.Join("&", array);
}

Related

Get property names of a object as a Dictionary<string,string> in .net

I working on a string tokenizer function.
This is the sample object
var obj= new
{
Order= new
{
CustomerName = "John",
OrderTotal= "10.50",
Qty =2,
Address= new
{
Street= "Park Road",
Country= "UAS"
}
}
};
I need to get property vs values onto Dictionary<string, string>
expecting result :
<Order.CustomerName,"John">
<Order.OrderTotal,"10.50">
<Order.Qty ,"2">
<Order.CustomerName.Address.Street,"Park Road">
<Order.CustomerName.Address.Country,"USA">
This is how I tried
private Dictionary<string, string> GetValueByPropertyToken(object obj)
{
var result = new Dictionary<string, string>();
// get the type:
var objType = obj.GetType();
// iterate the properties
var prop = (from property in objType.GetProperties() select property).ToList();
foreach (var item in prop)
{
var name = item.Name;
var nextProperty = item?.GetValue(obj);
}
return result;
}
You can achieve this via recursion and reflection:
Extension.cs class:
public static class Extensions
{
/// <summary>
/// Extension method for object to explode to dictionary.
/// </summary>
/// <param name="values"></param>
/// <returns>Dictionary with key-value pairs for prefix and primite values</returns>
public static IDictionary<string, object> ToDictionary(this object values)
{
var dict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
GetValues("", values, dict);
return dict;
}
/// <summary>
/// Recursively go through obj and explode into key-value pairs for prefix + value
/// </summary>
/// <param name="prefix">Prefix for key</param>
/// <param name="obj">The object to explode</param>
/// <param name="dict">The resulting dictionary</param>
private static void GetValues(string prefix, object obj, Dictionary<string, object> dict)
{
var properties = TypeDescriptor.GetProperties(obj);
// Base case
if (properties.Count == 0) return;
// Go through all children objects
foreach (PropertyDescriptor property in properties)
{
// Get next object and prefix
var nextObject = property.GetValue(obj);
string nextPrefix = (String.IsNullOrEmpty(prefix) ? property.Name : $"{prefix}.{property.Name}");
// If it´s generic we continue down the object
// If it´s primitive we add it to the dictionary
if (nextObject.GetType().IsGenericType)
GetValues(nextPrefix, nextObject, dict);
else
dict.Add(nextPrefix, nextObject);
}
}
}
Program.cs class:
private static void Main(string[] args)
{
var obj = new
{
Order = new
{
CustomerName = "John",
OrderTotal = "10.50",
Qty = 2,
Address = new
{
Street = "Park Road",
Country = "UAS"
}
}
};
var dict = obj.ToDictionary();
foreach(string key in dict.Keys)
{
Console.WriteLine($"{key}: {dict[key]}");
}
}
The output:
Order.CustomerName: John
Order.OrderTotal: 10.50
Order.Qty: 2
Order.Address.Street: Park Road
Order.Address.Country: UAS
Be careful that this code probably won´t handle more complicated scenarios involving arrays and other complicated data structures. You will have to expand on this code to fulfill your needs.
firslty, you did not add dictionary object (result), secondly for sub element you have to call it again.
public Dictionary<string,string> GetDictionaryByObject(object obj)
{
{
var dict = new Dictionary<string, string>();
foreach (var prop in obj.GetType().GetProperties())
{
if (prop.PropertyType.IsPrimitive || prop.PropertyType == typeof(string))
{
dict.Add(prop.Name, prop.GetValue(obj).ToString());
}
else
{
var subDict = GetDictionaryByObject(prop.GetValue(obj));
foreach (var subProp in subDict)
{
dict.Add($"{prop.Name}.{subProp.Key}", subProp.Value);
}
}
}
return dict;
}
}
Result:

how to flatten nested tuples in c#?

I have nested tuples like - Tuple<Tuple<Tuple<string,string>, string>, string>.
I want to flatten them like Tuple<string,string,string,string>. I see that is can be done in f#. Is there a version of - F# flatten nested tuples - in c#
You can convert your Tuple to the flat list with recursion and Deep-First Search. Try this method:
public static IEnumerable<object> DFS(object t)
{
var type = t.GetType();
if (type.FullName?.StartsWith("System.Tuple") != true) // or check inheritanse from ITuple
yield return t;
var items = type.GetProperties()
.Where(p => p.Name.StartsWith("Item"))
.Select(p => p.GetValue(t))
.ToArray();
foreach (var item in items)
{
foreach (var innerItem in DFS(item))
{
yield return innerItem;
}
}
}
You can use it like this:
var input = Tuple.Create(Tuple.Create(Tuple.Create("a0", "a1"), "a2"), "b", "c");
var items = DFS(input).ToArray();
// items[2] would be "a2"
Please note that reflection may slow down your app so try to avoid it while it is possible
Assuming you insist in using this rather obnoxious design, here is how you could do it:
/// <summary>
/// Constructs a tuple our of an array of arguments
/// </summary>
/// <typeparam name="T">The type of argument.</typeparam>
/// <param name="values">The values.</param>
/// <returns></returns>
public static object ConstructTuple<T>(params T[] values)
{
Type genericType = Type.GetType("System.Tuple`" + values.Length);
Type[] typeArgs = values.Select(_ => typeof(T)).ToArray();
Type specificType = genericType.MakeGenericType(typeArgs);
object[] constructorArguments = values.Cast<object>().ToArray();
return Activator.CreateInstance(specificType, constructorArguments);
}
/// <summary>
/// Flattens a tupple into an enumeration using reflection.
/// </summary>
/// <typeparam name="T">The type of objects the nested tuple contains.</typeparam>
/// <param name="tuple">The tuple to flatten.</param>
/// <returns></returns>
public static IEnumerable<T> FlattenTupple<T>(object tuple)
{
List<T> items = new List<T>();
var type = tuple.GetType();
if (type.GetInterface("ITuple") == null)
throw new ArgumentException("This is not a tuple!");
foreach (var property in type.GetProperties())
{
var value = property.GetValue(tuple);
if (property.PropertyType.GetInterface("ITuple") != null)
{
var subItems = FlattenTupple<T>(value);
items.AddRange(subItems);
}
else
{
items.Add((T)value);
}
}
return items;
}
Sample usage:
Tuple<Tuple<Tuple<string, string>, string>, string> tuple =
new Tuple<Tuple<Tuple<string, string>, string>, string>(new Tuple<Tuple<string, string>, string>(new Tuple<string, string>("value1", "value2"), "value2b"), "value2c");
var items = FlattenTupple<string>(tuple);
var flattened = ConstructTuple(items.ToArray());
Console.WriteLine(flattened);
Output:
(value1, value2, value2b, value2c)
Sample 2 (integers):
Tuple<Tuple<Tuple<int, int>, int>, int> intTuple =
new Tuple<Tuple<Tuple<int, int>, int>, int>(new Tuple<Tuple<int, int>, int>(new Tuple<int, int>(1, 2), 3), 4);
var intItems = FlattenTupple<int>(intTuple);
var flattened2 = ConstructTuple(intItems.ToArray());
Console.WriteLine(flattened2);
Output:
(1, 2, 3, 4)
If you have a single item then create a converter which is easier to manage especially if you have a List eventually.
public Tuple<string, string, string, string> ConvertSomething(Tuple<Tuple<Tuple<string,string>, string>, string> original)
{
return new Tuple<string, string, string, string>
(
original.Item1.Item1.Item1,
original.Item1.Item1.Item2,
original.Item1.Item2,
original.Item2
);
}
Please note that Tuple is not meant to be modify along the way. If you do need it, it mean you actually have a very bad design. Best solution remain to rethink how things work.

How to verify in a foreach loop whether the element is last in the collection?

I have a foreach loop in which I need to verify whether the element is last in the collection I'm iterating on. What I've tried:
foreach (var character in list) // list is of type `string`
{
if (character == list.Last())
{
}
}
But in this case if I have "la la la" the if statement will execute on a second character.
Question: How to write the if statement so that it will execute when accessing the last element of a sequence?
I would advise iterating using the index rather than the object reference i.e.
for (int i = 0; i <= list.Count-1; i++)
{
if (i == list.Count-1)
{
// so something special with last item
}
}
foreach (var character in list) // list is of type `string`
{
if (character == list[list.Count - 1])
{
}
}
Here is a DEMO.
As an alternative, since List implements IEnumerable interface, you can use Enumerable.Last method
Returns the last element of a sequence.
foreach (var character in list) // list is of type `string`
{
if (character == list.Last())
{
}
}
Here is a DEMO.
Since your list is actually a string, you need to convert it into a list.
var elementList = list.Split(" ");
You can then find the last element.
var lastElement = elementList.LastOrDefault();
Just check using IsNullOrEmpty to handle the case of an empty list.
If you only want to do an action on the last character, then Mare Infinitus' code should do the trick.
What about:
var elementList = list.Split(" ");
if (elementList.Last().Equals(character))
{
// do something here
}
this should do it, no need for foreach.
However, if you want to loop and do a specific action for the last character, then you can use a standard for loop. James' answer:
for (int i = 0; i <= list.Count-1; i++)
{
if (i == list.Count-1)
{
// so something special with last item
}
}
If you find yourself doing this kind of thing often, you can use an extension method which will let you ask if an element in a sequence is the last item.
This was originally written by Jon Skeet; he called it "Smart Enumerable", and I believe it is part of the MoreLinq Linq Extensions (also by Jon Skeet).
If you do use such a thing, your code would looks something like this:
foreach (var character in list.AsSmartEnumerable())
if (character.IsLast)
// Do something with character.Value
Here's a slightly modified copy of Jon Skeet's implementation:
/// <summary>
/// Static class to make creation easier. If possible though, use the extension
/// method in SmartEnumerableExt.
/// </summary>
public static class SmartEnumerable
{
/// <summary> method to make life easier.</summary>
/// <typeparam name="T">Type of enumerable</typeparam>
/// <param name="source">Source enumerable</param>
/// <returns>A new SmartEnumerable of the appropriate type</returns>
public static SmartEnumerable<T> Create<T>(IEnumerable<T> source)
{
return new SmartEnumerable<T>(source);
}
}
/// <summary>Wrapper methods for SmartEnumerable[T].</summary>
public static class SmartEnumerableExt
{
/// <summary>Extension method to make life easier.</summary>
/// <typeparam name="T">Type of enumerable</typeparam>
/// <param name="source">Source enumerable</param>
/// <returns>A new SmartEnumerable of the appropriate type</returns>
public static SmartEnumerable<T> AsSmartEnumerable<T>(this IEnumerable<T> source)
{
return new SmartEnumerable<T>(source);
}
}
/// <summary>
/// Type chaining an IEnumerable<T> to allow the iterating code
/// to detect the first and last entries simply.
/// </summary>
/// <typeparam name="T">Type to iterate over</typeparam>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="This is too general to end in 'Collection'")]
public class SmartEnumerable<T>: IEnumerable<SmartEnumerable<T>.Entry>
{
/// <summary>Enumerable to which we proxy</summary>
readonly IEnumerable<T> _enumerable;
/// <summary>Constructor.</summary>
/// <param name="enumerable">Collection to enumerate. Must not be null.</param>
public SmartEnumerable(IEnumerable<T> enumerable)
{
if (enumerable==null)
{
throw new ArgumentNullException("enumerable");
}
this._enumerable = enumerable;
}
/// <summary>
/// Returns an enumeration of Entry objects, each of which knows
/// whether it is the first/last of the enumeration, as well as the
/// current value.
/// </summary>
public IEnumerator<Entry> GetEnumerator()
{
using (IEnumerator<T> enumerator = _enumerable.GetEnumerator())
{
if (!enumerator.MoveNext())
{
yield break;
}
bool isFirst = true;
bool isLast = false;
int index = 0;
while (!isLast)
{
T current = enumerator.Current;
isLast = !enumerator.MoveNext();
yield return new Entry(isFirst, isLast, current, index++);
isFirst = false;
}
}
}
/// <summary>Non-generic form of GetEnumerator.</summary>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Represents each entry returned within a collection,
/// containing the value and whether it is the first and/or
/// the last entry in the collection's. enumeration
/// </summary>
public class Entry
{
internal Entry(bool isFirst, bool isLast, T value, int index)
{
this._isFirst = isFirst;
this._isLast = isLast;
this._value = value;
this._index = index;
}
/// <summary>The value of the entry.</summary>
public T Value
{
get
{
return _value;
}
}
/// <summary>Whether or not this entry is first in the collection's enumeration.</summary>
public bool IsFirst
{
get
{
return _isFirst;
}
}
/// <summary>Whether or not this entry is last in the collection's enumeration.</summary>
public bool IsLast
{
get
{
return _isLast;
}
}
/// <summary>The 0-based index of this entry (i.e. how many entries have been returned before this one)</summary>
public int Index
{
get
{
return _index;
}
}
readonly bool _isFirst;
readonly bool _isLast;
readonly T _value;
readonly int _index;
}
}
If you only have an IEnumerable, you have to manually trigger the enumerator. Here is a sample extension method which maybe helps you:
public static class IEnumerableExtensions
{
public static void Action<T>(this IEnumerable<T> source, Action<T> sequenceElement, Action<T> lastElement)
{
if (source == null)
throw new ArgumentNullException("source");
if (sequenceElement == null)
throw new ArgumentNullException("sequenceElement");
if (lastElement == null)
throw new ArgumentNullException("lastElement");
T element = default(T);
using (var enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
element = enumerator.Current;
while (enumerator.MoveNext())
{
sequenceElement(element);
element = enumerator.Current;
}
lastElement(element);
}
}
}
You can then call it this way:
var myElements = Enumerable.Range(1, 10);
myElements.Action(value => Console.WriteLine("Something out of the sequence: " + value),
value => Console.WriteLine("The last element: " + value));

DataContractJsonSerializer produces list of hashes instead of hash

I would expect a Dictionary object of the form:
var dict = new Dictionary<string,string>()
{
{"blah", "bob"},
{"blahagain", "bob"}
};
to serialize into JSON in the form of:
{ "blah": "bob", "blahagain": "bob" }
NOT
[ { "key": "blah", "value": "bob" }, { "key": "blahagain", "value": "bob"}]
What is the reason for what appears to be a monstrosity of a generic attempt at serializing collections?
The DataContractJsonSerializer uses the ISerializable interface to produce this thing. It seems to me as though somebody has taken the XML output from ISerializable and mangled this thing out of it.
Is there a way to override the default serialization used by .Net here? Could I just derive from Dictionary and override the Serialization methods?
Posting to hear of any caveats or suggestions people might have.
Well, I resolved to sidestep the DataContract serialization for Dictionary objects.
I create an two virtual methods in my root object.
public virtual void prepareForSerialization();
public virtual void postDeserialize();
Then specify string DataMember class properties for each dictionary attribute of my classes. The strings become serialized and the Dictionary is no longer serialized directly.
[DataMember]
public string dictionaryString;
public Dictionary<int, string> dict;
Then, when my code calls serialize, it additionally first calls prepareForSerialization.
public override void prepareForSerialization() {
base.prepareForSerialization();
}
public override void postDeSerialize() {
base.postDeSerialize();
}
Derived classes with Dictionary members will then call my own serializer for the Dictionary.
Note: this is a simple serialization that suits my purposes. YMMV.
Javascript ticks credit to another stackoverfow post. Forget which one.
/// <summary>
/// Extension methods needed to implement Javascript dates for C#
/// </summary>
public static class MyExtensions{
public static double JavascriptTicks(this DateTime dt) {
DateTime d1 = new DateTime(1970, 1, 1);
DateTime d2 = dt.ToUniversalTime();
TimeSpan ts = new TimeSpan(d2.Ticks - d1.Ticks);
return ts.TotalMilliseconds;
}
}
/// <summary>
/// Serialize a single value
/// </summary>
/// <param name="o">An object to serialize</param>
/// <returns>A JSON string of the value</returns>
if (o is string) {
return string.Format("\"{0}\"", o);
} else if (o is DateTime) {
return string.Format("new Date({0})", ((DateTime)o).JavascriptTicks()); ;
} else if(o.GetType().IsValueType) {
return o.ToString();
} else {
//Here you want a check of the form if is IMySerializer, then call your prepare before
//using the .Net one.
DataContractJsonSerializer json = new DataContractJsonSerializer(o.GetType());
using(MemoryStream ms = new MemoryStream())
using (StreamReader sr = new StreamReader(ms)) {
json.WriteObject(ms, o);
ms.Position = 0;
return sr.ReadToEnd();
}
}
/// <summary>
/// Serializes a List object into JSON
/// </summary>
/// <param name="list">The IList interface into the List</param>
/// <returns>A JSON string of the list</returns>
public string SerializeList(IList list) {
string result = null;
if (list != null) {
IEnumerator it = list.GetEnumerator();
StringBuilder sb = new StringBuilder();
long len = list.Count;
sb.Append("[");
while (it.MoveNext()) {
if (it.Current is IList) {
sb.Append(SerializeList((IList)it.Current));
} else if (it.Current is IDictionary) {
sb.Append(SerializeDictionary((IDictionary)it.Current));
} else {
sb.Append(SerializeValue(it.Current));
}
--len;
if (len > 0) sb.Append(",");
}
sb.Append("]");
result = sb.ToString();
}
return result;
}
/// <summary>
/// Returns a stringified key of the object
/// </summary>
/// <param name="o">The key value</param>
/// <returns></returns>
public string SerializeKey(object o) {
return string.Format("\"{0}\"", o);
}
/// <summary>
/// Serializes a dictionary into JSON
/// </summary>
/// <param name="dict">The IDictionary interface into the Dictionary</param>
/// <returns>A JSON string of the Dictionary</returns>
public string SerializeDictionary(IDictionary dict) {
string result = null;
if (dict != null) {
IDictionaryEnumerator it = dict.GetEnumerator();
StringBuilder sb = new StringBuilder();
long len = dict.Count;
sb.Append("{");
while (it.MoveNext()) {
sb.Append(SerializeKey(it.Key));
sb.Append(":");
if (it.Value is IList) {
sb.Append(SerializeList((IList)it.Value));
} else if (it.Value is IDictionary) {
sb.Append(SerializeDictionary((IDictionary)it.Value));
} else {
sb.Append(SerializeValue(it.Value));
}
--len;
if (len > 0) sb.Append(",");
}
sb.Append("}");
result = sb.ToString();
}
return result;
}

Get the Enum<T> value Description

I have my enumHelper class that contains these:
public static IList<T> GetValues()
{
IList<T> list = new List<T>();
foreach (object value in Enum.GetValues(typeof(T)))
{
list.Add((T)value);
}
return list;
}
and
public static string Description(Enum value)
{
Attribute DescAttribute = LMIGHelper.GetAttribute(value, typeof(DescriptionAttribute));
if (DescAttribute == null)
return value.ToString();
else
return ((DescriptionAttribute)DescAttribute).Description;
}
my enum is something like:
public enum OutputType
{
File,
[Description("Data Table")]
DataTable
}
So far so good. All the previous work fine.
Now I want to add a new helper to return BindingList>, so I can link any enum to any combo using
BindingList<KeyValuePair<OutputType, string>> list = Enum<OutputType>.GetBindableList();
cbo.datasource=list;
cbo.DisplayMember="Value";
cbo.ValueMember="Key";
For that I added:
public static BindingList<KeyValuePair<T, string>> GetBindingList()
{
BindingList<KeyValuePair<T, string>> list = new BindingList<KeyValuePair<T, string>>();
foreach (T value in Enum<T>.GetValues())
{
string Desc = Enum<T>.Description(value);
list.Add(new KeyValuePair<T, string>(value, Desc));
}
return list;
}
But "Enum.Description(value)" is not even compiling:
Argument '1': cannot convert from 'T' to 'System.Enum'
How can I do that? Is that even possible?
Thank you.
Take a look at this article. You can do this using the System.ComponentModel.DescriptionAttribute or creating your own attribute:
/// <summary>
/// Provides a description for an enumerated type.
/// </summary>
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field,
AllowMultiple = false)]
public sealed class EnumDescriptionAttribute : Attribute
{
private string description;
/// <summary>
/// Gets the description stored in this attribute.
/// </summary>
/// <value>The description stored in the attribute.</value>
public string Description
{
get
{
return this.description;
}
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="EnumDescriptionAttribute"/> class.
/// </summary>
/// <param name="description">The description to store in this attribute.
/// </param>
public EnumDescriptionAttribute(string description)
: base()
{
this.description = description;
}
}
You then need to decorate the enum values with this new attribute:
public enum SimpleEnum
{
[EnumDescription("Today")]
Today,
[EnumDescription("Last 7 days")]
Last7,
[EnumDescription("Last 14 days")]
Last14,
[EnumDescription("Last 30 days")]
Last30,
[EnumDescription("All")]
All
}
All of the "magic" takes place in the following extension methods:
/// <summary>
/// Provides a static utility object of methods and properties to interact
/// with enumerated types.
/// </summary>
public static class EnumHelper
{
/// <summary>
/// Gets the <see cref="DescriptionAttribute" /> of an <see cref="Enum" />
/// type value.
/// </summary>
/// <param name="value">The <see cref="Enum" /> type value.</param>
/// <returns>A string containing the text of the
/// <see cref="DescriptionAttribute"/>.</returns>
public static string GetDescription(this Enum value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
string description = value.ToString();
FieldInfo fieldInfo = value.GetType().GetField(description);
EnumDescriptionAttribute[] attributes =
(EnumDescriptionAttribute[])
fieldInfo.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
description = attributes[0].Description;
}
return description;
}
/// <summary>
/// Converts the <see cref="Enum" /> type to an <see cref="IList" />
/// compatible object.
/// </summary>
/// <param name="type">The <see cref="Enum"/> type.</param>
/// <returns>An <see cref="IList"/> containing the enumerated
/// type value and description.</returns>
public static IList ToList(this Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
ArrayList list = new ArrayList();
Array enumValues = Enum.GetValues(type);
foreach (Enum value in enumValues)
{
list.Add(new KeyValuePair<Enum, string>(value, GetDescription(value)));
}
return list;
}
}
Finally, you can then simply bind the combobox:
combo.DataSource = typeof(SimpleEnum).ToList();
You should change:
public static string Description(Enum value)
{
...
}
to
public static string Description(T value)
{
...
}
so it accepts a value of the enumeration. Now here is where it gets tricky: you have a value, but attributes decorate the field which holds the value.
You actually need to reflect over the enumeration's fields and check the value of each against the value you've been given (results should be cached for performance):
foreach(var field in typeof(T).GetFields())
{
T fieldValue;
try
{
fieldValue = (T) field.GetRawConstantValue();
}
catch(InvalidOperationException)
{
// For some reason, one of the fields returned is {Int32 value__},
// which throws an InvalidOperationException if you try and retrieve
// its constant value.
//
// I am unsure how to check for this state before
// attempting GetRawConstantValue().
continue;
}
if(fieldValue == value)
{
var attribute = LMIGHelper.GetAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
return attribute == null ? value.ToString() : attribute.Description;
}
}
Edit addressing the follow-up question
The FillComboFromEnum method is missing the type parameter for the enum. Try this:
public static void FillComboFromEnum<T>(ComboBox Cbo, BindingList<KeyValuePair<T, string>> List) where T : struct
Notice I constrained the type to be a struct. It's not a full enumeration constraint, but it's closer than nothing.
Enum doesn't have a Description() method. The best you could do is have your enum implement an interface that has the Description() method. If you do that, then you can have
public static BindingList<KeyValuePair<T extends _interface_, String>> getBindingList()
and then inside of that you can refer to
T foo = ...?
foo.Description(...);

Categories