Automapper naming convention with underscore/PascalCase properties - c#

I have 2 classes I want to map with Automapper:
namespace AutoMapperApp
{
public class Class1
{
public string Test { get; set; }
public string property_name { get; set; }
}
}
namespace AutoMapperApp
{
public class Class2
{
public string Test { get; set; }
public string PropertyName { get; set; }
}
}
This is my Automapper config:
using AutoMapper;
namespace AutoMapperApp
{
public static class AutoMapperConfig
{
public static MapperConfiguration MapperConfiguration;
public static void RegisterMappings()
{
MapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Class1, Class2>();
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
});
}
}
}
According the Wiki of Automapper this should work:
https://github.com/AutoMapper/AutoMapper/wiki/Configuration
But my unittest fails:
using Xunit;
using AutoMapperApp;
namespace AutoMapperTest
{
public class Test
{
[Fact]
public void AssertConfigurationIsValid()
{
AutoMapperConfig.RegisterMappings();
AutoMapperConfig.MapperConfiguration.AssertConfigurationIsValid();
}
}
}
Exception:
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
=============================================
Class1 -> Class2 (Destination member list)
AutoMapperApp.Class1 -> AutoMapperApp.Class2 (Destination member list)
Unmapped properties:
PropertyName
Why?

With help from the AutoMapper project in GitHub:
Try the CreateMap after you set the convention.
public static void RegisterMappings()
{
MapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
cfg.CreateMap<Class1, Class2>();
});
}

public class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Class1, Class2>();
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
});
}
}
I'm assuming you are calling this in your app_start method. AutoMapperConfig.RegisterMappings();
For organizational purposes you can separate your mappings into profiles, register them and set your conventions on a profile-by-profile basis if you don't need the convention to be global like in your example.
To answer your question, it looks like you created a mapper configuration but did not initialize it so Automapper doesn't know what mapping you're talking about.

Related

AutoMapper - Get error when trying to map two classes

I am trying to use AutoMapper to map a DTO to an Entity class but I keep getting an error.
Here is the DTO Class:
public class Product
{
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public PriceTiers PriceTiers { get; set; }
}
and here is the Entity:
public partial class Product
{
public Product()
{
PriceTiers = new List<PriceTiers>();
}
[Key]
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public virtual ICollection<PriceTiers> PriceTiers { get; set; }
}
Why do I keep getting the following error?
{"Missing type map configuration or unsupported
mapping.\r\n\r\nMapping types:\r\nPriceTiers ->
ICollection1\r\nWeb.Areas.DEAR.DTOs.PriceTiers -> System.Collections.Generic.ICollection1[[Web.Areas.DEAR.Data.PriceTiers,
Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]\r\n\r\n
Destination Member:\r\nPriceTiers\r\n"}
This is what I have in the Profile class:
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
and this is what I use to map the classes:
var products = _mapper.Map<IEnumerable<Product>>(result.Products);
This is what is in the Program.cs:
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
The exception message is quite clear, the AutoMapper doesn't know how to map the data from DTOs.PriceTiers to ICollection<Data.PriceTiers>.
Solution 1: Map from DTOs.PriceTiers to ICollection<Data.PriceTiers>
I believe that Custom Type Converters is what you need.
Create Custom Type Converters.
public class ICollectionDataPriceTiersTypeConverter : ITypeConverter<DTOs.PriceTiers, ICollection<Data.PriceTiers>>
{
public ICollection<Data.PriceTiers> Convert(DTOs.PriceTiers src, ICollection<Data.PriceTiers> dest, ResolutionContext context)
{
if (src == null)
return default;
var singleDest = context.Mapper.Map<Data.PriceTiers>(src);
return new List<Data.PriceTiers>
{
singleDest
};
}
}
Add to mapping profile.
CreateMap<DTOs.PriceTiers, ICollection<Data.PriceTiers>>()
.ConvertUsing<ICollectionDataPriceTiersTypeConverter>();
Demo # .NET Fiddle
Solution 2: Map from ICollection<DTOs.PriceTiers> to ICollection<Data.PriceTiers>
If the PriceTiers in DTOs.Product supports multiple items and mapping with many to many (to ICollection<Data.ProductTiers>), then consider modifying the property as the ICollection<DTOs.PriceTiers> type.
namespace DTOs
{
public class Product
{
...
public ICollection<PriceTiers> PriceTiers { get; set; }
}
}
Did you added "CreateMapper()" method after your configurations?
Try something like that.
public class MappingProfile : Profile
{
public MappingProfile {
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
}
}
After that, on your container service, inject this dependency:
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
After some more research I found out that my mapping profile was not in the right order. These are the changes I made.
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
AllowNullCollections = true;
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
CreateMap<DTOs.Product, Data.Product>()
.ForMember(dto => dto.PriceTiers, opt => opt.MapFrom(x => x.PriceTiers));
}
}
Now it maps perfectly

