Automapper mapping Enum to Class - c#

I get the generic error of 'Missing type map configuration or unsupported mapping.' when I try to create this map. Any ideas?
Mapper.CreateMap<MyEnum, MyClass>().ConvertUsing(c =>
{
MyAttribute attribute = c.GetCustomAttribute<MyEnum, MyAttribute>();
return new MyClass()
{
Id = c.ToString(),
Name = attribute == null ? c.ToString() : attribute.DisplayName
};
});
And...
protected override void Configure()
{
base.Configure();
Mapper.CreateMap<MyEnum, MyClass>()
.ForMember(d => d.Id, opt => opt.MapFrom(s => s.ToString()))
.ForMember(d => d.Name, opt => opt.ResolveUsing<DisplayNameResolver>());
}
private class DisplayNameResolver : ValueResolver<MyEnum, string>
{
protected override string ResolveCore(MyEnum e)
{
MyAttribute attribute = e.GetCustomAttribute<MyEnum, MyAttribute>();
return attribute == null ? e.ToString() : attribute.DisplayName;
}
}
Don't seem to work.
Thanks.

One symptom of this error is that you did not call the Configure() in your application root. If there is a problem with your configuration.. I would suggest you make a Unit test and call Automapper's AssertConfigurationIsValid() - Configuration Validation Page
[TestMethod]
public void BaseMapperWorks()
{
//MapperConfig is my static MapperCongfiguration Class
MapperConfig.Configure();
Mapper.AssertConfigurationIsValid();
}
AutoMapper Validatior will give you everything that is wrong with your mappings

Related

how to map using Automapper

