AutoMapper convert from multiple sources - c#

Let's say I have two model classes:
public class People {
public string FirstName {get;set;}
public string LastName {get;set;}
}
Also have a class Phone:
public class Phone {
public string Number {get;set;}
}
And I want to convert to a PeoplePhoneDto like this:
public class PeoplePhoneDto {
public string FirstName {get;set;}
public string LastName {get;set;}
public string PhoneNumber {get;set;}
}
Let's say in my controller I have:
var people = repository.GetPeople(1);
var phone = repository.GetPhone(4);
// normally, without automapper I would made
return new PeoplePhoneDto(people, phone) ;
Is this possible ?

You cannot directly map many sources to single destination - you should apply maps one by one, as described in Andrew Whitaker answer. So, you have to define all mappings:
Mapper.CreateMap<People, PeoplePhoneDto>();
Mapper.CreateMap<Phone, PeoplePhoneDto>()
.ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));
Then create destination object by any of these mappings, and apply other mappings to created object. And this step can be simplified with very simple extension method:
public static TDestination Map<TSource, TDestination>(
this TDestination destination, TSource source)
{
return Mapper.Map(source, destination);
}
Usage is very simple:
var dto = Mapper.Map<PeoplePhoneDto>(people)
.Map(phone);

You could use a Tuple for this:
Mapper.CreateMap<Tuple<People, Phone>, PeoplePhoneDto>()
.ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.Item1.FirstName))
.ForMember(d => d.LastName, opt => opt.MapFrom(s => s.Item1.LastName))
.ForMember(d => d.Number, opt => opt.MapFrom(s => s.Item2.Number ));
In case you would have more source models you can use a different representation (List, Dictionary or something else) that will gather all these models together as a source.
The above code should preferaby be placed in some AutoMapperConfiguration file, set once and globally and then used when applicable.
AutoMapper by default supports only a single data source. So there is no possibility to set directly multiple sources (without wrapping it up in a collection) because then how would we know what in case if for example two source models have properties with the same names?
There is though some workaround to achieve this:
public static class EntityMapper
{
public static T Map<T>(params object[] sources) where T : class
{
if (!sources.Any())
{
return default(T);
}
var initialSource = sources[0];
var mappingResult = Map<T>(initialSource);
// Now map the remaining source objects
if (sources.Count() > 1)
{
Map(mappingResult, sources.Skip(1).ToArray());
}
return mappingResult;
}
private static void Map(object destination, params object[] sources)
{
if (!sources.Any())
{
return;
}
var destinationType = destination.GetType();
foreach (var source in sources)
{
var sourceType = source.GetType();
Mapper.Map(source, destination, sourceType, destinationType);
}
}
private static T Map<T>(object source) where T : class
{
var destinationType = typeof(T);
var sourceType = source.GetType();
var mappingResult = Mapper.Map(source, sourceType, destinationType);
return mappingResult as T;
}
}
And then:
var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>(people, phone);
But to be quite honest, even though I am using AutoMapper for already a few years I have never had a need to use mapping from multiple sources.
In cases when for example I needed multiple business models in my single view model I simply embedded these models within the view model class.
So in your case it would look like this:
public class PeoplePhoneDto {
public People People { get; set; }
public Phone Phone { get; set; }
}

Try this if you're using C# 7+ (a slight variation of #Paweł Bejgerthat's answer that will make it even simpler):
Mapper.CreateMap<(People people, Phone phone), PeoplePhoneDto>()
.ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.people.FirstName))
.ForMember(d => d.LastName, opt => opt.MapFrom(s => s.people.LastName))
.ForMember(d => d.Number, opt => opt.MapFrom(s => s.phone.Number ));
And then use it like this:
var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>((people, phone));
And yes, you will need a couple of brackets around the arguments, it's not a mistake. The reason behind it is that you're passing one single source (not two) which happens to be a (People, Phone) tuple.

I'd write an extension method as below:
public static TDestination Map<TSource1, TSource2, TDestination>(
this IMapper mapper, TSource1 source1, TSource2 source2)
{
var destination = mapper.Map<TSource1, TDestination>(source1);
return mapper.Map(source2, destination);
}
Then usage would be:
mapper.Map<People, Phone, PeoplePhoneDto>(people, phone);

perhaps it sounds to be an old post but there might be some guys still struggling with the same issue, referring to AutoMapper IMapper Map function documentation, we can reuse the same existing destination object for mapping from a new source, provided that you already created a map for each source to destination in profile, then you can use this simple Extention method:
public static class MappingExtentions
{
public static TDestination Map<TDestination>(this IMapper mapper, params object[] sources) where TDestination : new()
{
return Map(mapper, new TDestination(), sources);
}
public static TDestination Map<TDestination>(this IMapper mapper, TDestination destination, params object[] sources) where TDestination : new()
{
if (!sources.Any())
return destination;
foreach (var src in sources)
destination = mapper.Map(src, destination);
return destination;
}
}
please note that I have created a constraint for destination type which says it must be an instantiate-able type. if your type is not like that use default(TDestination) instead of new TDestination().
Warning: this type of mapping is a bit dangerous sometimes because the destination mapping properties might be overwritten by multiple sources and tracing the issue in larger apps can be a headache, there is a loose workaround you can apply, you can do as below, but again it is not a solid solution at all:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
}
public class Contact
{
public string Address { get; set; }
public string PhoneNumber { get; set; }
public string Other{ get; set; }
}
public class PersonContact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address{ get; set; }
public string PhoneNumber { get; set; }
}
public class PersonMappingProfile : MappingProfile
{
public PersonMappingProfile()
{
this.CreateMap<Person, PersonContact>();
this.CreateMap<Phone, PersonContact>()
.ForMember(dest => dest.PhoneNumber, opt => opt.MapFrom((src, dest) => dest.PhoneNumber ?? src.PhoneNumber)) // apply mapping from source only if the phone number is not already there, this way you prevent overwriting already initialized props
.ForAllOtherMembers(o => o.Ignore());
}
}

There is a breaking change in AutoMapper 9.0 that no longer provides an API for static Mapper. So, we need to use an instance now. For those using the newer versions, an extension method that uses inferred types with/without a destination object follow:
public static class AutoMapperExtensions
{
public static TDestination Map<TDestination>(this IMapper mapper, params object[] source) where TDestination : class
{
TDestination destination = mapper.Map<TDestination>(source.FirstOrDefault());
foreach (var src in source.Skip(1))
destination = mapper.Map(src, destination);
return destination;
}
public static TDestination Map<TDestination>(this IMapper mapper, TDestination destination, params object[] source) where TDestination : class
{
foreach (var src in source)
destination = mapper.Map(src, destination);
return destination;
}
}

Using FluentAPI style for better discoverability and guidance usage.
public static class MapperExtensions
{
public static IMultiMapBuilder<TDestination> StartMultiMap<TDestination>(this IMapper mapper, object source)
{
return new MultiMapBuilder<TDestination>(mapper, source);
}
}
public interface IMultiMapBuilder<T>
{
IMultiMapBuilder<T> Then<TSource>(TSource source);
T Map();
}
public class MultiMapBuilder<T> : IMultiMapBuilder<T>
{
private readonly IMapper _mapper;
private readonly T _mappedObject;
public MultiMapBuilder(IMapper mapper, object source)
{
_mapper = mapper;
_mappedObject = mapper.Map<T>(source);
}
public IMultiMapBuilder<T> Then<TSource>(TSource source)
{
_mapper.Map(source, _mappedObject);
return this;
}
public T Map()
{
return _mappedObject;
}
}
Sample Usage:
//-- By IMapper Extension
var mapped = _mapper.StartMultiMap<SomeType>(source1)
.Then(source2)
.Then(source3)
.Map();
or
//-- new instance of MultiMapBuilder
var mapped = new MultiMapBuilder<SomeType>(_mapper, source1)
.Then(source2)
.Then(source3)
.Map();