Automapper custom resolver using inline map causes tests to fail

Got a mapper configuration which includes:
this.CreateMap<MyProp1, MyDesintationProp>();
this.CreateMap<MyProp2, MyDesintationProp>();
Then, inside the mapper for a more complex object, both these are used inside an inline ResolveUsing to merge into a list of MyDestinationProp:
.ForMember(dest => dest.MyDesintationPropList, opt => opt.ResolveUsing(tc =>
{
var results = new List<MyDesintationProp>();
if (tc.MyProp1 != null)
{
results.Add(Mapper.Map<MyDestinationProp>(tc.MyProp1));
}
if (tc.MyProp2 != null)
{
results.Add(Mapper.Map<MyDestinationProp>(tc.MyProp2));
}
return results;
}))
This works fine in production, but causes tests on this mapping to blow up, complaining
Property:
MyDestinationProp
---- System.InvalidOperationException : Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
Which I guess makes sense because the mapper inside the mapper hasn't been initialised. But what's the best way to go about fixing this?
This is a very trivial example, that really doesn't need to use the context, cause it would also work with simple mappings.
public class Source
{
public string Name { get; set; }
public SourceChild Child { get; set; }
public override string ToString() => $"{typeof(Source)} -> {Name}";
}
public class Destination
{
public string Name { get; set; }
public DestinationChild Child { get; set; }
public override string ToString() => $"{typeof(Destination)} -> {Name}";
}
public class SourceChild
{
public string Name { get; set; }
public override string ToString() => $"{typeof(SourceChild)} -> {Name}";
}
public class DestinationChild
{
public string Name { get; set; }
public override string ToString() => $"{typeof(DestinationChild)} -> {Name}";
}
public class MappingContextProfile : Profile
{
public MappingContextProfile()
{
CreateMap<Source, Destination>()
.ForMember(source => source.Child, conf => conf.MapFrom((source, destination, destinationChild, context) =>
{
// Not really needed in this case, cause it does just some simple mapping.
// But if you would need to add some informations from outside
// (e.g. some interface mapping, etc)
// this would be the place without losing the magic of AutoMapper.
return context.Mapper.Map<DestinationChild>(source.Child);
}));
CreateMap<SourceChild, DestinationChild>();
}
}
public class MappingSimpleProfile : Profile
{
public MappingSimpleProfile()
{
CreateMap<Source, Destination>();
CreateMap<SourceChild, DestinationChild>();
}
}
public static class Program
{
public static async Task<int> Main(string[] args)
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<MappingContextProfile>());
var mapper = config.CreateMapper();
var sources = Enumerable.Range(1, 10)
.Select(i => new Source { Name = $"{i}", Child = new SourceChild { Name = $"{i * 100}" } })
.ToList();
var destinations = mapper.Map<List<Destination>>(sources);
foreach (var item in destinations)
{
Console.WriteLine($"{item} -> {item.Child}");
}
return 0;
}
}

Why I get exception when I try to use AutoMapper?

