Dictionary<Guid, Guid> mapping from appsettings.json in C# - c#

Working with VS 2017 on console application. When I use Dictionary<string, Guid> all works fine, however, when I use Dictionary<Guid, Guid> it doesn't map to AppSettings object (dict count is 0). Is there any way around it, rather then using string and converting it to Guid in the application?
appsetting.json
{
"AppSettings": {
"DictionaryTest": {
"55789653-86A3-485C-95E8-5E23C3219130": "74F87895-4965-447A-B07F-F702573218B7",
"FB1E7891-B3C2-4E83-A6BF-7F321C11BFFA": "FA7892A9-7925-4150-82A7-5DD3213D1242"
}
}
}
in Program.cs
var appPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).Replace(#"file:\", "");
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile($#"{appPath}\appsettings.json");
Configuration = builder.Build();
appSettings = new AppSettings();
Configuration.GetSection("AppSettings").Bind(appSettings);
When I use Dictionary<string, Guid> scenario there are 2 items in appSettings.DictionaryTest variable.
But when I use Dictionary<Guid, Guid> scenario there are 0 items in appSettings.DictionaryTest variable.
Dictionary<string, Guid> scenario:
AppSettings.cs
public class AppSettings
{
public Dictionary<string, Guid> DictionaryTest { get; set; }
}
Dictionary<Guid, Guid> scenario:
AppSettings.cs
public class AppSettings
{
public Dictionary<Guid, Guid> DictionaryTest { get; set; }
}

At the moment, it appears that the Configuration Binder doesn't support Guid as a key type. The only supported types are string and Enum types.
This is actually stated in the source code:
if (keyType != typeof(string) && !keyTypeIsEnum)
{
// We only support string and enum keys
return;
}
But I don't think it is documented anywhere else.

While Kevin Eaton is correct and the official package doesn't support this, nothing stops you from shamelessly copying that file with the Extension methods and with just a couple of new lines of code, implement this yourself
The below works
//Install Nuget Package Microsoft.Extensions.Configuration.Binder
//Install Nuget Package Microsoft.Extensions.Configuration.Json
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder().AddJsonFile($"appsettings.json", true, true);
var configuration = builder.Build();
var appSettings = new AppSettings();
var configurationSection = configuration.GetSection("AppSettings");
configurationSection.BindWhichSupportsGuidAsKey(appSettings);
Console.WriteLine("Finished!");
}
}
public class AppSettings
{
public Dictionary<Guid, Guid> DictionaryTest { get; set; }
}
}
All I did was
Renamed the class to CustomConfigurationBinderExtesions so that the methods don't clash
Renamed the Bind method to BindWhichSupportsGuidAsKey
Change if (keyType != typeof(string) && !keyTypeIsEnum) to
if (keyType != typeof(string) && keyType != typeof(Guid) && !keyTypeIsEnum)
Added this
else if (keyType == typeof(Guid))
{
Guid key = Guid.Parse(child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
Below is the full file(The original is here)
(You can remove most of the code here and keep only what you need but I'm too lazy to do that :))
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// Static helper class that allows binding strongly typed objects to configuration values.
/// </summary>
public static class CustomConfigurationBinderExtesions
{
private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <typeparam name="T">The type of the new instance to bind.</typeparam>
/// <param name="configuration">The configuration instance to bind.</param>
/// <returns>The new instance of T if successful, default(T) otherwise.</returns>
//public static T Get<T>(this IConfiguration configuration)
// => configuration.Get<T>(_ => { });
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <typeparam name="T">The type of the new instance to bind.</typeparam>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
/// <returns>The new instance of T if successful, default(T) otherwise.</returns>
//public static T Get<T>(this IConfiguration configuration, Action<BinderOptions> configureOptions)
//{
// if (configuration == null)
// {
// throw new ArgumentNullException(nameof(configuration));
// }
// object result = configuration.Get(typeof(T), configureOptions);
// if (result == null)
// {
// return default(T);
// }
// return (T)result;
//}
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="type">The type of the new instance to bind.</param>
/// <returns>The new instance if successful, null otherwise.</returns>
//public static object Get(this IConfiguration configuration, Type type)
// => configuration.Get(type, _ => { });
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="type">The type of the new instance to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
/// <returns>The new instance if successful, null otherwise.</returns>
public static object Get(this IConfiguration configuration, Type type, Action<BinderOptions> configureOptions)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
var options = new BinderOptions();
configureOptions?.Invoke(options);
return BindInstance(type, instance: null, config: configuration, options: options);
}
/// <summary>
/// Attempts to bind the given object instance to the configuration section specified by the key by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="key">The key of the configuration section to bind.</param>
/// <param name="instance">The object to bind.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, string key, object instance)
=> configuration.GetSection(key).BindWhichSupportsGuidAsKey(instance);
/// <summary>
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="instance">The object to bind.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, object instance)
=> configuration.BindWhichSupportsGuidAsKey(instance, o => { });
/// <summary>
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="instance">The object to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, object instance, Action<BinderOptions> configureOptions)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (instance != null)
{
var options = new BinderOptions();
configureOptions?.Invoke(options);
BindInstance(instance.GetType(), instance, configuration, options);
}
}
/// <summary>
/// Extracts the value with the specified key and converts it to type T.
/// </summary>
/// <typeparam name="T">The type to convert the value to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <returns>The converted value.</returns>
public static T GetValue<T>(this IConfiguration configuration, string key)
{
return GetValue(configuration, key, default(T));
}
/// <summary>
/// Extracts the value with the specified key and converts it to type T.
/// </summary>
/// <typeparam name="T">The type to convert the value to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <param name="defaultValue">The default value to use if no value is found.</param>
/// <returns>The converted value.</returns>
public static T GetValue<T>(this IConfiguration configuration, string key, T defaultValue)
{
return (T)GetValue(configuration, typeof(T), key, defaultValue);
}
/// <summary>
/// Extracts the value with the specified key and converts it to the specified type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="type">The type to convert the value to.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <returns>The converted value.</returns>
public static object GetValue(this IConfiguration configuration, Type type, string key)
{
return GetValue(configuration, type, key, defaultValue: null);
}
/// <summary>
/// Extracts the value with the specified key and converts it to the specified type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="type">The type to convert the value to.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <param name="defaultValue">The default value to use if no value is found.</param>
/// <returns>The converted value.</returns>
public static object GetValue(this IConfiguration configuration, Type type, string key, object defaultValue)
{
IConfigurationSection section = configuration.GetSection(key);
string value = section.Value;
if (value != null)
{
return ConvertValue(type, value, section.Path);
}
return defaultValue;
}
private static void BindNonScalar(this IConfiguration configuration, object instance, BinderOptions options)
{
if (instance != null)
{
foreach (PropertyInfo property in GetAllProperties(instance.GetType()))
{
BindProperty(property, instance, configuration, options);
}
}
}
private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options)
{
// We don't support set only, non public, or indexer properties
if (property.GetMethod == null ||
(!options.BindNonPublicProperties && !property.GetMethod.IsPublic) ||
property.GetMethod.GetParameters().Length > 0)
{
return;
}
object propertyValue = property.GetValue(instance);
bool hasSetter = property.SetMethod != null && (property.SetMethod.IsPublic || options.BindNonPublicProperties);
if (propertyValue == null && !hasSetter)
{
// Property doesn't have a value and we cannot set it so there is no
// point in going further down the graph
return;
}
propertyValue = BindInstance(property.PropertyType, propertyValue, config.GetSection(property.Name), options);
if (propertyValue != null && hasSetter)
{
property.SetValue(instance, propertyValue);
}
}
private static object BindToCollection(Type type, IConfiguration config, BinderOptions options)
{
Type genericType = typeof(List<>).MakeGenericType(type.GenericTypeArguments[0]);
object instance = Activator.CreateInstance(genericType);
BindCollection(instance, genericType, config, options);
return instance;
}
// Try to create an array/dictionary instance to back various collection interfaces
private static object AttemptBindToCollectionInterfaces(Type type, IConfiguration config, BinderOptions options)
{
if (!type.IsInterface)
{
return null;
}
Type collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyList<>), type);
if (collectionInterface != null)
{
// IEnumerable<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyDictionary<,>), type);
if (collectionInterface != null)
{
Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(type.GenericTypeArguments[0], type.GenericTypeArguments[1]);
object instance = Activator.CreateInstance(dictionaryType);
BindDictionary(instance, dictionaryType, config, options);
return instance;
}
collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
if (collectionInterface != null)
{
object instance = Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(type.GenericTypeArguments[0], type.GenericTypeArguments[1]));
BindDictionary(instance, collectionInterface, config, options);
return instance;
}
collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyCollection<>), type);
if (collectionInterface != null)
{
// IReadOnlyCollection<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
// ICollection<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(IEnumerable<>), type);
if (collectionInterface != null)
{
// IEnumerable<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
return null;
}
private static object BindInstance(Type type, object instance, IConfiguration config, BinderOptions options)
{
// if binding IConfigurationSection, break early
if (type == typeof(IConfigurationSection))
{
return config;
}
var section = config as IConfigurationSection;
string configValue = section?.Value;
object convertedValue;
Exception error;
if (configValue != null && TryConvertValue(type, configValue, section.Path, out convertedValue, out error))
{
if (error != null)
{
throw error;
}
// Leaf nodes are always reinitialized
return convertedValue;
}
if (config != null && config.GetChildren().Any())
{
// If we don't have an instance, try to create one
if (instance == null)
{
// We are already done if binding to a new collection instance worked
instance = AttemptBindToCollectionInterfaces(type, config, options);
if (instance != null)
{
return instance;
}
instance = CreateInstance(type);
}
// See if its a Dictionary
Type collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
if (collectionInterface != null)
{
BindDictionary(instance, collectionInterface, config, options);
}
else if (type.IsArray)
{
instance = BindArray((Array)instance, config, options);
}
else
{
// See if its an ICollection
collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
BindCollection(instance, collectionInterface, config, options);
}
// Something else
else
{
BindNonScalar(config, instance, options);
}
}
}
return instance;
}
private static object CreateInstance(Type type)
{
if (type.IsInterface || type.IsAbstract)
{
throw new InvalidOperationException("SR.Format(SR.Error_CannotActivateAbstractOrInterface, type)");
}
if (type.IsArray)
{
if (type.GetArrayRank() > 1)
{
throw new InvalidOperationException("SR.Format(SR.Error_UnsupportedMultidimensionalArray, type)");
}
return Array.CreateInstance(type.GetElementType(), 0);
}
if (!type.IsValueType)
{
bool hasDefaultConstructor = type.GetConstructors(DeclaredOnlyLookup).Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
if (!hasDefaultConstructor)
{
throw new InvalidOperationException("SR.Format(SR.Error_MissingParameterlessConstructor, type)");
}
}
try
{
return Activator.CreateInstance(type);
}
catch (Exception ex)
{
throw new InvalidOperationException("SR.Format(SR.Error_FailedToActivate, type), ex");
}
}
private static void BindDictionary(object dictionary, Type dictionaryType, IConfiguration config, BinderOptions options)
{
// IDictionary<K,V> is guaranteed to have exactly two parameters
Type keyType = dictionaryType.GenericTypeArguments[0];
Type valueType = dictionaryType.GenericTypeArguments[1];
bool keyTypeIsEnum = keyType.IsEnum;
if (keyType != typeof(string) && keyType != typeof(Guid) && !keyTypeIsEnum)
{
// We only support string and enum keys
return;
}
PropertyInfo setter = dictionaryType.GetProperty("Item", DeclaredOnlyLookup);
foreach (IConfigurationSection child in config.GetChildren())
{
object item = BindInstance(
type: valueType,
instance: null,
config: child,
options: options);
if (item != null)
{
if (keyType == typeof(string))
{
string key = child.Key;
setter.SetValue(dictionary, item, new object[] { key });
}
else if (keyType == typeof(Guid))
{
Guid key = Guid.Parse(child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
else if (keyTypeIsEnum)
{
object key = Enum.Parse(keyType, child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
}
}
}
private static void BindCollection(object collection, Type collectionType, IConfiguration config, BinderOptions options)
{
// ICollection<T> is guaranteed to have exactly one parameter
Type itemType = collectionType.GenericTypeArguments[0];
MethodInfo addMethod = collectionType.GetMethod("Add", DeclaredOnlyLookup);
foreach (IConfigurationSection section in config.GetChildren())
{
try
{
object item = BindInstance(
type: itemType,
instance: null,
config: section,
options: options);
if (item != null)
{
addMethod.Invoke(collection, new[] { item });
}
}
catch
{
}
}
}
private static Array BindArray(Array source, IConfiguration config, BinderOptions options)
{
IConfigurationSection[] children = config.GetChildren().ToArray();
int arrayLength = source.Length;
Type elementType = source.GetType().GetElementType();
var newArray = Array.CreateInstance(elementType, arrayLength + children.Length);
// binding to array has to preserve already initialized arrays with values
if (arrayLength > 0)
{
Array.Copy(source, newArray, arrayLength);
}
for (int i = 0; i < children.Length; i++)
{
try
{
object item = BindInstance(
type: elementType,
instance: null,
config: children[i],
options: options);
if (item != null)
{
newArray.SetValue(item, arrayLength + i);
}
}
catch
{
}
}
return newArray;
}
private static bool TryConvertValue(Type type, string value, string path, out object result, out Exception error)
{
error = null;
result = null;
if (type == typeof(object))
{
result = value;
return true;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (string.IsNullOrEmpty(value))
{
return true;
}
return TryConvertValue(Nullable.GetUnderlyingType(type), value, path, out result, out error);
}
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (converter.CanConvertFrom(typeof(string)))
{
try
{
result = converter.ConvertFromInvariantString(value);
}
catch (Exception ex)
{
error = new InvalidOperationException("SR.Format(SR.Error_FailedBinding, path, type), ex");
}
return true;
}
if (type == typeof(byte[]))
{
try
{
result = Convert.FromBase64String(value);
}
catch (FormatException ex)
{
error = new InvalidOperationException("SR.Format(SR.Error_FailedBinding, path, type), ex");
}
return true;
}
return false;
}
private static object ConvertValue(Type type, string value, string path)
{
object result;
Exception error;
TryConvertValue(type, value, path, out result, out error);
if (error != null)
{
throw error;
}
return result;
}
private static Type FindOpenGenericInterface(Type expected, Type actual)
{
if (actual.IsGenericType &&
actual.GetGenericTypeDefinition() == expected)
{
return actual;
}
Type[] interfaces = actual.GetInterfaces();
foreach (Type interfaceType in interfaces)
{
if (interfaceType.IsGenericType &&
interfaceType.GetGenericTypeDefinition() == expected)
{
return interfaceType;
}
}
return null;
}
private static IEnumerable<PropertyInfo> GetAllProperties(Type type)
{
var allProperties = new List<PropertyInfo>();
do
{
allProperties.AddRange(type.GetProperties(DeclaredOnlyLookup));
type = type.BaseType;
}
while (type != typeof(object));
return allProperties;
}
}
}

Try getting DictionaryTest as JSON and converting it to a Dictionary<Guid, Guid> manually with a JSON library. I wouldn't doubt if the conversion from appSettings isn't designed to handle that type of cast out of the box. Newtonsoft I know for sure can convert from JSON to a dictionary with a Guid key though. Right now I'm thinking something like this (using newtonsoft):
public class AppSettings
{
public JObject DictionaryTest { get; set; }
public Dictionary<Guid, Guid> DictionaryTestConvert
{
get { return JObject.toObject<Dictionary<Guid, Guid>>(DictionaryTestJson); }
}
}

Related

Custom serialization of enums in MongoDB

Referencing this SO question about custom serialization of strings to enums and vice versa in Json.NET, decorating the enum members using the EnumMember attribute - is there a way to get MongoDB to perform the same feat?
I have just refactored some previously string fields to enums and was wondering if there is any way to instruct Mongo to also read the EnumMember values when (de-)serializing and avoid me having to go through the database and update all the current text values.
i´m using package: PackageReference Include="MongoDB.Bson" Version="2.12.1"
my map class:
public class OfferMap
{
public static void Configure()
{
BsonClassMap.RegisterClassMap<Offer>(map => //Offer is a class
{
map.AutoMap();
map.SetIgnoreExtraElements(true);
map
.SetIsRootClass(true);
map
.MapMember(x => x.OfferType)
.SetSerializer(new EnumSerializer<OfferType>(MongoDB.Bson.BsonType.String)) // OfferType is an Enum
.SetElementName("offerType")
.SetIgnoreIfNull(false)
.SetIsRequired(true);
I used a CustomEnumSerializer to handle the EnumMember attribute
public class CustomEnumSerializer<TEnum> : StructSerializerBase<TEnum>, IRepresentationConfigurable<CustomEnumSerializer<TEnum>> where TEnum : struct
{
private static readonly Dictionary<Type, Dictionary<string, object>> _fromValueMap = new Dictionary<Type, Dictionary<string, object>>(); // string representation to Enum value map
private static readonly Dictionary<Type, Dictionary<object, string>> _toValueMap = new Dictionary<Type, Dictionary<object, string>>(); // Enum value to string map
// private fields
private readonly BsonType _representation;
// constructors
/// <summary>
/// Initializes a new instance of the <see cref="EnumSerializer{TEnum}"/> class.
/// </summary>
public CustomEnumSerializer()
: this((BsonType)0) // 0 means use underlying type
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EnumSerializer{TEnum}"/> class.
/// </summary>
/// <param name="representation">The representation.</param>
public CustomEnumSerializer(BsonType representation)
{
switch (representation)
{
case 0:
case BsonType.Int32:
case BsonType.Int64:
case BsonType.String:
break;
default:
var message = string.Format("{0} is not a valid representation for an EnumSerializer.", representation);
throw new ArgumentException(message);
}
// don't know of a way to enforce this at compile time
var enumTypeInfo = typeof(TEnum).GetTypeInfo();
if (!enumTypeInfo.IsEnum)
{
var message = string.Format("{0} is not an enum type.", typeof(TEnum).FullName);
throw new BsonSerializationException(message);
}
_representation = representation;
if (representation == BsonType.String)
{
var enumType = typeof(TEnum);
if (!_fromValueMap.ContainsKey(enumType))
{
Dictionary<string, object> fromMap = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
Dictionary<object, string> toMap = new Dictionary<object, string>();
FieldInfo[] fields = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (FieldInfo field in fields)
{
string name = field.Name;
object enumValue = Enum.Parse(enumType, name);
// use EnumMember attribute if exists
EnumMemberAttribute enumMemberAttrbiute = field.GetCustomAttribute<EnumMemberAttribute>();
if (enumMemberAttrbiute != null)
{
string enumMemberValue = enumMemberAttrbiute.Value;
fromMap[enumMemberValue] = enumValue;
toMap[enumValue] = enumMemberValue;
}
else
{
toMap[enumValue] = name;
}
fromMap[name] = enumValue;
}
_fromValueMap[enumType] = fromMap;
_toValueMap[enumType] = toMap;
}
}
}
// public properties
/// <summary>
/// Gets the representation.
/// </summary>
/// <value>
/// The representation.
/// </value>
public BsonType Representation
{
get { return _representation; }
}
// public methods
/// <summary>
/// Deserializes a value.
/// </summary>
/// <param name="context">The deserialization context.</param>
/// <param name="args">The deserialization args.</param>
/// <returns>A deserialized value.</returns>
public override TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonReader = context.Reader;
var bsonType = bsonReader.GetCurrentBsonType();
switch (bsonType)
{
case BsonType.Int32: return (TEnum)Enum.ToObject(typeof(TEnum), bsonReader.ReadInt32());
case BsonType.Int64: return (TEnum)Enum.ToObject(typeof(TEnum), bsonReader.ReadInt64());
case BsonType.Double: return (TEnum)Enum.ToObject(typeof(TEnum), (long)bsonReader.ReadDouble());
case BsonType.String:
var fromValue = FromValue(typeof(TEnum), bsonReader.ReadString());
return (TEnum)Enum.Parse(typeof(TEnum), fromValue.ToString());
default:
throw CreateCannotDeserializeFromBsonTypeException(bsonType);
}
}
/// <summary>
/// Serializes a value.
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="args">The serialization args.</param>
/// <param name="value">The object.</param>
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnum value)
{
var bsonWriter = context.Writer;
switch (_representation)
{
case 0:
var underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(typeof(TEnum)));
if (underlyingTypeCode == TypeCode.Int64 || underlyingTypeCode == TypeCode.UInt64)
{
goto case BsonType.Int64;
}
else
{
goto case BsonType.Int32;
}
case BsonType.Int32:
bsonWriter.WriteInt32(Convert.ToInt32(value));
break;
case BsonType.Int64:
bsonWriter.WriteInt64(Convert.ToInt64(value));
break;
case BsonType.String:
var val = ToValue(typeof(TEnum), value);
bsonWriter.WriteString(val);
break;
default:
throw new BsonInternalException("Unexpected EnumRepresentation.");
}
}
private string ToValue(Type enumType, object obj)
{
Dictionary<object, string> map = _toValueMap[enumType];
return map[obj];
}
private object FromValue(Type enumType, string value)
{
Dictionary<string, object> map = _fromValueMap[enumType];
if (!map.ContainsKey(value))
return value;
return map[value];
}
/// <summary>
/// Returns a serializer that has been reconfigured with the specified representation.
/// </summary>
/// <param name="representation">The representation.</param>
/// <returns>The reconfigured serializer.</returns>
public CustomEnumSerializer<TEnum> WithRepresentation(BsonType representation)
{
if (representation == _representation)
{
return this;
}
else
{
return new CustomEnumSerializer<TEnum>(representation);
}
}
// explicit interface implementations
IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
{
return WithRepresentation(representation);
}
}
I needed a custom deserializer that would return the default value when encountering an unexpected value in the data, rather than the default behavior of throwing a deserialization exception.
public class CustomEnumSerializer<TEnum>: MongoDB.Bson.Serialization.Serializers.EnumSerializer<TEnum>
where TEnum : struct, IComparable, IFormattable, IConvertible
{
public override TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonReader = context.Reader;
var bsonType = bsonReader.GetCurrentBsonType();
var val = "";
switch (bsonType)
{
case BsonType.String:
val = bsonReader.ReadString() ?? "";
break;
case BsonType.Int32:
val = bsonReader.ReadInt32().ToString();
break;
case BsonType.Int64:
val = bsonReader.ReadInt64().ToString();
break;
case BsonType.Null:
return default(TEnum);
default:
return base.Deserialize(context, args);
}
if(Enum.TryParse(val, true, out TEnum result)){
return result;
}
return default(TEnum);
}
}
To implement it in your repository:
static MyRepository()
{
BsonClassMap.RegisterClassMap<MyDataType>(ms =>
{
ms.AutoMap();
ms.GetMemberMap(i => i.MyEnum)
.SetSerializer(new CustomEnumSerializer<MyEnumType>());
});
}

OData Delta Patch Security

I have a working PATCH for my user class with Delta in Web API 2. By using the .patch method I can easily detect only the changes that were sent over and then update accordingly, rather than have to receive the entire user!
The problem is there are several fields that I want to protect so they are never updated.
I saw one example on SO but it didn't leverage Delta rather seemed to be slightly more dated and practically wrote all of the patch code by hand. Is there not a way to easily tell OData's patch to skip over properties you designate (maybe I need to override patch and tell it to avoid some properties)?
How would I even begin to go about doing this (or what should I search for / research to get started)? Do action filters / validation have a role here? Do I look into model binding? Is it overriding patch?
Thanks!
Depending on what you want to do if someone tries to update protected fields you can either:
Update only fields that can be modified. For this you can construct new Delta with only these fields like this:
Delta<User> filteredDelta = new Delta<User>();
if (originalDelta.GetChangedPropertyNames().Contains("FirstName"))
{
filteredDelta.TrySetPropertyValue("FirstName", originalDelta.GetEntity().FirstName);
}
if (originalDelta.GetChangedPropertyNames().Contains("LastName"))
{
filteredDelta.TrySetPropertyValue("LastName", originalDelta.GetEntity().LastName);
}
filteredDelta.Patch(selectedUser);
Fail the PATCH request (I would say this is preferred and least surprising way to deal with such requests). This would be even simpler:
if (originalDelta.GetChangedPropertyNames().Contains("ModifiedDate"))
{
return InternalServerError(new ArgumentException("Attribue is read-only", "ModifiedDate"));
}
There's a couple of possibilities, depending on you use case...
You want to exclude the changes if they are supplied
You want to throw an error if non-editable fields are updated.
Start with an attribute to mark appropriate properties
/// <summary>
/// Marks a property as non-editable.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class NonEditableAttribute : Attribute
{
}
Then we can write some extensions against Delta to take advantage of this
public static class PatchExtensions
{
/// <summary>
/// Get the properties of a type that are non-editable.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IList<string> NonEditableProperties(this Type type)
{
return type.GetProperties().Where(x => Attribute.IsDefined(x, typeof(NonEditableAttribute))).Select(prop => prop.Name).ToList();
}
/// <summary>
/// Get this list of non-editable changes in a <see cref="Delta{T}"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <returns></returns>
public static IList<string> NonEditableChanges<T>(this Delta<T> delta)
where T : class
{
var nec = new List<string>();
var excluded = typeof(T).NonEditableProperties();
nec.AddRange(delta.GetChangedPropertyNames().Where(x => excluded.Contains(x)));
return nec;
}
/// <summary>
/// Exclude changes from a <see cref="Delta{T}"/> based on a list of property names
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <param name="excluded"></param>
/// <returns></returns>
public static Delta<T> Exclude<T>(this Delta<T> delta, IList<string> excluded)
where T : class
{
var changed = new Delta<T>();
foreach (var prop in delta.GetChangedPropertyNames().Where(x => !excluded.Contains(x)))
{
object value;
if (delta.TryGetPropertyValue(prop, out value))
{
changed.TrySetPropertyValue(prop, value);
}
}
return changed;
}
/// <summary>
/// Exclude changes from a <see cref="Delta{T}"/> where the properties are marked with <see cref="NonEditableAttribute"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <returns></returns>
public static Delta<T> ExcludeNonEditable<T>(this Delta<T> delta)
where T : class
{
var excluded = typeof(T).NonEditableProperties();
return delta.Exclude(excluded);
}
}
And a domain class
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
[NonEditable]
public string SecurityId { get; set; }
}
Finally your controller can then take advantage of this in the Patch method
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Customer> delta)
{
var patch = delta.ExcludeNonEditable();
// TODO: Your patching action here
}
or
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Customer> delta)
{
var nonEditable = delta.NonEditableChanges();
if (nonEditable.Count > 0)
{
throw new HttpException(409, "Cannot update as non-editable fields included");
}
// TODO: Your patching action here
}
I had the same need and I ended up writing an extension method to Delta that accepts additional parameters to limit which fields to update (similar to TryUpDateModel)
I know there must be a better way to do this, but for now this works.
I had to recreate some of the Delta private methods and classes. Most of the code is from https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.OData/OData/Delta.cs, https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/OData/src/System.Web.Http.OData/OData/PropertyAccessor.cs and https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.OData/OData/CompiledPropertyAccessor.cs (or similar, these are not the exact url's I copied from)
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Linq.Expressions;
namespace MyProject.ODataExtensions
{
public static class ODataExtensions
{
public static void Patch<TEntityType>(this System.Web.OData.Delta<TEntityType> d, TEntityType original, String[] includedProperties, String[] excludedProperties) where TEntityType : class
{
Dictionary<string, PropertyAccessor<TEntityType>> _propertiesThatExist = InitializePropertiesThatExist<TEntityType>();
var changedProperties = d.GetChangedPropertyNames();
if (includedProperties != null) changedProperties = changedProperties.Intersect(includedProperties);
if (excludedProperties != null) changedProperties = changedProperties.Except(excludedProperties);
PropertyAccessor<TEntityType>[] array = (
from s in changedProperties
select _propertiesThatExist[s]).ToArray();
var array2 = array;
for (int i = 0; i < array2.Length; i++)
{
PropertyAccessor<TEntityType> propertyAccessor = array2[i];
propertyAccessor.Copy(d.GetEntity(), original);
}
}
private static Dictionary<string, PropertyAccessor<T>> InitializePropertiesThatExist<T>() where T : class
{
Type backingType = typeof(T);
return backingType.GetProperties()
.Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null)
.Select<PropertyInfo, PropertyAccessor<T>>(p => new CompiledPropertyAccessor<T>(p))
.ToDictionary(p => p.Property.Name);
}
internal abstract class PropertyAccessor<TEntityType> where TEntityType : class
{
protected PropertyAccessor(PropertyInfo property)
{
if (property == null)
{
throw new System.ArgumentException("Property cannot be null","property");
}
Property = property;
if (Property.GetGetMethod() == null || Property.GetSetMethod() == null)
{
throw new System.ArgumentException("Property must have public setter and getter", "property");
}
}
public PropertyInfo Property
{
get;
private set;
}
public void Copy(TEntityType from, TEntityType to)
{
if (from == null)
{
throw new System.ArgumentException("Argument cannot be null", "from");
}
if (to == null)
{
throw new System.ArgumentException("Argument cannot be null", "to");
}
SetValue(to, GetValue(from));
}
public abstract object GetValue(TEntityType entity);
public abstract void SetValue(TEntityType entity, object value);
}
internal class CompiledPropertyAccessor<TEntityType> : PropertyAccessor<TEntityType> where TEntityType : class
{
private Action<TEntityType, object> _setter;
private Func<TEntityType, object> _getter;
public CompiledPropertyAccessor(PropertyInfo property)
: base(property)
{
_setter = MakeSetter(Property);
_getter = MakeGetter(Property);
}
public override object GetValue(TEntityType entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
return _getter(entity);
}
public override void SetValue(TEntityType entity, object value)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
_setter(entity, value);
}
private static Action<TEntityType, object> MakeSetter(PropertyInfo property)
{
Type type = typeof(TEntityType);
ParameterExpression entityParameter = Expression.Parameter(type);
ParameterExpression objectParameter = Expression.Parameter(typeof(Object));
MemberExpression toProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
UnaryExpression fromValue = Expression.Convert(objectParameter, property.PropertyType);
BinaryExpression assignment = Expression.Assign(toProperty, fromValue);
Expression<Action<TEntityType, object>> lambda = Expression.Lambda<Action<TEntityType, object>>(assignment, entityParameter, objectParameter);
return lambda.Compile();
}
private static Func<TEntityType, object> MakeGetter(PropertyInfo property)
{
Type type = typeof(TEntityType);
ParameterExpression entityParameter = Expression.Parameter(type);
MemberExpression fromProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
UnaryExpression convert = Expression.Convert(fromProperty, typeof(Object));
Expression<Func<TEntityType, object>> lambda = Expression.Lambda<Func<TEntityType, object>>(convert, entityParameter);
return lambda.Compile();
}
}
}
}

