'AutoMapper problem with custom convert from source to destination
I'm using AutoMapper to map my db models with my api models. But I have a problem with custom mapping. I'll try to explain my problem:
So I have the db and api models like this:
public class ApiModel
{
public List<ApiSubModel> Colors { get; set; }
}
public class DbModel
{
public string Type { get; set; }
public string Settings { get; set; }
}
Generally the DbModel Settings property is the serialized version of the ApiModel . So I want to achieve that with a custom convert when creating the maping:
Startup.cs:
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies())
Mapper profile class:
internal class DbToApiMapping : Profile
{
public DbToApiMapping()
{
CreateMap<ApiModel, DbModel>()
.ConvertUsing((source, dest, context) => new DbModel
{
Type = context.Items["Type"].ToString(),
Settings = JsonConvert.SerializeObject(source)
});
CreateMap<DbModel, ApiModel>()
.ConstructUsing((source, context) =>
{
var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
return new ApiModel
{
Colors = res.Colors
};
});
}
}
For the first map I use it like this:
var settings = modelMapper.Map<DbModel>(req.Settings, opt => opt.Items["Type"] = "Setpoint");
For the second map I use it like that:
var ss = modelMapper.Map<ApiModel>(settings.Settings);
The error I get when try to map is as follows:
Message:
AutoMapper.AutoMapperMappingException : Missing type map configuration or unsupported mapping.
Mapping types:
Object -> ApiModel
System.Object -> CommonLibrary.Models.ApiModel
I'm sure that I'm doing something wrong...but I can't quite catch what to look exactly. For the second mapping I tried with .ConvertUsing() method, but the error is the same.
Can someone help with this one.
Thanks in advance
Julian
---EDIT--- As suggested in the comments I tried without the DI. Here is the code:
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration( cfg =>
{
cfg.CreateMap<ApiModel, DbModel>()
.ConvertUsing((source, dest, context) => new DbModel
{
Type = context.Items["Type"].ToString(),
Settings = JsonConvert.SerializeObject(source)
});
cfg.CreateMap<DbModel, ApiModel>()
.ConstructUsing((source, context) =>
{
var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
return new ApiModel
{
Colors = res.Colors
};
});
});
var modelMapper = config.CreateMapper();
var apiObj = new ApiModel
{
Colors = new List<ApiSubModel>
{
new ApiSubModel
{
SomeProp = "Test",
SubModel = new ApiSubSubModel
{
IntProp = 3,
StringProp = "Alabala"
}
}
}
};
DbModel dbRes = modelMapper.Map<DbModel>(apiObj, opt => opt.Items["Type"] = "Setpoint");
var dbObj = new DbModel
{
Type = "Setpoint",
Settings = "{\"Colors\":[{\"SomeProp\":\"Test\",\"SubModel\":{\"IntProp\":3,\"StringProp\":\"Alabala\"}}]}"
};
var apiRes = modelMapper.Map<ApiModel>(dbObj);
Console.WriteLine(dbRes.Settings);
Console.WriteLine(apiRes.Colors[0].SomeProp);
Console.WriteLine(apiRes.Colors[0].SubModel.StringProp);
Console.ReadLine();
}
}
public class ApiModel
{
public List<ApiSubModel> Colors { get; set; }
}
public class DbModel
{
public string Type { get; set; }
public string Settings { get; set; }
}
public class ApiSubModel
{
public string SomeProp { get; set; }
public ApiSubSubModel SubModel { get; set; }
}
public class ApiSubSubModel
{
public int IntProp { get; set; }
public string StringProp { get; set; }
}
It IS working, but there is something strange, when I want to debug the program and put a break point after var apiRes = modelMapper.Map<ApiModel>(dbObj); and try to debug the value of apiRes it says apiRes error CS0103: The name 'apiRes' does not exist in the current context
Solution 1:[1]
I tweaked your code a bit and now it works(although I had to use my own JSON and my own SubApiModel) but you can ask me about it in the comments if you're unsure
My models
public class ApiModel
{
public List<ApiSubModel> Colors { get; set; }
}
public class ApiSubModel
{
public string Name { get; set; }
}
public class DbModel
{
public DbModel(string type, string settings)
{
Type = type;
Settings = settings;
}
public string Type { get; set; }
public string Settings { get; set; }
}
and my JSON
{
Colors:
[
{
"Name": "AMunim"
}
]
}
and this is my mapping configuration:
.CreateMap<DbModel, ApiModel>()
.ConstructUsing((source, context) =>
{
var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
return res;
});
This basically deserializes the whole JSON and since it has a property Color which is a list of ApiSubModels, I can simply convert the whole string to Object(of type ApiModel).
This is my complete testing code
using AutoMapper;
using Newtonsoft.Json;
using StackAnswers;
using StackAnswers.Automapper;
using System.Numerics;
DbModel dbModel = new DbModel("very important type", "{Colors: [{\"Name\": \"AMunim\"}]}");
MapperConfiguration config = new(cfg =>
{
cfg.CreateMap<DbModel, ApiModel>()
.ConstructUsing((source, context) =>
{
var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
return res;
});
});
Mapper mapper = new(config);
ApiModel apiModel = mapper.Map<DbModel, ApiModel>(dbModel);
Console.WriteLine(apiModel.Colors.Count);
Console.WriteLine(apiModel.Colors.FirstOrDefault()?.Name);
Console.Read();
EDIT You can register your profiles/Mappings individually and force DI to use that i.e. register that
var config = new MapperConfiguration(c => {
//profile
c.AddProfile<DbToApiMapping>();
//mappings
c.CreateMap<DbModel, ApiModel>()
.ConstructUsing((source, context) =>
{
var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
return res;
});
});
//now register this instance
services.AddSingleton<IMapper>(s => config.CreateMapper());
Now when you request this service you will get the instance with configuration applied in your ctor
public class BadClass
{
private readonly IMapper _mapper;
BadClass(IMapper mapper)
{
_mapper = mapper;
}
public void HandleFunction()
{
//here you can use this
ApiModel apiModel = _mapper.Map<DbModel, ApiModel>(dbModel);
}
}
Solution 2:[2]
Maybe you could use the built-in pattern matching support in Bash:
test.sh
# https://github.com/micromatch/posix-character-classes#posix-character-classes
declare -r pat='^[[:space:]]*([[:digit:]]+):\[([^]]+)\]$'
declare -r str=' 3:[numbers and text]'
if [[ $str =~ $pat ]]; then
declare -p BASH_REMATCH
echo "number: '${BASH_REMATCH[1]}'"
echo "string: '${BASH_REMATCH[2]}'"
fi
$ ./test.sh
declare -a BASH_REMATCH=([0]=" 3:[numbers and text]" [1]="3" [2]="numbers and text")
number: '3'
string: 'numbers and text'
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |

