Automapper configuration to ignore a destination class's base member - c#

ViewModel:
Class BaseViewModel
{
public string ViewOnlyProperty{get; set;}
...
}
Class VmClass<T,A>:BaseViewModel where T:IVmShared A:IVmSpecific
{
...
}
DomainModel:
Class BaseDomainModel
{
...
}
Class DomainClass<T,A>:BaseDomainModel where T:IDomainShared A:IDomainSpecific
{
...
}
Automapper:
Mapper.CreateMap<BaseDomainModel, BaseViewModel>()
.Include<IDomainClass<IDomainShared,IDomainSpecific>, VmClass<IVmShared,IVmSpecific>>()
// Included other concrete classes implements Domain and View models interfaces
.ForMember(x => x.ViewOnlyProperty, opt => opt.Ignore());
Mapper.CreateMap<IDomainClass<IDomainShared,IDomainSpecific>, VmClass<IVmShared,IVmSpecific>>()
.ForMember(x => x.ViewOnlyProperty, opt => opt.Ignore()).ReverseMap();
I have the same configurations for ViewModel to Domain mappings. But I am getting the below exception after Mapper.AssertConfigurationIsValid();
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
VmClass`2 -> DomainClass`2 (Source member list)
VmClass`2[[IVmShared],[IVmSpecific]] -> DomainClass`2[[IDomainShared],[IDomainSpecific]] (Source member list)
---------------------------------------------------------------------------------------
ViewOnlyProperty
Why I am getting exception for viewmodel property when it's validating viewModel->DomainModel mapping, where destination doesn't have this property at all in the base class?

I have found the cause for this issue. It was:
Mapper.CreateMap<IDomainClass<IDomainShared,IDomainSpecific>, VmClass<IVmShared,IVmSpecific>>()
.ForMember(x => x.ViewOnlyProperty, opt => opt.Ignore()).ReverseMap();
ReverseMap() doesn't work as ViewOnlyProperty doesn't exist in the destination. But Automapper was trying to map the "ViewOnlyProperty" from domain to view model, because of the reverse mapping was done on the same CreateMap(). Issue is fixed by splitting the mapping to:
Mapper.CreateMap<IDomainClass<IDomainShared,IDomainSpecific>, VmClass<IVmShared,IVmSpecific>>()
.ForMember(x => x.ViewOnlyProperty, opt => opt.Ignore());
Mapper.CreateMap<VmClass<IVmShared,IVmSpecific>,IDomainClass<IDomainShared,IDomainSpecific>>();

Related

Mapping from primtive type property to child object

I am trying to use AutoMapper for the usual scenario Dto -> Entity. But one of the Dto's properties should map to my Entity's child.
My Employee DTO has a property of type int, JobTitleId.
My Employee Entity has a child Entity JobTitle, which has an property of id. This is where i want to map to. So summarized it will be something like:
EmployeeDto.JobTitleId ---> Employee.JobTitle.Id
The only thing that worked for me was adding a Custom Automapper Resolver, but it gets too nasty when I need to do that for many different but similar scenarios.
class EmployeeDto
{
int JobTitleId;
}
Class Employee
{
JobTitle jobTitle;
}
Class JobTitle
{
int Id;
}
I would like something similar to this:
public class EmployeeMapperProfiles : Profile
{
public EmployeeMapperProfiles()
{
CreateMap<EmployeeDto, Employee>()
.ForMember(dest => dest.JobTitle.Id,
opt => opt.MapFrom(src => src.JobTitleId));
}
}
However, my result with this approach is:
System.ArgumentException: 'Expression 'dest => dest.JobTitle.Id' must resolve to top-level member and not any child object's properties. You can use ForPath, a custom resolver on the child type or the AfterMap option instead. (Parameter 'lambdaExpression')'.
But AfterMap and custom resolver approaches are not what I want.
So it must be ForPath, can't get it to work though.
Explanation
Exception message is telling you that you cannot specify custom mapping rules for child object properties, like this:
CreateMap<EmployeeDto, Employee>()
.ForMember(
employee => employee.JobTitle.Id,
options => options.MapFrom(employeeDto => employeeDto.JobTitleId));
That's because only top-level members are supported, and for deeper mapping you should use tools like value resolvers, type converters, explicit path configurations or additional mappings between types.
Solution
To specify source property for Id of Employee you should use path configuration like you already figured it out. It's pretty straightforward, like this:
CreateMap<EmployeeDto, Employee>()
.ForPath(
employee => employee.JobTitle.Id,
options => options.MapFrom(employeeDto => employeeDto.JobTitleId));

