Our ReflectionHelper class (and related classes used by it) are monsters, so I'll just post the relevant methods here:
Code:
public static class ReflectionHelper
{
...
public static void SetPropertyValue(object instance, string propertyName, object value)
{
ArgumentHelper.RequireValue("instance", instance);
PropertyInfo property = GetProperty(instance.GetType(), propertyName, BindingFlags.Instance);
if (property == null)
{
throw new MissingMemberException(instance.GetType().Name, propertyName);
}
try
{
SetPropertyValueCore(instance, property, value);
}
catch (Exception x)
{
throw new MemberAccessException(
string.Format(
"Failed to set property {0}.{1} to '{2}' on instance '{3}'",
instance.GetType().Name,
propertyName,
value ?? "<null>",
instance.ToString()),
x);
}
}
public static PropertyInfo GetProperty(Type searchType, string propertyName, BindingFlags bindingFlags)
{
return GetProperty(searchType, propertyName, bindingFlags, false);
}
public static PropertyInfo GetProperty(Type searchType, string propertyName, BindingFlags bindingFlags, bool throwExceptionIfMissing)
{
ArgumentHelper.RequireValue("searchType", searchType);
ArgumentHelper.RequireValue("propertyName", propertyName);
bindingFlags |= BindingFlags.Public;
bindingFlags |= BindingFlags.NonPublic;
bindingFlags |= BindingFlags.DeclaredOnly;
while (searchType != null)
{
foreach (PropertyInfo property in searchType.GetProperties(bindingFlags))
{
if (property.Name == propertyName)
{
return property;
}
}
if (searchType.IsInterface)
{
PropertyInfo inheritedInterfaceProperty;
foreach (Type subInterfaceType in searchType.GetInterfaces())
{
inheritedInterfaceProperty = GetProperty(
subInterfaceType, propertyName, bindingFlags);
if (inheritedInterfaceProperty != null)
{
return inheritedInterfaceProperty;
}
}
}
else
{
searchType = searchType.BaseType;
}
}
if (throwExceptionIfMissing)
{
throw new MissingMemberException(searchType.FullName, propertyName);
}
return null;
}
private static void SetPropertyValueCore(
object instance,
PropertyInfo property,
object value)
{
try
{
property.SetValue(instance, value, null);
}
catch (InvalidCastException)
{
object convertedValue = Convert.ChangeType(value, property.PropertyType);
property.SetValue(instance, convertedValue, null);
}
}
}
/// <summary>
/// A helper class for enforcing requirements on method and property arguments.
/// </summary>
/// <remarks>
/// Using this class allows you to write concise, declarative-style code
/// for enforcing argument requirements. The methods provided cover the large
/// majority of such needs, freeing your code of "if/then" clutter while providing
/// consistent exception types and messages.
/// </remarks>
public static class ArgumentHelper
{
/// <summary>
/// Requires an argument to implement a given type
/// (throwing an exception if it does not).
/// </summary>
/// <param name="requiredType">The type that the argument must implement.</param>
/// <param name="argumentName">The actual name of the argument or property.</param>
/// <param name="argumentValue">The value to check.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramRef>argumentValue</paramRef> does not implement
/// <paramRef>requiredType</paramRef>.
/// </exception>
public static void RequireType(
Type requiredType, string argumentName, object argumentValue)
{
RequireValue("requiredType", requiredType);
RequireValue("argumentName", argumentName);
if (argumentValue != null)
{
RequireType(requiredType, argumentName, argumentValue.GetType());
}
}
/// <summary>
/// Requires an argument to implement a given type
/// (throwing an exception if it does not).
/// </summary>
/// <param name="requiredType">The type that the argument must implement.</param>
/// <param name="argumentName">The actual name of the argument or property.</param>
/// <param name="argumentType">The type to check.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramRef>argumentType</paramRef> does not implement
/// <paramRef>requiredType</paramRef>.
/// </exception>
public static void RequireType(
Type requiredType, string argumentName, Type argumentType)
{
RequireValue("requiredType", requiredType);
RequireValue("argumentName", argumentName);
RequireValue("argumentType", argumentType);
if (!requiredType.IsAssignableFrom(argumentType))
{
string argumentTypeName;
string requiredTypeName;
if (argumentType.FullName != requiredType.FullName)
{
argumentTypeName = argumentType.FullName;
requiredTypeName = requiredType.FullName;
}
else if (argumentType.AssemblyQualifiedName != requiredType.AssemblyQualifiedName)
{
argumentTypeName = argumentType.AssemblyQualifiedName;
requiredTypeName = requiredType.AssemblyQualifiedName;
}
else
{
argumentTypeName = string.Format(
CultureInfo.CurrentCulture,
"{0} loaded from {1}",
argumentType.AssemblyQualifiedName,
SubstituteWhereEmpty(argumentType.Assembly.Location, "(unknown)"));
requiredTypeName = string.Format(
CultureInfo.CurrentCulture,
"{0} loaded from {1}",
requiredType.AssemblyQualifiedName,
SubstituteWhereEmpty(requiredType.Assembly.Location, "(unknown)"));
}
throw new ArgumentOutOfRangeException(argumentName, argumentType,
string.Format(
"Argument '{0}' type '{1}' is not assignable to type '{2}'.",
argumentName,
argumentTypeName,
requiredTypeName));
}
}
/// <summary>
/// Requires an argument to have a value
/// (throwing an exception if it does not).
/// </summary>
/// <param name="argumentName">The actual name of the argument or property.</param>
/// <param name="argumentValue">The collection to check.</param>
/// <exception cref="ArgumentNullException">
/// <paramRef>argumentName</paramRef> or <paramRef>argumentValue</paramRef>
/// is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentNotValuedException">
/// <paramRef>argumentName</paramRef> or <paramRef>argumentValue</paramRef>
/// has a trimmed length of zero.
/// </exception>
public static void RequireValue(string argumentName, object argumentValue)
{
if (argumentValue == null)
{
throw new ArgumentNullException(argumentName);
}
else
{
// TODO: make an IRequireValue interface for classes and services.
// Then provide a way to register these interfaces by type,
// and automatically register the two below.
//
// That way, this front-end service can always be used,
// without know any details of how to determine if a "value" is present
// (some types can be "unvalued" even when not null.)
// It will also allow for when a separate service must be used,
// such as the case for strings, because we can't subclass string
// to put an HasValue() method on it ...
switch (argumentValue.GetType().FullName)
{
case "System.String":
TextHelper.RequireValue(argumentName, argumentValue);
break;
case "System.Text.StringBuilder":
TextHelper.RequireValue(argumentName, argumentValue.ToString());
break;
}
}
}
/// <summary>
/// Requires a collection have at least one element
/// (throwing an exception if it does not).
/// </summary>
/// <param name="argumentName">The actual name of the argument or property.</param>
/// <param name="argumentValue">The collection to check.</param>
/// <exception cref="ArgumentNullException">
/// <paramRef>argumentName</paramRef> or <paramRef>argumentValue</paramRef>
/// is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentNotValuedException">
/// <paramRef>argumentValue</paramRef> has no elements,
/// or <paramRef>argumentName</paramRef> or <paramRef>argumentValue</paramRef>
/// has a trimmed length of zero.
/// </exception>
public static void RequireNonEmptyCollection(
string argumentName, ICollection argumentValue)
{
RequireValue(argumentName, argumentValue);
if (argumentValue.Count == 0)
{
throw new ArgumentNotValuedException(argumentName, argumentValue,
"The collection must have at least one element.");
}
}
/// <summary>
/// Return either the specified value, or its substitute if empty.
/// </summary>
/// <param name="valueToTry">The value to return, if not empty.</param>
/// <param name="valueToSubstitute">The value to return if
/// <paramref>valueToTry</paramref> is empty.</param>
/// <returns><paramref>valueToTry</paramref> if not empty,
/// otherwise <paramref>valueToSubstitute</paramref>.</returns>
/// <remarks>
/// Values of <b>Null</b> <see cref="DBNull.Value"/> are considered empty.
/// Blank strings or strings containing only whitespace are also considered empty.
/// </remarks>
public static object SubstituteWhereEmpty(object valueToTry, object valueToSubstitute)
{
return (TextHelper.HasValue(valueToTry) ? valueToTry : valueToSubstitute);
}
}
/// <summary>
/// Indicates that an argument, while not null, is still "unvalued"
/// in some way specific to the argument type, that makes it invalid
/// for use by the receiving method or property.
/// </summary>
/// <example>
/// <see cref="ArgumentHelper.RequireValue">
/// ArgumentHelper.RequireValue</see> requires arguments of type String and
/// StringBuilder to contain values with non-zero trimmed lengths.
/// <see cref="ArgumentHelper.RequireNonEmptyCollection">
/// ArgumentHelper.RequireNonEmptyCollection</see> requires specified
/// collections to have at least one element.
/// Both of these methods throw this type of exception
/// if their requirements are not met.
/// </example>
///
[Serializable]
public class ArgumentNotValuedException : ArgumentOutOfRangeException
{
public ArgumentNotValuedException(
string argumentName, object value, string message)
: base(argumentName, value, message)
{
}
public ocArgumentNotValuedException()
: base()
{
}
public ArgumentNotValuedException(string message, Exception innerException)
: base(message, innerException)
{
}
public ArgumentNotValuedException(string argumentName)
: base(argumentName)
{
}
protected ArgumentNotValuedException(
SerializationInfo info,
StreamingContext context)
: base(info, context)
{
}
}
public static class TextHelper
{
...
public static void RequireValue(string argumentName, object argumentValue)
{
if (argumentValue == null)
{
throw new ArgumentNullException(argumentName);
}
if (!HasValue(argumentValue))
{
throw new ArgumentNotValuedException(argumentName, argumentValue,
"The argument must have a non-zero trimmed length.");
}
}
/// <summary>
/// Determines if a value is usable (non-<b>null</b> and non-blank).
/// </summary>
/// <param name="value">The value to check.</param>
/// <returns>
/// <b>true</b> if <paramref>value</paramref> is non-<b>null</b> and non-blank,
/// otherwise <b>false</b>.
/// </returns>
public static bool HasValue(object value)
{
return (NullToBlankString(value).Trim().Length > 0);
}
public static string NullToBlankString(object parameter)
{
object cleanedParameter = DbNullToNull(parameter);
return (cleanedParameter == null ? string.Empty : cleanedParameter.ToString());
}
public static object DbNullToNull(object parameter)
{
object result;
if (parameter != null && parameter.GetType() == System.DBNull.Value.GetType())
{
result = null;
}
else
{
result = parameter;
}
return result;
}
}