with enum retrieve const value from class c# - c#

i have an enum like
public enum DecimailPrecision
{
One,
Two,
}
and class as
class DecimailPrecision1
{
public const string One = "#,##0.0";
public const string Two = "#,##0.00";
}
i want to retrieve const string from class with enum. i already doing this with if and switch as
string format = string.Empty;
switch (value)
{
case DecimailPrecision.One:
format = DecimailPrecision1.One.ToString(); break;
case DecimailPrecision.Two:
format = DecimailPrecision1.Two.ToString(); break;
default:
format = DecimailPrecision1.Two.ToString(); break;
}
if (value == "One"){
format = DecimailPrecision1.One.ToString();}
else if (value == "Two"){
format = DecimailPrecision1.Two.ToString();}
}
i need a better way because i have lot items in enum.
thanks.

Why not just create a Dictionary<DecimailPrecision, string> and hold the mappings in that?
That way you can simply look up your DecimailPrecision value in the dictionary and retrieve the appropriately mapped string.
You could even store the mapping in config and read it from that, so you wouldn't need to recompile your code to add new mappings.
To explicitly apply this to your code (I'd recommend changing the name of your consts to DecimalPrecisionFormat):
var precisionMap = new Dictionary<DecimailPrecision, string>
{
{ DecimailPrecision.One, DecimailPrecision1.One }
, { DecimailPrecision.Two, DecimailPrecision1.Two }
};
var formatTwo = precisionMap[DecimailPrecision.Two];

For similar needs we developed a custom attribute and a few extension methods.
Usage is like this.
public enum DecimailPrecision
{
[EnumCode("#,##0.0")]
One,
[EnumCode("#,##0.00")]
Two
}
string format = DecimailPrecision.One.GetCode();
It may be meaningless for your case but reverse is valid through like this
string format ="#,##0.00";
DecimailPrecision dp = format.ToEnum<DecimailPrecision>();
Extensions and Atrribute are like:
public static class EnumExtensions
{
private static readonly Dictionary<Type, EnumCodePair[]> EnumCodeCache = new Dictionary<Type, EnumCodePair[]>();
public static string GetCode(this Enum enumValue) {
var codePairs = GetEnumCodePairs(enumValue.GetType());
return codePairs.First(cp => Equals(cp.Enum, enumValue)).Code;
}
public static T ToEnum<T>(this string enumCode) {
var codePairs = GetEnumCodePairs(typeof(T));
return (T)codePairs.First(cp => Equals(cp.Code, enumCode)).Enum;
}
private static IEnumerable<EnumCodePair> GetEnumCodePairs(Type type) {
if(!EnumCodeCache.ContainsKey(type)) {
var enumFields = type.GetFields(BindingFlags.Public | BindingFlags.Static);
var codePairs = new List<EnumCodePair>();
foreach(var enumField in enumFields) {
var enumValue = Enum.Parse(type, enumField.Name);
var codePair = new EnumCodePair {
Enum = enumValue
};
var attrs = enumField.GetCustomAttributes(typeof(EnumCodeAttribute), false);
codePair.Code = attrs.Length == 0
? enumField.Name
: ((EnumCodeAttribute)attrs[0]).Code;
codePairs.Add(codePair);
}
EnumCodeCache.Add(type, codePairs.ToArray());
}
return EnumCodeCache[type];
}
class EnumCodePair
{
public object Enum { get; set; }
public string Code { get; set; }
}
}
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class EnumCodeAttribute : Attribute
{
public EnumCodeAttribute(string code) {
Code = code;
}
public string Code { get; private set; }
}

Related

How to use generics with classes full of constants?

I hope it becomes clear what I mean. I have multiple static class full of options:
static class Thing1
{
public const string Name = "Thing 1";
// More const fields here.
}
static class Thing2
{
public const string Name = "Thing 2";
// More const fields here.
}
Now I want to use those options to create a class which includes the contents of one of these classes.
public void Create<T>()
{
var foo = new Foo(T.Name);
foo.Prop = T.Something;
if (T.HasValue)
foo.Add(T.Value);
}
But of course this doesn't work. I would use interfaces, but static classes can't implement interfaces.
Is there any way to make this work elegantly? Making Thing1 and Thing2 singletons would work, but that isn't a very nice solution.
I could create a struct and put the objects into another static class, but I was wondering whether you could do something like the above.
Well, you can create an interface and make your classes non-static and inherit from this interface:
public class Thing1 : IThing
{
public string Name { get; } = "Thing 1";
// More const fields here.
}
public class Thing2 : IThing
{
public string Name { get; } = "Thing 2";
// More fields here.
}
interface IThing
{
string Name { get; }
}
And then use it for your method together with type parameter constraint:
public void Create<T>(T t) where T : IThing
{
// Now compiler knows that `T` has all properties from `IThing`
var foo = new Foo(t.Name);
foo.Prop = t.Something;
if (t.HasValue)
foo.Add(t.Value);
}
You can try Reflection: scan assemblies for static classes, obtain public const string fields with their values from them and materialize them as a Dictionary<T>
using System.Linq;
using System.Reflection;
...
// Key : type + field name, say Tuple.Create(typeof(Thing1), "Name")
// Value : corresponding value, say "Thing 1";
static Dictionary<Tuple<Type, string>, string> s_Dictionary = AppDomain
.CurrentDomain
.GetAssemblies() // I've taken all assemblies; you may want to add Where here
.SelectMany(asm => asm.GetTypes())
.Where(t => t.IsAbstract && t.IsSealed) // All static types, you may want to add Where
.SelectMany(t => t
.GetFields() // All constant string fields
.Where(f => f.FieldType == typeof(string))
.Where(f => f.IsPublic && f.IsStatic)
.Where(f => f.IsLiteral && !f.IsInitOnly) // constants only
.Select(f => new {
key = Tuple.Create(t, f.Name),
value = f.GetValue(null)
}))
.ToDictionary(item => item.key, item => item.value?.ToString());
If you want to scan not all loaded but just one (executing) assembly
static Dictionary<Tuple<Type, string>, string> s_Dictionary = Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => t.IsAbstract && t.IsSealed)
...
Now you can wrap the dictionary, say
public static string ReadConstant<T>(string name = null) {
if (string.IsNullOrEmpty(name))
name = "Name";
if (s_Dictionary.TryGetValue(Tuple.Create(typeof(T), name), out string value))
return value;
else
return null; // Or throw exception
}
Usage
string name1 = ReadConstant<Thing1>();
You could use non static classes and add those to a dictionary having a Type key. But you would have to use read-only properties.
public interface IConstants
{
string Name { get; }
double InitialHealth { get; }
public int? MaxTries { get; }
}
public class Thing1 : IConstants
{
public string Name => "Thing 1";
public double InitialHealth => 100.0;
public int? MaxTries => null;
}
public class Thing2 : IConstants
{
public string Name => "Thing 2";
public double InitialHealth => 80.0;
public int? MaxTries => 10;
}
Initialize the dictionary:
public static readonly Dictionary<Type, IConstants> Constants =
new Dictionary<Type, IConstants> {
[typeof(Thing1)] = new Thing1(),
[typeof(Thing2)] = new Thing2(),
};
The Create function:
public void Create<T>()
{
Type key = typeof(T);
var foo = new Foo(key.Name);
IConstants constants = Constants[key];
foo.InitialHealth = constants.InitialHealth;
if (constants.MaxTries is int maxTries) { // Only true if MaxTries.HasValue.
// Converts to int at the same time.
foo.Add(maxTries);
}
}
Instead of a static classes with constants. You can create a class with properties and static instances with the desired values.
public class Thing
{
private Thing(string name, string something, bool hasValue, string value)
{
Name = name;
Something = something;
HasValue = hasValue;
Value = value;
}
public string Name { get; }
public string Something{ get; }
public bool HasValue { get; }
public string Value{ get; }
public static Thing Thing1 { get; } = new Thing("Thing1", "Something1", true, "Value1");
public static Thing Thing2 { get; } = new Thing("Thing2", "Something2", false, null);
}
And then your method would just take that class.
public void Create(Thing t)
{
var foo = new Foo(t.Name);
foo.Prop = t.Something;
if (t.HasValue)
foo.Add(t.Value);
}
Then you'd call it with either
Create(Thing.Thing1);
or
Create(Thing.Thing2);
After some reflection I came up with another solution. Why select the constants by type? It is much easier if we use the same type to store the different sets of constants.
public class Constants
{
public string Name { get; set; }
public double Health { get; set; }
public int? MaxTries { get; set; }
}
We then identify the sets through an enum:
public enum SetType
{
Set1, // Please use speaking names in a real implementation!
Set2,
Set3
}
We define the values of the constants while creating the dictionary of constants sets:
public static readonly Dictionary<SetType, Constants> ConstantSets =
new Dictionary<SetType, Constants> {
[SetType.Set1] = new Constants { Name = "Set 1", Health = 100, MaxTries = null },
[SetType.Set2] = new Constants { Name = "Set 2", Health = 80, MaxTries = 5 },
...
};
The Create method becomes
public void Create(SetType set)
{
var constants = ConstantSets[set];
var foo = new Foo(constants.Name) {
Health = constants.Health
};
if (constants.MaxTries is int maxTries) {
foo.Add(maxTries);
}
}
No generics, no reflection, no fancy stuff required.