I have requirement to map two objects. The requirement is train reservation details which will have towardsjourney and return journey details.
public class ReservationSource
{
public string ReservationNumber{get;set;}
public TravelS towardsTravelS{get;set;}
public TravelS returnTravelS{get;set;}
}
This is the class which is in the ReservationSource class which is to capture towards and return journey details.
public class TravelS
{
public string travelId{get;set;}
public ICollection<JourneyS> Journeys{get;set;}
}
Above is the reservation source object. This source needs a mapping to the destination object. Destination object is given below.
public class ReservationDestination
{
public string ReservationNumber{get;set;}
public TravelD towardsTravelD{get;set;}
public TravelD returnTravelD{get;set;}
}
public class TravelD
{
public string travelId{get;set;}
public ICollection<JourneyD> Journeys{get;set;}
}
public class JourneyD
{
public string JourneyId{get;set;}
}
public class JourneyS
{
public string JourneyId{get;set;}
}
This is my destination object . Here i want to map my source to destination. How do i define mapping config and map .
var config = new mappingConfiguration(cfg=>
{
cfg.CreateMap<ReservationSource,ReservationDestination>()
});
Imapper map = config.CreateMapper();
This part of code maps only the reservationNumber to the destination object. Can someone help me to map all objects. That is towardsTravelS to towardsTravelD and returnTravelS to returnTravelD.
.net core version : 3.1
First of all you forgot to mention this but I assume there also is a class TravelS that looks like this:
public class TravelS
{
public string TravelId { get; set; }
}
There are a few things missing in your configuration. At the moment AutoMapper doesn't know it has to map properties with different names (TowardsTravelS => TowardsTravelD etc) so we have to define those aswell:
cfg.CreateMap<ReservationSource, ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
Here we tell AutoMapper that these properties that have different names need to be mapped.
Secondly TravelS and TravelD are different classes so we need to configure them for mapping as well:
cfg.CreateMap<TravelS, TravelD>();
So we now have something like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ReservationSource, ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
cfg.CreateMap<TravelS, TravelD>();
});
var mapper = config.CreateMapper();
var source = new ReservationSource
{
ReservationNumber = "9821",
ReturnTravelS = new TravelS
{
TravelId = "1"
},
TowardsTravelS = new TravelS
{
TravelId = "2"
}
};
var destination = mapper.Map<ReservationDestination>(source);
Console.WriteLine(JsonSerializer.Serialize(destination));
Output:
{"ReservationNumber":"9821","TowardsTravelD":{"TravelId":"2"},"ReturnTravelD":{"TravelId":"1"}}
Try it for yourself here: https://dotnetfiddle.net/FfccVR
Add this in your services in startup :
it's reusable and cleaner
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
}
add these interface and class in your project
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
using AutoMapper;
using System;
using System.Linq;
using System.Reflection;
public class MappingProfile : Profile
{
public MappingProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces()
.Any(i =>i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping")
?? type.GetInterface("IMapFrom`1").GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
and your source model be like this (map ReservationSource to ReservationSource):
public class ReservationSource : IMapFrom<ReservationSource>
{
public string Name { get; set; }
public string City { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<ReservationSource,ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
}
}

Using enums as criteria in Automapper CreateMap and Projection

Using AutoMapper v6.1
Without having to hard code that value in lieu of the enum ((int)POStatusOptions.Ordered), how can you accomplish this map with Projection:
CreateMap<WorkOrderDetail, WorkOrderDetailsListViewModel>(MemberList.Destination)
.ForMember(vm => vm.QtyOnPOs, opt => opt.MapFrom(entity =>
entity.Item.PODetails
.Where(pod => pod.POHeader.StatusId >= (int)POStatusOptions.Ordered)
.Sum(pod => pod.QtyOrdered)
)))
My configuration for automapper is using profiles. So I have
My Config Class:
public static class AutoMapperConfiguration
{
public static void Configure()
{
//see https://github.com/AutoMapper/AutoMapper/wiki/Configuration
//see https://github.com/AutoMapper/AutoMapper/wiki/Configuration-validation
Mapper.Initialize(am =>
{
am.AddProfile<AutoMapperViewModelProfile>();
am.AddProfile<AutoMapperViewModelProfileAdmin>();
});
//uncomment this during testing to get a list of all errors in the browser when you run any page in otis
Mapper.AssertConfigurationIsValid();
}
}
Which is called in Application_Start() like:
AutoMapperConfiguration.Configure();
My profile class:
public class AutoMapperViewModelProfile : Profile
{
public AutoMapperViewModelProfile()
{
CreateMap<WorkOrderDetail, WorkOrderDetailsListViewModel>(MemberList.Destination)
.ForMember(vm => vm.QtyOnPOs, opt => opt.MapFrom(entity =>
entity.Item.PODetails
.Where(pod => pod.POHeader.StatusId >= (int)POStatusOptions.Ordered)
.Sum(pod => pod.QtyOrdered)
)))
}
In AutoMapper it's called Parameterization. Please see AutoMapper doc.
In your case it would be:
POStatusOption poStatusOption = POStatusOption.Whatever;
CreateMap<WorkOrderDetail, WorkOrderDetailsListViewModel>(MemberList.Destination)
.ForMember(
vm => vm.QtyOnPOs,
opt => opt.MapFrom(entity =>
entity.Item.PODetails
.Where(pod => pod.POHeader.StatusId >= (int)poStatusOption)
.Sum(pod => pod.QtyOrdered)
)
)
And you need to use it like this:
dbContext.WorkOrderDetails.ProjectTo<WorkOrderDetailsListViewModel>(Config, new { poStatusOption = POStatusOptions.Ordered });

C# Automapper How to resolve using property from customresolver

I am using the following mapping to map my data object to viewmodel object.
ObjectMapper.cs
public static class ObjectMapper
{
public static void Configure()
{
Mapper.CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Title,
opt => opt.ResolveUsing<TitleValueResolver>())
.ForMember(dest => dest.Name,
opt => opt.ResolveUsing<NameValueResolver >())
.ForMember(dest => dest.ShortName,
opt => opt.ResolveUsing<ShortNameValueResolver >());
}
}
Parser
public class Parser{
public string GetTitle(string title){
/* add some logic */
return title;
}
public string GetName(string title){
/* add some logic */
return name;
}
public string GetShortName(string title){
/* add some logic */
return shortname;
}
}
AutoMapperCustomResolvers.cs
public class TitleValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public TitleValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetTitle(source.TITLE);
}
}
public class NameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public NameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetName(source.TITLE);
}
}
public class ShortNameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public ShortNameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetShortName(source.TITLE);
}
}
I am using the above code to add logic to the destination property using the separate custom value resolvers. Not sure is this the right approach.
i) Is there a better way to achieve this?
ii) And how to use unity to resolve in case i want to inject some dependency to custom resolver constructor?
Thanks
As I understand your question, you want to utilize a ValueResolver, that resolves multiple source properties into an intermediate data object, which is used to resolve multiple target properties. As an example, I assume the following source, target, intermediate and resolver types:
// source
class User
{
public string UserTitle { get; set; }
}
// target
class UserViewModel
{
public string VM_Title { get; set; }
public string VM_OtherValue { get; set; }
}
// intermediate from ValueResolver
class UserTitleParserResult
{
public string TransferTitle { get; set; }
}
class TypeValueResolver : ValueResolver<User, UserTitleParserResult>
{
protected override UserTitleParserResult ResolveCore(User source)
{
return new UserTitleParserResult { TransferTitle = source.UserTitle };
}
}
You need a target property in order to utilize opt.ResolveUsing<TypeValueResolver>(). This means, you can establish a mapping, where an appropriate target property is available.
So, for the moment, lets wrap the result into an appropriate container type:
class Container<TType>
{
public TType Value { get; set; }
}
And create a mapping
Mapper.CreateMap<User, Container<UserViewModel>>()
.ForMember(d => d.Value, c => c.ResolveUsing<TypeValueResolver>());
And another mapping
Mapper.CreateMap<UserTitleParserResult, UserViewModel>()
.ForMember(d => d.VM_Title, c => c.MapFrom(s => s.TransferTitle))
.ForMember(d => d.VM_OtherValue, c => c.Ignore());
And another mapping
Mapper.CreateMap<User, UserViewModel>()
.BeforeMap((s, d) =>
{
Mapper.Map<User, Container<UserViewModel>>(s, new Container<UserViewModel> { Value = d });
})
.ForAllMembers(c => c.Ignore());
// establish more rules for properties...
The last mapping is a bit special, since it relies on a nested mapping in order to update the destination with values from source via separately configured mapping rules. You can have multiple different transfer mappings for different properties by adding appropriate intermediate mappings and calls in BeforeMap of the actual mapped type. The properties that are handled in other mappings need to be ignored, since AutoMapper doesn't know about the mapping in BeforeMap
Small usage example:
var user = new User() { UserTitle = "User 1" };
// create by mapping
UserViewModel vm1 = Mapper.Map<UserViewModel>(user);
UserViewModel vm2 = new UserViewModel() { VM_Title = "Title 2", VM_OtherValue = "Value 2" };
// map source properties into existing target
Mapper.Map(user, vm2);
Dunno if this helps you. There might be better ways if you rephrase your question to describe your initial problem instead of what you suspect to be a solution.

