Search code examples
c#.net-core.net-5mapster

How to create a reusable mapping profile with Mapster?


I have a .Net 5 Web Api project and want to use

Mapster v7.2.0

to avoid mapping objects manually. The following code shows a sample scenario

  • setup a mapping configuration
  • map from multiple sources
  • map to fields with different names

.

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    [HttpGet]
    public ActionResult<UsernameWithTodoTitle> Get()
    {
        TypeAdapterConfig<(User, Todo), UsernameWithTodoTitle>
            .NewConfig()
            .Map(dest => dest, src => src.Item1) // map everything from user
            .Map(dest => dest, src => src.Item2) // map everything from todo
            .Map(dest => dest.TodoTitle, src => src.Item2.Title); // map the special fields from todo
        
        var user = new User { Username = "foo", FieldFromUser = "x" };
        var todo = new Todo { Title = "bar", FieldFromTodo = "y" };
        
        var usernameWithTodoTitle = (user, todo).Adapt<(User, Todo), UsernameWithTodoTitle>();
        
        return Ok(usernameWithTodoTitle);
    }
}

public class User
{
    public string Username { get; set; }
    public string FieldFromUser { get; set; }
}

public class Todo
{
    public string Title { get; set; } // !! map this one to the TodoTitle field !!
    public string FieldFromTodo { get; set; }
}

public class UsernameWithTodoTitle
{
    public string Username { get; set; }
    public string TodoTitle { get; set; } // !! this one is special, is has a different name !!
    public string FieldFromUser { get; set; }
    public string FieldFromTodo { get; set; }
}

When running the app the mapping seems to work fine this way

enter image description here

I had to setup the configuration this way, other ways didn't work for me. But there are 3 things left to be solved

  • The configuration looks wrong to me. It maps everything from the todo and maps the special field again ... so it might loop through multiple times? This might get expensive, if there are multiple fields with different names
  • I created the configuration inside the controller. How can I create a reusable mapping profile class registered once globally?
  • When having a mapping profile this line var usernameWithTodoTitle = (user, todo).Adapt<(User, Todo), UsernameWithTodoTitle>(); looks quite messy to me. Better would be var usernameWithTodoTitle = UsernameWithTodoTitle.Adapt((user, todo)) /* pass in as a tuple */ because based on the parameter type it chooses the correct mapping profile

Do you guys have any ideas how to create such a mapping profile?


Solution

  • Updated: Couldn't find way to do what you are trying to do with Mapster, but here is an example of it working with Automapper.

    using AutoMapper;
    using System;
    
    namespace ConsoleApp5
    {
        class A { public string FirstName { get; set; } }
    
        public class B { public string Address1 { get; set; } }
    
        public class C
        {
            public string FirstName { get; set; }
            public string Address1 { get; set; }
        }
    
        public class DemoProfile : Profile
        {
            public DemoProfile()
            {
                CreateMap<(A, B), C>()
                    .ForMember(dest=> dest.FirstName, opts => opts.MapFrom(src => src.Item1.FirstName))
                    .ForMember(dest => dest.Address1, opts => opts.MapFrom(src => src.Item2.Address1));
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                var config = new MapperConfiguration(cfg => {
                    cfg.AddProfile<DemoProfile>();
                });
    
                var mapper = config.CreateMapper();
                var destination = mapper.Map<C>((new A {  FirstName = "Test" }, new B { Address1 = "Addr" }));
    
                Console.ReadKey();
            }
        }
    }
    

    Hey I haven't used Mapster before till now but here is what I gather. It is very specific about the type of tuple you use Tuple<T1,T2> over (T1,T2) but aside from that minor thing I was able to get it running and mapping without issues. Here is a small console example as example.

    using Mapster;
    using System;
    
    namespace ConsoleApp5
    {
        class A { public string FirstName { get; set; } }
    
        public class B { public string Address1 { get; set; } }
    
        public class C
        {
            public string FirstName { get; set; }
            public string Address1 { get; set; }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                // Option 1
                TypeAdapterConfig<Tuple<A, B>, C>.NewConfig()
                    .Map(dest => dest.FirstName, src => src.Item1.FirstName)
                    .Map(dest => dest.Address1, src => src.Item2.Address1);
    
                var destObject = new Tuple<A, B>(new A { FirstName = "Test" }, new B { Address1 = "Address 1" })
                    .Adapt<Tuple<A, B>, C>();
    
                // Option 2
                TypeAdapterConfig<(A, B), C>.NewConfig()
                    .Map(dest => dest.FirstName, src => src.Item1.FirstName)
                    .Map(dest => dest.Address1, src => src.Item2.Address1);
    
                var destObject2 = (new A { FirstName = "Test" }, new B { Address1 = "Address 1" })
                    .Adapt<(A, B), C>();
    
                Console.ReadKey();
            }
        }
    }