23

I have the following classes domain and Dto classes:

public class Profile
{
   public string Name { get; set; }
   public string SchoolGrade { get; set; } 
}

public class ProfileDTO
{
   public string Name { get; set; }
   public SchoolGradeDTO SchoolGrade { get; set; } 
}

public enum SchoolGradeDTO 
{
   [Display(Name = "Level One"]
   LevelOne,
   [Display(Name = "Level Two"]
   LevelTwo,
}

I used the following method:

 Mapper.CreateMap<Profile, ProfileDTO>()
       .ForMember(d => d.SchoolGrade , op => op.MapFrom(o => o.SchoolGrade))

Afterwards, I get the following error:

Requested value 'Level Two' was not found.

How do I map it correctly?

Ian CT
  • 1,301
  • 2
  • 12
  • 22
gog
  • 11,788
  • 23
  • 67
  • 129
  • Any reason why you're passing the display attribute value for the enum? I.e. why you're using "Level Two" instead of "LevelTwo". Doing that makes this task slightly harder (will require reflection to resolve). – Umair Mar 04 '15 at 15:03
  • Using `LevelTwo` will allow automapper to map directly (no need for the `ForMember` bit) – Umair Mar 04 '15 at 15:12
  • What is the question? – Alex Sanséau Mar 04 '15 at 15:12

3 Answers3

27

Since you're mapping from the display name and not the enum name you'll need to build a custom mapping function to scan the attributes to find the enum with that display name. You can use ResolveUsing instead of MapFrom to use a custom mapping function:

Mapper.CreateMap<Profile, ProfileDTO>()
      .ForMember(d => d.SchoolGrade, 
                op => op.ResolveUsing(o=> MapGrade(o.SchoolGrade)));

public static SchoolGradeDTO MapGrade(string grade)
{
    //TODO: function to map a string to a SchoolGradeDTO
}

You could cache the names in a static dictionary so you don't use reflection every time.

A few methods of doing that can be found here.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • 7
    Note: with the upgrade to AutoMapper 8 `ResolveUsing`has to be replaced by `MapFrom`. See https://docs.automapper.org/en/stable/8.0-Upgrade-Guide.html?highlight=resolveusing. – Jan Oct 20 '20 at 15:56
11

Expanding on D Stanley's answer from above in a little more detail, and modified the EnumHelper class from this other discussion to focus on your specific situation as this question really spans two areas, AutoMapper and correctly obtaining an Enum's value from a string.

Enhancing D Stanley's original answer:

public static class QuestionAutoMapperConfig
{
    public static void ConfigureAutoMapper()
    {
        Mapper.CreateMap<Profile, ProfileDTO>()
            .ForMember(d => d.SchoolGrade,
                op => op.ResolveUsing(o => MapGrade(o.SchoolGrade)));
    }

    public static SchoolGradeDTO MapGrade(string grade)
    {
        //TODO: function to map a string to a SchoolGradeDTO
        return EnumHelper<SchoolGradeDTO>.Parse(grade);
    }
}

I have adjusted the EnumHelper from the mentioned example to quickly show an option where by you could modify the Parse method to first try the standard Enum.Parse(), and failing that to try to do a more detailed comparison of the Enum type by creating a dictionary of the values based either on the enum value name, or it's Display attribute text (if used).

public static class EnumHelper<T>
{
    public static IDictionary<string, T> GetValues(bool ignoreCase)
    {
        var enumValues = new Dictionary<string, T>();

        foreach (FieldInfo fi in typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public))
        {
            string key = fi.Name;

            var display = fi.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];
            if (display != null)
                key = (display.Length > 0) ? display[0].Name : fi.Name;

            if (ignoreCase)
                key = key.ToLower();

            if (!enumValues.ContainsKey(key))
                enumValues[key] = (T)fi.GetRawConstantValue();
        }

        return enumValues;
    }

    public static T Parse(string value)
    {
        T result;

        try
        {
            result = (T)Enum.Parse(typeof(T), value, true);
        }
        catch (Exception)
        {
            result = ParseDisplayValues(value, true);
        }


        return result;
    }

    private static T ParseDisplayValues(string value, bool ignoreCase)
    {
        IDictionary<string, T> values = GetValues(ignoreCase);

        string key = null;
        if (ignoreCase)
            key = value.ToLower();
        else
            key = value;

        if (values.ContainsKey(key))
            return values[key];

        throw new ArgumentException(value);
    }
}
Community
  • 1
  • 1
Scott
  • 111
  • 4
3

in mapping configuration

{
CreateMap<string, CUSTOM_ENUM>().ConvertUsing<StringToEnumConverter<CUSTOM_ENUM>>();
}

converters

public class StringToEnumConverter<T> : ITypeConverter<string, T>, ITypeConverter<string, T?> where T : struct
    {
        public T Convert(ResolutionContext context)
        {
            T t;
            if (Enum.TryParse(source, out t))
            {
                return t;
            }

            var source = (string)context.SourceValue;
            if (StringToEnumBase<T>.HasDisplayAttribute())
            {
                var result = StringToEnumBase<T>.Parse(source);
                return result;
            }

            throw new ConverterException();
        }

        T? ITypeConverter<string, T?>.Convert(ResolutionContext context)
        {
            var source = (string)context.SourceValue;
            if (source == null) return null;

            return Convert(context);
        }
    }

    public static class StringToEnumBase<T> where T:struct
        {
            public static T Parse(string str)
            {
                var type = typeof (T);

                var enumMembers = type.GetMembers(BindingFlags.Public | BindingFlags.Static);

                var enumMembersCollection = enumMembers
                    .Select(enumMember => new
                    {
                        enumMember,
                        attributes = enumMember.GetCustomAttributes(typeof(DisplayAttribute), false)
                    })
                    .Select(t1 => new
                    {
                        t1, value = ((DisplayAttribute) t1.attributes[0]).Name
                    })
                    .Select(t1 => new Tuple<string, string>(t1.value, t1.t1.enumMember.Name))
                    .ToList();
                var currentMember = enumMembersCollection.FirstOrDefault(item => item.Item1 == str);
                if (currentMember == null) throw new ConverterException();

                T t;
                if (Enum.TryParse(currentMember.Item2, out t))
                {
                    return t;
                }

                throw new ConverterException();
            }

            public static bool HasDisplayAttribute()
            {
                var type = typeof (T);
                var attributes = type.GetCustomAttributes(typeof(DisplayAttribute), false);
                return attributes.Length > 0;
            }
        }
m4a
  • 187
  • 1
  • 11