Init Ilist in a automapper in the same line

I seem to have some issue init a IList defined in a class, using automapper, and then adding a element to this list.
var config = new MapperConfiguration(.ForMember(dest => dest.Catalogs = new List<dto>);
can this not be done in the same line?
First thing you should note is that AutoMapper will create collections automatically if it kowns how to map the elements of these collections.
For example, given the following classes:
public class Source {
public IList<SourceObj> SourceCollection {get; set;}
}
public class Destination {
public DestDto[] DestinationCollection {get; set;}
}
The collections will be mapped correctly with this configuration:
CreateMap<SourceObj, DestDto>();
CreateMap<Source, Destination>()
.ForMember(dest => dest.DestinationCollection, opt => opt.MapFrom(src => src.SourceCollection));
Check if this mapping works:
var destDto = Mapper.Map<Destination>(sourceObj);
Assert.IsNotNull(destDto.DestinationCollection);
If you always want to set the destination collection to an empty list, you can do that with ResolveUsing:
CreateMap<Source, Destination>()
.ForMember(dest => dest.DestinationCollection, opt => opt.ResolveUsing(src => new List<DestDto>()));

AutoMapper.Mapper.CreateMap<TSource,TDestination>()' is obsolete

I have to classes Like
class A
{
public int id {get; set;}
}
class B
{
public C c {get; set;}
}
class C
{
public int id {get; set;}
public string Name {get; set;}
}
My requirement is to map id of class A to id of class C.
Now what I was doing till now was:
Mapper.CreateMap().ForMember(des => des.C.Id, src => src.MapFrom(x => x.id));
and it was working fine.
Now seems like Auto mapper has changed their implementation. and I am getting warning as below:
AutoMapper.Mapper.CreateMap()' is obsolete: 'Dynamically creating maps will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed, or Mapper.Initialize. Use CreateMapper to create a mapper instance.
I need to map some properties of classes which has different name and structure. Any help on this.
Previously
Mapper.CreateMap<Src, Dest>()
.ForMember(d => d.UserName, opt => opt.MapFrom(/* ????? */));
The problem here is mapping definitions are static, defined once and reused throughout the lifetime of the application. Before 3.3, you would need to re-define the mapping on every request, with the hard-coded value. And since the mapping configuration is created in a separate location than our mapping execution, we need some way to introduce a runtime parameter in our configuration, then supply it during execution.
This is accomplished in two parts: the mapping definition where we create a runtime parameter, then at execution time when we supply it. To create the mapping definition with a runtime parameter, we “fake” a closure that includes a named local variable:
Mapper.Initialize(cfg => {
string userName = null;
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.UserName,
opt => opt.MapFrom(src => userName)
);
});
For more information see this
For one or more classes
cfg.CreateMissingTypeMaps = true;
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.UserName,
opt => opt.MapFrom(src => userName)
);
cfg.CreateMap<AbcEditViewModel, Abc>();
cfg.CreateMap<Abc, AbcEditViewModel>();
});
In mapping class
IMapper mapper = config.CreateMapper();
var source = new AbcEditViewModel();
var dest = mapper.Map<AbcEditViewModel, Abct>(source);
Another way that seems a bit cleaner is to make a MappingProfile class which inherits from the Profile class of AutoMapper
public class MappingProfile:Profile
{
public MappingProfile()
{
CreateMap<Source1, Destination1>();
CreateMap<Source2, Destination2>();
...
}
}
Then you initialize the mapping with Mapper.Initialize(c => c.AddProfile<MappingProfile>()); in your startup code
That will allow you to use the mapping anywhere by calling
destination1Collection = source1Collection.Select(Mapper.Map<Source1, Destination1>);
Finally I found the resolution. I was doing: Mapper.Initialize{ Mapping field from source to destination }
in the App_start and adding this file to the global.asax--> Application_Start() --> GlobalConfiguration.
I need to add one more line inside my Mapper.Initialize which is cfg.CreateMissingTypeMaps = true;
Now this code will work for explicit mapping where two classes don't have the same structure and names of properties.
Apart from this, if we need to map properties of two class with the same structure the code Mapper.map(source, destination) will also work, which was not working earlier.
Let me know if someone is having difficulty with the solution. Thanks all for the above reply.

