I'm having this exception when I'm trying to map from one object to another.
On my global.asax.cs I got this:
RoleManager<IdentityRole> roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new AppContext()));
Mapper.Initialize(cfg =>
{
cfg.CreateMap<AppUser, TokenAuthorizationModel>()
.ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.UserName))
.ForMember(dest => dest.Role, opt => opt.MapFrom(src => roleManager.FindById(src.Roles.First().RoleId).Name));
});
And I got this AutoMapper.AutoMapperMappingException exception on my login controller, especifically on this line:
TokenAuthorizationModel tokenClaims = Mapper.Map<TokenAuthorizationModel>(validUser);
And these are my models:
public class AppUser : IdentityUser
{
public virtual List<CourseModel> Courses { get; set; }
public string FullName { get; set; }
public int Reputation { get; set; }
}
And destination:
public class TokenAuthorizationModel
{
public string UserName { get; set; }
public string Role { get; set; }
}
Can anybody give me hand? Thanks in advance! :)
I think the problem is at this part:
.ForMember(dest => dest.Role, opt => opt.MapFrom(src => roleManager.FindById(src.Roles.First().RoleId).Name));
I would not use MapFrom in this case. Try to use ResolveUsing method instead which takes a lambda function.
.ForMember(dest => dest.Role, opt => opt.ResolveUsing(src => roleManager.FindById(src.Roles.First().RoleId).Name));
If this does not work please let me know!
You should write a resolver for the Role property and use dependency injection inside it so you have the right DbContext as you would anywhere else in your app.
Related
I have got a problem using Automapper when conditionally Mapping a table.
Here is an example:
public class DepositsVm : IMapFrom<Deposits>
{
public long DepId { get; set; }
public AddressDto Address { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<Deposits, DepositsVm>()
.ForMember(d => d.DepId, s => s.MapFrom(s => s.DepId))
.ForMember(d => d.Address, opt => opt.MapFrom(s => s.ProcessingId != null ? s.DataProcessing.GridCollect.Grid.Address : s.Reduction.DataCollect.Tower.Address));
}
}
This results in an Object reference error.
I can see we can use https://docs.automapper.org/en/stable/Conditional-mapping.html#preconditions but this allow to check for only one condition. I expect to map a table using different join condition in failure and success scenarios.
But this works, because I'm projecting each Address separately. But, this is not desired. Because both are from Same Address Table
public class DepositsVm : IMapFrom<Deposits>
{
public long DepId { get; set; }
public AddressDto GridAddress { get; set; }
public AddressDto TowerAddress { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<Deposits, DepositsVm>()
.ForMember(d => d.DepId, s => s.MapFrom(s => s.DepId))
.ForMember(d => d.GridAddress, opt => opt.MapFrom(s => s.DataProcessing.GridCollect.Grid.Address));
.ForMember(d => d.TowerAddress, opt => opt.MapFrom(s => s.Reduction.DataCollect.Tower.Address));
}
}
Either some part of s.DataProcessing.GridCollect.Grid.Address is null, or some part of s.Reduction.DataCollect.Tower.Address is null
I have the following models:
public class Stuff
{
...
public IList<Place> Places { get; set; } = null!;
...
}
public class Place
{
...
public IList<Stuff> Stuffs { get; set; } = null!;
...
}
public class StuffEntity
{
...
public IList<PlaceStuffEntity> Places { get; set; } = null!;
...
}
public class PlaceEntity
{
...
public IList<PlaceStuffEntity> Stuffs { get; set; } = null!;
...
}
public class PlaceStuffEntity
{
public int StuffId { get; private set; }
public StuffEntity Stuff { get; set; } = null!;
public int PlaceId { get; private set; }
public PlaceEntity Place { get; set; } = null!;
}
cfg.CreateMap<StuffEntity, Stuff>()
.ForMember(d => d.Places,
opt => opt.MapFrom(s => s.Places.Select(y => y.Place).ToList()));
cfg.CreateMap<PlaceEntity, Place>()
.ForMember(d => d.Stuffs,
opt => opt.MapFrom(s => s.Places.Select(y => y.Stuff).ToList()));
cfg.CreateMap<PlaceAndStuffEntity, Stuff>() // < -- Issue
.IncludeMembers(entity=> entity.Stuff);
cfg.CreateMap<PlaceAndStuffEntity, Place>() // < -- Issue
.IncludeMembers(entity=> entity.Place);
by some reason when I add both last lines, conversion does not work ...
But if I add only one line for example for converting PlaceAndStuffEntity -> Stuff works only one conversion from PlaceEntity -> Place
var place = mapper.Map<Place>(placeEntity); // <- This works
var stuff = mapper.Map<Stuff>(stuffEntity); // <- Does not work !!
Is there a way properly handle the following conversions ?
It sounds like you want to map through the joining table (PlaceAndStuff) to get to the other entity type. For instance in your Place to get a list of Stuff, and Stuff to get a list of Place, you want to direct Automapper how to navigate through the joining table.
For instance:
cfg.CreateMap<StuffEntity, Stuff>()
.ForMember(x => x.Places, opt => opt.MapFrom(src => src.PlaceEntity));
// Where StuffEntity.Places = PlaceAndStuffEntities, to map Stuff.Places use PlaceAndStuffs.PlaceEntity
cfg.CreateMap<PlaceEntity, Place>()
.ForMember(x => x.Stuffs, opt => opt.MapFrom(src => src.StuffEntity));
So rather than trying to tell EF how to map the joining entity PlaceStuffEntity, we focus on the PlaceEntity and StuffEntity, and tell Automapper to navigate through the joining entity to get at the actual Stuff and Place relatives via the joining entity.
Change
cfg.CreateMap<PlaceEntity, Place>()
.ForMember(d => d.Stuffs,
opt => opt.MapFrom(s => s.Places.Select(y => y.Stuff).ToList()));
to
cfg.CreateMap<PlaceEntity, Place>()
.ForMember(d => d.Stuffs,
opt => opt.MapFrom(s => s.Stuffs.Select(y => y.Stuff).ToList()));
Source type PlaceEntity does not have a property named Places, only Stuffs.
I'm using automapper to map my entities. But entities have different structure.
Source:
public class SourceEntity
{
public string Name { get; set; }
public Type Type { get; set; }
public Communication SelectedCommunication { get; set; }
}
public enum Type
{
Type1=1,
Typ2
}
[Flags]
public enum Communication
{
Phone =1,
Email =2,
Post =4
}
Also I have HasFlag() extension method that will return true if flag is selected.
Destination entity:
public class DestinationEntity
{
public string Name { get; set; }
public bool Type1_PhoneSelected { get; set; }
public bool Type1_EmailSelected { get; set; }
public bool Type1_PostSelected { get; set; }
public bool Type2_PhoneSelected { get; set; }
public bool Type2_EmailSelected { get; set; }
public bool Type2_PostSelected { get; set; }
}
My map:
CreateMap<SourceEntity, DestinationEntity>()
.ForMember(v => v.Name, opt => opt.MapFrom(i => i.Name));
But I can't figure out the best way to map Types properties.
Is it possible to map it without typing something like:
.ForMemeber(v=>v.Test1_PhoneSelected, opt=>opt.MapFrom(i=>i.SelectedCommunication.HasFlag(Communication.Phone)))
.ForMemeber(v=>v.Test2_PhoneSelected, opt=>opt.MapFrom(i=>i.SelectedCommunication.HasFlag(Communication.Phone)))
For each of this properties.
Is there any way to map by naming convention?
Or any other ways?
You can use custom value resolvers
Although AutoMapper covers quite a few destination member mapping
scenarios, there are the 1 to 5% of destination values that need a
little help in resolving. Many times, this custom value resolution
logic is domain logic that can go straight on our domain. However, if
this logic pertains only to the mapping operation, it would clutter
our source types with unnecessary behavior. In these cases,
AutoMapper allows for configuring custom value resolvers for
destination members.
Example of custom value resolver:
public class YourCustomResolver
: IMemberValueResolver<object, object, Communication, bool>
{
private Communication _communication;
public YourCustomResolver(
Communication communication)
{
}
public bool Resolve(
object source,
object destination,
Communication sourceMember,
bool destMember,
ResolutionContext context)
{
return _communication == sourceMember;
}
}
Your mapping will look like this:
CreateMap<SourceEntity, DestinationEntity>()
.ForMember(dest => dest.Type1_PhoneSelected, opt => opt.ResolveUsing(new YourCustomResolver(Communication.Phone), src => src.SelectedCommunication))
.ForMember(dest => dest.Type1_EmailSelected, opt => opt.ResolveUsing(new YourCustomResolver(Communication.Email), src => src.SelectedCommunication))
.ForMember(dest => dest.Type1_PostSelected , opt => opt.ResolveUsing(new YourCustomResolver(Communication.Post) , src => src.SelectedCommunication))
.ForMember(dest => dest.Type2_PhoneSelected, opt => opt.ResolveUsing(new YourCustomResolver(Communication.Phone), src => src.SelectedCommunication))
.ForMember(dest => dest.Type2_EmailSelected, opt => opt.ResolveUsing(new YourCustomResolver(Communication.Email), src => src.SelectedCommunication))
.ForMember(dest => dest.Type2_PostSelected , opt => opt.ResolveUsing(new YourCustomResolver(Communication.Post) , src => src.SelectedCommunication));
I'm a AutoMapper newb. My mappings are not working as expected and I'm sure I'm doing somthing wrong but can't figure it out. Sorry if this question is confusing, but i'll do my best to be clear. Lets say we have three classes:
public class Person
{
public ContactInfo1 Contact { get; set; }
}
public class ContactInfo1
{
public string Name { get; set; }
}
public class ContactInfo2
{
public string AnotherName { get; set; }
}
Now, I want to setup my mappings so that ContactInfo1 can map to and from ContactInfo2. And then I want to be able to map Person1 -> ContactInfo2 (which might look stange, but I need to do it anyway). I have tried the following mapping config:
var autoMapperConfig = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<ContactInfo1, ContactInfo2>()
.ForMember(dest => dest.AnotherName, opt => opt.MapFrom(src => src.Name)).ReverseMap();
cfg.CreateMap<ContactInfo2, Person>()
.ForMember(dest => dest.Contact, opt => opt.MapFrom(src => src)).ReverseMap();
});
var mapper = autoMapperConfig.CreateMapper();
For the test data:
var testPerson = new Person();
testPerson.Contact = new ContactInfo1() { Name = "Person1" };
I do the following:
var contactInfo2Test = mapper.Map<Person, ContactInfo2>(testPerson);
This does NOT give me any errors, but contactInfo2Test.AnotherName is empty. Please advise! Thanks.
Please note that I realize I could go:
cfg.CreateMap<Person, ContactInfo2>()
.ForMember(dest => dest.AnotherName, opt => opt.MapFrom(src => src.Contact.Name));
Bu then I would have mapped Contact1->Contact2 all over again, and in a more complex scenario I really want to avoid that.
Here's one way of doing it:
var autoMapperConfig = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<ContactInfo1, ContactInfo2>()
.ForMember(dest => dest.AnotherName, opt => opt.MapFrom(src => src.Name))
.ReverseMap();
cfg.CreateMap<Person, ContactInfo2>()
.ConstructUsing((p, ctx) => ctx.Mapper.Map<ContactInfo2>(p.Contact));
});
I'm following this answer AutoMapper mapping int[] or List<int> from ViewModel to a List<Type> in Domain Model and have got it binding with my CategoryId to SelectedCategoryIds on UploadSongViewModel, however I want to also get it to bind SongId to Id on UploadSongViewModel.
namespace App
{
public static class AutoMapper
{
public static MapperConfiguration Config;
public static void Initialize()
{
//It works with CategoryId, but throws an error when I include SondId.
Config = new MapperConfiguration(x =>
{
x.CreateMap<int, SongCategory>()
.IgnoreAllNonExisting()
.ForMember(dest => dest.CategoryId, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.SongId, opt => opt.MapFrom(src => src)); //Code that's throwing exception
x.CreateMap<UploadSongViewModel, Song>()
.IgnoreAllNonExisting()
.ForMember(dest => dest.AudioName, opt => opt.MapFrom(src => src.SongName))
.ForMember(dest => dest.AudioPath, opt => opt.MapFrom(src => src.SongPath))
.ForMember(dest => dest.SongCategories, opt => opt.MapFrom(src => src.SelectedCategoryIds))
.ForMember(dest => dest.SongCategories, opt => opt.MapFrom(src => src.Id)); //Code that's throwing exception
});
//Throws exception here because it can't bind SongId property
Config.AssertConfigurationIsValid();
}
}
}
public abstract class Entity
{
public int Id { get; set; }
}
public class SongCategory : Entity
{
public int SongId { get; set; }
public int CategoryId { get; set; }
}
public class Song : Audio
{
public string AlbumName { get; set; }
public string ArtistName { get; set; }
public List<SongCategory> SongCategories { get; set;}
}
public class UploadSongViewModel
{
public int Id { get; set; }
public string ArtistName { get; set; }
public string AlbumName { get; set; }
public int[] SelectedCategoryIds { get; set; }
public MultiSelectList CategoriesSelectList { get; set; }
}
I don't really understand what the auto mapper code is doing from Darin Dimitrov answer so it's hard to debug. If someone could explain how this mapping works for categoryId and why it is not working for SongId that would be great.
The exception I get is:
The following property on System.Collections.Generic.List1[[SoundVast.Models.SongCategory, SoundVast, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] cannot be mapped:
SongCategories
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type System.Collections.Generic.List1[[SoundVast.Models.SongCategory, SoundVast, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].
Context:
Mapping to property SongCategories from System.Int32 to System.Collections.Generic.List`1[[SoundVast.Models.SongCategory, SoundVast, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Mapping from type SoundVast.Areas.Upload.Models.UploadSongViewModel to SoundVast.Models.Song
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
This can be easily achieved with some custom mapping once the basic rules have been applied:
x.CreateMap<int, SongCategory>()
.IgnoreAllNonExisting()
.ForMember(dest => dest.CategoryId, opt => opt.MapFrom(src => src));
x.CreateMap<UploadSongViewModel, Song>()
.IgnoreAllNonExisting()
.ForMember(dest => dest.AudioName, opt => opt.MapFrom(src => src.SongName))
.ForMember(dest => dest.AudioPath, opt => opt.MapFrom(src => src.SongPath))
.ForMember(dest => dest.SongCategories, opt => opt.MapFrom(src => src.SelectedCategoryIds))
.AfterMap((viewModel, model) =>
{
foreach (var item in model.SongCategories)
{
item.Id = viewModel.Id;
}
});