/// <summary> /// Creates a dictionary that is populated with key/value pairs from a command line /// that supports syntax where options are provided in the form "/key=value". /// This method supports the ability to specify delimiter characters for options in /// the command line. /// </summary> /// <param name="arguments">Key/value pairs.</param> /// <param name="keyCharacter">A character that precedes a key.</param> /// <param name="valueCharacter">A character that separates a key from a value.</param> /// <returns></returns> public static CommandLineDictionary FromArguments(IEnumerable <string> arguments, char keyCharacter, char valueCharacter) { CommandLineDictionary cld = new CommandLineDictionary(); cld.KeyCharacter = keyCharacter; cld.ValueCharacter = valueCharacter; foreach (string argument in arguments) { cld.AddArgument(argument); } return(cld); }
/// <summary> /// Creates a string that represents key/value arguments for the properties of the /// specified object. For example, an object with a name (string) of "example" and a /// priority value (integer) of 1 translates to '/name=example /priority=1'. This /// can be used to send data structures through the command line. /// </summary> /// <param name="valueToConvert">Value to create key/value arguments from.</param> /// <returns>Space-delimited key/value arguments.</returns> public static string ToString(object valueToConvert) { IEnumerable <PropertyDescriptor> properties = TypeDescriptor.GetProperties(valueToConvert).Cast <PropertyDescriptor>(); IEnumerable <PropertyDescriptor> propertiesOnParent = TypeDescriptor.GetProperties(valueToConvert.GetType().BaseType).Cast <PropertyDescriptor>(); properties = properties.Except(propertiesOnParent); CommandLineDictionary commandLineDictionary = new CommandLineDictionary(); foreach (PropertyDescriptor property in properties) { commandLineDictionary[property.Name] = property.GetValue(valueToConvert).ToString(); } return(commandLineDictionary.ToString()); }
/// <summary> /// Sets properties on an object from a series of key/value string /// arguments that are in the form "/PropertyName=Value", where the /// value is converted from a string into the property type. /// </summary> /// <param name="valueToPopulate">The object to set properties on.</param> /// <param name="args">The key/value arguments describing the property names and values to set.</param> /// <returns> /// Indicates whether the properties were successfully set. Reasons for failure reasons include /// a property name that does not exist or a value that cannot be converted from a string. /// </returns> /// <exception cref="System.ArgumentException">Thrown when one of the key/value strings cannot be parsed into a property.</exception> public static void ParseArguments(this object valueToPopulate, IEnumerable <string> args) { CommandLineDictionary commandLineDictionary = CommandLineDictionary.FromArguments(args); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(valueToPopulate); // Ensure required properties are specified. foreach (PropertyDescriptor property in properties) { // See whether any of the attributes on the property is a RequiredAttribute. if (property.Attributes.Cast <Attribute>().Any(attribute => attribute is RequiredAttribute)) { // If so, and the command line dictionary doesn't contain a key matching // the property's name, it means that a required property isn't specified. if (!commandLineDictionary.ContainsKey(property.Name)) { throw new ArgumentException("A value for the " + property.Name + " property is required."); } } } foreach (KeyValuePair <string, string> keyValuePair in commandLineDictionary) { PropertyDescriptor property = properties[keyValuePair.Key]; if (property == null) { throw new ArgumentException("A matching property of name " + keyValuePair.Key + " on type " + valueToPopulate.GetType() + " could not be found."); } // If the value is null/empty and the property is a bool, we // treat it as a flag, which means its presence means true. if (String.IsNullOrEmpty(keyValuePair.Value) && (property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?))) { property.SetValue(valueToPopulate, true); continue; } object valueToSet; // We support a limited set of collection types. Setting a List<T> // is one of the most flexible types as it supports three different // interfaces, but the catch is that we don't support the concrete // Collection<T> type. We can expand it to support Collection<T> // in the future, but the code will get a bit uglier. switch (property.PropertyType.Name) { case "IEnumerable`1": case "ICollection`1": case "IList`1": case "List`1": MethodInfo methodInfo = typeof(CommandLineParser).GetMethod("FromCommaSeparatedList", BindingFlags.Static | BindingFlags.NonPublic); Type[] genericArguments = property.PropertyType.GetGenericArguments(); MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments); valueToSet = genericMethodInfo.Invoke(null, new object[] { keyValuePair.Value }); break; default: TypeConverter typeConverter = TypeDescriptor.GetConverter(property.PropertyType); if (typeConverter == null || !typeConverter.CanConvertFrom(typeof(string))) { throw new ArgumentException("Unable to convert from a string to a property of type " + property.PropertyType + "."); } valueToSet = typeConverter.ConvertFromInvariantString(keyValuePair.Value); break; } properties[keyValuePair.Key].SetValue(valueToPopulate, valueToSet); } return; }