I have a record AuthResult
with a class object User
in it:
public record AuthResult(
User User,
string Token);
public class User
{
public Guid Id { get; set; } = Guid.NewGuid();
public string UserName { get; set; } = null!;
public string Email { get; set; } = null!;
public string Password { get; set; } = null!;
}
Which I want to map to a flat record AuthResponse
:
public record AuthResponse(
Guid Id,
string UserName,
string Email,
string Token);
Mapping:
_mapper.Map<AuthResponse>(authResult)
Configurations I tried:
CreateMap<User, AuthResponse>();
// Gives "AuthResponse does not have a matching constructor with a parameter named 'Token'" exception
CreateMap<AuthResult, AuthResponse>()
.ForCtorParam(ctorParamName: nameof(AuthResponse.Token), opt => opt.MapFrom(src => src.Token))
.AfterMap((src, dest, context) => context.Mapper.Map(src.User, dest));
// Gives "AuthResponse needs to have a constructor with 0 args or only optional args."
CreateMap<AuthResult, AuthResponse>().IncludeMembers(src => src.User);
// Gives "AuthResponse needs to have a constructor with 0 args or only optional args."
CreateMap<AuthResult, AuthResponse>()
.ConvertUsing((wrapper, destination, context) =>
context.Mapper.Map<AuthResponse>(wrapper.User));
Am I missing something or that impossible to do with the AutoMapper?
CodePudding user response:
The other answer does work. It also requires you to change your mapping every time the record or class changes their structure/constructors.
If you want to create the map once, and never touch it again even when your entities change, I can think of two alternatives:
- Match the properties in your
record
with the nested class, like so:
// Include the word 'User' to reflect the nested AuthResult.User
public record AuthResponse(
Guid UserId,
string UserUserName,
string UserEmail,
string Token);
// This makes the mapping extremely straightforward:
cfg.CreateMap<AuthResult, AuthResponse>();
- Or, allow your
record
to be created with a 0-arg constructor (respecting the error you saw)
public record AuthResponse(
Guid Id = default,
string UserName = default!,
string Email = default!,
string Token = default!);
// This allows you to use the following
cfg.CreateMap<User, AuthResponse>();
cfg.CreateMap<AuthResult, AuthResponse>()
.IncludeMember(src => src.User);
/* If the above doesn't work, try instead: */
//.AfterMap((src, dest, context) => context.Mapper.Map(src.User, dest));
The 2nd option will likely be negligibly less efficient.
CodePudding user response:
I tried the following code, and it seems to work,
IConfigurationProvider configuration = new MapperConfiguration(cfg =>
cfg.CreateMap<AuthResult, AuthResponse>()
.ForCtorParam(ctorParamName: nameof(AuthResponse.Id), m => m.MapFrom(s => s.User.Id))
.ForCtorParam(ctorParamName: nameof(AuthResponse.UserName), m => m.MapFrom(s => s.User.UserName))
.ForCtorParam(ctorParamName: nameof(AuthResponse.Email), m => m.MapFrom(s => s.User.Email)));
var mapper = configuration.CreateMapper();
var result = mapper.Map<AuthResponse>(new AuthResult(new User { UserName = "Test", Email = "sas" }, "token"));