Is this a bug with AutoMapper's PascalCase naming convention regex? - c#

I'm attempting to leverage AutoMapper in order to not have to manually write a lot of code mappings. This appears to be working fine for everything other than this one class:
CreateMap<AccountConnection, AccountConnectionDto>();
CreateMap<Account, AccountDto>();
CreateMap<Address, AddressDto>() // <--- this one
.ForMember(dest => dest.StreetAddress1, opt => opt.MapFrom(src => src.street_address_1))
.ForMember(dest => dest.StreetAddress2, opt => opt.MapFrom(src => src.street_address_2))
.ForMember(dest => dest.StreetAddress3, opt => opt.MapFrom(src => src.street_address_3));
If I don't manually map those 3 members, then when I run config.AssertConfigurationIsValid(); it throws.
Exception Details: 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
=================================================
Address -> AddressDto (Destination member list)
Proj.Data.Address -> Proj.API.AddressDto (Destination member list)
Unmapped properties:
StreetAddress1
StreetAddress2
StreetAddress3
I am using the following naming conventions in my profile:
SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
DestinationMemberNamingConvention = new PascalCaseNamingConvention();
These are the only properties with numbers in the property name so I dug in to the AutoMapper source on GitHub and found the Regex for the PascalCaseNamingConvention that I'm using in my project. It is:
(\p{Lu}+(?=$|\p{Lu}[\p{Ll}0-9])|\p{Lu}?[\p{Ll}0-9]+)
If I throw that in https://regex101.com and then test it against my property name ShippingAddress1 I get two matches, Shipping and Address1.
Ruh-roh! My source property name is shipping_address_1 (Don't ask) so that isn't going to work. Is this because my naming convention is broken, or should the PascalCaseNamingConvention match shipping_address_x to ShippingAddressX? (Went to raise an issue on the AutoMapper github but they ask newcomers to post on SO first, to see if people think it is a legitimate bug or not).

From my testing detailed below, I believe the naming conventions specified are the wrong way around:
public class Address
{
public string StreetAddress1 { get; set; }
}
public class AddressDto
{
public string street_address_1 { get; set; }
}
static void Main(string[] args)
{
// Prints nothing
PerformMappingTest(new PascalCaseNamingConvention(), new LowerUnderscoreNamingConvention());
// Prints "Test"
PerformMappingTest(new LowerUnderscoreNamingConvention(), new PascalCaseNamingConvention());
Console.ReadKey();
}
static void PerformMappingTest(INamingConvention source, INamingConvention destination)
{
var config = new MapperConfiguration(cfg => {
cfg.SourceMemberNamingConvention = source;
cfg.DestinationMemberNamingConvention = destination;
cfg.CreateMap<Address, AddressDto>();
});
var mapper = config.CreateMapper();
var address = new Address { StreetAddress1 = "Test" };
var addressDto = mapper.Map<Address, AddressDto>(address);
Console.WriteLine(addressDto.street_address_1);
}

Related

Automapper list to string ignore strange value

I have a destination that is a List<string> and a source that's a string. I setup my config to ignore the List<string> field but I keep getting the List type as the string value
class MyClass
{
string MyList {get;set;}
}
class MyClassDto
{
List<string> MyList {get;set;}
}
//My cfg is like this
cfg.CreateMap<MyClassDto, MyClass>().ForMember(x => x.MyList, opt => opt.Ignore());
//I've mapped this way
ObjectMapper.Map(input, dest);
//and this way
var destClass = ObjectMapper.Map<MyClass>(input);
It doesn't throw an error but it makes my string field this when its empty list:
System.Collections.Generic.List`1[System.String]
Can someone just explain why this is?
From the example you provide, it seems that the mapper you are using does not know of your configuration. In AutoMapper 9 you could use the configuration to create the mapper, e.g.:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<MyClassDto, MyClass>().ForMember(x => x.MyList, opt => opt.Ignore());
});
var mapper = config.CreateMapper();
var dest = mapper.Map<MyClass>(input);
Further information can be found in the docs.

Automapper unmapped properties error with simple objects mappings

I need to map a model object coming from API to my actual entity object on DbContext. It is used when creating a new machine object using a POST action.
As always, I created a simple map for the source/destination objects.
In this case we consider the source object as the API model and the destination object as the entity. Also the model has just a subset of properties of the entity.
Source/destination types
// Destination (entity on DbContext)
public class Machine
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string MnmConfiguration { get; set; }
public MachineBootStatus Status { get; set; }
public long MachineDriverId { get; set; }
public MachineDriver MachineDriver { get; set; }
public string DriverConfiguration { get; set; }
public string DriverStatus { get; set; }
}
// Source (from API controller)
public class MachineCreateModel
{
public string Name { get; set; }
public string Description { get; set; }
public string MnmConfiguration { get; set; }
public long MachineDriverId { get; set; }
}
Mapping configuration
public class DomainProfile : Profile
{
public DomainProfile()
{
//CreateMap<MachineCreateModel, Machine>();
// Update 2019/01/30 with proposed solution
CreateMap<MachineCreateModel, Machine>(MemberList.Source);
}
}
I'm using Unity DI container and the configuration of AutoMapper is this:
container = new UnityContainer();
// ... some other configurations...
container.RegisterType<IMapper, Mapper>(new InjectionConstructor(new MapperConfiguration(cfg => cfg.AddProfile<DomainProfile>())));
Version
Using AutoMapper v8.0.0.
Expected behavior
I expect to obtain a simple automatic mapping without errors, since my source model is just a subset of properties of the destination model, with same names.
Actual behavior
I get this error about unmapped properties when I hit this line of code:
Machine entity = Mapper.Map<Machine>(request.Machine);
[14:08:34.363 8 2e62361a INF] Creating new machine: TEST M1
[14:08:36.205 8 bd577466 ERR] An unhandled exception has occurred while executing the request.
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
=================================================================================================
AutoMapper created this type map for you, but your types cannot be mapped using the current configuration.
MachineCreateModel -> Machine (Destination member list)
MyApplication.Dcs.Application.Models.MachineCreateModel -> MyApplication.Dcs.Domain.Entities.Machine (Destination member list)
Unmapped properties:
Id
Status
MachineDriver
DriverConfiguration
DriverStatus
at AutoMapper.ConfigurationValidator.AssertConfigurationIsValid(IEnumerable`1 typeMaps)
at lambda_method(Closure , MachineCreateModel , Machine , ResolutionContext )
at lambda_method(Closure , Object , Object , ResolutionContext )
at AutoMapper.Mapper.AutoMapper.IMapper.Map[TDestination](Object source)
at MyApplication.Dcs.Application.Commands.MachineCreateCommandHandler.Handle(MachineCreateCommand request, CancellationToken cancellationToken) ..Commands\MachineCreateCommand.cs:line 28
Note
In my solution I've many projects and 3 of them are making use of AutoMapper (same version for all). There're 3 different DomainProfile.cs files (1 for each project) with the needed mappings.
In the other 2 DomainProfile classes I've some manual mappings (see example below) because I need to "translate" an object with italian property names to another one with english property names. So there're many lines for each object mapping, such as:
CreateMap<ArticleCreateUpdateModel, Articoli>()
.ForMember(d => d.Categoria, opt => opt.MapFrom(src => src.Category))
.ForMember(d => d.CodiceArticolo, opt => opt.MapFrom(src => src.Code))
.ForMember(d => d.Descrizione, opt => opt.MapFrom(src => src.Description))
.ForMember(d => d.Famiglia, opt => opt.MapFrom(src => src.Family))
.ForMember(d => d.Note, opt => opt.MapFrom(src => src.Note))
...
I don't know if the usage of those manual members mapping on one or more DomainProfile class, obliges me in some way to always explain all the subsequent mappings, even if they should be simple like those of this example.
By default, AutoMapper validates the destination properties. As there are neither matching properties nor ForMember constructs for a bunch of properties in your destination type you get the exception above.
Try to validate on the source properties instead:
CreateMap<ArticleCreateUpdateModel, Articoli>(MemberList.Source)
.ForMember(d => d.Categoria, opt => opt.MapFrom(src => src.Category))
// ...
Remark:
On the other hand, I have to mention that this is the typical case when AutoMapper is an overkill. Apart from trivial cases I would never use it anymore.
I had to use it in a project for more than a year but actually it is only good for making simple things more complicated than necessary. Some FromDto and ToDto [extension] methods are just simpler, faster, easier to debug and more reactive to code changes. Mapping between different class layouts or property names often results practically as much code (or even more with tons of lambdas) as simply writing the mapping manually. See also this link.

