Automapper based on dynamic conditions - c#

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

AutoMapper ForMember and MapFrom is not executed

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.

AutoMapper not mapping sub entity list

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

Map a class instance to a list of another class with Automapper

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();

Automapper, CustomMapping not loading fields of a virtual property

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

Automapper - Flatten with list at the same time

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");
}
}
}

Categories