AutoMapper cannot resolve Object from JSON string - c#

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.

Related

PUT request with without every field from the DTO model

I want to make PUT request in ASP .NET CORE that user can make to update his note.
It's my UpdateNoteDTO:
public class UpdateNoteDto
{
[MaxLength(50)]
public string NoteTitle { get; set; }
[MaxLength(500)]
public string NoteBody { get; set; }
public int Color { get; set; }
}
and it's my Update method:
public void Update(UpdateNoteDto dto, int noteID)
{
var note = _dbContext
.Notes
.FirstOrDefault(r => r.Id == noteID);
if (note is null)
{
throw new NotFoundException("Note not found");
}
note.NoteTitle = dto.NoteTitle == string.Empty
|| dto.NoteTitle is null
? note.NoteTitle : dto.NoteTitle;
note.NoteBody = dto.NoteBody == string.Empty
|| dto.NoteBody is null
? note.NoteBody : dto.NoteBody;
note.Color = dto.Color == 1 ? note.Color : dto.Color;
_dbContext.SaveChanges();
}
I want to make request that user can make to change single field without need for declare all of them. This code what i wrote is working but i bet there is better solution for this :D
The way you are doing it looks fine but I would suggest two changes,
If you plan to use the endpoint for updating one or some values it's better to change request type to PATCH, the HTTP PATCH request method applies partial modifications to a resource whereas HTTP PUT changes whole resource.
To easily add conditions for mapping DTOs to entity models I would rather use Automapper which allows user to declare mapping conditions in a single spot and use IMapper interface to map the models.
For example if you have two classes:
public class UpdateNoteDto
{
[MaxLength(50)]
public string NoteTitle { get; set; }
[MaxLength(500)]
public string NoteBody { get; set; }
public int Color { get; set; }
}
public class Note
{
public int Id { get; set; }
public string NoteTitle { get; set; }
public string NoteBody { get; set; }
public int Color { get; set; }
}
you can map them by creating a mapping profile with conditions instead of using if, switch or any other way of comparing whether to map or not
mapping Profile could look like this:
public class NoteProfile: Profile
{
public NoteProfile()
{
CreateMap<UpdateNoteDto, Note>()
.ForMember(dest => dest.NoteTitle, opt => opt.Condition(src => !string.IsNullOrEmpty(src.NoteTitle)))
.ForMember(dest => dest.NoteBody, opt => opt.Condition(src => !string.IsNullOrEmpty(src.NoteBody)))
.ForMember(dest => dest.Color, opt => opt.Condition(src => src.Color != default))
.ForMember(dest => dest.Id, opt => opt.Ignore());
}
}
and update the Update(UpdateNoteDto dto, int noteID) function correspondingly:
public void Update(UpdateNoteDto dto, int noteID)
{
var noteToUpdate = _dbContext
.Notes
.FirstOrDefault(r => r.Id == noteID);
if (note is null)
{
throw new NotFoundException("Note not found");
}
var note = _mapper.Map(dto, note);
_dbContext.Update(note);
_dbContext.SaveChanges();
}
client should handled this issue
Or
if you want update an entity, it is better to use PATCH
If the client wants the previous data not to be edited
It should send you the previous data
Maybe, for example, the user wants to field the "NoteBody" value as empty In which case it cannot do this

Automapper - how to apply ForMember configuration at a deep level