MethodCallExpression used in AssignmentExpression returns default values

I am working on a simple ORM for my company and have used reflection for automatic property population from queries to this point. That is obviously quite slow and I would like to improve that performance with Expression Trees. I spent a good amount of time following examples and reading into the appropriate calls and my code compiles and executes now! However, I have a MethodCallExpression that performs basic DBNull checks and the likes that appears to be working properly, but when the value is assigned to the property it sends the types default value.
First up the actual code:
public static void PopulateFromReaderUsingExpression(Descriptor descriptor, IDataRecord reader)
{
if (descriptor == null)
throw new ArgumentNullException("descriptor");
if (reader == null)
throw new ArgumentNullException("reader");
string[] AvailableColumnsInReader = new string[reader.FieldCount];
for (int i = 0; i <= reader.FieldCount - 1; i++)
{
AvailableColumnsInReader[i] = reader.GetName(i).ToUpper(CultureInfo.InvariantCulture);
}
var statements = new List<Expression>();
ParameterExpression readerExp = Expression.Parameter(typeof(IDataRecord));
ParameterExpression descriptorExp = Expression.Variable(descriptor.GetDescriptorType(), "descriptor");
BinaryExpression createInstanceExp = Expression.Assign(
descriptorExp, Expression.New(descriptor.GetDescriptorType()));
statements.Add(createInstanceExp);
foreach (KeyValuePair<PropertyInfo, PropertyMapping> pair in descriptor.ReadablePropertiesAndDataNames
.Where(property => AvailableColumnsInReader.Contains(property.Value.ReturnName.ToUpper(CultureInfo.InvariantCulture))))
{
MemberExpression propertyExp = Expression.Property(descriptorExp, pair.Key);
IndexExpression readValue =
Expression.MakeIndex(readerExp, typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) }),
new[] { Expression.Constant(pair.Value.ReturnName) });
MethodCallExpression castValueExp =
Expression.Call(typeof(Descriptor)
.GetMethod("CastValue",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[]
{
typeof(Type), typeof(object), typeof(object)
},
null), Expression.Constant(pair.Key.PropertyType, typeof(Type)), readValue, Expression.Constant(null));
BinaryExpression assignmentExp = Expression.Assign(propertyExp, Expression.Convert(castValueExp, pair.Key.PropertyType));
statements.Add(assignmentExp);
}
var body = Expression.Block(new ParameterExpression[] { descriptorExp }, statements);
Expression.Lambda<Action<IDataRecord>>(body, readerExp).Compile()(reader);
}
The method that is being call by the MethodCallExpression and its overload:
/// <summary>
/// Detects if a value is DBNull, null, or has value.
/// </summary>
/// <param name="newType">The new type.</param>
/// <param name="value">The value.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="typeName">Name of the type from the database (used for date/time to string conversion).</param>
/// <returns>Value as type T if value is not DBNull, null, or invalid cast; otherwise defaultValue.</returns>
public static object CastValue(Type newType, object value, object defaultValue, string typeName)
{
object returnValue;
if (value is DBNull || value == null)
returnValue = defaultValue;
else if (newType == typeof(bool) && (value.GetType() == typeof(Int16) || value.GetType() == typeof(Int32)))
returnValue = ((object)(int.Parse(value.ToString(), CultureInfo.InvariantCulture) > 0 ? true : false));
else if (newType == typeof(int) && value.GetType() == typeof(long))
returnValue = ((object)((int)((long)value)));
else if (newType == typeof(int) && value.GetType() == typeof(decimal))
returnValue = ((object)((int)((decimal)value)));
else if (newType == typeof(string))
{
returnValue = value.ToString();
if (!string.IsNullOrEmpty(typeName))
if (typeName == "date")
returnValue = ((DateTime)value).ToString("MM/dd/yyyy", CultureInfo.CurrentCulture);
}
else
returnValue = value;
return returnValue;
}
/// <summary>
/// Casts the value.
/// </summary>
/// <param name="newType">The new type.</param>
/// <param name="value">The value.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>System.Object.</returns>
public static object CastValue(Type newType, object value, object defaultValue)
{
return CastValue(newType, value, defaultValue, null);
}
I have confirmed that values are making into the method and returned, but are the lost along the way, my guess is in the assignment operation.
Here I will include relevant method signatures used in the code example:
public virtual Dictionary<PropertyInfo, PropertyMapping> ReadablePropertiesAndDataNames
/// <summary>
/// THe object Orochi CRUDE uses to map properties to database actions.
/// </summary>
public class PropertyMapping
{
/// <summary>
/// Initializes a new instance of the <see cref="PropertyMapping" /> class.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="parameterName">Name of the parameter.</param>
/// <param name="returnName">Name of the return.</param>
/// <exception cref="System.ArgumentNullException">property</exception>
public PropertyMapping(PropertyInfo property, string returnName)
{
if (property == null)
throw new ArgumentNullException("property");
Contract.EndContractBlock();
this.ReturnName = !string.IsNullOrWhiteSpace(returnName) ? returnName : property.Name;
}
/// <summary>
/// Gets the name of the return.
/// </summary>
/// <value>
/// The name of the return.
/// </value>
public string ReturnName { get; private set; }
}
Here is the DebugInfo from my statement block:
.Block(Models.Billing.Claims.BillingQueue $descriptor) {
$descriptor = .New Models.Billing.Claims.BillingQueue();
$descriptor.ClaimWorkQueueId = (System.Int32).Call Orochi.CrudeData.Descriptor.CastValue(
.Constant<System.Type>(System.Int32),
$var1.Item["ClaimWorkQueueId"],
null);
$descriptor.WorkQueueName = (System.String).Call Orochi.CrudeData.Descriptor.CastValue(
.Constant<System.Type>(System.String),
$var1.Item["WorkQueueName"],
null);
$descriptor.Count = (System.Int32).Call Orochi.CrudeData.Descriptor.CastValue(
.Constant<System.Type>(System.Int32),
$var1.Item["ClaimCount"],
null)
}
I missed that I was creating a new instance of an object and setting values to it instead of by reference. I added the return statement and modified the code to use a return value.
/// <summary>
/// Builds the mapping expression.
/// </summary>
/// <param name="descriptor">The descriptor.</param>
/// <param name="reader">The reader.</param>
/// <returns>LambdaExpression.</returns>
public static Expression<Func<IDataRecord, object>> BuildMappingExpression(IDescriptor descriptor, IDataRecord reader, string[] availableColumns)
{
var statements = new List<Expression>();
ParameterExpression readerExp = Expression.Parameter(typeof(IDataRecord));
ParameterExpression descriptorExp = Expression.Variable(descriptor.GetDescriptorType(), "descriptor");
BinaryExpression createInstanceExp = Expression.Assign(
descriptorExp, Expression.New(descriptor.GetDescriptorType()));
statements.Add(createInstanceExp);
foreach (KeyValuePair<PropertyInfo, PropertyMapping> pair in descriptor.ReadablePropertiesAndDataNames
.Where(property => availableColumns.Contains(property.Value.ReturnName.ToUpper(CultureInfo.InvariantCulture))))
{
MemberExpression propertyExp = Expression.Property(descriptorExp, pair.Key);
IndexExpression readValue =
Expression.MakeIndex(readerExp, typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) }),
new[] { Expression.Constant(pair.Value.ReturnName) });
MethodCallExpression castValueExp =
Expression.Call(typeof(Descriptor)
.GetMethod("CastValue",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[]
{
typeof(Type), typeof(object), typeof(object)
},
null), Expression.Constant(pair.Key.PropertyType, typeof(Type)), readValue, Expression.Constant(null));
BinaryExpression assignmentExp = Expression.Assign(propertyExp, Expression.Convert(castValueExp, pair.Key.PropertyType));
statements.Add(assignmentExp);
}
statements.Add(descriptorExp);
var body = Expression.Block(new ParameterExpression[] { descriptorExp }, statements);
return Expression.Lambda<Func<IDataRecord, object>>(body, readerExp);
}