I use AutoMapper in my .NET CORE 2.2 project.
I get this exception:
Missing type map configuration or unsupported mapping.
Mapping types:
SaveFridgeTypeModel -> FridgeType
College.Refrigirator.Application.SaveFridgeTypeModel ->
College.Refrigirator.Domain.FridgeType
On This row:
var fridgeType = _mapper.Map<SaveFridgeTypeModel, FridgeType>(model);
Here is defenition of FridgeType class:
public class FridgeType : IEntity , IType
{
public FridgeType()
{
Fridges = new HashSet<Fridge>();
}
public int ID { get; set; }
//Description input should be restricted
public string Description { get; set; }
public string Text { get; set; }
public ICollection<Fridge> Fridges { get; private set; }
}
Here is defenition of SaveFridgeTypeModel class:
public class SaveFridgeTypeModel
{
public string Description { get; set; }
public string Text { get; set; }
}
I add this row:
services.AddAutoMapper(typeof(Startup));
To ConfigureServices function in Startup class.
UPDATE
I forgot to add mappin configuration to the post.
Here is mapping configs class:
public class ViewModelToEntityProfile : Profile
{
public ViewModelToEntityProfile()
{
CreateMap<SaveFridgeTypeModel, FridgeType>();
}
}
Any idea why I get the exception above?
You need to use the type from the assembly where your maps are when registering automapper with DI.
AddAutomapper(typeof(ViewModelToEntityProfile));
If you had multiple assemblies with maps - you could use another overload:
AddAutomapper(typeof(ViewModelToEntityProfile), typeof(SomeOtherTypeInOtherAssembly));
After creating mapping config class you need to add the AutoMapperConfiguration in the Startup.cs as shown below:
public void ConfigureServices(IServiceCollection services) {
// .... Ignore code before this
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new ViewModelToEntityProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddMvc();
}

Mapping class array using AutoMapper