I am trying to learn asp.NetCore 2.2. I am trying to setup a simple one page site. I have run into a problem with Automapper where manual Mappinng using forMember() is working at a top level for CreateMap<Listing, ListingSearchResultsDto>().ForMember(ListingPhotosUrl) but not at a lower level. I have another mapping CreateMap<User, UserDetailsDto>() where user contains an object Mylistings of type Listing. Mylistings is correctly auto mapped to ListingSearchResultsDto but manual configuration CreateMap<Listing, ListingSearchResultsDto>().ForMember(ListingPhotosUrl) is not applied.
I Have tried CreateMap<User, UserDetailsDto>().Formember(dest.Mylistings.ListingPhotosUrl,src.Mylistings.Photos.Url) but it seems that is not possible.
I Also tried this-> But no luck
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserDetailsDto>();
cfg.CreateMap<Listing, ListingSearchResultsDto>()
.ForMember(dest => dest.ListingPhotosUrl, opt =>
{
opt.MapFrom(src => src.Photos.FirstOrDefault(p => p.IsMain).Url);
});
});
var mapper = config.CreateMapper();
The Code:
AutoMappperProfiles
public AutoMapperProfiles()
{
CreateMap<Listing, ListingSearchResultsDto>()
.ForMember(dest => dest.ListingPhotosUrl, opt =>
{
opt.MapFrom(src => src.Photos.FirstOrDefault(p => p.IsMain).Url);
});
CreateMap<User, UserDetailsDto>();
CreateMap<ListingPhoto, ListingPhotosDetailedDto>();
}
User
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<Listing> MyListings { get; set; }
}
UserDetailsDto
public class UserDetailsDto
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<ListingSearchResultsDto> MyListings { get; set;}
}
Listing
public int id { get; set; }
public string Category { get; set; }
public string Title { get; set; }
public ICollection<ListingPhoto> Photos { get; set; }
ListingSearchResultsDto
public class ListingSearchResultsDto
{
public int id { get; set; }
public string Title { get; set; }
public string ListingPhotosUrl { get; set; }
}
I am using CreateMap<Listing, ListingSearchResultsDto>().Formember(des,src) to manually map a destination property ListingPhotosUrl. I have another mapping CreateMap<User, UserDetailsDto>(). Inside User & UsedetailsDto classes i have a objects called MyListings of types ICollection<Listing> and ICollection<ListingSearchResultsDto> respectively. MyListings object is auto mapped correctly but ListingPhotosUrl manual mapping is not being applied. CreateMap<Listing,ListingSearchResultsDto>.Formember(des,src)) manual mapping is working at top level, but not at deeper level inside CreateMap<User, UserDetailsDto>(), is there anyway to fix this? thanks
FIXED - Automapper was working fine. Issue in Entity Framework DbContext. I did not include the photos as related data in the EF Core method for loading USER data GETUSER(). It was working with EF Core method for loading LISTING GetListing() because i had an include for photos Include(p => p.Photos).
After adding .ThenInclude(p => p.Photos) in GetUser(), the photos were returned with USER data and automapper successfully mapped User data and ListingPhotosUrl manual mapping was applied successfully.
Entity Framework Core DbContext:
public async Task<User> GetUser(int id)
{
var user = await _context.Users
.Include(a => a.Avatar)
.Include(l => l.MyListings)
.ThenInclude(p => p.Photos)
.FirstOrDefaultAsync(u => u.Id == id);
return user;
}
public async Task<Listing> GetListing(int id)
{
var listing = await _context.Listings
.Include(p => p.Photos)
.FirstOrDefaultAsync(l => l.id == id);
return listing;
}

Can AutoMapper mappings be composed?

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?

how do I map this using Automapper

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

Need to speed up automapper...It takes 32 seconds to do 113 objects

