I'm new in AutoMapper and I have problem with mapping my generic class to generic dto (using AutoMapper). I have this class:
public class Document<T>
{
public string Id { get; set; }
public Tag[] Tags { get; set; }
public T? Data { get; set; }
}
and this dto:
public class DocumentDto<T>
{
public string Id { get; set; }
public string[] Tags { get; set; }
public T? Data { get; set; }
}
and I need to make two-way mapping.. I created mapper profile in which I define this mapping like this:
public MapperProfile()
{
CreateMap<Document<FinancialReport>, DocumentDto<FinancialReportDto>>()
.ForMember(docDto => docDto.Tags, opt => opt.MapFrom(doc => doc.Tags.Select(tag => tag.Value).ToArray()));
CreateMap<DocumentDto<FinancialReportDto>, Document<FinancialReport>>()
.ForMember(docDto => docDto.Tags, opt => opt.MapFrom(doc => doc.Tags.Select(tag => new Tag { Value = tag }).ToArray()));
}
And then I setting this profile in extension method for dependency injection for IMapper:
public static IServiceCollection AddAutoMapper(this IServiceCollection services)
{
services.AddSingleton<IMapper>(sp =>
{
var config = new MapperConfiguration(cfg => {
cfg.AddProfile<MapperProfile>();
});
return config.CreateMapper();
});
return services;
}
And after this all when I try remap from DocumentDto=>Document or vice versa I got error: AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I tryed googled for hours but nothing helped... Any ideas what I'm doing wrong?
UPDATE 1:
I'm calling mapping like this:
public async Task<IResponse> UpdateFinancialReport(DocumentDto<FinancialReportDto> documentDto)
{
var doc = _mapper.Map<Document<FinancialReport>>(documentDto);
}
ok, I found the problem.. In MapperProfile I was missing mapping config for generic types:
CreateMap<FinancialReport, FinancialReportDto>()
.ReverseMap();
Related
We do a lot of mapping where I work. We use and love AutoMapper.
We would like to validate our mapping profiles. We also often want to ignore some legacy fields. It would be great to use MemberList.Source for this. And it mostly works fine, unless we want some fields to have some sort of special treatment. AutoMapper gets upset during validation if we want to use extension methods on fields or even ValueResolvers.It claims said fields are not mapped.
Is this by design, a bug, am I “holding it wrong” or just missing something obvious? Error message and repro follows.
Both tests errors out with this message
AutoMapper.AutoMapperConfigurationException
# Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
Source -> Destination (Source member list)
AutomapperRepro.Source -> AutomapperRepro.Destination (Source member list)
Unmapped properties:
FieldB
Code:
using AutoMapper;
using Xunit;
namespace AutomapperRepro;
public class MappingTests
{
[Fact]
public void MappingProfile_IsValid()
{
var config = new MapperConfiguration(config => config.AddProfile<MappingProfile>());
config.AssertConfigurationIsValid();
}
[Fact]
public void MappingProfileValueResolver_IsValid()
{
var config = new MapperConfiguration(config => config.AddProfile<MappingProfileValueResolver>());
config.AssertConfigurationIsValid();
}
}
public class MappingProfile: Profile
{
public MappingProfile()
{
CreateMap<Source, Destination>(MemberList.Source)
.ForMember(dest => dest.FieldBPadded, opt => opt.MapFrom(s => s.FieldB.PadFieldB()));
}
}
public class MappingProfileValueResolver : Profile
{
public MappingProfileValueResolver()
{
CreateMap<Source, Destination>(MemberList.Source)
.ForMember(dest => dest.FieldBPadded, opt => opt.MapFrom(new PaddingResolver(), src => src.FieldB));
}
}
public static class PaddingExtentions
{
public static string PadFieldB(this string src)
{
return src.PadLeft(10, '0');
}
}
public class PaddingResolver : IMemberValueResolver<object, object, string, string>
{
public string Resolve(object source, object destination, string sourceMember, string destinationMember,
ResolutionContext context)
{
return sourceMember.PadFieldB();
}
}
public record Source
{
public string FieldA { get; set; } = string.Empty;
public string FieldB { get; set; } = string.Empty;
}
public record Destination
{
public string FieldA { get; set; } = string.Empty;
public string FieldBPadded { get; set; } = string.Empty;
public string SomeLegacyFieldThatCanBeIgnored { get; set; } = string.Empty;
public string SomeLegacyFieldThatCanBeIgnored2 { get; set; } = string.Empty;
public string SomeLegacyFieldThatCanBeIgnored3 { get; set; } = string.Empty;
}
Both suggestions from #LucianBargaoanu are valid👍 Combining MapFrom with a transformer worked great for me:
public class MappingProfileValueTransformer : Profile
{
public MappingProfileValueTransformer()
{
CreateMap<Source, Destination>(MemberList.Source)
.ForMember(dest => dest.FieldBPadded, opt =>
{
opt.MapFrom(s => s.FieldB);
opt.AddTransform(dest => dest.PadFieldB());
});
}
}
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
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?
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();
}
}
}
I managed to do it with Mapster. What I did was
in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Some other magical code
// Tell Mapster to scan this assambly searching for the Mapster.IRegister
// classes and execute them
TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetExecutingAssembly());
}
Create another class like this
using Mapster;
namespace Your.Cool.Namespace
{
public class MappingConfig : IRegister
{
public void Register(TypeAdapterConfig config)
{
// Put your mapping logic here
config
.NewConfig<MySourceType, MyDestinyType>()
.Map(dest => dest.PropA, src => src.PropB);
}
}
}
The key part is using TypeAdapterConfig.GlobalSettings, which is a static public singleton used by Mapster to hold the mappig config. If you do what Jack suggests, it will be a complety new TypeAdapterConfig and not the actual one being used by Mapster and won't work (at least it didn't for me).
On your unit tests remember to load the mapping profile too
[AssemblyInitialize] // Magic part 1 ~(˘▾˘~)
public static void AssemblyInitialization(TestContext testContext)
{
// Magic part 2 (~˘▾˘)~
TypeAdapterConfig.GlobalSettings.Scan(AppDomain.CurrentDomain.GetAssemblies());
}
You can use next:
var config = new TypeAdapterConfig()
{
RequireExplicitMapping = true,
RequireDestinationMemberSource = true,
Compiler = exp => exp.CompileFast()
};
config.Scan("Your assembly");
services.AddSingleton(config);
services.AddTransient<IMapper, ServiceMapper>();
public class RegisterConfig : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<TSource, TDestination>();
}
}
Where services is IServiceCollection
Based on #Felipe Ramos answer I wasn't able to solve it with Mapster but with Automapper. This is my solution just for the sake of completeness. Please let me know if there is a solution for Mapster!
I installed the packages
AutoMapper v10.1.1
AutoMapper.Extensions.Microsoft.DependencyInjection v8.1.1
Inside the method Startup.ConfigureServices I added the line services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
The whole code then looks like
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
private readonly IMapper _mapper;
public MyController(IMapper mapper)
{
_mapper = mapper;
}
[HttpGet]
public ActionResult<UsernameWithTodoTitle> Get()
{
var user = new User { Username = "foo", FieldFromUser = "x" };
var todo = new Todo { Title = "bar", FieldFromTodo = "y" };
var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>((user, todo));
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; }
}
public class UsernameWithTodoTitleMappingProfile : Profile
{
public UsernameWithTodoTitleMappingProfile()
{
CreateMap<(User, Todo), UsernameWithTodoTitle>()
.ForMember(
destination => destination.Username,
memberOptions => memberOptions.MapFrom(source => source.Item1.Username))
.ForMember(
destination => destination.TodoTitle,
memberOptions => memberOptions.MapFrom(source => source.Item2.Title))
.ForMember(
destination => destination.FieldFromUser,
memberOptions => memberOptions.MapFrom(source => source.Item1.FieldFromUser))
.ForMember(
destination => destination.FieldFromTodo,
memberOptions => memberOptions.MapFrom(source => source.Item2.FieldFromTodo));
}
}
There is a question, that describes what i want to get very precisely, but they are using inline mapping.
Source/destination types
public class SrcInner
{
public int A {get;set;} // imagine here 100500 properties
}
public class SrcOuter
{
public int B {get;set;}
public SrcInner C {get;set}
}
public class Dest
{
public int A {get;set;} // so here imagine 100500 same properties, as in SrcInner
public int B {get;set;}
}
Mapping configuration
public static void AddMapper(this IServiceCollection services)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SrcInner, Dest>();
cfg.CreateMap<SrcOuter, Dest>();
});
var mapper = config.CreateMapper();
services.AddSingleton(mapper);
}
Expected behavior
Both A and B properties are filled after mapping.
Actual behavior
Only B property is filled after mapping.
Steps to reproduce
public class Foo
{
IMapper Mapper{get;set;}
public Foo(IMapper mapper) // comes through dependency injection
{
Mapper = mapper;
}
public Bar()
{
var test = new SrcOuter()
{
B = 10;
C = new SrcInner()
{
A = 10;
}
}
var testDest = new Dest();
mapper.Map(test, Dest);
}
}
Is there a proper way to set configuration that way, so such mapping will work?
UPDATE
Is there a way to map Dest to SrcOuter with filling SrcInner?
The documentation describes how this can be achieved using IncludeMembers here
In your case, the config would be:
cfg.CreateMap<SrcOuter, Dest>().IncludeMembers(s => s.C);
cfg.CreateMap<SrcInner, Dest>(MemberList.None);
If you want this to work both ways, the most idiomatic way to do this is to prefix your destination members with the nested source object name, so that flattening and unflattening works automatically.
E.g. if you have the following objects:
public class SrcInner
{
public int A { get; set; }
}
public class SrcOuter
{
public int B { get; set; }
public SrcInner Inner { get; set; }
}
public class Dest
{
public int InnerA { get; set; }
public int B { get; set; }
}
You don't need any more configuration than:
cfg.CreateMap<SrcOuter, Dest>().ReverseMap();
I think you need to look at this: https://docs.automapper.org/en/stable/Projection.html
And then you need to
.ForMember(dest => dest.A, opt => opt.MapFrom(src => src.C.A))
I have the following code
IList<ConfigurationDto> result = new List<ConfigurationDto>();
foreach (var configuration in await configurations.ToListAsync())
{
var configurationDto = _mapper.Map<ConfigurationDto>(configuration);
configurationDto.FilePath = _fileStorage.GetShortTemporaryLink(configuration.FilePath);
result.Add(configurationDto);
}
return result;
How can I use automapper instead if foreach? I can map collection, but how to call _fileStorage.GetShortTemporaryLink for each item?
I have looked at AfterMap but I don't know how to get FilePath from dest and map it to src one by one. Can I use automapper for that?
public class ConfigurationDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public DateTime CreateDateTime { get; set; }
public long Size { get; set; }
public string FilePath { get; set; }
}
You can use the IValueResolver interface to configure your map to map a property from a function. Something like the sample bellow.
public class CustomResolver : IValueResolver<Configuration, ConfigurationDto, string>
{
private readonly IFileStorage fileStorage;
public CustomResolver(IFileStorage fileStorage)
{
_fileStorage= fileStorage;
}
public int Resolve(Configuration source, ConfigurationDto destination, string member, ResolutionContext context)
{
return _fileStorage.GetShortTemporaryLink(source.FilePath);
}
}
Once we have our IValueResolver implementation, we’ll need to tell AutoMapper to use this custom value resolver when resolving a specific destination member. We have several options in telling AutoMapper a custom value resolver to use, including:
MapFrom<TValueResolver>
MapFrom(typeof(CustomValueResolver))
MapFrom(aValueResolverInstance)
Then you should configure your map to use the custom resolver for mapping the FilePath property on ConfigurationDto.
var configuration = new MapperConfiguration(cfg => cfg.CreateMap<Configuration, ConfigurationDto>()
.ForMember(dest => dest.FilePath, opt => opt.MapFrom<CustomResolver>()));
You can see more about custom value resolvers at this link: http://docs.automapper.org/en/stable/Custom-value-resolvers.html
This is probably a simple answer, but i'm just starting to get the hand of AutoMapper.
Anyway, i have a domain object like this:
public class User
{
public string Name { get; set; }
public FacebookUser FacebookUser { get; set; }
}
And a ViewModel like this:
public class UserViewModel
{
public string Name { get; set; }
public long FacebookUniqueId { get; set; }
}
Here's what i have in my AutoMapper configuration:
Mapper.CreateMap<User,UserViewModel>()
.ForMember(dest => dest.FacebookUniqueId, opt => opt.MapFrom(src => src.FacebookUser.FacebookUniqueId))
But it throws an exception when the FacebookUser object is null, which is to be expected.
How do i tell AutoMapper:
Map UserViewModel.FacebookUniqueId to User.FacebookUser.FacebookUniqueId, except for when it's null, then use 0.
Any ideas?
opt => opt.MapFrom(src => src.FacebookUser == null ? 0 : src.FacebookUser.FacebookUniqueId)
Yikes, if I had just checked if the first idea worked, thought the MapFrom method only took an expression that pointed to a property to resolve it that way...
The code you provided works on the latest Automapper. I investigated the sources a little and found the following code:
try
{
var result = _method((TSource)source.Value);
return source.New(result, MemberType);
}
catch (NullReferenceException)
{
return source.New(null, MemberType);
}
As you can see now Automapper catches all the Null exceptions while resolving chain mappings. The following test is green:
[TestFixture]
public class AutomapperChainingMappingTest
{
[Test]
public void ChainMapping_NullProperty_DefaultValueSet()
{
AutoMapper.Mapper.CreateMap<User, UserViewModel>()
.ForMember(x => x.FacebookUniqueId, o => o.MapFrom(x => x.FacebookUser.FacebookUniqueId));
var source = new User();
var model = AutoMapper.Mapper.Map<UserViewModel>(source);
Assert.AreEqual(model.FacebookUniqueId, default(long));
}
}
public class User
{
public FacebookUser FacebookUser { get; set; }
}
public class UserViewModel
{
public long FacebookUniqueId { get; set; }
}
public class FacebookUser
{
public long? FacebookUniqueId { get; set; }
}