How to map two expressions of differing types?

I'll start with some classes...
The Domain Entity:
public class Account
{
public int Id { get; set; }
public double Balance { get; set; }
public string CustomerName { get; set; }
}
The View Model:
public class AccountModel
{
public int Id { get; set; }
public double Bal { get; set; }
public string Name { get; set; }
}
The Repository:
My repository has a method on it that takes an expression and returns a list, like this:
public interface IAccountRepository
{
IEnumerable<Account> Query(Expression<Func<Account, bool>> expression);
}
The Problem
My application generates an Expression<Func<AccountModel, bool>> in the UI. I need to somehow convert or map the EXPRESSION from AccountModel to Account so that I can use it in my Query method. I say "map" because, if you notice, my model and domain objects are similar, but don't necessarily have the same property names.
How can this be done?
This sounds like a job for AutoMapper. Automapper allows you to map one class to another at one point in time and use this mapping configuration later on.
See the Projection page on the wiki for the kind of thing you are after.
Update As you are using Entity Framework, here is an update for remapping your expression from using AccountModel to Account.
In the CompositionRoot of your application, set up AutoMapper like so (ignore Code Contract statements if you do not use Code Contracts):
var accountModelMap = Mapper.CreateMap<AccountModel, Account>();
Contract.Assume(accountModelMap != null);
accountModelMap.ForMember(account => account.Id, expression => expression.MapFrom(model => model.Id));
accountModelMap.ForMember(account => account.Balance, expression => expression.MapFrom(model => model.Bal));
accountModelMap.ForMember(account => account.CustomerName, expression => expression.MapFrom(model => model.Name));
This configures how the two data types relate to eachother.
Implement an ExpressionVisitor to use AutoMapper to rebind member access from one type to another.
/// <summary>
/// An <see cref="ExpressionVisitor"/> implementation which uses <see href="http://automapper.org">AutoMapper</see> to remap property access from elements of type <typeparamref name="TSource"/> to elements of type <typeparamref name="TDestination"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor
{
private readonly ParameterExpression _newParameter;
private readonly TypeMap _typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
/// <summary>
/// Initialises a new instance of the <see cref="AutoMapVisitor{TSource, TDestination}"/> class.
/// </summary>
/// <param name="newParameter">The new <see cref="ParameterExpression"/> to access.</param>
public AutoMapVisitor(ParameterExpression newParameter)
{
Contract.Requires(newParameter != null);
_newParameter = newParameter;
Contract.Assume(_typeMap != null);
}
[ContractInvariantMethod]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
private void ObjectInvariant()
{
Contract.Invariant(_typeMap != null);
Contract.Invariant(_newParameter != null);
}
/// <summary>
/// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression"/>.
/// </summary>
/// <returns>
/// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
/// </returns>
/// <param name="node">The expression to visit.</param>
protected override Expression VisitMember(MemberExpression node)
{
var propertyMaps = _typeMap.GetPropertyMaps();
Contract.Assume(propertyMaps != null);
// Find any mapping for this member
var propertyMap = propertyMaps.SingleOrDefault(map => map.SourceMember == node.Member);
if (propertyMap == null)
return base.VisitMember(node);
var destinationProperty = propertyMap.DestinationProperty;
Contract.Assume(destinationProperty != null);
var destinationMember = destinationProperty.MemberInfo;
Contract.Assume(destinationMember != null);
// Check the new member is a property too
var property = destinationMember as PropertyInfo;
if (property == null)
return base.VisitMember(node);
// Access the new property
var newPropertyAccess = Expression.Property(_newParameter, property);
return base.VisitMember(newPropertyAccess);
}
}
Then implement an extension method to make this easier to use:
/// <summary>
/// A class which contains extension methods for <see cref="Expression"/> and <see cref="Expression{TDelegate}"/> instances.
/// </summary>
public static class ExpressionExtensions
{
/// <summary>
/// Remaps all property access from type <typeparamref name="TSource"/> to <typeparamref name="TDestination"/> in <paramref name="expression"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
/// <typeparam name="TResult">The type of the result from the lambda expression.</typeparam>
/// <param name="expression">The <see cref="Expression{TDelegate}"/> to remap the property access in.</param>
/// <returns>An <see cref="Expression{TDelegate}"/> equivalent to <paramref name="expression"/>, but applying to elements of type <typeparamref name="TDestination"/> instead of <typeparamref name="TSource"/>.</returns>
public static Expression<Func<TDestination, TResult>> RemapForType<TSource, TDestination, TResult>(this Expression<Func<TSource, TResult>> expression)
{
Contract.Requires(expression != null);
Contract.Ensures(Contract.Result<Expression<Func<TDestination, TResult>>>() != null);
var newParameter = Expression.Parameter(typeof (TDestination));
Contract.Assume(newParameter != null);
var visitor = new AutoMapVisitor<TSource, TDestination>(newParameter);
var remappedBody = visitor.Visit(expression.Body);
if (remappedBody == null)
throw new InvalidOperationException("Unable to remap expression");
return Expression.Lambda<Func<TDestination, TResult>>(remappedBody, newParameter);
}
}
This can subsequently be used like so (in an NUnit test):
[TestFixture]
public class RemappingTests
{
#region Setup/Teardown
/// <summary>
/// Sets up the variables before each test.
/// </summary>
[SetUp]
public void Setup()
{
var accountModelMap = Mapper.CreateMap<AccountModel, Account>();
Contract.Assume(accountModelMap != null);
accountModelMap.ForMember(account => account.Id, expression => expression.MapFrom(model => model.Id));
accountModelMap.ForMember(account => account.Balance, expression => expression.MapFrom(model => model.Bal));
accountModelMap.ForMember(account => account.CustomerName, expression => expression.MapFrom(model => model.Name));
}
[TearDown]
public void Teardown()
{
Mapper.Reset();
}
#endregion
/// <summary>
/// Checks that <see cref="ExpressionExtensions.RemapForType{TSource, TDestination, TResult}(Expression{Func{TSource, TResult}})"/> correctly remaps all property access for the new type.
/// </summary>
/// <param name="balance">The balance to use as the value for <see cref="Account.Balance"/>.</param>
/// <returns>Whether the <see cref="Account.Balance"/> was greater than 50.</returns>
[TestCase(0, Result = false)]
[TestCase(80, Result = true)]
public bool RemapperUsesPropertiesOfNewDataType(double balance)
{
Expression<Func<AccountModel, bool>> modelExpr = model => model.Bal > 50;
var accountExpr = modelExpr.RemapForType<AccountModel, Account, bool>();
var compiled = accountExpr.Compile();
Contract.Assume(compiled != null);
var hasBalance = compiled(new Account {Balance = balance});
return hasBalance;
}
}
In case that is too much code to find the exact call, here it is:
Expression<Func<AccountModel, bool>> modelExpr = model => model.Bal > 50;
var accountExpr = modelExpr.RemapForType<AccountModel, Account, bool>();
You can use an ExpressionVisitor to rewrite the Expression:
public class AccountModelRewriter : ExpressionVisitor
{
private Stack<ParameterExpression[]> _LambdaStack = new Stack<ParameterExpression[]>();
protected override Expression VisitLambda<T>(Expression<T> node)
{
var lambda = (LambdaExpression)node;
_LambdaStack.Push(
lambda.Parameters.Select(parameter => typeof(AccountModel) == parameter.Type ? Expression.Parameter(typeof(Account)) : parameter)
.ToArray()
);
lambda = Expression.Lambda(
this.Visit(lambda.Body),
_LambdaStack.Pop()
);
return lambda;
}
protected override Expression VisitMember(MemberExpression node)
{
var memberExpression = (MemberExpression)node;
var declaringType = memberExpression.Member.DeclaringType;
var propertyName = memberExpression.Member.Name;
if (typeof(AccountModel) == declaringType)
{
switch (propertyName)
{
case "Bal" :
propertyName = "Balance";
break;
case "Name" :
propertyName = "CustomerName";
break;
}
memberExpression = Expression.Property(
this.Visit(memberExpression.Expression),
typeof(Account).GetProperty(propertyName)
);
}
return memberExpression;
}
protected override Expression VisitParameter(ParameterExpression node)
{
node = (ParameterExpression)base.VisitParameter(node);
if (typeof(AccountModel) == node.Type)
{
node = this._LambdaStack.Peek().Single(parameter => parameter.Type == typeof(Account));
}
return node;
}
}
This visitor switches the input parameters from type AccountModel to Account (that's the VisitLambda and VisitParameter methods), and also changes all property accessors to use this new parameter as well as switching the property names if appropriate (that's the VisitMember part).
Usage is as follows:
Expression<Func<AccountModel, bool>> accountModelQuery = a => a.Bal == 0 && a.Name != null && a.Id != 7;
var accountQuery = (Expression<Func<Account, bool>>)new AccountModelRewriter().Visit(accountModelQuery);

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