简体   繁体   中英

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

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();

and the output: 证明它有效的证据

EDIT You can register your profiles/Mappings individually and force DI to use that ie 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);
       
    }
}

I managed to find my mistake, and I have to admit it is a silly one, but took me a lot of time to figure. The problem is in the line var ss = modelMapper.Map<ApiModel>(settings.Settings); See I have the Profile like this:

CreateMap<DbModel, ApiModel>()
                .ConstructUsing((source, context) => 
                {
                    var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
                    return new ApiModel
                    {
                        Colors = res.Colors
                    };
                });

It expects the source to be a DbModel object, but in fact I pass a property of this object which is in fact a string. And I do not have defined that kind of mapping, that is why I get the error.

The right usage have to be: var ss = modelMapper.Map<ApiModel>(settings);

So thanks for all your suggestions!

Regards, Julian

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM