How to make Automapper use exact value without creating a new object?
using System.Collections.Generic;
using AutoMapper;
namespace Program
{
public class A { }
public class B
{
public A Aprop { get; set; }
}
public class C
{
public A Aprop { get; set; }
}
class Program
{
private static void Main(string[] args)
{
AutoMapper.Mapper.Initialize(cnf =>
{
// I really need this mapping. Some additional Ignores are present here.
cnf.CreateMap<A, A>();
// The next mapping should be configured somehow
cnf.CreateMap<B, C>(); //.ForMember(d => d.Aprop, opt => opt.MapFrom(...)) ???
});
A a = new A();
B b = new B() {Aprop = a};
C c = Mapper.Map<C>(b);
var refToSameObject = b.Aprop.Equals(c.Aprop); // Evaluates to false
}
}
}
How should I change cnf.CreateMap<B, C>(); line in order to make refToSameObject variable have true value? If I remove cnf.CreateMap<A, A>(); it will work this way however I cannot remove it because sometimes I use automapper to update A classes from other A classes.
One way around this is to use ConstructUsing and set Aprop during the construction of C:
AutoMapper.Mapper.Initialize(cnf =>
{
cnf.CreateMap<A, A>();
cnf.CreateMap<B, C>()
.ConstructUsing(src => new C() { Aprop = src.Aprop })
.ForMember(dest => dest.Aprop, opt => opt.Ignore());
});
This should work and isn't too painful assuming it's really just one property.
Related
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));
}
}
With the following example (LinqPad):
void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, DestinationNested>()
.ConstructUsing((source, context) => new DestinationNested(source.InnerValue));
cfg.CreateMap<Source, DestinationOuter>()
.ForMember(x => x.OuterValue, y => y.MapFrom(z => z.OuterValue))
.ConstructUsing((source, context) =>
{
return new DestinationOuter(source.OuterValue, context.Mapper.Map<DestinationNested>(source));
});
});
var src = new Source { OuterValue = 999, InnerValue = 111 };
var mapper = config.CreateMapper();
var mapped = mapper.Map<DestinationOuter>(src);
mapped.Dump();
mapper.ConfigurationProvider.AssertConfigurationIsValid();
}
public class Source
{
public int OuterValue { get; set; }
public int InnerValue { get; set; }
}
public class DestinationOuter
{
public int OuterValue { get; private set; }
public DestinationNested destinationNested { get; private set; }
public DestinationOuter(int outerValue, DestinationNested destinationNested)
{
this.OuterValue = outerValue;
this.destinationNested = destinationNested;
}
}
public class DestinationNested
{
public int NestedValue { get; private set; }
public DestinationNested(int nestedValue)
{
this.NestedValue = nestedValue;
}
}
AssertConfigurationIsValid() currently throws an exception regarding the properties as I'm using ContructUsing.
In practice it does map correctly, but I'd like AssertConfigurationIsValid as part of my test suite to look for regressions (without needing to do manual tests of the mapper).
I'd like reassurance that all my properties are mapped from the source to the destination via the contructor. I wish to use a contructor as it's my Domain tier and the contructor enforces the mandatory items.
I don't wish to ignore all private setters via the IgnoreAllPropertiesWithAnInaccessibleSetter() feature, as I might be ignoring something which I haven't actually set.
Ideally I also don't want to need to do manual Ignore() on each of the properties which appear in the constructor as that leaves a gap for code drift.
I've tried various combinations in Automapper but no luck so far.
I suppose it's a static analysis challenge; I wish to know that my contructor covers all properties in the Destination. And I wish to know that the contructor is being passed everything from the source.
I realise that Automapper isn't doing very much automatic at this point, is there a nice way to lean on automapper for this testing or is this instead a static analysis problem?
Here's my take.
static void Main(string[] args)
{
try{
var mapperCfg = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, DestinationOuter>().ForCtorParam("destinationNested", o => o.MapFrom(s => new DestinationNested(s.InnerValue)));
});
mapperCfg.AssertConfigurationIsValid();
var mapper = mapperCfg.CreateMapper();
var src = new Source { OuterValue = 999, InnerValue = 111 };
mapper.Map<DestinationOuter>(src).Dump();
}catch(Exception ex){
ex.ToString().Dump();
}
}
public class Source
{
public int OuterValue { get; set; }
public int InnerValue { get; set; }
}
public class DestinationOuter
{
public int OuterValue { get; }
public DestinationNested DestinationNested { get; }
public DestinationOuter(int outerValue, DestinationNested destinationNested)
{
this.OuterValue = outerValue;
this.DestinationNested = destinationNested;
}
}
public class DestinationNested
{
public int NestedValue { get; private set; }
public DestinationNested(int nestedValue)
{
this.NestedValue = nestedValue;
}
}
After reading lots of docs, stepping through the integration tests with the debugger and a few days of good'ol experimentation, this is the best I have:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, DestinationNested>()
.ForCtorParam("nestedValue", x => x.MapFrom(y => y.InnerValue))
.ForMember(x => x.NestedValue, x => x.MapFrom(y => y.InnerValue));
cfg.CreateMap<Source, DestinationOuter>()
.ForPath(x => x.destinationNested.NestedValue, x => x.MapFrom(y => y.InnerValue))
.ForCtorParam("destinationNested", x => x.MapFrom(y => y));
});
I'm pretty happy with it; it's getting rid of the ContructUsing() smell which in my broader codebase was constructing the nested objects. And it's warning me if my destination object is not populated. Ideally, the constructor param string would be type safe, but I understand why it cannot (perhaps this is something for a fun Roslyn code analyser project for another day :-) )
The secret sauce (hot of the press) was the x => x.MapFrom(y => y) coupled with the .ForPath(x => x.destinationNested.NestedValue, x => x.MapFrom(y => y.InnerValue)) it seemed to give AutoMapper enough hints that the destinationNested was related to InnerValue and that the contructor param was renamed to "destinationNested". The magic came in that rather than using the context to contruct the nested object the innocent looking x.MapFrom(y => y) seemed to let it use the property mapping instead*.
*That's my layman's explanation, I haven't yet followed enough of the AutoMapper source code to understand the relationship between property mapping and constructor mapping truely. Reading through some GitHub tickets, I thought they were separate concepts.
I also haven't seen x.MapFrom(y => y) mentioned in the docs, so I'd be interested in learning more about it.
I currently have 4 classes that inherits Buildable class. For each derived class, I have to set it's position every time I inherits the buildable class. Is there any way to make this more cleaner? I think showing the code will make it more easier to understand.
public class BuildableData
{
public Vector3 position;
}
public class StockpileData : BuildableData
{
public int woodCount = 0;
public static StockpileData Create(Stockpile stockpile)
{
return new StockpileData
{
position = stockpile.transform.position,
woodCount = stockpile.WoodCount
};
}
}
public class HouseData : BuildableData
{
public static HouseData Create(House house)
{
return new HouseData
{
position = house.transform.position, // I'm talking about this one? I have to set it everytime I inherit BuildableData
};
}
}
Is there any way to make it automatically set by just passing the object to the constructor or maybe using reflection?
AutoMapper is the right library here to solve the mentioned problem as it helps to map one object's properties to another object's properties. Below code snippet will help to configure the same in your project.
using AutoMapper;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
var mapper = mappingConfig.CreateMapper();
var house = new House();
var houseData = mapper.Map<HouseData>(house);
var stockpile = new Stockpile();
var stockpileData = mapper.Map<StockpileData>(stockpile);
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<House, HouseData>()
.ForMember(destination => destination.Position,
source => source.MapFrom(m => m.transform.Position));
CreateMap<Stockpile, StockpileData>()
.ForMember(destination => destination.Position,
source => source.MapFrom(m => m.transform.Position));
}
}
}
I'm using Automapper to map a class with a null collection to a destination with the same collection. I need the destination collection to also be null.
There is a property on the Profile class called AllowNullCollections. It is not affecting the mapping.
If I set cfg.AllowNullCollections to True the mapping does leave the destination collection as null (as I want).
I can't set the AllowNullCollections to True for all mappings in my system, it must only apply to my profile.
using System.Collections.Generic;
using AutoMapper;
using NUnit.Framework;
using Assert = NUnit.Framework.Assert;
namespace Radfords.FreshCool.Web.Tests
{
[TestFixture]
[Category("UnitTest")]
class AutomapperTests
{
private IMapper _mapper;
// this says that AllowNullCollections does work at the profile level, in May.
//https://github.com/AutoMapper/AutoMapper/issues/1264
[SetUp]
public void SetUp()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<TestMappingProfile>();
// I want the profile to set the configuration, if I set this here the test passes
//cfg.AllowNullCollections = true;
});
_mapper = config.CreateMapper();
}
[Test]
[Category("UnitTest")]
public void MapCollectionsTest_MustBeNull()
{
var actual = _mapper.Map<Destination>(new Source());
Assert.IsNull(actual.Ints, "Ints must be null.");
}
}
internal class TestMappingProfile : Profile
{
public TestMappingProfile()
{
AllowNullCollections = true;
CreateMap<Source, Destination>();
}
}
internal class Source
{
public IEnumerable<int> Ints { get; set; }
}
internal class Destination
{
public IEnumerable<int> Ints { get; set; }
}
}
Will Ray has submitted an issue on github. The current state is that you cannot set AllowNullCollections at the profit level, you must set it at the config level.
Can you replace TestMappingProfile Ctor with below and it should work :
public TestMappingProfile()
{
CreateMap<Source, Destination>().ForMember(dest => dest.Ints, opt => opt.Condition(src => (src.Ints != null)));
}
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.