Map just one field with Automapper - c#

I'm using Automapper to define a mapping configuration between some objects. I need to retrieve single mapped values for the destination based on the configuration.
Let's say I have a domain object
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
and a corresponding DTO
class PersonDTO
{
public string FullName { get; set; }
}
and a mapping
Mapper.CreateMap<Person, PersonDTO>()
.ForMember(
dest => dest.FullName,
opts => opts.MapFrom(src => src.FirstName + " " + src.LastName));
I'd like a method like Mapper.MapField<PersonDTO>(person, x => x.FullName) that returns just one mapped value (that would get assigned to the specified field in a full mapping). Is there any way I can do this without mapping the whole object and extracting the value from the result?

First create a resolver and then use the resolve using method in your mapper call.
public class PersonNameResolver : ValueResolver<Person, PersonDTO>
{
protected override PersonDTO ResolveCore(Person source)
{
return new PersonDTO
{
FirstName = source.FirstName,
LastName = source.LastName
};
}
}
Once this is created, use the following in your mapper call.
Mapper.CreateMap<Person, PersonDTO>()
.ForMember(
dest => dest.Name,
opts => opts.ResolveUsing<PersonNameResolver>().FromMember(o => o))

Related

Map two classes in runtime using column mapping information provided in a JSON file

I have two Classes like this.
public class InputModel
{
public int studentid { get; set; }
public string studentname { get; set; }
public string studentcity { get; set; }
}
public class OutputModel
{
public int StudentIDColumn { get; set; }
public string StudentNameColumn { get; set; }
public string StudentCityColumn { get; set; }
}
Now the requirement is like this:
I will receive an object of InputModel Class. From this, I need to create an object of OutputModel class.
It's simple if we use a library like AutoMapper. But the problem is, the Column-To-Column Mapping information will be supplied via a Json File like this:
{
"studentid": "StudentIDColumn",
"studentname": "StudentNameColumn",
"studentcity": "StudentCityColumn"
}
Based on the JSON mapping data, I need to Map the columns in runtime and generate the Output class object.
I tried to map the two classes using Automapper. But I am not sure how to do it in runtime using the JSON file.
var MapperConfig = new MapperConfiguration(c =>
c.CreateMap<InputCSVModel, OutputIDMModel>()
.ForMember(dest => dest.StudentIDColumn, act => act.MapFrom(src => src.studentid))
.ForMember(dest => dest.StudentNameColumn, act => act.MapFrom(src => src.studentname))
.ForMember(dest => dest.StudentCityColumn, act => act.MapFrom(src => src.studentcity))
);
var mapper = new Mapper(MapperConfig);
OutputIDMModel outModel = mapper.Map<OutputIDMModel>(inputModel);
I know that it's might be possible to do this with Reflection. But is there any better approach ?
I was able to read the JSOn file and pass the strings in the automapper config like this.
var MapperConfig = new MapperConfiguration(c =>
c.CreateMap<InputModel, OutputModel>()
.ForMember("StudentIDColumn", opt => opt.MapFrom("studentid"))
.ForMember("StudentNameColumn", opt => opt.MapFrom("studentname"))
.ForMember("StudentCityColumn", opt => opt.MapFrom("studentcity"))
);

How to map from a tuple without specifying each member field?

Given the following sample classes
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 UsernameWithTodoTitle.TodoTitle !!
public string FieldFromTodo { get; set; }
}
public class UsernameWithTodoTitle
{
public string Username { get; set; }
public string TodoTitle { get; set; } // !! this field represents Todo.Title !!
public string FieldFromUser { get; set; }
public string FieldFromTodo { get; set; }
}
I want to map a user and a todo to UsernameWithTodoTitle. To solve this I'm using the packages
AutoMapper v10.1.1
AutoMapper.Extensions.Microsoft.DependencyInjection v8.1.1
When dealing with multiple sources this solution works fine for me:
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));
}
}
So I could map a user and a todo like so
var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>((user, todo));
Since I'm using a tuple I have to specify each field because obviously Automapper can't know where to search for these fields when dealing with a tuple. The only different field names are Todo.Title and UsernameWithTodoTitle.TodoTitle. I would like to know if I can simplify the mapping profile, maybe I don't even have to use a tuple? The following profile is just a pseudo implementation what I would like to achieve
public class UsernameWithTodoTitleMappingProfile : Profile
{
public UsernameWithTodoTitleMappingProfile()
{
CreateMap<(User, Todo), UsernameWithTodoTitle>()
// map all fields by name as expected and ...
.ForMember(
destination => destination.TodoTitle,
memberOptions => memberOptions.MapFrom(source => source.Item2.Title));
}
}
As you already mention, you don't need to use a Tuple - see the update below if you really do.
You can map a single target object from multiple sources.
Set up regular mappings from your sources User and Todo to the UsernameWithTodoTitle target.
CreateMap<User, UsernameWithTodoTitle>()
CreateMap<Todo, UsernameWithTodoTitle>()
.ForMember(
destination => destination.TodoTitle,
memberOptions => memberOptions.MapFrom(source => source.Title)
);
To instantiate an UsernameWithTodoTitle object, apply one of both mappings; here from User.
var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>(user);
Secondly, you update the newly created UsernameWithTodoTitle by applying the next mapping; here from Todo.
_mapper.Map(todo, usernameWithTodoTitle);
UPDATE
If you really want/need that Tuple mapping, such one can be set up as below, via an IncludeMembers rule upon the Tuple parts which will trigger the corresponding mappings.
This makes that the separate mapping rules from above are still required.
CreateMap<(User, Todo), UsernameWithTodoTitle>()
.IncludeMembers(o => o.Item1, o => o.Item2);
You might prefer named Tuple parts.
CreateMap<(User User, Todo Todo), UsernameWithTodoTitle>()
.IncludeMembers(o => o.User, o => o.Todo);
Complete example:
CreateMap<User, UsernameWithTodoTitle>();
CreateMap<Todo, UsernameWithTodoTitle>()
.ForMember(
destination => destination.TodoTitle,
memberOptions => memberOptions.MapFrom(source => source.Title)
);
CreateMap<(User User, Todo Todo), UsernameWithTodoTitle>()
.IncludeMembers(o => o.User, o => o.Todo);
var user = new User { Username = "alfki" };
var todo = new Todo { Title = "work" };
var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>((user, todo));
Console.WriteLine(usernameWithTodoTitle.Username); // alfki
Console.WriteLine(usernameWithTodoTitle.TodoTitle); // work