How to use Attributes to control the serialization of a property in C#

I would like to control how a particular class serializes it's data... for example:
[TimeSeriesSerialization(TimeSeriesSerializationType.Dates)]
public TimeSeries DailyValues { get; set; }
[TimeSeriesSerialization(TimeSeriesSerializationType.Normal)]
public TimeSeries IntraDayValues { get; set; }
In my example here, I have a class called TimeSeries that has a List of DateTimes and a List of decimals... when I serialize it I want to do two things... create a JSON string that is a set of time/value pairs... for example:
{"2018-01-30T09:30:01":9958.2289,"2018-01-30T09:30:02":9958.2284,...}
But I would also like to have the option to serialize this class as Date values (without the time)... as so:
{"2018-01-30":9958.2289,"2018-01-31":9958.2284,...}
Or as only values:
[9958.2289,9958.2284,...]
Any help?
public enum TimeSeriesSerializationType
{
Times,
Dates,
ValuesOnly,
}
public class TimeSeries
{
public List<DateTime> Times = new List<DateTime>();
public List<decimal> Values = new List<decimal>();
public void Add(DateTime time, decimal value)
{
Times.Add(time);
Values.Add(value);
}
public string Serialize(TimeSeriesSerializationType timeSeriesSerializationType)
{
switch (timeSeriesSerializationType)
{
case TimeSeriesSerializationType.Dates:
{
var data = new Dictionary<DateTime, decimal>();
for (var i = 0; i < Times.Count; i++)
{
if (!data.ContainsKey(Times[i])) // duplicate dates???
data.Add(Times[i], Values[i]);
}
var isoDateTimeConverter = new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd" };
var json = JsonConvert.SerializeObject(data, Formatting.None, isoDateTimeConverter);
return json;
}
case TimeSeriesSerializationType.ValuesOnly:
{
var json = JsonConvert.SerializeObject(Values, Formatting.None);
return json;
}
default:
{
var data = new Dictionary<DateTime, decimal>();
for (var i = 0; i < Times.Count; i++)
{
if (!data.ContainsKey(Times[i])) // duplicate times???
data.Add(Times[i], Values[i]);
}
var isoDateTimeConverter = new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz" };
var json = JsonConvert.SerializeObject(data, Formatting.None, isoDateTimeConverter);
return json;
}
}
}
public void Deserialize(string str)
{
if (string.IsNullOrEmpty(str)) return;
var data = JsonConvert.DeserializeObject<Dictionary<DateTime, decimal>>(str);
if (data.Count <= 0) return;
foreach (var d in data)
this.Add(d.Key, d.Value);
}
}
I think you already have most of what you need.
What's missing is the attribute itself.
You'd define it like this (you can read more about the AttributeUsage-Attribute here):
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
class TimeSeriesSerializationAttribute : Attribute
{
public TimeSeriesSerializationType SerializationType { get; }
public TimeSeriesSerializationAttribute(TimeSeriesSerializationType timeSeriesSerializationType)
{
SerializationType = timeSeriesSerializationType;
}
}
Now you can add those above your properties just like you already wrote.
Time for the evaluation part. You can use something like this to read all the properties and use them to serialize your TimeSeries.
By the way if the attribute you get turns out as null, the attribute just isn't set. You can either skip the property entirely or use a default SerializationType, whatever you need/want.
public static void SerializeToSomewhere(YourClassWithTimeSeries instance) {
// get all public, instance properties
IEnumerable<PropertyInfo> allProps = typeof(YourClassWithTimeSeries).GetProperties(BindingFlags.Public | BindingFlags.Instance);
// only take TimeSeries-properties
IEnumerable<PropertyInfo> timeSeriesProps = allProps.Where(p => p.PropertyType == typeof(TimeSeries));
foreach (PropertyInfo property in timeSeriesProps)
{
// get the attribute
TimeSeriesSerializationAttribute attribute = property.GetCustomAttribute<TimeSeriesSerializationAttribute>();
if (attribute == null) continue; // or do something else like use a default SerializationType
// get the value
TimeSeries value = (TimeSeries)property.GetValue(instance, null);
// serialize using the type from the attribute
string serializedValue = value.Serialize(attribute.SerializationType);
// Do whatever with the serialized value (for example save it to a file or whatever)
}
}
Hope this helps you. There are also plenty of attribute tutorials and docs if you need additional information. I'll also try to answer questions you might have so feel free to ask.
Edit:
Small thing to add. In your code your TimeSeries-class exposes the public fields Times and Values. You shouldn't do that.
Instead of doing this:
public List<DateTime> Times = new List<DateTime>();
public List<decimal> Values = new List<decimal>();
You should do this. It stops you from accidentally assigning it again from within or outside the class.
public List<DateTime> Times { get; } = new List<DateTime>();
public List<decimal> Values { get; } = new List<decimal>();
A more pedestrian, but effective approach(for json)...
[JsonIgnore]
public PriceSeries DailyValues { get; set; }
[JsonProperty(PropertyName = "DailyValues")]
public string DailyValuesSerialized {
get
{
return (DailyValues == null) ?
null :
DailyValues.Serialize(TimeSeriesSerializationType.ValuesOnly);
}
set
{
DailyValues = (value == null) ? null : new PriceSeries(value);
}
}
[JsonIgnore]
public PriceSeries IntraDayValues { get; set; }
[JsonProperty(PropertyName = "IntraDayValues")]
public string IntraDayValuesSerialized
{
get
{
return (IntraDayValues == null) ?
null :
IntraDayValues.Serialize(TimeSeriesSerializationType.Normal);
}
set
{
IntraDayValues = (value == null) ? null : new PriceSeries(value);
}
}
Or this (for a database)...
[NotMapped]
public PriceSeries DailyValues { get; set; }
[Column("DailyValues")]
public string DailyValuesSerialized
{
get
{
return (DailyValues == null) ?
null :
DailyValues.Serialize(TimeSeriesSerializationType.ValuesOnly);
}
set
{
DailyValues = (value == null) ? null : new PriceSeries(value);
}
}
[NotMapped]
public PriceSeries IntraDayValues { get; set; }
[Column("IntraDayValues")]
public string IntraDayValuesSerialized
{
get
{
return (IntraDayValues == null) ?
null :
IntraDayValues.Serialize(TimeSeriesSerializationType.Normal);
}
set
{
IntraDayValues = (value == null) ? null : new PriceSeries(value);
}
}

