18

I am creating a simple MVC4 application

I have a automapper

 Mapper.CreateMap<SourceClass, DestinationClass>()
      .ForMember(dest => dest.IntphoneNo, 
                  opt => opt.MapFrom(src => src.Stringphoneno));

IntphoneNo is of DataType int ( IntphoneNo is an variable of my class Person) Source attribute Stringphoneno is of Datatype string.

When i am mapping , i am getting follwoing error.

An exception of type 'AutoMapper.AutoMapperMappingException' occurred in AutoMapper.dll but was not handled in user code

But when i am changing the Dataype of IntphoneNo from int to string then my program is running successfully.

Unfortunately i cant change the Datatype inmy model

Is theer any way to change Datatupe in mapping .. Something like below

.ForMember(dest => dest.IntphoneNo, 
                  opt => opt.MapFrom(src => src.Int32(Stringphoneno));

After some research I came one step futher ..
If my StringPhoneNo is = 123456
then follwoing code is working. I dont need to parse it to string

Mapper.CreateMap<SourceClass, DestinationClass>()
      .ForMember(dest => dest.IntphoneNo, 
                  opt => opt.MapFrom(src => src.Stringphoneno));

but when my StringPhoneNo is = 12 3456 ( there is a space after 12) then my code is not working. Is there any way to trim spaces in Stringphoneno (Stringphoneno i am geting from webservice) in automapper.

Something like below..

Mapper.CreateMap<SourceClass, DestinationClass>()
      .ForMember(dest => dest.IntphoneNo, 
                  opt => opt.MapFrom(src => src.Trim(Stringphoneno))); 
Zippy
  • 1,804
  • 5
  • 27
  • 36
user2739679
  • 827
  • 4
  • 14
  • 24
  • 1
    I wouldn't advise storing phonenumbers(i assume) as ints, you'll lose the international/regional zero prefix numbers. Also, some numbers can be very long. (unless you use a int64) With 11 numbers you got some overflow problems. – Jeroen van Langen Sep 12 '13 at 14:52
  • @JeroenvanLangen I came across the same kind of advice at one point when someone pointed out that we shouldn't be using the money sql type to store money and should instead use decimal. I had a good laugh when I pointed out that we worked in dollars and if we ever had an order where the purchase actually was larger than the money type, I would do the order by hand, we were a small wine company. Not really relevant, just a story I always liked. – Yuriy Faktorovich Sep 12 '13 at 15:06
  • @Yuriy Faktorovich - But finding a International phone number larger than 10 digit is much easier than scoring an order larger than max money-type :) – Jeroen van Langen Sep 12 '13 at 15:09
  • Data type of source is not in my control. I am getting source thru a webservice.. – user2739679 Sep 12 '13 at 15:10
  • @JeroenvanLangen I agree, just a story I always liked. – Yuriy Faktorovich Sep 12 '13 at 15:12

4 Answers4

25
Mapper.CreateMap<SourceClass, DestinationClass>() 
    .ForMember(dest => dest.IntphoneNo, 
        opt => opt.MapFrom(src => int.Parse(src.Stringphoneno)));

Here is some sample working code using the map described

class SourceClass
{
    public string Stringphoneno { get; set; }
}

class DestinationClass
{
    public int IntphoneNo { get; set; }
}

var source = new SourceClass {Stringphoneno = "8675309"};
var destination = Mapper.Map<SourceClass, DestinationClass>(source);