Model to ViewModel mappings not passed to child objects

I'm new with AutoMapper but I've read a few tutorials and decided to try it.
In those tutorials there was a good idea which I decided to adopt. The authir suggested that the mapping code for the view model should stay in the view model and not in the AutoMapper configuration. This will make it smaller and easily readable:
Here are the base files to do this using reflections, AutoMapperConfiguration:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutoMapper;
public class AutoMapperConfig
{
private Assembly assembly;
public AutoMapperConfig(Assembly assembly)
{
this.assembly = assembly;
}
public void Execute()
{
var types = this.assembly.GetExportedTypes();
LoadStandardMappings(types);
LoadCustomMappings(types);
}
private static void LoadStandardMappings(IEnumerable<Type> types)
{
var maps = from t in types
from i in t.GetInterfaces()
where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>) && !t.IsAbstract && !t.IsInterface
select new { Source = i.GetGenericArguments()[0], Destination = t };
foreach (var map in maps)
{
Mapper.CreateMap(map.Source, map.Destination);
}
}
private static void LoadCustomMappings(IEnumerable<Type> types)
{
var maps = from t in types
from i in t.GetInterfaces()
where typeof(IHaveCustomMappings).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface
select (IHaveCustomMappings)Activator.CreateInstance(t);
foreach (var map in maps)
{
map.CreateMappings(Mapper.Configuration);
}
}
}
public interface IMapFrom<T>
{
}
public interface IHaveCustomMappings
{
void CreateMappings(IConfiguration configuration);
}
The IMapFrom and IHaveCustom interfaces are here only to mark the mapping classes. Now we come to the interesting part. When I do for example a class like the following
public class BasicAddressViewModel : IHaveCustomMappings
{
public string Id { get; set; }
[Display(Name = "Name")]
public string Label { get; set; }
[Display(Name = "Number")]
public string Location { get; set; }
public void CreateMappings(IConfiguration configuration)
{
var map = configuration.CreateMap<Address, BasicAddressViewModel>();
map.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id));
map.ForMember(dest => dest.Label, opt => opt.MapFrom(src => src.Label));
map.ForMember(dest => dest.Location, opt => opt.MapFrom(src => src.Location));
}
}
I'm setting the mappings in the CreateMappings method but if I decide to use this class as a parent those mappings will not be available to my child class and therefor I will have to reuse the same code for all my children classes:
public class IndexAddressViewModel : BasicAddressViewModel
{
public void CreateMappings(IConfiguration configuration)
{
var map = configuration.CreateMap<Address, IndexAddressViewModel >();
map.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id));
map.ForMember(dest => dest.Label, opt => opt.MapFrom(src => src.Label));
map.ForMember(dest => dest.Location, opt => opt.MapFrom(src => src.Location));
}
}
In reality what I want to achieve is this
public class IndexAddressViewModel : BasicAddressViewModel
{
}
Thanks in advance for any suggestions.
You can create a protected method in your base class that creates the custom mappings
protected virtual IMappingExpression<BasicAddressViewModel, TDestination> CreateBaseMappings<TDestination>(IMappingExpression<BasicAddressViewModel, TDestination> mappingExpression)
where TDestination : BasicAddressViewModel
and make your CreateMappings(IConfiguration configuration) method virtual so you can override it in your derived class and call the base CreateBaseMappings method that returns IMappingExpression<BasicAddressViewModel, TDestination> which means you can add more mappings for other members
EDIT
I found a better solution. :)
Since AutoMapper 2.0 you can use the IncludeBase<BaseModel, BaseViewModel>() to call the mapping for the base type. So the new solution is to make your CreateMappings(IConfiguration configuration) method virtual in the base ViewModel class and override it in your derived class where you call:
configuration.CreateMap().IncludeBase();