I'm new to AutoMapper and trying to map Array class ItemLink[].
public class ViewModel
{
public ItemLink[] ItemLinks { get; set; }
}
public class ItemLink
{
public string Description { get; set; }
}
I tried:
Mapper.Map<viewModel.ItemLink>(db.ItemLinks);
Error:
"Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance."
Can't it be simple mapping?
EDIT 1
To clarify more, I'm getting similar class structure from database. Example,
public class Db
{
public ItemLink[] ItemLinks { get; set; }
}
So, I want to map ViewModel.ItemLink[] with Db.ItemLink[].
You cannot provide variable to a generic parameter like you do in Mapper.Map<viewModel.ItemLink>(db.ItemLinks);. It is called Type parameter and must be a type.
As #gisek mentioned in his answer you need to configure mapper first. Normally it is done at application startup.
You can consider to fetch full objects from db, but you also have an option to use Queryable Extensions which are there to only fetch data you need for your view model.
The configuration. I assume that you have namespace DB for entity in database, and View namespace for view model.
Mapper.Initialize(cfg => {
cfg.CreateMap<DB.ItemLink, View.ItemLink>();
});
Mapper.Configuration.AssertConfigurationIsValid();
Fetch full entity from DB and then map it to property:
var viewModel = new View.Item();
viewModel.ItemLinks = Mapper.Map<View.ItemLink[]>(db.ItemLinks.ToArray());
Project entity from DB to view model:
var viewModel = new View.Item();
viewModel.ItemLinks = db.ItemLinks.ProjectTo<View.ItemLink>().ToArray();
I assumed you are using .net mvc
Firstly you need Initialize your mapping on Application Start, there is no need to initialize every time in your controller etc.
Following error means you didn't initialize mapper, automapper doesn't know your source and destination object.
Error:
"Mapper not initialized. Call Initialize with appropriate
configuration. If you are trying to use mapper instances through a
container or otherwise, make sure you do not have any calls to the
static Mapper.Map methods, and if you're using ProjectTo or
UseAsDataSource extension methods, make sure you pass in the
appropriate IConfigurationProvider instance."
For Solution:
Create an AutoMapperConfig object in your App_Start folder.
public class AutoMapperConfig
{
public static void Register()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<source,destination>(); /*If source and destination object have same propery */
cfg.CreateMap<source, destination>()
.ForMember(dest => dest.dId, opt => opt.MapFrom(src => src.sId)); /*If source and destination object haven't same property, you need do define which property refers to source property */
});
}
}
In your Global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AutoMapperConfig.Register(); // Call Register method on App_Start
}
In your controller
var mappedArray = Mapper.Map<viewmodel.ItemLink[]>(db.ItemLinks);
or
var mappedArray = Mapper.Map<viewmodel.ItemLink[],ItemLinks[]>(db.ItemLinks); //ItemLinks[] refers to your dto.
You need to configure the mapper first.
There are 2 possible approaches, static and non-static. I lean towards non-static as it allows you to create multiple mappers, which can use different mapping strategies.
Non-static example:
using AutoMapper;
namespace Experiments
{
class Program
{
static void Main(string[] args)
{
var links = new ItemLink[]
{
new ItemLink {Description = "desc 1"},
new ItemLink {Description = "desc 2"},
};
var item = new Item
{
ItemLinks = links,
};
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ItemLink, ItemLink>(); // you can extend this part of the configuration here
cfg.CreateMap<Item, Item>();
cfg.CreateMap<ItemLink, MyCustomClass>()
.ForMember(myCustomClass => myCustomClass.DescriptionWithDifferentName,
expression => expression.MapFrom(itemLink => itemLink.Description)); // to map to a different type
// more configs can do here
// e.g. cfg.CreateMap<Item, SomeOtherClass>();
});
IMapper mapper = new Mapper(config);
ItemLink linkClone = mapper.Map<ItemLink>(links[0]);
ItemLink[] linkArrayClone = mapper.Map<ItemLink[]>(item.ItemLinks);
Item itemClone = mapper.Map<Item>(item);
MyCustomClass myCustomClassObject = mapper.Map<MyCustomClass>(links[0]);
}
}
public class Item
{
public ItemLink[] ItemLinks { get; set; }
}
public class ItemLink
{
public string Description { get; set; }
}
public class MyCustomClass
{
public string DescriptionWithDifferentName { get; set; }
}
}
Static example:
using AutoMapper;
namespace Experiments
{
class Program
{
static void Main(string[] args)
{
var links = new ItemLink[]
{
new ItemLink {Description = "desc 1"},
new ItemLink {Description = "desc 2"},
};
var item = new Item
{
ItemLinks = links,
};
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ItemLink, ItemLink>(); // you can extend this part of the configuration here
cfg.CreateMap<Item, Item>();
cfg.CreateMap<ItemLink, MyCustomClass>()
.ForMember(myCustomClass => myCustomClass.DescriptionWithDifferentName,
expression => expression.MapFrom(itemLink => itemLink.Description));
// to map to a different type
// more configs can do here
// e.g. cfg.CreateMap<Item, SomeOtherClass>();
});
ItemLink linkClone = Mapper.Map<ItemLink>(links[0]);
ItemLink[] linkArrayClone = Mapper.Map<ItemLink[]>(item.ItemLinks);
Item itemClone = Mapper.Map<Item>(item);
MyCustomClass myCustomClassObject = Mapper.Map<MyCustomClass>(links[0]);
}
public class Item
{
public ItemLink[] ItemLinks { get; set; }
}
public class ItemLink
{
public string Description { get; set; }
}
public class MyCustomClass
{
public string DescriptionWithDifferentName { get; set; }
}
}
}
You can also configure Automapper to create missing maps automatically with cfg.CreateMissingTypeMaps = true;
using AutoMapper;
namespace Experiments
{
class Program
{
static void Main(string[] args)
{
var links = new ItemLink[]
{
new ItemLink {Description = "desc 1"},
new ItemLink {Description = "desc 2"},
};
var item = new Item
{
ItemLinks = links,
};
Mapper.Initialize(cfg =>
{
// now AutoMapper will try co create maps on it's own
cfg.CreateMissingTypeMaps = true;
// we can still add custom maps like that
cfg.CreateMap<ItemLink, MyCustomClass>()
.ForMember(myCustomClass => myCustomClass.DescriptionWithDifferentName,
expression => expression.MapFrom(itemLink => itemLink.Description));
});
ItemLink linkClone = Mapper.Map<ItemLink>(links[0]);
ItemLink[] linkArrayClone = Mapper.Map<ItemLink[]>(item.ItemLinks);
Item itemClone = Mapper.Map<Item>(item);
// without custom map myCustomClassObject.DescriptionWithDifferentName would be null
MyCustomClass myCustomClassObject = Mapper.Map<MyCustomClass>(links[0]);
}
public class Item
{
public ItemLink[] ItemLinks { get; set; }
}
public class ItemLink
{
public string Description { get; set; }
}
public class MyCustomClass
{
public string DescriptionWithDifferentName { get; set; }
}
}
}
I am not sure that I understood what you need,
I guess that you are trying to map from a list of ItemLink to a list of viewModel.ItemLink
so Mapper.Map<viewModel.ItemLink>(db.ItemLinks);
become
var listOfViewModelItemLink = Mapper.Map<List<viewModel.ItemLink>>(db.ItemLinks);
you can call ToArray() on listOfViewModelItemLink then assign to ItemLinks property of Item class
I am not sure but I guess that you are trying to map from an array of ItemLink to an array of viewModel.ItemLink. You should do it like this:
var viewModels = db.ItemLinks
.ToArray()
.Select(x=>Mapper.Map<viewModel.ItemLink>(x))
.ToArray();