Automapper foces subobjects to be mapped

I am trying to map a response object from webservice to a Class in my project. I thought the Automapper will map even the sub objects automatically, it does not unless and until is forcefully set for the member. Why should i do this ?
Mapper.CreateMap<GetIfpQuoteResponse.Quote, QuoteWSModel>()
.ForMember(dest => dest.CarrierRate, opt => opt.MapFrom(src => src.Carriers))
.ForMember(dest => dest.DroppedCarriers, opt => opt.MapFrom(src => src.DroppedRates))
.ForMember(dest => dest.MemberPlans, opt => opt.MapFrom(src => src.MemberPlans));
Why Wont the automapper map my su bobjects when i mention the class mapping like this
Mapper.CreateMap<GetIfpQuoteResponse.Quote, QuoteWSModel>();
Mapper.CreateMap<GetIfpQuoteResponse.Quote.Carrier, CarrierRateModel>();
Mapper.CreateMap<GetIfpQuoteResponse.Quote.DroppedCarrier, DroppedCarrierModel>();
AutoMapper only map top level object.
If your class is built in the following way it will not work:
Class A
{
B b;
}
Class B
{
}
Class A will not know how to map property B inside class A.
To do this you will need to create a Profile class.
Auto Mapper tutorial

Automapper convention based mapping for collection

I have a project where I am trying to map a dictionary to a ViewModel.NamedProperty. I am trying to use an AutoMapper custom resolver to perform the mapping based on a convention. My convention is that if the named property exists for the source dictionary key then map a property from the dictionary's value. Here are my example classes:
class User
{
string Name {get;set;}
Dictionary<string, AccountProp> CustomProperties {get;set;}
}
class AccountProp
{
string PropertyValue {get;set;}
//Some other properties
}
class UserViewModel
{
string Name {get;set;}
DateTime LastLogin {get;set;}
string City {get;set}
}
var user = new User()
{
Name = "Bob"
};
user.CustomProperties.Add("LastLogin", new AccountProp(){PropertyValue = DateTime.Now};
user.CustomProperties.Add("City", new AccountProp(){PropertyValue = "SomeWhere"};
I want to map the User CustomProperties dictionary to the flattened UserViewModel by convention for all properties and I do not want to specify each property individually for the mapping.
What is the best way to go about this? I was thinking Custom value resolver but it seems that I have to specify each member I want to map individually. If I wanted to do that I would just manually perform the mapping without AutoMapper.
Below is code that serve the purpose. Not sure whether it is good or not.
Mapper.CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) // Optional
.ForMember(dest => dest.City, opt => opt.MapFrom(src => src.CustomProperties.FirstOrDefault(x => x.Key == "City").Value.PropertyValue.ToString())) // Handle null
.ForMember(dest => dest.LastLogin, opt => opt.MapFrom(src => Convert.ToDateTime(src.CustomProperties.FirstOrDefault(x => x.Key == "LastLogin").Value.PropertyValue))); //Handle null
I ended up creating a custom type converter to deal with this scenario and it works great:
public class ObjectToPropertyTypeConverter<TFromEntity> : ITypeConverter<TFromEntity, HashSet<Property>>
{
//perform custom conversion here
}
I then implemented the Custom mapping as follows:
AutoMapper.Mapper.CreateMap<MyViewModel, HashSet<Property>>()
.ConvertUsing<ObjectToPropertyTypeConverter<MyViewModel>>();

Categories