AutoMapper catches and ignores NullReferenceException

Maybe this is by design, but we initially did not expect automapper to catch and ignore all NullReferenceExceptions in our mappings. We mostly use the MapFrom and create sometimes complex expressions. We want these mappings to fail if there's any exception, even a NullReferenceException, but we cant get AutoMapper to do that. Is there any way to make automapper not ignore all these exceptions without having to write a custom value resolver for every case? This would mean alot of extra code for us, so much in fact that it probably would be less code without using automapper in the first place.
These are the test that we would expect to all pass:
[TestFixture]
public class Tests
{
[SetUp]
public void Setup() { Mapper.Reset(); }
[Test]
public void ShouldThrowMappingExceptionUsingMapFromExpression()
{
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.DestinationMember, o => o.MapFrom(s => s.SourceMember.SourceProperty))
;
Assert.Throws<AutoMapperMappingException>(() => Mapper.Map<Source, Destination>(new Source()));
}
[Test]
public void ShouldThrowMappingExceptionUsingResolveUsingExpression()
{
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.DestinationMember, o => o.ResolveUsing(s => s.SourceMember.SourceProperty))
;
Assert.Throws<AutoMapperMappingException>(() => Mapper.Map<Source, Destination>(new Source()));
}
[Test]
public void ShouldThrowMappingExceptionUsingResolverInstance()
{
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.DestinationMember, o => o.ResolveUsing(new TestValueResolver()).FromMember(s => s.SourceMember))
;
Assert.Throws<AutoMapperMappingException>(() => Mapper.Map<Source, Destination>(new Source()));
}
[Test]
public void ShouldThrowMappingExceptionUsingResolverType()
{
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.DestinationMember, o => o.ResolveUsing<TestValueResolver>().FromMember(s => s.SourceMember))
;
Assert.Throws<AutoMapperMappingException>(() => Mapper.Map<Source, Destination>(new Source()));
}
}
public class Destination
{
public string DestinationMember { get; set; }
}
public class Source
{
public SourceChild SourceMember { get; set; }
}
public class SourceChild
{
public string SourceProperty { get; set; }
}
public class TestValueResolver : ValueResolver<SourceChild, string>
{
protected override string ResolveCore(SourceChild source)
{
return source.SourceProperty;
}
}
This behaviour has now been changed in AutoMapper! :-)
https://github.com/AutoMapper/AutoMapper/issues/122

Categories

Resources