Custom mapping with asp.net boilerplate and Automapper

I am new of ASP.NET BoilerPlate (ABP) and I am trying to understand how to create custom mappings using AutoMapper and, maybe, the ABP automapper attributes: AutoMap, AutoMapFrom, AutoMapTo.
With ABP I can map two classes in this way:
[AutoMapTo(typeof(DestClass)]
public class SourceClass {
public string A { get; set; }
public string B { get; set; }
}
public class DestClass {
public string A { get; set; }
public string B { get; set; }
}
But if I have two classes like the following where I want the property AB to be automapped as a join of A and B:
[AutoMapTo(typeof(DestClass)]
public class SourceClass {
public string A { get; set; }
public string B { get; set; }
}
public class DestClass {
public string AB { get; set; }
}
Are there some attributes with ABP? Or do I need to use the "classical" AutoMapper code:
Mapper.CreateMap<SourceClass, DestClass>()
.ForMember(dest => dest.AB,
opts => opts.MapFrom(src => (src.A + ", " + src.B)));
And where do I have to place such init code?
I found a solution I share here.
In "MyProject.Application" project I defined my automapper customs (I used profiles):
public class MyProjectAutoMapperProfile : AutoMapper.Profile {
protected override void Configure() {
CreateMap<SourceClass, DestClass>()
.ForMember(dest => dest.AB,
opts => opts.MapFrom(src => (src.A + ", " + src.B)));
// other customs here...
}
Then I registered it for injection in the Initialize method of the class MyProjectApplicationModule:
[DependsOn(typeof(MyProjectCoreModule), typeof(AbpAutoMapperModule))]
public class MyProjectApplicationModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
// --- MY CODE for registering custom automapping
var mapperConfiguration = new MapperConfiguration(cfg => {
cfg.AddProfile(new MyProjectMapperProfile()); // <= here my custom mapping
});
var mapper = mapperConfiguration.CreateMapper();
IocManager.IocContainer.Register(
Castle.MicroKernel.Registration.Component.For<IMapper>().Instance(mapper)
);
// --- MY CODE
}
}
Note that I directly used the Castle IOC register methods because I did not find any useful registering method for objects in ABP. Do you know one?
Finally I used my custom mapping as injection in my Application Service and used it directly:
public class MyAppService : MyNewHouseAppServiceBase, IMyAppService {
// ...
public MyAppService(IRepository<SourceClass, long> myRepository, AutoMapper.IMapper mapper) {
_myRepo = myRepository;
_mapper = mypper;
}
public async Task<DestClass> GetSource(long id) {
var source = await _myRepo.Find(id);
// USE THE INJECTED MAPPER
return _mapper.Map<DestClass>(source);
}
public async Task<ListResultOutput<DestClass>> GetSources() {
var sources = await _myRepo.GetAllListAsync();
return new ListResultOutput<DestClass>(
// USE THE INJECTED MAPPER
_mapper.Map<List<DestClass>>(sources)
);
}
}
No need to list all the customer mapping on the Module. Just tell the module to find all the classes which extend AutoMapper.Profile:
Assembly thisAssembly = typeof(AbpProjectNameApplicationModule).GetAssembly();
IocManager.RegisterAssemblyByConvention(thisAssembly);
cfg.AddProfiles(thisAssembly);

Categories