Automapper don't map null sources

I'm using an Automapper and I need to map a List of objects into a nested object. I have these objects:
public abstract class FooSrcBase
{
}
public class FooSrc : FooSrcBase
{
public bool Prop { get; set; }
}
public class FooDest
{
public bool Prop { get; set; }
}
public class FooDestGroup
{
public FooDest FooDest { get; set; }
}
public class Dest
{
public FooDestGroup FooDestGroup { get; set; }
}
I have IEnumerable<FooSrc> which contains FooSrc objects (there are many implementations and only one object per each type may exist in the source) and I need to map it into Dest object. I need this because of mapping into the view models for front end.
When I register mapping like this:
CreateMap<IEnumerable<FooSrc>, Dest>().ForPath(dest => dest.FooDestGroup.FooDest, opt => opt.MapFrom(src => src.FirstOrDefault(x => x.GetType() == typeof(FooSrc))));
CreateMap<FooSrc, FooDest>();
When I map empty list, a problem occurs in Dest object - FooDestGroup is an instance of object, which has a property FooDest with null value.
How it would be possible to make Dest property FooDestGroup map to null, if I provide empty list as a source?
Firstly, your abstract FooSrc class will need a different name (conflicts with your concrete class name FooSrc)
How about modifying the constructor on Dest to avoid the nesting issue?
Automapper is capable of mapping to the constructor parameter automatically, but if you need more advanced behaviour you can refer to https://docs.automapper.org/en/stable/Construction.html
Something like this should work:
public class Dest
{
public FooGroup FooGroup { get; set; }
public Dest(FooDest fooDest)
{
FooGroup = new FooGroup { FooDest = fooDest };
}
}
[..]
Mapper.CreateMap<FooSrc, Dest>();
Mapper.Map<List<Dest>>(listOfFooSrc);
This fixed my problem:
CreateMap<IEnumerable<FooSrcBase>, Dest>()
.ForMember(dest => dest.FooDestGroup, opt => opt.MapFrom(src => src.FirstOrDefault(x => x.GetType() == typeof(FooSrc))));
CreateMap<FooSrc, FooDestGroup>()
.ForMember(dest => dest.FooDest, opt => opt.MapFrom(src => src));
CreateMap<FooSrc, FooDest>();

Automapper not ForMember not mapping (Saying property is unmapped)

I am using Automapper 6.2.2.0, and trying to map from an entity object to a DTO, with different property names.
I am getting the following error:
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
CompanyInfo -> OwnerDto (Destination member list) Db.CompanyInfo ->
TransferObjects.OwnerDto (Destination member list)
Unmapped properties: Name
My mapping looks like this:
cfg.CreateMap<CompanyInfo, OwnerDto>().ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.CompanyName));
My OwnerDto class:
public class OwnerDto
{
public int Id { get; set; }
public string Name { get; set; }
}
And my CompanyInfo Class:
public class CompanyInfo
{
public int Id { get; set; }
public string CompanyName { get; set; }
}
My mapping:
var results = Mapper.Map<List<CompanyInfo>,List <OwnerDto>>(data);
Any suggestions on what I am missing?
Initializer:
public static class AutomapperMaps {
Mapper.Initialize(cfg => {
cfg.CreateMap<User, UserDto>().ReverseMap();
cfg.CreateMap<CompanyInfo, OwnerDto>().ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.CompanyName));
// There are more mappings like the user one above. Those are all working fine.
}
}

AutoMapper: Create a map with included classes

I have these two view models:
public class SettingsViewModel
{
public string UserId { get; set; }
public PersonalViewModel Personal { get; set; }
}
public class PersonalViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
After implementing these view models I created a map with automapper:
Mapper.CreateMap<User, PersonalViewModel>()
.ForMember(vm => vm.Birthday, m => m.MapFrom(
u => (u.Birthday.HasValue) ? u.Birthday.Value.ToString("dd.MM.yyyy") : DateTime.Now.ToString("dd.MM.yyyy")));
Mapper.CreateMap<User, SettingsViewModel>()
.ForMember(vm => vm.UserId, m => m.MapFrom(
u => u.Id));
var viewModel = Mapper.Map<User, SettingsViewModel>(user);
Now I have the problem, that my property Personal in my SettingsViewModel is null. How can I combine my two mappings? How can I fill my Personal property with the data from my user object? In my user object, there are properties for FirstName und LastName.
You could set up your mapping this way:
Mapper.CreateMap<User, SettingsViewModel>()
.ForMember(vm => vm.UserId, m => m.MapFrom(u => u.Id))
.ForMember(vm => vm.Personal, opt => opt.MapFrom(u => u));
Here, we're saying map the Personal property from the User object itself. When you specify this, the mapping from User → PersonalViewModel will automatically be used.
Example: https://dotnetfiddle.net/YJnPDq

Categories

Resources