I have two classes
public class SourceClass
{
public Guid Id { get; set; }
public Guid Provider { get; set; }
}
public class DestinationClass
{
public Guid Id { get; set; }
public Guid Provider { get; set; }
public Guid CustomerId {get; set;}
}
I've initialized my mapping by using the following code
CreateMap<SourceClass, DestinationClass>();
And then in my controller, I have :
Mapper.Map<List<DestinationClass>>(requests)
where "requests" is a List of SourceClass objects being passed in to my controller.
Now, this code works and the mapping works as configured. However, I also get passed a CustomerId and want to set it in the DestinationClass accordingly.
Is there any way to do this while the mapping is occuring, so that I don't have to write an additional loop to set CustomerId in every object in the list?
You can pass additional parameter by passing key-value to mapper (as suggested by #LucianBargaoanu). The custom value resolver and map execution can be implemented as:
// Configuration
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceClass, DestinationClass>()
.ForMember(dest => dest.CustomerId, opt =>
opt.MapFrom((src, dest, destMember, context) =>
context.Items["CustomerId"]));
});
var mapper = configuration.CreateMapper();
//Sample source class
var sourceClass = new SourceClass { Id = Guid.NewGuid(), Provider = Guid.NewGuid() };
var destClass = mapper.Map<SourceClass, DestinationClass>(sourceClass,
opt => opt.Items["CustomerId"] = "96b4b6e6-7937-4579-ba01-4a051bc0b93b");
The CustomerId member of destClass object is populated with passed GUID.
Note:SourceClass and DestinationClass definition are taken from OP.
Related
So I am having an issue using AutoMapper to resolve my Cart items from the database where they are stored as a string. I am using a passing a CustomerCartDto to the front-end. When I manually map the properties everything works but I can't seem to get AutoMapper to recognize how to map the the items.
This is what I have in the controller. I have commented out the manual mapping below that works. The mapper function above that code block is what throws the error.
public async Task<ActionResult<CustomerCartDto>> GetCurrentUserCart()
{
var user = await _userManager.FindUserByClaimsPrinciple(HttpContext.User);
var cart = await _unitOfWork.Carts.GetCartAsync(user);
await _unitOfWork.Complete();
var returnedCart = _mapper.Map<CustomerCart, CustomerCartDto>(cart);
// var returnedCart = new CustomerCartDto
// {
// Id = cart.Id,
// Username = cart.AppUser.UserName,
// Email = cart.AppUser.Email,
// Items = JsonConvert.DeserializeObject<List<CartItemDto>>(cart.Items)
//};
return Ok(returnedCart);
}
I have the mapping profiles pulled out into another file here:
CreateMap<CustomerCart, CustomerCartDto>()
.ForMember(d => d.Items, o => o.MapFrom<CartItemsFromJsonResolver>())
.ForMember(d => d.Username, o => o.MapFrom(d => d.AppUser.UserName))
.ForMember(d => d.Username, o => o.MapFrom(d => d.AppUser.Email));
Because I am mapping from a JSON string to a class I have the actually resolver in another function here:
public class CartItemsFromJsonResolver: IValueResolver<CustomerCart, CustomerCartDto, List<CartItemDto>>
{
public CartItemsFromJsonResolver()
{
//
}
public List<CartItemDto> Resolve(CustomerCart source, CustomerCartDto destination, List<CartItemDto> destMember, ResolutionContext context)
{
if (!string.IsNullOrEmpty(source.Items))
{
return JsonConvert.DeserializeObject<List<CartItemDto>>(source.Items);
}
return null;
}
}
As I said if I manually map the properties in my controller and don't use AutoMapper it works fine without any issues but I would like to keep my controller as skinny as possible. Here is the error that gets thrown out in Postman.
Image of Postman Error
Edit : Here are the class being used:
public class CustomerCartDto
{
public CustomerCartDto(AppUser appUser, List<CartItemDto> items)
{
Items = items;
Username = appUser.UserName;
}
public CustomerCartDto(AppUser appUser, List<CartItemDto> items, string id)
{
Id = id;
Items = items;
Username = appUser.UserName;
}
public CustomerCartDto()
{
//
}
public string Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public List<CartItemDto> Items { get; set; }
}
And also how the model stored in the DB -
public class CustomerCart
{
public string Id { get; set; }
public AppUser AppUser { get; set; }
public string Items { get; set; }
// public float Subtotal { get; set; }
}
I don't know how IValueResolver is supposed to work in AutoMapper mapping configuration, but since your manual mapping works just fine, I'm assuming this line -
Items = JsonConvert.DeserializeObject<List<CartItemDto>>(cart.Items)
in your manual mapping is doing it's job as expected.
If so, then changing your current mapping configuration to following should solve your issue -
CreateMap<CustomerCart, CustomerCartDto>()
.ForMember(d => d.Username, o => o.MapFrom(s => s.AppUser.UserName))
.ForMember(d => d.Username, o => o.MapFrom(s => s.AppUser.Email))
.AfterMap((s, d) =>
{
d.Items = JsonConverter.DeserializeObject<List<CartItemDto>>(s.Items);
});
Let us know if your issue still remains.
Edit :
And also, rename Items property to something else in either CustomerCart or CustomerCartDto class. That's because when a map is not defined for a property, AutoMapper will look for a identically named property in the source model and automatically map from it.
The models I'm working with include an entry object which I'd like to map as if its child object were the entire object.
Here is a simplified version of the problem. I'd like an instance of OurWrappedSource to map directly to OurTarget.
class OurTarget
{
public Guid Id { get; set; }
public string Notes { get; set; }
public int Quantity { get; set; }
}
class OurSource
{
public Guid Id { get; set; }
public string Notes { get; set; }
public int Quantity { get; set; }
}
class OurWrappedSource
{
public OurSource Source { get; set; }
}
private static void TestUnwrapUsingConfig(MapperConfiguration config)
{
config.AssertConfigurationIsValid();
IMapper mapper = new Mapper(config);
var wrappedSource = new OurWrappedSource
{
Source = new OurSource
{
Id = new Guid("123e4567-e89b-12d3-a456-426655440000"),
Notes = "Why?",
Quantity = 27
}
};
var target = mapper.Map<OurTarget>(wrappedSource);
Assert.Equal(wrappedSource.Source.Id, target.Id);
Assert.Equal(wrappedSource.Source.Notes, target.Notes);
Assert.Equal(wrappedSource.Source.Quantity, target.Quantity);
}
The following configuration works, but is unwieldy for more than a couple of members:
// Works, but isn't *auto* enough
TestUnwrapUsingConfig(new MapperConfiguration(cfg =>
{
cfg.CreateMap<OurWrappedSource, OurTarget>()
.ForMember(src => src.Id, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Id))
.ForMember(src => src.Notes, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Notes))
.ForMember(src => src.Quantity, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Quantity));
}));
What I'd like to be able to do is define two intermediate mappings an then compose them:
Map OurWrappedSource directly to OurSource
Map OurSource directly to OurTarget
Map OurWrappedSource to OurTarget by composing mapping 1 with mapping 2
After some hammering, I have this configuration:
// Works, but #3 probably isn't ProjectTo-friendly
TestUnwrapUsingConfig(new MapperConfiguration(cfg =>
{
// 1
cfg.CreateMap<OurWrappedSource, OurSource>()
.ConvertUsing(wrappedSource => wrappedSource.Source);
// 2
cfg.CreateMap<OurSource, OurTarget>();
// 3
cfg.CreateMap<OurWrappedSource, OurTarget>()
.ConstructUsing((wrappedSource, ctx) =>
ctx.Mapper.Map<OurTarget>(ctx.Mapper.Map<OurSource>(wrappedSource))
)
.ForAllOtherMembers(opts => opts.Ignore());
}));
This works exactly as specified, but mapping 3 seems perhaps a little more explicit and/or kludgey than it should. It involves code in a Func (rather than an expression), which makes me think it probably won't optimize well when used with ProjectTo(). Is there a way to rewrite mapping 3 to address these issues?
I am trying to map one entity object to different view models. I have a base view model and two other view models that derive from the base. The view models are created based on their type property in run time via a factory method. There are no separate entities for the derived view models and source entity has all the properties for the derived view models. The problem is that Automapper is able to create the correct objects through the factory method but the properties in the derived objects are not mapped at all. Only the base view model's properties are mapped.
Sample entities:
public class VehicleEntity
{
public int Type { get; set; }
public int LoadCapacity { get; set; }
public TrailerEntity Trailer { get; set; }
}
public class TrailerEntity
{
public int Capacity { get; set; }
}
View models:
public class VehicleVM
{
public int Type { get; set; }
}
public class CarVM: VehicleVM
{
public TrailerVM Trailer { get; set; }
}
public class TruckVM : VehicleVM
{
public int LoadCapacity { get; set; }
}
public class TrailerVM
{
public int Capacity { get; set; }
}
public static class VehicleFactory
{
public static VehicleVM GetInstance(int type)
{
switch (type)
{
case 1:
return new CarVM();
case 2:
return new TruckVM();
default:
return new VehicleVM();
}
}
}
Finally the mapping:
List<VehicleEntity> vehicleList = new List<VehicleEntity>();
vehicleList.Add(new VehicleEntity()
{
Type = 1,
LoadCapacity = 0,
Trailer = new TrailerEntity()
{
Capacity = 120
}
});
vehicleList.Add(new VehicleEntity()
{
Type = 2,
LoadCapacity = 8000,
Trailer = null
});
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<VehicleEntity, VehicleVM>()
.ConstructUsing((Func<VehicleEntity, VehicleVM>)(rc => VehicleFactory.GetInstance(rc.Type)))
.Include<VehicleEntity, TruckVM>()
.Include<VehicleEntity, CarVM>();
cfg.CreateMap<VehicleEntity, TruckVM>();
cfg.CreateMap<VehicleEntity, CarVM>();
cfg.CreateMap<TrailerEntity, TrailerVM>();
});
IMapper mapper = config.CreateMapper();
var vehicleVMs = mapper.Map<List<Data.VehicleEntity>, List<VehicleVM>>(vehicleList);
In above example only the Type properties are mapped in CarVM and TruckVM. Other properties are not... I have also tried using ForMember method in order to map the derived class properties from the source entity but no luck.
cfg.CreateMap<VehicleEntity, TruckVM>().ForMember(dst => dst.LoadCapacity, opt => opt.MapFrom(src => src.LoadCapacity));
Is it possible to achieve this?
You can use following configuration
var config = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<VehicleEntity, VehicleVM>()
.ConstructUsing(rc => VehicleFactory.GetInstance(rc.Type))
.BeforeMap((s, d, c) => c.Mapper.Map(s, d));
cfg.CreateMap<VehicleEntity, TruckVM>();
cfg.CreateMap<VehicleEntity, CarVM>();
cfg.CreateMap<TrailerEntity, TrailerVM>();
});
In this case ConstructUsing creates proper destination object based on Type property and then in BeforeMap maps source VehicleEntity instance to created destination object. You can also do it in AfterMap method.
Or you can create destination object and map source on it immediately in ConstructUsing
var config = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<VehicleEntity, VehicleVM>()
.ConstructUsing((rc, context) => context.Mapper.Map(rc, VehicleFactory.GetInstance(rc.Type)));
cfg.CreateMap<VehicleEntity, TruckVM>();
cfg.CreateMap<VehicleEntity, CarVM>();
cfg.CreateMap<TrailerEntity, TrailerVM>();
});
I am in need to map the below scenario.
public class Customer
{
public string CustomerJson { get; set; }
}
public class CustomerTO
{
public object CustomerJson { get; set; }
}
From DAL I get CustomerJson value as below.
Customer.CustomerJson = {
"name": "Ram",
"city": "India"
}
I am in need to Deserialize this string. so I tried the below stuff while mapping.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerTO>()
.ForMember(dest => dest.CustName, opt => opt.MapFrom(src => JsonConvert.DeserializeObject(src.CustName)));
});
But this gives me run time error.
Unhandled Exception: AutoMapper.AutoMapperMappingException: Error mapping types.
So I kept it simple.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerTO>()
.ForMember(dest => dest.CustName, opt => opt.MapFrom(src => (src.CustName));
});
And I tried to deserialize it while consuming. But this give compile time error.
var custJson = JsonConvert.DeserializeObject(customerTO.CustomerJson );
Error 2 The best overloaded method match for 'Newtonsoft.Json.JsonConvert.DeserializeObject(string)' has some invalid arguments
I know customerTO.CustomerJson is not string but how do should I do the required mapping?
Thanks.
Based on your previous question and given the information above you seem to be confusing what you're trying to do here.
So I'm going to amalgamate the data from both in an attempt to solve the issues.
Entity Classes:
public class Customer
{
public int CustomerId {get; set; }
public string CustomerName { get; set; }
public string CustomerJson { get; set; }
}
public class CustomerTO
{
public int CustId { get; set; }
public object CustData { get; set; }
public object CustomerJson { get; set; }
}
AppMapper Class:
public static class AppMapper
{
public static MapperConfiguration Mapping()
{
return new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerTO>()
.ForMember(dest => dest.CustId, opt => opt.MapFrom(src => src.CustomerId))
.ForMember(dest => dest.CustData, opt => opt.MapFrom(src => src.CustName))
.ForMember(dest => dest.CustomerJson, opt => opt.MapFrom(src => JsonConvert.DeserializeObject(src.CustomerJson));
});
}
}
Main:
public class Program
{
static void Main(string[] args)
{
var config = AppMapper.Mapping();
var mapper = config.CreateMapper();
// From Previous question get list of Customer objects
var customers = AddCustomers();
var mappedCustomers = mapper.Map<IEnumerable<CustomerTO>>(customers);
}
}
A couple of things to point out
I'm not sure what the purpose of CustData is in CustomerTO. It seems to be duplicating CustomerJson and if so remove it and the associated mapping.
Also, you never mention mapping from DTO back to entity, but for the JsonObject you just need to configure it to map the serialized string to the appropriate Property.
This is how I addressed my requirement.
Db Entity
public class Customer
{
public string CustomerData { get; set; }
// & other properties
}
My DTO
public class CustomerTO
{
public object CustomerData { get; set;}
// & other properties
}
I created a Utility like class with name AppMapper. This is how my AppMapper.cs looks like.
public class AppMapper
{
private IMapper _mapper;
public AppMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerTO>();
//& other such mapping
});
_mapper = config.CreateMapper();
}
public CustomerTO Map(Customer customerEntity)
{
var customerTo= _mapper.Map<Customer,CustomerTO>(customerEntity);
return customerTo;
}
Now when I needed the mapping.
class DAL
{
public CustomerTO GetCustomers()
{
var customers= //LINQ To get customers
var customerTO = Mapping(customer);
return customerTO;
}
//However, this mapping glue in some internal class to retain SOLID principles
private CustomerTO Mapping(Customer custEntity)
{
var custTO = _appMapper.Map(custEntity);
var str = JsonConvert.Serialize(custTO.CustomerData);
custTO.CustomerData = JsonConvert.Deserialize(str);
return custTO;
}
}
That's it.
#Barry O'Kane - Sincere thanks for your inputs.
Points to be noted:-
I don't need to map manually any of the properites since the property name is same. Plus I am casting string to object. So no issues.
If you use .Map() for one property, then I found that I need to map each property else it gives default value of the data type.(Ex. for int it gives 0).
Yes. agreed there could be other method in Automapper which allows me specify that for a particulay property do this manual mapping & for rest use Automapping mechanism. But I am not sure on that.
Please feel free to improve this ans in any way.
Hope this helps :)
The input view model:
public class FacilityInputModel
{
public int Id { get; set; }
public string Name { get; set; }
}
The domain model:
public class FacilityInputModel
{
public int Id { get; set; }
public string Name { get; set; }
public string OriginalName { get; set; }
}
I am to allow users to change the name of a facility but still keep its original name.
Say facility is (I am writing json just for convenience)
{id:1, name='Facility1', originalName='Facility1'}
when created.
I am to change the name by posting a FacilityInputModel.
{id:1, name='Facility2'}
In C# code to update the entity:
var entity = _repository.Find(id);
_repository.Detach(entity);
entity = Mapper.Map<Facility>(model);
_repository.Update(entity);
_repository.Save();
The entity I get before Mapper.Map
{id:1, name='Facility1', originalName='Facility1'}
But after the mapping, the entity is
{id:1, name='Facility2', originalName=null}
instead of
{id:1, name='Facility2', originalName='Facility1'}
In my mapping, I tried to Ignore the OriginalName
CreateMap<Facility, FacilityInputModel>()
.ReverseMap()
.ForMember(x => x.OriginalName, opt => opt.Ignore());
But it never worked. Also tried
.ForMember(x => x.NameInWebAdmin, opt => opt.DoNotUseDestinationValue());
Still won't work.
So the question is how to avoid existing values from being wiped away in the mapping. And get an after-mapping entity:
{id:1, name='Facility2', originalName='Facility1'}
You're getting a completely new object when you call entity = Mapper.Map<Facility>(model);. Try using Mapper.Map<Source, Destination>(source, destination) to map to an existing one.