What's the accepted way to handle values that can be different data types in C#?

I'm basically reading a config file
[Section]
Key=value
Where the value can be either a string, an integer, a double, or a boolean value.
While working in this context, I have a class that looks like this...
public class Setting
{
public string Section {get; set;}
public string Key {get; set;}
public <string, int, double, or bool> Value {get; set;}
public Setting(string section, string key, <string, int, double, or bool> value)
{
Section = section;
Key = key;
Value = value;
}
public void Write()
{
//if Value is an int, call third-party code to write an integer to the config file.
//if Value is a string, call third-party code to write a string to the config file.
//...
}
}
In this situation, what is the accepted way to handle the Value property of this class?
In addition, I'd like to be able to store a bunch of these objects in an Array, List, or other types of collections.
UPDATE:
I'm not reading/writing to the configuration file directly, that part of the code is not controlled by me. Basically, I need to call different functions in the third-party code, based on the type of Value
UPDATE:
One thought was to use generics, and have a class like this...
public class Setting<T>
{
public string Section { get; set; }
public string Key { get; set; }
public T Value { get; set; }
public Setting(string section, string key, T value)
{
Section = section;
Key = key;
Value = value;
}
public void Write()
{
switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.Int32:
//Call third-party code to write an integer
break;
case TypeCode.String:
//Call third-party code to write a string
break;
default:
break;
}
}
}
But then I'd only be able to store a single type of setting in a List.
System.Collections.Generic.List<Setting<string>> settings = new List<Setting<string>>();
So I'd have to have a list for each type of setting.
UPDATE:
Another option might be to use and interface, and classes for each type of setting that implement the interface...
interface ISetting
{
string Section { get; set; }
string Key { get; set; }
void Write();
}
public class StringSetting : ISetting
{
public string Section { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public StringSetting(string section, string key, string value)
{
Section = section;
Key = key;
Value = value;
}
public void Write()
{
//Call third-party code to write the setting.
}
}
But that seems like a lot of duplicate code, so making changes in the future might be error prone.
UPDATE:
Another option, is to make Value a dynamic type.
public class DynamicSetting
{
public string Section { get; set; }
public string Key { get; set; }
public dynamic Value { get; set; }
public DynamicSetting(string section, string key, dynamic value)
{
Section = section;
Key = key;
Value = value;
}
public void Write()
{
switch (Type.GetTypeCode(Value.GetType()))
{
case TypeCode.Int32:
//Call third-party code to write an integer
break;
case TypeCode.String:
//Call third-party code to write a string
break;
default:
break;
}
}
}
Then I can create a bunch of DynamicSetting objects, and store them in a collection like I want.
DynamicSetting IntSetting = new DynamicSetting("Section", "Key", 1);
DynamicSetting StringSetting = new DynamicSetting("Section", "Key", "1");
DynamicSetting DoubleSetting = new DynamicSetting("Section", "Key", 1.0);
System.Collections.Generic.List<DynamicSetting> settings = new List<DynamicSetting>();
settings.Add(IntSetting);
settings.Add(StringSetting);
settings.Add(DoubleSetting);
foreach(DynamicSetting setting in settings)
{
setting.Write();
}
UPDATE:
I could also make Value an object
public class ObjectSetting
{
public string Section { get; set; }
public string Key { get; set; }
public object Value { get; set; }
public ObjectSetting(string section, string key, object value)
{
Section = section;
Key = key;
Value = value;
}
public void Write()
{
switch (Type.GetTypeCode(Value.GetType()))
{
case TypeCode.Int32:
//Call third-party code to write an integer
break;
case TypeCode.String:
//Call third-party code to write a string
break;
case TypeCode.Double:
//Call third-party code to write a string
break;
default:
break;
}
}
}
And it would work just like dynamic
ObjectSetting IntSetting = new ObjectSetting("Section", "Key", 1);
ObjectSetting StringSetting = new ObjectSetting("Section", "Key", "1");
ObjectSetting DoubleSetting = new ObjectSetting("Section", "Key", 1.0);
System.Collections.Generic.List<ObjectSetting> settings = new List<ObjectSetting>();
settings.Add(IntSetting);
settings.Add(StringSetting);
settings.Add(DoubleSetting);
foreach(ObjectSetting setting in settings)
{
setting.Write();
}
The simplest way is to accept Value as an object in the constructor and the setter, both of which would validate the Type against your list of valid types. Use a Switch in your Write method to determine which third-party code to call. You can store all your Settings in a single collection. Alternatively, you could write overloads for the constructor and a SetValue method. That's a little more code, but would provide design time type-checking.
Example for ISettingValue:
public interface ISettingValue
{
void Write();
}
public class StringSetting : ISettingValue
{
readonly string _data;
public StringSetting(string data) => _data = data;
public void Write()
{
//Call third-party code to write the string (value of _data).
}
}
public class IntSetting : ISettingValue
{
readonly int _data;
public IntSetting(int data) => _data = data;
public void Write()
{
//Call third-party code to write the integer (value of _data).
}
}
public class Setting
{
public string Section { get; set; }
public string Key { get; set; }
public ISettingValue Value { get; set; }
public Setting(string section, string key, ISettingValue value)
{
Section = section;
Key = key;
Value = value;
}
public void Write()
{
Value.Write();
}
}
Maybe something like that?
public abstract class Setting {
public abstract Type keyType { get; }
public string Key { get; protected set; }
public object value { get; protected set; }
protected abstract Action writer { get; }
public void Write() => writer();
}
public class Setting<T> : Setting {
public override Type keyType => typeof(T);
protected override Action writer => () => typeWriter(Value);
public string Section { get; set; }
public T Value {get; set;}
private Action<T> typeWriter { get; }
public Setting(string section, string key, T value, Action<T> writer) {
Section = section;
Key = key;
this.value = Value = value;
typeWriter = writer;
}
}
public class Usage {
private List<Setting> settings = new List<Setting>() {
new Setting<double>("", "x", 10, n => Debug.WriteLine(n % 4)),
new Setting<string>("", "y", "abc", s => Debug.WriteLine(s.ToUpper())),
new Setting<bool>("", "z", true, b => Debug.Write(!b)),
};
public Usage() {
foreach (var s in settings) {
Debug.Write($"{s.keyType.Name} {s.Key} =");
s.Write();
}
}
}

Enum with parameters

Is it possible in C# to collect information in an enum instead of a dedicated class?
Example how it would work in Java:
public enum Action {
JUMP( "JUMP", 1),
CROUCH ("CROUCH", 2),
;
private String animationId;
private int buttonId;
private Action( String animationId, int buttonId) {
this.animationId = animationId;
this.buttonId = buttonId;
}
public String getAnimationId() {
return animationId;
}
public int getButtonId() {
return buttonId;
}
}
You can use enum with attributes:
public enum Action{
[MyValue("JUMP", 1)]
JUMP,
[MyValue("CROUCH", 2)]
CROUCH
}
[AttributeUsage(
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class MyValueAttribute : System.Attribute{
public string Value{get; private set}
public string AnimationId{get; private set;}
public MyValueAttribute(string animationId, string value){
AnimationId = animationId;
Value = value;
}
and you can get value as follows:
public static class EnumExtensions{
public static string GetValue(this Enum value)
{
var type = value.GetType();
var name = Enum.GetName(type, value);
if (name == null) return string.Empty;
var field = type.GetField(name);
if (field == null) return string.Empty;
var attr = Attribute.GetCustomAttribute(field, typeof(MyValueAttribute)) as MyValueAttribute;
return attr != null ? attr.Value: string.Empty;
}
public static string GetAnimationId(this Enum value)
{
var type = value.GetType();
var name = Enum.GetName(type, value);
if (name == null) return string.Empty;
var field = type.GetField(name);
if (field == null) return string.Empty;
var attr = Attribute.GetCustomAttribute(field, typeof(MyValueAttribute)) as MyValueAttribute;
return attr != null ? attr.AnimationId: string.Empty;
}
}
Usage:
Action.JUMP.GetValue();
Action.JUMP.GetAnimationId();
or you can use one method which return for example Tuple with AnimationId and Value
No, but you can use static class fields instead:
public sealed class Action
{
public static readonly Action JUMP = new Action("JUMP", 1);
public static readonly Action CROUCH = new Action("CROUCH", 2);
public string AnimationId { get; }
public int ButtonId { get; }
private Action(String animationId, int buttonId)
{
AnimationId = animationId;
ButtonId = buttonId;
}
public override string ToString() => AnimationId;
}
You could definitely use attributes like suggested. However, you can call .ToString() on an enum value to get its name as a string value, and you can also assign int values to them. By default they are assigned 0-X based on index. However you could do this
public enum Action {
JUMP=1,
CROUCH=2
}
And then to access these values
Action action = Action.JUMP;
int value = (int) action; //Is set to 1
string name = action.ToString(); //Is set to "JUMP"
While this certainly will not work in every case depending on how much you want your enum to store, for the situation you described this is much easier.

passing around values to an AutoMapper Type Converter from outside

I have a multilingual database, which returns values based on a key and an enum Language. When I convert a DB object to a model, I want the model to contain the translated value based on the key and the current language.
The key comes from the DB object but how can I pass the current language to the the Mapper.Map() function?
Currently, I am using a [ThreadStatic] attribute to set the culture before calling Mapper.Map<>, and to retrieve it in the TypeConverter.
public enum Language
{
English, French, Italian, Maltese
}
public class MultilingualValue<T>
{
public Dictionary<Language, T> Value { get; set; }
public MultilingualValue()
{
this.Value = new Dictionary<Language, T>();
}
}
public class PersonData
{
public string FirstName { get; set; }
public MultilingualValue<string> City { get; set; }
}
public void MapPerson()
{
PersonData personData = new PersonData();
personData.FirstName = "John";
personData.City = new MultilingualValue<string>();
personData.City.Value[ Language.English] = "The Zurrieq";
personData.City.Value[Language.French] = "Le Zurrieque";
MultilingualValueData.CurrentLanguage = Language.English;
var personModel = Mapper.Map<PersonData, PersonModel>(personData);
}
public class MultilingualValueToBasicDataTypeConverter<T> : ITypeConverter<MultilingualValue<T>, T>
{
public T Convert(ResolutionContext context)
{
var currentLanguage = MultilingualValueData.CurrentLanguage; //THIS IS THE [ThreadStatic] VARIABLE
if (currentLanguage == null) throw new InvalidOperationException("Please make sure to fill in CurrentLanguage");
MultilingualValue<T> sourceMultilingualValue = (MultilingualValue < T > )context.SourceValue;
T destinationValue = default(T);
if (sourceMultilingualValue != null)
{
destinationValue = sourceMultilingualValue.Value[currentLanguage.Value];
}
return destinationValue;
}
}
public static class MultilingualValueData
{
[ThreadStatic]
public static Language? CurrentLanguage;
}
I left out the configurations as I think they're unneccessary for this example. If you need them, I'll post them as well.
While this works, I find this workaround quite ugly. Is there any way to pass data through the ResolutionContext?
Just use the Map overload that takes a Action<IMappingOperationOptions>. You can add configuration elements to the Items property that are then passed to your ITypeConverter
public class CustomConverter : ITypeConverter<string, string>
{
public string Convert(ResolutionContext context)
{
return "translated in " + context.Options.Items["language"];
}
}
internal class Program
{
private static void Main(string[] args)
{
AutoMapper.Mapper.CreateMap<string, string>().ConvertUsing<CustomConverter>();
var result = AutoMapper.Mapper.Map<string, string>("value" , opt => opt.Items["language"] = "english");
Console.Write(result); // prints "translated in english"
Console.ReadLine();
}
}

Categories

Resources