If you have a scenario when destination type should be mapped from one of sources and you want to use linq projections, you can do following.
Mapper.CreateMap<People, PeoplePhoneDto>(MemberList.Source);
Mapper.CreateMap<Phone, PeoplePhoneDto>(MemberList.Source)
.ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));
CreateMap<PeoplePhoneDto,(People,Phone)>(MemberList.Destination)
.ForMember(x => x.Item1, opts => opts.MapFrom(x => x))
.ForMember(x => x.Item2, opts => opts.MapFrom(x => x.PhoneNumber))
.ReverseMap();
I needed this mostly for cross apply queries like this.
var dbQuery =
from p in _context.People
from ph in _context.Phones
.Where(x => ...).Take(1)
select ValueTuple.Create(p, ph);
var list = await dbQuery
.ProjectTo<PeoplePhoneDto>(_mapper.ConfigurationProvider)
.ToListAsync();

There's lots of options already provided, but none of them really fit what I wanted. I was falling asleep last night and had the thought:
Lets say you want to map your two classes, People and Phone to PeoplePhoneDto
public class People {
public string FirstName {get;set;}
public string LastName {get;set;}
}
+
public class Phone {
public string Number {get;set;}
}
=
public class PeoplePhoneDto {
public string FirstName {get;set;}
public string LastName {get;set;}
public string PhoneNumber {get;set;}
}
All you really need is another wrapper class for Automapper purposes.
public class PeoplePhone {
public People People {get;set;}
public Phone Phone {get;set;}
}
And then define the mapping:
CreateMap<PeoplePhone, PeoplePhoneDto>()
And use it
var dto = Map<PeoplePhoneDto>(new PeoplePhone
{
People = people,
Phone = phone,
});

Related

Automapper List to CustomCollection

I am trying to map 2 Entities. One has a List of strings and other one is using a custom collection. Can anybody please advice how can I map these 2 entities using AutoMapper.
public class SourceItem
{
public string Name { get; set; }
public List<string> ShipsTo { get; set; }
}
public class DestItem
{
public string Name { get; set; }
public MyCollection ShipsTo { get; set; }
}
public class MyCollection : CollectionBase
{
private readonly List<string> _list;
public MyCollection()
{
_list = new List<string>();
}
public MyCollection(List<string> list)
{
_list = list;
}
public void Add(string item)
{
_list.Add(item);
}
}
Here is my mapping code...
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SourceItem, DestItem>()
.ForMember(d => d.ShipsTo, o => o.ResolveUsing<CustomResolver>());
});
Here is how my custom resolver looks like...
public class CustomResolver : IValueResolver<SourceItem, DestItem, MyCollection>
{
public MyCollection Resolve(SourceItem source, DestItem destination, MyCollection destMember, ResolutionContext context)
{
return new MyCollection(source.ShipsTo);
}
}
When I try to run this code, I get an error saying
No coercion operator is defined between types
'System.Collections.Generic.List`1[System.Object]' and 'MyCollection'.
Any help would be really appreciated.
I don't see that you need a custom resolver, try this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SourceItem, DestItem>()
.ForMember(d => d.ShipsTo, o => o.MapFrom(src => new MyCollection(src.ShipsTo)));
});
The answer is: you cannot use custom value resolver here, because there is a bug (I have added item to backlog, you can track it). But you can specify conversion between List<string> and MyCollection as Jimmy suggested. And everything will work fine:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<List<string>, MyCollection>().ConvertUsing(s => new MyCollection(s));
cfg.CreateMap<SourceItem, DestItem>();
});
BTW Seems like issue already fixed, and it should be available in next release of Automapper.
I could not really explain it, but it works, if you define explicit mapping configuration from MyCollection to MyCollection:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<MyCollection, MyCollection>().ConvertUsing((a,b)=>a);
cfg.CreateMap<SourceItem, DestItem>()
.ForMember(d => d.ShipsTo, o => o.ResolveUsing<CustomResolver>());
});
I have debugged code of automapper to find why. Reason is, Automapper is looking for suitable mapper to map MyCollection to MyCollection. It looks in a collection of all mappers (defined in your config and predefined). Actually, what it should find is a AssignableMapper, that just assigns one variable to another, but first he checks an EnumerableMapper and it looks ok, because your CollectionBase is Enumerable. The exception comes from this EnumerableMapper, because it just couldn't map this two Collections. Solution is to explicit define, that it just should assign one variable to another.
But. I am agree with Sergey. This is some ugly bug, what i have suggested is exact the same ugly workaround. But i've managed get it to work :)