Console.WriteLine(destination.IntphoneNo); //8675309
Yuriy Faktorovich
  • 67,283
  • 14
  • 105
  • 142
  • 1
    you need to capture the exception, maybe the casting to string--> int is not possible. – ZaoTaoBao Sep 12 '13 at 14:48
  • 1
    http://stackoverflow.com/questions/136035/catch-multiple-exceptions-at-once to capture the exception!! maybe the string is not a number... – ZaoTaoBao Sep 12 '13 at 14:57
  • @user2739679 int.Parse and Int32.Parse are literally the same thing, int maps to Int32. You can place a breakpoint into the lambda expression that's doing the parsing to see what the value is of Stringphoneno to know if you need to format it, strip it of dashes for example, first. – Yuriy Faktorovich Sep 12 '13 at 15:05
  • i cant put a break point ons line inside Mapper.CreateMap. When i am trying to put a break point it is selecting whole area of automapper – user2739679 Sep 12 '13 at 15:12
  • @user2739679 highlight `int.Parse(src.Stringphoneno)` right click on it select insert breakpoint. – Yuriy Faktorovich Sep 12 '13 at 15:14
  • value of **src.stringphoneNo** is a 12345678... but **int.Parse** is not working evenif i change the Datatype of **IntphoneNo** to **string**... – user2739679 Sep 12 '13 at 15:29
  • @user2739679 you're going to have to be specific about what that number is, otherwise your problem is elsewhere. I've posted some working code. – Yuriy Faktorovich Sep 12 '13 at 15:55
  • @ Yuriy Faktorovich.. **StringPhoneNo** i am getting from webservice.. Dont have any control over **StringPhoneNo**.. Only thing i see is DATATYPE of **StringPhoneNo** is string and DATATYPE of **IntPhoneNo** in my model is string.. – user2739679 Sep 13 '13 at 07:02
  • @ Yuriy Faktorovich.. to be precise value in **StringPhoneNo ** is 12 23456 ..( there is a space after 12)... – user2739679 Sep 13 '13 at 07:06
  • @user2739679 if it's your model, then you should change it to string. Also you could just replace that space before int.Parse. – Yuriy Faktorovich Sep 13 '13 at 12:35
12

The problem you may face is when it is unable to parse the string, one option would be to use a ResolveUsing:

Mapper.CreateMap<SourceClass, DestinationClass>() 
.ForMember(dest => dest.intphoneno, opts => opts.ResolveUsing(src =>
                double.TryParse(src.strphoneno, out var phoneNo) ? phoneNo : default(double)));
StuckOverflow
  • 991
  • 2
  • 10
  • 21
  • 1
    this can be a one-liner (...`src => double.TryParse(src.strphoneno, out var phoneNo) ? phoneNo : default(double)`) – Dinerdo Dec 14 '18 at 22:33
  • 6
    This solution doesn't seem to work any more. `ResolveUsing` was removed, and the recommended alternative in the docs, `MapFrom`, uses expression trees so you cannot use `out` arguments. – Guttsy Feb 10 '21 at 22:20
3

Yuriy Faktorovich's solution can be generalized for all strings that should be converted to ints by defining an extension method for IMappingExpression and usage of some custom attribute:

[AttributeUsage(AttributeTargets.Property)]
public class StringToIntConvertAttribute : Attribute
{
}

// tries to convert string property value to integer
private static int GetIntFromString<TSource>(TSource src, string propertyName)
{
    var propInfo = typeof(TSource).GetProperty(propertyName);
    var reference = (propInfo.GetValue(src) as string);
    if (reference == null)
        throw new MappingException($"Failed to map type {typeof(TSource).Name} because {propertyName} is not a string);

    // TryParse would be better
    var intVal = int.Parse(reference);
    return intVal;
}

public static IMappingExpression<TSource, TDestination> StringToIntMapping<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
    var srcType = typeof(TSource);
    foreach (var property in srcType.GetProperties().Where(p => p.GetCustomAttributes(typeof(StringToIntConvertAttribute), inherit: false).Length > 0))
    {
        expression.ForMember(property.Name, opt => opt.MapFrom(src => GetIntFromString(src, property.Name)));
    }

    return expression;
}

In order to make this work, the following steps must be performed:

  1. Mapping between source and destination must append the mapping extension. E.g.:

    var config = new MapperConfiguration(cfg =>
    {
       // other mappings
    
       cfg.CreateMap<SrcType, DestType>().StringToIntMapping();
    });
    
  2. Apply the attribute to all source string properties that must automatically be converted to integer values

Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
0

Here's a simpler, but less scalable approach. Whenever you call Mapper.Initialize, remember to call .ToInt() or you'll get a run time error.

public class NumberTest
{
    public static void Main(string[] args)
    {
        Console.WriteLine("".ToInt());
        // 0
        Console.WriteLine("99".ToInt());
        // 99
    }
}

public static class StringExtensions
{
    public static int ToInt(this string text)
    {
        int num = 0;
        int.TryParse(text, out num);
        return num;
    }
}

// Use like so with Automapper
.ForMember(dest => dest.WidgetID, opts => opts.MapFrom(src => src.WidgetID.ToInt()));
Mitch Stewart
  • 1,253
  • 10
  • 12