Hi I have some major problems with auto mapper and it being slow. I am not sure how to speed it up.
I am using nhibernate,fluent nhibernate and asp.net mvc 3.0
[Serializable()]
public class Test
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual DateTimeDate { get; set; }
public virtual IList<Reminder> Reminders { get; set; }
public virtual IList<Reminder2> Reminders2 { get; set; }
public virtual Test2 Test2 { get; set; }
public Test()
{
Reminders = new List<Reminders>();
Reminders2 = new List<Reminders2>();
}
}
So as you can see I got some properties, Some other classes as in my database I have references between them.
I then do this
var a = // get all items (returns a collection of Test2)
var List<MyViewModel> collection = new List<MyViewModel>();
foreach (Test2 t in a)
{
MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());
collection.Add(vm);
}
// view model
public class MyViewModel
{
public int Id { get; private set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime DateTimeDate { get; set; }
public string FormatedDueDate { get; set; }
public string Test2Prefix { get; set; }
public string Test2BackgroundColor { get; set; }
public string SelectedDateFilter { get; set; }
public bool DescState { get; set; }
public bool AlertState { get; set; }
/// <summary>
/// Constructor
/// </summary>
public MyViewModel()
{
// Default values
SelectedDateFilter = "All";
DescState = false;
AlertState = false;
}
/// <summary>
/// Sets the date formatter string used
/// </summary>
/// <param name="dateFormat"></param>
public void SetDateFormat(DateTime dueDate, string dateFilter)
{
// simple if statement to format date.
}
}
// mapping
Mapper.CreateMap<Test2,MyViewModel>().ForMember(dest => dest.DescState, opt =>
opt.ResolveUsing<DescStateResolver>())
.ForMember(dest => dest.AlertState, opt =>
opt.ResolveUsing<AlertStateResolver>());
// resolvers
public class AlertStateResolver : ValueResolver<Task, bool>
{
protected override bool ResolveCore(Task source)
{
if (source.Reminders.Count > 0 || source.Reminders2.Count > 0)
{
return true;
}
else
{
return false;
}
}
}
public class DescStateResolver : ValueResolver<Task,bool>
{
protected override bool ResolveCore(Task source)
{
if (String.IsNullOrEmpty(source.Description))
{
return false;
}
else
{
return true;
}
}
}
Ignore the weird names and any typos my real object works just fine and makes sense.
So I used the stop watch and did this
Stopwatch a = new Stopwatch()
foreach (Test2 t in a)
{
a.Start()
MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
a.Stop()
vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());
collection.Add(vm);
}
var b = a.Elapsed; // comes back with 32 seconds.
I need to optimized this very badly.
Instead of:
var a = // get all items (returns a collection of Test2)
List<MyViewModel> collection = new List<MyViewModel>();
foreach (Test2 t in a)
{
MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());
collection.Add(vm);
}
Try:
var a = // get all items (returns a collection of Test2)
List<MyViewModel> collection = Mapper.Map<IEnumerable<Test2>, IEnumerable<MyViewModel>>(a);
which is equivalent to the first except the SetDateFormat call which you could do at your mapping definition. It might also be faster.
If you have a mapping defined between Test2 => MyViewModel AutoMapper automatically provides one for IEnumerable<Test2> => IEnumerable<MyViewModel> so that you don't need to loop.
Also you have mentioned NHibernate in your question. Make sure that your source object along with its collections is eagerly loaded from the database before passing it to the mapping layer or you cannot blame AutoMapper for being slow because when it tries to map one of the collections of your source object it hits the database because NHibernate didn't fetch this collection.
Another thing to look for is mapping code that throws exceptions. AutoMapper will catch these silently, but catching exceptions in this way impacts performance.
So if SomethingThatMightBeNull is often null, then this mapping will perform poorly due to the NullreferenceExceptions :
.ForMember(dest => dest.Blah, c.MapFrom(src=>src.SomethingThatMightBeNull.SomeProperty))
I've found making a change like this will more than half the time the mapping takes:
.ForMember(dest => dest.Blah, c.MapFrom(src=> (src.SomethingThatMightBeNull == null
? null : src.SomethingThatMightBeNull.SomeProperty)))
Update: C# 6 syntax
.ForMember(dest => dest.Blah, c.MapFrom(src => (src.SomethingThatMightBeNull?.SomeProperty)))
Was Able to improve Launch Time when added this
.ForAllMembers(options => options.Condition(prop => prop.SourceValue != null));
at end of Each
.CreateMap<..,..>()
If your subcollections are large you might benefit from using "Any()" instead of the "Count > 1". The Any function will only have to iterate once while the Count might need to iterate the entmes collection (depending on the implementation).
Not sure if this is causing any issues in your case, but beware of serializing auto-implemented properties.
Each time your code is compiled, the name of each (anonymous) backing field is picked at random by the compiler. So you may see some surprising exceptions if you serialize data with a progran that is compiled at one time and de-serialize it with a different program.
I fixed the same issue as yours. It also costs me 32s for mapping only one object.
So, I use opts.Ignore() to deal with some customized object as below:
CreateMap<SiteConfiguration, Site>()
.ForMember(x => x.SubSystems, opts => opts.Ignore())
.ForMember(x => x.PointInformations, opts => opts.Ignore())
.ForMember(x => x.Schedules, opts => opts.Ignore())
.ForMember(x => x.EquipmentDefinitions, opts => opts.Ignore());
After that, it just cost a few milliseconds.
A good tip is to optimize the configuration of AutoMapper, use Ignore for the properties of the ViewModels, and make the method call to validate the mappings "Mapper.AssertConfigurationIsValid()".
Mapper.Initialize(cfg =>
{
cfg.ValidateInlineMaps = true;
cfg.AllowNullCollections = false;
cfg.AllowNullDestinationValues = true;
cfg.DisableConstructorMapping(); // <= In the case of my project, I do not use builders, I had a performance gain.
cfg.AddProfile<DomainToViewModelMappingProfile>();
cfg.AddProfile<ViewModelToDomainMappingProfile>();
});
Mapper.AssertConfigurationIsValid();

Categories