Best way to pass a get/set property as an argument without reflection

Sometimes, when handling data transfer objects (for instance retrieved from the database or a csv file), it's nice to write some helper functions to move the data.
For instance:
class MyDto
{
public string Name { get; set; }
}
class MyBusinessObject
{
public string Name { get; set;}
}
I'd like to write something like:
MyDto source;
MyBusinessObject target;
var hasChanged = target.Set(source, source => source.Name, target => target.Name); // lamdba expressions, or whatever it takes to make it work
with the extension method:
public static bool Set<TS, TT, TValue>(this TS source, IGetProperty<TS, TValue> sourceGetProperty, IGetOrSetProperty<TT, TValue> targetGetOrSetProperty)
{
var sourceValue = sourceGetProperty.Invoke(source);
var actualValue = targetGetOrSetProperty.Invoke(target);
if(sourceValue != actualValue)
{
targetGetOrSetPropery.Invoke(target, sourceValue);
return true;
}
return false;
}
I made up the IGetProperty and IGetOrSetProperty. Is it possible to implement them some way without using reflection (so that it's compile-time checked)?
Or is there an elegant way to handle this kind of situation?
EDIT: the example was misleading because the goal wasn't to use an Automapper, but to represent somehow properties as objects. I realize that it's pretty close in fact to the idea of using properties as "ref" for instance, so it's more a language-related question that has always been answered here: Passing properties by reference in C#
It's not exactly possible without reflection, but the expression lambda gives you compile time checking:
public static bool Set<TTarget, TValue>(
this TTarget target,
Expression<Func<TTarget, TValue>> targetProperty,
TValue sourceValue)
{
var actualValue = targetProperty.Compile().Invoke(target);
if (actualValue.Equals(sourceValue))
{
return false;
}
var property = (PropertyInfo)((MemberExpression)targetProperty.Body).Member;
property.SetValue(target, sourceValue);
return true;
}
Usage looks like so:
var hasChanged = target.Set(t => t.Name, source.Name);
Working example: https://dotnetfiddle.net/CJVxIS
Why you should not do this:
targetProperty.Compile() is slow,
Automapper does such mappings for you.
You can consider serialising/deserialising. It may be less efficient (but then again, rudimentary implementation of reflection is expensive too) but it will be syntactically more readable and elegant. The other benefit is, Json.Net is very flexible so you can customise the copy behaviours (e.g map Name property to a property of another name)
class MyDto
{
public string Name { get; set; }
public string Other { get; set; }
public string Remap { get; set; }
}
class MyBusinessObject
{
[JsonIgnore]
public string Other { get; set; }
public string Name { get; set; }
[JsonProperty(PropertyName = "Remap")]
public string RemmapedField { get; set; }
}
public T DeepCopy<T>(object o)
{
string json=JsonConvert.SerializeObject(o);
T newO=JsonConvert.DeserializeObject<T>(json);
return newO;
}
Usage
MyDto source = new MyDto() { Name = "JP", Other = "Something",Remap="R" };
var target = DeepCopy<MyBusinessObject>(source);
Result:
Name: "JP"
Other: null
RemmapedField: "R"

Mapping multiple source properties to a single destination property

I'd like to know if there would be to a way to handle this kind of scenario with some custom type or value resolver.
public class SuperDateTime
{
public DateTimeOffset Date { get; set; }
public string Timezone { get; set; }
}
public class Entity
{
public DateTimeOffset CreationDate { get; set; }
public string CreationDateZone { get; set; }
public DateTimeOffset EndDate { get; set; }
public string EndDateZone { get; set; }
}
public class Model
{
public SuperDateTime CreationDate { get; set; }
public SuperDateTime EndDate { get; set; }
}
When i have a SuperDateTime in the destination object, i'd like to instantiate this object with the associated DateTimeOffset and the timezone string in the source object.
Of course what i'd like to do is to make something generic, so not going thought the MapFrom in each CreateMap of every Entity
I tried to do it with a custom TypeConverter but it supports only a SourceType -> DestinationType
In my case i have a string and DateTimeOffset that has to create a SuperDateTime
In addition to what LiamK is suggesting, the next possible improvement is to write a helper method for doing .MapFrom. Depending on your requirements it can be simple or complex. I'm going to offer a simple one that makes a lot of assumptions, but you can modify and optimize it to suit your possible requirements.
static IMappingExpression<TFrom, TTo> MapSuperDateTime<TFrom, TTo>(
this IMappingExpression<TFrom, TTo> expression,
Expression<Func<TTo, object>> dest)
{
var datePropertyName = ReflectionHelper.FindProperty(dest).Name;
var timezomePropertyName = datePropertyName + "Zone";
var fromType = typeof (TFrom);
var datePropertyGetter = fromType.GetProperty(datePropertyName).ToMemberGetter();
var timezonePropertGetter = fromType.GetProperty(timezomePropertyName).ToMemberGetter();
return expression.ForMember(dest, opt => opt.MapFrom(src => new SuperDateTime
{
Date = (DateTimeOffset)datePropertyGetter.GetValue(src),
Timezone = (string)timezonePropertGetter.GetValue(src)
}));
}
And then you can specify your mapping like this:
Mapper.CreateMap<Entity, Model>()
.MapSuperDateTime(dest => dest.CreationDate)
.MapSuperDateTime(dest => dest.EndDate);
The assumption is that if your Entity DateTimeOffset is called bla, then your corresponding Entity string is called blaZone, and your Model SuperDateTime is called bla.
You can use the Customer Resolver for this. I used custom resolver for getting an object from int something like this;
Lets say you are creating a mapping like this(Althoug you didn't show how you are creating it):
Mapper.CreateMap<YourSource, YourDestination>()
.ForMember(x => x.DateTimeOffset, opt => opt.ResolveUsing(new DateTimeOffsetResolver(loadRepository)).FromMember(x => x.timezone));
And this how your resolver will look like:
public class DateTimeOffsetResolver : ValueResolver<string, DateTimeOffset>
{
private DatabaseLoadRepository loadRepository;
public personIdResolver(DatabaseLoadRepository repo)
{
this.loadRepository = repo;
}
protected override DateTimeOffset ResolveCore(string timeZone)
{
//Your logic for converting string into dateTimeOffset goes here
return DateTimeOffset; //return the DateTimeOffset instance
}
}
You can remove all the code related to Nhibernate Repository if you not need to access it.
You can further read about custom resolvers here
The short answer to you question is 'No', there isn't way to use a custom value resolver to map < string, DateTimeOffset > => SuperDateTime and avoid the repeated .MapFrom calls. In your example above, such a value resolver wouldn't be able to distinguish which strings and DateTimeOffsets went together during mapping.
Not sure if you have the .MapFrom code yourself, but if not, below is the best solution to your problem:
Mapper.CreateMap<Entity, Model>()
.ForMember(
dest => dest.CreationDate,
opt => opt.MapFrom(
src => new SuperDateTime()
{
Date = src.CreationDate,
TimeZone = src.CreationDateZone
};
));
If you really want to avoid excessive MapFrom declarations, see if there's a way to leverage mapping inheritance here.
EDIT: Modified instantiation of SuperDateTime to match the provided source code.
After sleeping on it, here is an alternative that feels more generic.
Assume, you want to do something like this:
Mapper.CreateMap<Entity, Model>()
.ForMemberType((member,src) => new SuperDateTime
{
Date = (DateTimeOffset)GetPropertyValue(src, member),
Timezone = (string)GetPropertyValue(src, member+"Zone")
});
This looks a bit nicer then my first answer, because here you specify that you want to map all members of SuperDateTime at once. (The type is inferred from the return type of the lambda.) Really, similar to ForAllMembers that AutoMapper already has. The only problem that you cannot use standard memberOptions as IMemberConfigurationExpression<TSource> does not give you access to the member currently being configured. For brevity, I removed the memberOptions parameter from ForMemberType signature completely, but it is very easy to add it back if you need that (that is to set some other options too - see example here).
So in order to be able to write the above all you need is GetPropertyValue method, that can look like this:
public object GetPropertyValue(object o, string memberName)
{
return o.GetType().GetProperty(memberName).ToMemberGetter().GetValue(o);
}
And the ForMemberType method itself, which would look like that:
public static IMappingExpression<TSource, TDestination> ForMemberType<TSource, TDestination, TMember>(
this IMappingExpression<TSource, TDestination> expression,
Func<string, TSource, TMember> memberMapping
)
{
return new TypeInfo(typeof(TDestination))
.GetPublicReadAccessors()
.Where(property => property.GetMemberType() == typeof(TMember))
.Aggregate(expression, (current, property)
=> current.ForMember(property.Name,
opt => opt.MapFrom(src => memberMapping(property.Name, src))));
}
And that's it. To avoid recompiling the property getter every time, you might want to add a very simple caching layer that compiles (executes ToMemberGetter) once for each type and remembers the result somewhere. Using AutoMapper own DictonaryFactory and then IDictionary.GetOrAdd is probably the most straight forward way of doing this:
private readonly IDictionary<string, IMemberGetter> _getters
= new DictionaryFactory().CreateDictionary<string, IMemberGetter>();
public object GetPropertyValue(object o, string memberName)
{
var getter = _getters.GetOrAdd(memberName + o.GetType().FullName, x => o.GetType()
.GetProperty(memberName).ToMemberGetter());
return getter.GetValue(o);
}

Can Automapper map a paged list?

I'd like to map a paged list of business objects to a paged list of view model objects using something like this:
var listViewModel = _mappingEngine.Map<IPagedList<RequestForQuote>, IPagedList<RequestForQuoteViewModel>>(requestForQuotes);
The paged list implementation is similar to Rob Conery's implementation here:
http://blog.wekeroad.com/2007/12/10/aspnet-mvc-pagedlistt/
How can you setup Automapper to do this?
Using jrummell's answer, I created an extension method that works with Troy Goode's PagedList. It keeps you from having to put so much code everywhere...
public static IPagedList<TDestination> ToMappedPagedList<TSource, TDestination>(this IPagedList<TSource> list)
{
IEnumerable<TDestination> sourceList = Mapper.Map<IEnumerable<TSource>, IEnumerable<TDestination>>(list);
IPagedList<TDestination> pagedResult = new StaticPagedList<TDestination>(sourceList, list.GetMetaData());
return pagedResult;
}
Usage is:
var pagedDepartments = database.Departments.OrderBy(orderBy).ToPagedList(pageNumber, pageSize).ToMappedPagedList<Department, DepartmentViewModel>();
AutoMapper does not support this out of the box, as it doesn't know about any implementation of IPagedList<>. You do however have a couple of options:
Write a custom IObjectMapper, using the existing Array/EnumerableMappers as a guide. This is the way I would go personally.
Write a custom TypeConverter, using:
Mapper
.CreateMap<IPagedList<Foo>, IPagedList<Bar>>()
.ConvertUsing<MyCustomTypeConverter>();
and inside use Mapper.Map to map each element of the list.
If you're using Troy Goode's PageList, there's a StaticPagedList class that can help you map.
// get your original paged list
IPagedList<Foo> pagedFoos = _repository.GetFoos(pageNumber, pageSize);
// map to IEnumerable
IEnumerable<Bar> bars = Mapper.Map<IEnumerable<Bar>>(pagedFoos);
// create an instance of StaticPagedList with the mapped IEnumerable and original IPagedList metadata
IPagedList<Bar> pagedBars = new StaticPagedList<Bar>(bars, pagedFoos.GetMetaData());
I needed to return a serializable version of IPagedList<> with AutoMapper version 6.0.2 that supports the IMapper interface for ASP.NET Web API. So, if the question was how do I support the following:
//Mapping from an enumerable of "foo" to a different enumerable of "bar"...
var listViewModel = _mappingEngine.Map<IPagedList<RequestForQuote>, PagedViewModel<RequestForQuoteViewModel>>(requestForQuotes);
Then one could do this:
Define PagedViewModel<T>
Source: AutoMapper Custom Type Converter not working
public class PagedViewModel<T>
{
public int FirstItemOnPage { get; set; }
public bool HasNextPage { get; set; }
public bool HasPreviousPage { get; set; }
public bool IsFirstPage { get; set; }
public bool IsLastPage { get; set; }
public int LastItemOnPage { get; set; }
public int PageCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalItemCount { get; set; }
public IEnumerable<T> Subset { get; set; }
}
Write open generic converter from IPagedList<T> to PagedViewModel<T>
Source: https://github.com/AutoMapper/AutoMapper/wiki/Open-Generics
public class Converter<TSource, TDestination> : ITypeConverter<IPagedList<TSource>, PagedViewModel<TDestination>>
{
public PagedViewModel<TDestination> Convert(IPagedList<TSource> source, PagedViewModel<TDestination> destination, ResolutionContext context)
{
return new PagedViewModel<TDestination>()
{
FirstItemOnPage = source.FirstItemOnPage,
HasNextPage = source.HasNextPage,
HasPreviousPage = source.HasPreviousPage,
IsFirstPage = source.IsFirstPage,
IsLastPage = source.IsLastPage,
LastItemOnPage = source.LastItemOnPage,
PageCount = source.PageCount,
PageNumber = source.PageNumber,
PageSize = source.PageSize,
TotalItemCount = source.TotalItemCount,
Subset = context.Mapper.Map<IEnumerable<TSource>, IEnumerable<TDestination>>(source) //User mapper to go from "foo" to "bar"
};
}
}
Configure mapper
new MapperConfiguration(cfg =>
{
cfg.CreateMap<RequestForQuote, RequestForQuoteViewModel>();//Define each object you need to map
cfg.CreateMap(typeof(IPagedList<>), typeof(PagedViewModel<>)).ConvertUsing(typeof(Converter<,>)); //Define open generic mapping
});
AutoMapper automatically handles conversions between several types of lists and arrays:
http://automapper.codeplex.com/wikipage?title=Lists%20and%20Arrays
It doesn't appear to automatically convert custom types of lists inherited from IList, but a work around could be:
var pagedListOfRequestForQuote = new PagedList<RequestForQuoteViewModel>(
AutoMapper.Mapper.Map<List<RequestForQuote>, List<RequestForQuoteViewModel>>(((List<RequestForQuote>)requestForQuotes),
page ?? 1,
pageSize
I created a little wrapper around AutoMapper to map PagedList<DomainModel> to PagedList<ViewModel>.
public class MappingService : IMappingService
{
public static Func<object, Type, Type, object> AutoMap = (a, b, c) =>
{
throw new InvalidOperationException(
"The Mapping function must be set on the MappingService class");
};
public PagedList<TDestinationElement> MapToViewModelPagedList<TSourceElement, TDestinationElement>(PagedList<TSourceElement> model)
{
var mappedList = MapPagedListElements<TSourceElement, TDestinationElement>(model);
var index = model.PagerInfo.PageIndex;
var pageSize = model.PagerInfo.PageSize;
var totalCount = model.PagerInfo.TotalCount;
return new PagedList<TDestinationElement>(mappedList, index, pageSize, totalCount);
}
public object Map<TSource, TDestination>(TSource model)
{
return AutoMap(model, typeof(TSource), typeof(TDestination));
}
public object Map(object source, Type sourceType, Type destinationType)
{
if (source is IPagedList)
{
throw new NotSupportedException(
"Parameter source of type IPagedList is not supported. Please use MapToViewModelPagedList instead");
}
if (source is IEnumerable)
{
IEnumerable<object> input = ((IEnumerable)source).OfType<object>();
Array a = Array.CreateInstance(destinationType.GetElementType(), input.Count());
int index = 0;
foreach (object data in input)
{
a.SetValue(AutoMap(data, data.GetType(), destinationType.GetElementType()), index);
index++;
}
return a;
}
return AutoMap(source, sourceType, destinationType);
}
private static IEnumerable<TDestinationElement> MapPagedListElements<TSourceElement, TDestinationElement>(IEnumerable<TSourceElement> model)
{
return model.Select(element => AutoMap(element, typeof(TSourceElement), typeof(TDestinationElement))).OfType<TDestinationElement>();
}
}
Usage:
PagedList<Article> pagedlist = repository.GetPagedList(page, pageSize);
mappingService.MapToViewModelPagedList<Article, ArticleViewModel>(pagedList);
It is important that you would have to use the element types!
If you have any question or suggestions, please feel free to comment :)
It is easy with Automapper .net core 8.1.1
You just need to add type map to your mapperProfile and mapping the object inside pagedList
CreateMap(typeof(IPagedList<>), typeof(IPagedList<>));
CreateMap<RequestForQuote, RequestForQuoteViewModel>().ReverseMap();
It also can be and abstract class like PagedList. It is not related with class/ınterface type
And you can use it directly in mapper.Map - Initialize mapper from IMapper in the class constructor
RequestForQuote result
_mapper.Map<IPagedList<RequestForQuoteViewModel>>(result);
If you are using X.PageList then you can simply use this code:
PagedList<exampleDTO> result = new PagedList<exampleDTO>(item, _mapper.Map<List<exampleDTO>>(item.ToList()));
The PageList lets you to create new PageList with modified items.
more information

Auto mapping a IDictionary<string, myclass> with fluent nhibernate

I have a simple class that looks like this:
public class Item {
// some properties
public virtual IDictionary<string, Detail> Details { get; private set; }
}
and then I have a map that looks like this:
map.HasMany(x => x.Details).AsMap<string>("Name").AsIndexedCollection<string>("Name", c => c.GetIndexMapping()).Cascade.All().KeyColumn("Item_Id"))
with this map I get the following error and I don't know how to solve it?
The type or method has 2 generic parameter(s), but 1 generic argument(s) were provided. A generic argument must be provided for each generic parameter.
I found a workaround for this. Basically I'm preventing the automapper from attempting to map an IDictionary. It forces me to have to map it manually in an override but at least it works.
I'm using an AutomappingConfiguration derived from DefaultAutomappingConfiguration.
public override bool ShouldMap(Member member)
{
if ( member.PropertyType.IsGenericType )
{
if (member.PropertyType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>))
return false;
}
return base.ShouldMap(member);
}
And here's a couple of sample classes and the associated mapping that I'm using to make this happen:
public class ComponentA
{
public virtual string Name { get; set; }
}
public class EntityF : Entity
{
private IDictionary<string, ComponentA> _components = new Dictionary<string, ComponentA>();
public IDictionary<string, ComponentA> Components
{
get { return _components; }
set { _components = value; }
}
}
public class EntityFMap : IAutoMappingOverride<EntityF>
{
public void Override(AutoMapping<EntityF> mapping)
{
mapping.HasMany<ComponentA>(x => x.Components)
.AsMap<string>("IndexKey")
.KeyColumn("EntityF_Id")
.Table("EntityF_Components")
.Component(x =>
{
x.Map(c => c.Name);
})
.Cascade.AllDeleteOrphan();
}
}
I've just spent several hours to make this work, so I hope this saves someone else an evening of hair-pulling.

Categories