Is it possible to created a collection on the dest based on a collection in source?

Say I have
public class A
{
public List<int> Ids {get;set;}
}
public class B
{
public List<Category> Categories {get;set;}
}
public class Category
{
public string Name {get;set;} //will be blank on map
public int CategoryId {get;set;}
}
var source = new A {...};
var b = mapper.Map<A, B>(source);
so when mapped it will actually create a new collection on the dest but will map the ids based on what's in the source collection, other properties of the dest will be blank because there is nothing to map from.
How to setup the configuration to do this mapping?
You need a combination of ForMember, MapFrom and ForAllOtherMembers:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<A, B>()
.ForMember(dest => dest.Categories, opt => opt.MapFrom(src => src.Ids));
cfg.CreateMap<int, Category>()
.ForMember(dest => dest.CategoryId, opt => opt.MapFrom(src => src))
.ForAllOtherMembers(opt => opt.Ignore());
});
MapFrom will allow you to override the default mapping-by-name that AM normally does. As you can see in line 4, we can say that Ids in the source maps to Categories in the destination class.
But now you need to override how an int gets mapped, since that is the type of thing in Ids. With MapFrom, you don't (necessarily) have to provide a property for the source--the entire source itself can be the thing being mapped. So in line 7, we are mapping the ints that came from the mapping in line 4 and saying that they should map to the destination class' CategoryId property. Finally, we simply tell AM that we don't care to map any remaining properties in the target class by specifying the ForAllOtherMembers option.

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 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