I have multiple classes
I am using automapper to map the classes
class Country
{
public int Countryid {get;set}
public string CountryEnglishName {get;set;}
public string CountryArabicName {get;set;}
public Location Location{get;set;}
}
class Location
{
public int Locationid {get;set}
public string LocationEnglishName {get;set;}
public string LocationArabicName {get;set;}
}
class Customer
{
public int Customerid {get;set}
public string CustomerName {get;set;}
public int LocationId
public Location Location{get;set;}
public string PropertyArabicName {get;set;}
public string PropertyEnglishName{get;set;}
}
And my DTO looks like below
class CustomerDetailsReadDTO
{
public int Customerid {get;set}
public string PropertyName {get;set;}
public string CustomerName {get;set;}
public string CountryName {get;set;}
public string LocationName {get;set;}
}
If user pass API parameter as "en-US" then CountryDTO class field Country should contain with CountryEnglishName like that Location field also.
My mapping looks like below
class AutomapperProfile :Profile
{
CreateMap<Customer,CustomerDetailsReadDTO>
.ForMember(dest=>dest.CountryName,opt.MapFrom<CustomResolver,string>(src=>src.Location.Country.CountryEnglishName))
.ForMember(dest=>dest.LocationName,opt.MapFrom<CustomResolver,string>(src=>src.Location.LocationEnglishName))
.ForMember(dest=>dest.PropertyName,opt.MapFrom<CustomResolver,string>(src=>src.Location.PropertyNameEnglishName));
}
my interface and customresolver looks like below
This interface has been scoped in startup class
public interface ILanguage
{
string Language{get;set;}
}
public class CustomResolver:IMemberResolver<object,object,string,string>
{
private string _Lang;
public CustomResolver(ILanguage ilanguage)
{
_Lang = ilanguage;
}
public string Resolve(object source,object destination,string sourcemember,string destmember,ResoutionContext context)
{
var type = source.GetType();
switch(Type.Name)
{
case "Country":
var country =(Country)source;
return _Lang == "en-US" ? country.CountryEnglishName :country.CountryArabicName;
case "Location":
var location =(Location)source;
return _Lang == "en-US" ? location.locationEnglishName :location.locationArabicName;
}
}
}
Iam getting correctvalues in PropertyName after mapping.While passing ar-SA iam getting arabicpropertyname else englishpropertyname.
Here the propblem is after mapping iam getting CountryName and LocationName value as empty string .
Can we get correct values for CountryName and LocationName based on language?
Can we solve this ?
Here is a fully working sample console project, that does what you want:
using System;
using System.Diagnostics;
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
namespace IssueConsoleTemplate
{
class Country
{
public int Countryid { get; set; }
public string CountryEnglishName { get; set; }
public string CountryArabicName { get; set; }
}
class Location
{
public int Locationid { get; set; }
public string LocationEnglishName { get; set; }
public string LocationArabicName { get; set; }
public Country Country { get; set; }
}
class Customer
{
public int Customerid { get; set; }
public string CustomerName { get; set; }
public int LocationId { get; set; }
public Location Location { get; set; }
public string PropertyArabicName { get; set; }
public string PropertyEnglishName { get; set; }
}
class CustomerDetailsReadDTO
{
public int Customerid { get; set; }
public string PropertyName { get; set; }
public string CustomerName { get; set; }
public string CountryName { get; set; }
public string LocationName { get; set; }
}
class AutomapperProfile : Profile
{
public AutomapperProfile(IServiceProvider serviceProvider)
{
CreateMap<Customer, CustomerDetailsReadDTO>()
.ForMember(
dest => dest.CountryName,
opt => opt.MapFrom(
s => serviceProvider.GetService<ILanguage>().Language == "en-US"
? s.Location.Country.CountryEnglishName
: s.Location.Country.CountryArabicName))
.ForMember(
dest => dest.LocationName,
opt => opt.MapFrom(
s => serviceProvider.GetService<ILanguage>().Language == "en-US"
? s.Location.LocationEnglishName
: s.Location.LocationArabicName))
.ForMember(
dest => dest.PropertyName,
opt => opt.MapFrom(
s => serviceProvider.GetService<ILanguage>().Language == "en-US"
? s.PropertyEnglishName
: s.PropertyArabicName));
}
}
interface ILanguage
{
string Language { get; set; }
}
class CurrentLanguage : ILanguage
{
public string Language { get; set; }
}
internal static class Program
{
private static void Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddScoped<ILanguage>(p => new CurrentLanguage())
.BuildServiceProvider();
var config = new MapperConfiguration(
cfg =>
{
cfg.ConstructServicesUsing(t => serviceProvider.GetService(t));
cfg.AddProfile(new AutomapperProfile(serviceProvider));
});
var mapper = config.CreateMapper();
var customer = new Customer
{
Customerid = 1,
CustomerName = "John",
LocationId = 1,
Location = new Location
{
Locationid = 1,
LocationEnglishName = "New York",
LocationArabicName = "نِيويورْك",
Country = new Country
{
Countryid = 1,
CountryEnglishName = "USA",
CountryArabicName = "الوِلايات المُتَّحِدة الأَمْريكيّة",
}
},
PropertyArabicName = "مبني المقاطعة الملكية",
PropertyEnglishName = "Empire State Building",
};
serviceProvider.GetService<ILanguage>().Language = "en-US";
var englishDto = mapper.Map<CustomerDetailsReadDTO>(customer);
serviceProvider.GetService<ILanguage>().Language = "ar-SA";
var arabicDto = mapper.Map<CustomerDetailsReadDTO>(customer);
Debug.Assert(englishDto.CountryName == "USA");
Debug.Assert(englishDto.LocationName == "New York");
Debug.Assert(englishDto.PropertyName == "Empire State Building");
Debug.Assert(arabicDto.CountryName == "الوِلايات المُتَّحِدة الأَمْريكيّة");
Debug.Assert(arabicDto.LocationName == "نِيويورْك");
Debug.Assert(arabicDto.PropertyName == "مبني المقاطعة الملكية");
}
}
}
I added a Country property to the Location class, to make sense of the model.
Though you could use IMemberResolver, you don't really need to (as shown in the sample code).
Related
ForMember/MapFrom is somehow not executed whatever I try.
Those are the classes to be mapped;
public class ImageParams : IEntityParams, IImage, IOperatorFields
{
public ImageParams()
{
}
public ImageParams(string userId, string title, string description, string imagePath, bool profilePhoto)
{
UserId = userId;
Title = title;
Description = description;
ImagePath = imagePath;
ProfilePhoto = profilePhoto;
}
public ImageParams(int id, string userId, string title, string description, string imagePath, bool profilePhoto)
{
Id = id;
UserId = userId;
Title = title;
Description = description;
ImagePath = imagePath;
ProfilePhoto = profilePhoto;
}
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[JsonProperty(PropertyName = "userId")]
public string UserId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
[JsonProperty(PropertyName = "imagePath")]
public string ImagePath { get; set; }
[JsonProperty(PropertyName = "profilePhoto")]
public bool ProfilePhoto { get; set; }
public Status Status { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? LastModifiedDate { get; set; }
}
public interface IImage: IEntity, IHasStatus, IDateOperationFields
{
string UserId { get; set; }
string Title { get; set; }
string Description { get; set; }
string ImagePath { get; set; }
bool ProfilePhoto { get; set; }
}
public class Image: IImage
{
public int Id { get; set; }
public string UserId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
public bool ProfilePhoto { get; set; }
public Status Status { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? LastModifiedDate { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public List<ReportImage> Reports { get; set; }
public List<Report> UserReports { get; set; }
}
I create the map as below;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Image, ImageParams>().ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath))).ReverseMap();
}
private static string ImagePathFormatting(string imagePath)
{
var formattedImagePath = imagePath.Contains(AppSettingsProvider.PictureBaseUrl) ? imagePath : $"{AppSettingsProvider.PictureBaseUrl}/{imagePath}";
return formattedImagePath;
}
}
I register my profile as below;
services.AddAutoMapper(typeof(MappingProfile));
And I try to map as below;
public ImageParams GetProfileImage(string userId)
{
var image = Entities.FirstOrDefault(x => x.UserId == userId && x.ProfilePhoto && x.Status == Status.Active);
return _mapper.Map<ImageParams>(image);
}
I am sure the MappingProfile is executed successfully and mapping Image object to ImageParams object as well however ImagePathFormatting function is not called.
I have tried so many variations like instead of ImagePathFormatting I have used anonymous funtion. I have also tried using IValueResolver as below;
public class CustomResolver : IValueResolver<ImageParams, Image, string>
{
public string Resolve(ImageParams source, Image destination, string imagePath, ResolutionContext context)
{
return source.ImagePath.Contains(AppSettingsProvider.PictureBaseUrl) ? source.ImagePath : $"{AppSettingsProvider.PictureBaseUrl}/{source.ImagePath}";
}
}
Whatever I try, I cannot make MapFrom nor CustomResolver invoked.
Any help would be appreciated.
First solution:
Remove from class ImageParams any constructor except default without parameters:
public class ImageParams : IImage
{
public ImageParams()
{
}
//Other members.
}
Second solution:
Add DisableConstructorMapping():
var config = new MapperConfiguration(cfg => {
cfg.AddProfile(new MappingProfile());
cfg.DisableConstructorMapping();
}
);
var mapper = config.CreateMapper();
Third solution:
CreateMap<ImageParams, Image>()
.ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath)))
.ReverseMap()
.ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath)))
.ConstructUsing(x => new ImageParams());
Source 1
Source 2
I'd strongly suggest you use AutoMapper Execution Plan Tool tool to see exactly what automapper is doing when it runs the mapping.
Confident that will solve your problem.
I mapping a model across, which has a child list sub map as well. However, after calling the map, the sub list is not being mapped? I am using AutoMapper 9.0.0 with AutoMapper.Extensions.Microsoft.DependencyInjection 7.0.0 (note this is the parent node in the package list.
I have as follows (reduced for brevity):
public class Agreement
{
//...
public List<Document> Documents { get; set; }
}
public class Document : Entity
{
public string Url { get; set; }
public string Location { get; set; }
public string MimeType { get; set; }
public string FileHash { get; set; }
public float FileSize { get; set; }
public string Notes { get; set; }
public string Type { get; set; }
public byte[] Data { get; set; }
}
public class AgreementDataGridOutputModel : BaseModel
{
//...
public List<DocumentOutputModel> Documents { get; set; }
}
public class DocumentOutputModel
{
public int Id { get; set; }
public string Url { get; set; }
public string MimeType { get; set; }
public string Notes { get; set; }
}
My Mappings are as follows;
CreateMap<Document, DocumentOutputModel>();
CreateMap<List<Document>, List<DocumentOutputModel>>();
CreateMap<Agreement, AgreementDataGridOutputModel>()
.ForMember(dest => dest.AgreementType, opt => opt.MapFrom(src => src.Type.Name))
.ForMember(dest => dest.CompanyName, opt => opt.MapFrom(src => src.Company.Name))
.ForMember(dest => dest.Documents, opt => opt.MapFrom(src => src.Documents));
CreateMap<List<Agreement>, List<AgreementDataGridOutputModel>>();
I then map in my controller as follows;
var response = await _agreementService.FindAsync(criteria);
var output = _mapper.Map<IList<Agreement>,IList<AgreementDataGridOutputModel>>(response.Result);
Can anyone see what I am doing wrong here please?
Mapping collections prevent the collection properties maps from working. See a working test below:
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
var list = new List<ParentSource> {
new ParentSource {
Id =1,
Name = "My name",
MyList = new List<ChildSource> {
new ChildSource { Id = 1, Name = "Child name" }
}
}
};
var conf = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ParentSource, ParentTarget>();
cfg.CreateMap<ChildSource, ChildTarget>();
/*
* This line prevents the list mappings from working...
*/
// cfg.CreateMap<List<ChildSource>, List<ChildTarget>>();
});
var mapper = new Mapper(conf);
var targets = mapper.Map<List<ParentSource>, IList<ParentTarget>>(list);
Console.WriteLine(JsonSerializer.Serialize(targets));
// Output: [{"Id":1,"Name":"My name","MyList":[{"Id":1,"Name":"Child name"}]}]
Console.ReadLine();
}
}
public class ParentSource
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildSource> MyList { get; set; }
}
public class ChildSource
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ParentTarget
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildTarget> MyList { get; set; }
}
public class ChildTarget
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Is it possible to map AddToClientCommand to List<AddToClient> ?
public class AddToClientCommand : IRequest<AddToClientResponse>
{
public List<int> AccountIds { get; set; }
public int ClientId { get; set; }
}
public class AddToClient
{
public int AccountId { get; set; }
public int ClientId { get; set; }
}
to achieve the following result:
var command = new AddToClientCommand
{
AccountIds = new List<int> { 1, 2 },
ClientId = 42
};
var result = // somehow automap with Automapper
var expected = new List<AddToClient>
{
new AddToClient { AccountId = 1, ClientId = 42 },
new AddToClient { AccountId = 2, ClientId = 42 }
};
expected.Should().BeEquivalentTo(result);
AutoMapper : As my understanding, it is impossible to map AddToClientCommand to List<AddToClient>. because the AotuMapper provide 2 ways for mapping such as following...
For Example: We have 2 classes like Employee and User
public class Employee
{
public int EmployeeId { get; set; }
public string EmployeeFName { get; set; }
public string EmployeeLName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public DateTime? DateOfJoining { get; set; }
}
public class User
{
public int Userid { get; set; }
public string UserFName { get; set; }
public string UserLName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public DateTime? DateOfJoining { get; set; }
}
Employee objEmployee = new Employee
{
EmployeeId = 1001,
EmployeeFName = "Manish",
EmployeeLName = "Kumar",
Address = "JAIPUR",
City = "JAIPUR",
State = "RAJASTHAN",
Zip = "302004",
DateOfJoining = DateTime.Now,
};
//1. Creates the map and all fields are copied if properties are same
Mapper.CreateMap<Employee, User>();
//2. If properties are different we need to map fields of employee to that of user as below.
AutoMapper.Mapper.CreateMap<Employee, User>()
.ForMember(o => o.Userid, b => b.MapFrom(z => z.EmployeeId))
.ForMember(o => o.UserFName, b => b.MapFrom(z => z.EmployeeFName))
.ForMember(o => o.UserLName, b => b.MapFrom(z => z.EmployeeLName));
User objuser = Mapper.Map<Employee, User>(objEmployee);
// But your requirement will be fullfill through Linq Query...
AddToClientCommand addCommand = new AddToClientCommand
{
AccountIds = new List<int> { 1, 2 },
ClientId = 42
};
List<AddToClient> addClientList = addCommand.AccountIds.Select(item => new AddToClient { AccountId = item, ClientId = addCommand.ClientId }).ToList();
Output:
No need to use AutoMapper here.
You can do what you want with a Linq query
var expected = command.AccountIds.Select(id => new AddToClient { AccountId = id, ClientId = command.ClientId }).ToList();
I have the following classes.
public class SomeModel
{
[Key]
public int Id { get; set; }
[Required]
public string UserId { get; set; }
public virtual User User { get; set; }
[Required]
public string Name { get; set; }
}
And:
public class SomeModelDetailsResponseModel : IMapFrom<SomeModel>, IHaveCustomMappings
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
public void CreateMappings(IConfiguration configuration)
{
configuration.CreateMap<SomeModel, SomeModelDetailsResponseModel>("name").AfterMap((b, r) =>
{
r.UserName = b.User.FirstName + b.User.LastName;
});
}
}
For some reason, when I project an IQueryable of SomeModel to an IQueryable of SomeModelDetailsResponseModel the UserName property turns out to be null.
Assuming these are you class definitions:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class SomeModel
{
public int Id { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public string Name { get; set; }
}
public class SomeModelDetailsResponseModel
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
}
Solution 1
Do your mapping like this:
var config = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<SomeModel, SomeModelDetailsResponseModel>().AfterMap((b, r) =>
{
r.UserName = b.User.FirstName + b.User.LastName;
});
});
var mapper = config.CreateMapper();
var response = mapper.Map<SomeModel, SomeModelDetailsResponseModel>(new SomeModel()
{
User = new User()
{
FirstName = "FN",
LastName = "LN"
}
});
Since you have your input as IQueryable<SomeModel> and you want to project it into IQueryable<SomeModelDetailsResponseModel>, then you can do this:
var result = q.Select(m => mapper.Map<SomeModel, SomeModelDetailsResponseModel>(m));
where q is your IQueryable<SomeModel> instance.
Solution 2
If you want to use ProjectTo<>, then initialize your mapper as the following:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SomeModel, SomeModelDetailsResponseModel>()
.ForMember(r => r.UserName, c => c.MapFrom(o => o.User.FirstName + o.User.LastName));
});
Then, do your projection as this:
var result = q.ProjectTo<SomeModelDetailsResponseModel>().ToArray();
Where q is your IQueryable<SomeModel>.
I have a view model that I've received from the client which looks something like this
public class OrderViewModel
{
public string Name{get;set;}
public string ContactDetails {get;set;}
public List<FunkyThing_ViewModel> {get;set;}
}
public class FunkyThing_ViewModel
{
public string ThingName{get;set;}
public string Colour{get;set;}
public string Size{get;set;}
}
I wish to map this to a list of domain models where each which looks something more like this:
public class Order
{
public string Name{get;set;}
public string ContactDetails {get;set;}
public string ThingName{get;set;}
public string Colour{get;set;}
public string Size{get;set;}
}
So I'm wanting do end up with something that looks like this:
List<Order> orders = new Orders();
Mapper.CreateMap<OrderViewModel, List<Order>>();
//Something in here to ensure each funky thing creates an additional order....
Mapper.Map(viewModel, orders);
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using NUnit.Framework;
using SharpTestsEx;
namespace StackOverflowExample.Automapper
{
public class OrderViewModel
{
public string Name { get; set; }
public string ContactDetails { get; set; }
public List<FunkyThingViewModel> FunkyThingViewModels { get; set; }
}
public class FunkyThingViewModel
{
public string ThingName { get; set; }
public string Colour { get; set; }
public string Size { get; set; }
}
public class Order
{
public string Name { get; set; }
public string ContactDetails { get; set; }
public string ThingName { get; set; }
public string Colour { get; set; }
public string Size { get; set; }
}
[TestFixture]
public class FlattenWithListTests
{
[Test]
public void FlattenListTest()
{
//arrange
var source = new OrderViewModel
{
Name = "name",
ContactDetails = "contact",
FunkyThingViewModels = new List<FunkyThingViewModel>
{
new FunkyThingViewModel {Colour = "red"},
new FunkyThingViewModel {Colour = "blue"}
}
};
Mapper.CreateMap<FunkyThingViewModel, Order>();
Mapper.CreateMap<OrderViewModel, Order>();
Mapper.CreateMap<OrderViewModel, List<Order>>()
.ConvertUsing(om => om.FunkyThingViewModels.Select(
ftvm =>
{
var order = Mapper.Map<Order>(om);
Mapper.Map(ftvm, order);
return order;
}).ToList());
//act
var mapped = Mapper.Map<List<Order>>(source);
//assert
mapped[0].Satisfy(m =>
m.Name == source.Name &&
m.ContactDetails == source.ContactDetails &&
m.Colour == "red");
mapped[1].Satisfy(m =>
m.Name == source.Name &&
m.ContactDetails == source.ContactDetails &&
m.Colour == "blue");
}
}
}