How to use AutoMapper? - c#

First time using AutoMapper and I'm have a hard time figuring out how to use it.
I'm trying to map a ViewModel to my Database Tables.
My ViewModel looks like this...
public class AddressEditViewModel
{
public AddressEdit GetOneAddressByDistrictGuid { get; private set; }
public IEnumerable<ZipCodeFind> GetZipCodes { get; private set; }
public AddressEditViewModel(AddressEdit editAddress, IEnumerable<ZipCodeFind> Zips)
{
this.GetOneAddressByDistrictGuid = editAddress;
this.GetZipCodes = Zips;
}
}
The Mapping I'm trying to use is...
CreateMap<Address, AddressEditViewModel>();
When I run this test...
public void Should_map_dtos()
{
AutoMapperConfiguration.Configure();
Mapper.AssertConfigurationIsValid();
}
I get this error...
AutoMapper.AutoMapperConfigurationException: The following 2 properties on JCIMS_MVC2.DomainModel.ViewModels.AddressEditViewModel
are not mapped:
GetOneAddressByDistrictGuid
GetZipCodes
Add a custom mapping expression, ignore, or rename the property on JCIMS_MVC2.DomainModel.Address.
I'm not sure how I am supposed to map those 2 properties. I would appreciate any direction. Thanks
Mark

Ok so I can see a few things you are doing that probably won't help.
Firstly this AutoMapper is used to copy Properties in one object to Properties in a diff object. Along the way it might interrogate or manipulate them to get the end result viewmodel in the correct state.
The properties are named 'Get...' which sounds more like a method to me.
The setters on your properties are private so AutoSetter won't be able to find them. Change these to minimum internal.
Use of a parametrized constructor is no longer needed when you use AutoMapper - as you are converting directly from one object to another. The parametised constructor is there mainly to show what is explicitly required by this object.
CreateMap<Address, AddressEditViewModel>()
.ForMember( x => x.GetOneAddressByDistrictGuid ,
o => o.MapFrom( m => m."GetOneAddressByDistrictGuid") )
.ForMember( x => x.GetZipCodes,
o => o.MapFrom( m => m."GetZipCodes" ) );
What Automapper is really good for is copying from DataObjects into POCO objects, or View Model objects.
public class AddressViewModel
{
public string FullAddress{get;set;}
}
public class Address
{
public string Street{get;set;}
public string Suburb{get;set;}
public string City{get;set;}
}
CreateMap<Address, AddressViewModel>()
.ForMember( x => x.FullAddress,
o => o.MapFrom( m => String.Format("{0},{1},{2}"), m.Street, m.Suburb, m.City ) );
Address address = new Address(){
Street = "My Street";
Suburb= "My Suburb";
City= "My City";
};
AddressViewModel addressViewModel = Mapper.Map(address, Address, AddressViewModel);

Related

Automapper - Mapping the index into a property of collection

I'm mapping a domain model to a DTO and vice versa. I'm trying to configure my API to accept a DTO with a collection, where the order of that collection will map to a int Sequence in my domain object for persistence.
public class Model {
public ICollection<Fields> Fields { get; set; }
}
public class Field {
public int Sequence { get; set; }
}
CreateMap<ModelView, Model>()
.ForMember(x => x.Fields, opt => opt...)
// here I want to specify that currentField.Sequence = Model.Fields.IndexOf(currentField)
// , or to set it equal to some counter++;
;
Is such a thing possible in Automapper, or would I have to write my own ConstructUsing() method to do this logic? I'm hesitant to use ConstructUsing() because I have a mapping specified for the Field DTO and I don't want to duplicate that logic.
I also would like to be able to configure it so that when I'm going back to my DTO (Model -> ModelView) that I can insert the Fields into the collection in the order specified by Sequence.
I think I found the solution I was looking for. Using AfterMap() I'm able to override these values from being mapped directly:
CreateMap<Model, ModelView>()
.AfterMap((m, v) =>
{
v.Fields = v.Fields?.OrderBy(x => x.Sequence).ToList();
//ensure that the DTO has the fields in the correct order
})
;
CreateMap<ModelView, Model>()
.AfterMap((v, m) =>
{
//override the sequence values based on the order they were provided in the DTO
var counter = 0;
foreach (var field in m.Fields)
{
field.Sequence = counter++;
}
})

Convert list of id to Object using AutoMapper

I have objects :
class Group {
public string GroupName ;
public List<Access> Details ;
}
Class Access {
public int Id;
public string Name;
}
Now I have a Dto like:
Class GroupDto
{
string GroupName;
List<int> Details ;
}
User can create/send GroupDto and converted to Group before saving.
how to define the MappingProfile for this ?
In your mapping profile just use MapFrom method and let AutoMapper know how to get the data like below:
CreateMap<Group, GroupDto>()
.ForMember(
destination => destination.Details,
options => options.MapFrom(
source => source.Details.Select(detail => detail.Id).ToList()
)
);
Side Note: please expose a property instead of field like you did in your sample. Also make sure that properties on GroupDto class are public too.
Like this:
Mapper.CreateMap<Group, GroupDto>()
.ForMember(d => d.Details,
opt =>
opt.MapFrom(
s => s.Details.Select(x=>x.Id).ToList()))

AutoMapper.Mapper.CreateMap<TSource,TDestination>()' is obsolete

I have to classes Like
class A
{
public int id {get; set;}
}
class B
{
public C c {get; set;}
}
class C
{
public int id {get; set;}
public string Name {get; set;}
}
My requirement is to map id of class A to id of class C.
Now what I was doing till now was:
Mapper.CreateMap().ForMember(des => des.C.Id, src => src.MapFrom(x => x.id));
and it was working fine.
Now seems like Auto mapper has changed their implementation. and I am getting warning as below:
AutoMapper.Mapper.CreateMap()' is obsolete: 'Dynamically creating maps will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed, or Mapper.Initialize. Use CreateMapper to create a mapper instance.
I need to map some properties of classes which has different name and structure. Any help on this.
Previously
Mapper.CreateMap<Src, Dest>()
.ForMember(d => d.UserName, opt => opt.MapFrom(/* ????? */));
The problem here is mapping definitions are static, defined once and reused throughout the lifetime of the application. Before 3.3, you would need to re-define the mapping on every request, with the hard-coded value. And since the mapping configuration is created in a separate location than our mapping execution, we need some way to introduce a runtime parameter in our configuration, then supply it during execution.
This is accomplished in two parts: the mapping definition where we create a runtime parameter, then at execution time when we supply it. To create the mapping definition with a runtime parameter, we “fake” a closure that includes a named local variable:
Mapper.Initialize(cfg => {
string userName = null;
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.UserName,
opt => opt.MapFrom(src => userName)
);
});
For more information see this
For one or more classes
cfg.CreateMissingTypeMaps = true;
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.UserName,
opt => opt.MapFrom(src => userName)
);
cfg.CreateMap<AbcEditViewModel, Abc>();
cfg.CreateMap<Abc, AbcEditViewModel>();
});
In mapping class
IMapper mapper = config.CreateMapper();
var source = new AbcEditViewModel();
var dest = mapper.Map<AbcEditViewModel, Abct>(source);
Another way that seems a bit cleaner is to make a MappingProfile class which inherits from the Profile class of AutoMapper
public class MappingProfile:Profile
{
public MappingProfile()
{
CreateMap<Source1, Destination1>();
CreateMap<Source2, Destination2>();
...
}
}
Then you initialize the mapping with Mapper.Initialize(c => c.AddProfile<MappingProfile>()); in your startup code
That will allow you to use the mapping anywhere by calling
destination1Collection = source1Collection.Select(Mapper.Map<Source1, Destination1>);
Finally I found the resolution. I was doing: Mapper.Initialize{ Mapping field from source to destination }
in the App_start and adding this file to the global.asax--> Application_Start() --> GlobalConfiguration.
I need to add one more line inside my Mapper.Initialize which is cfg.CreateMissingTypeMaps = true;
Now this code will work for explicit mapping where two classes don't have the same structure and names of properties.
Apart from this, if we need to map properties of two class with the same structure the code Mapper.map(source, destination) will also work, which was not working earlier.
Let me know if someone is having difficulty with the solution. Thanks all for the above reply.

Automapper (C#): Nested mappings not working

I have a simple mapping and it is working but it's not filling in Output.Details.
I am a bit confused, I think it maybe because I am using the source as "Task" for each one.
Mapper.CreateMap<Task, Output>();
Mapper.CreateMap<Task, Output.Details>().ForMember(
dest => dest.Item, opt => opt.MapFrom(src => src.Name));
As far as i know i have to create 2 maps, 1 for the object and 1 for object contained within.
Problem is the source for the OUTPUT and OUTPUT.DETAILS can be found in TASK
I tried delving into Details within the first map and specifying Mapfrom but it gives the following error which is why i must create 2 maps
must resolve to top-level member. Parameter name: lambdaExpression error
IList<Task> tempItems= GetItems();
IList<Output> items =
Mapper.Map<IList<Task>, IList<Output>>(tempItems);
The map works but my property "Item" availble in Output.Details is NULL
What am I doing wrong? Here is my Destination object.
It fills in Name no problem, but nothing inside DETAILS... they are left NULL.
Task is not my class, but I checked it and all values are there to be copied hence Tag has a value and is a STRING.
public class Output
{
public string Name { get; set; }
public Details Summary { get; private set; }
public class Details
{
public string Item{ get; set; }
}
public Output()
{
Summary = new Details();
}
}
EDIT
Here is an example of the Task class.
EDIT
They is a sample vs 2010 project here and it shows exactly the problem.
http://dl.dropbox.com/u/20103903/AutomapperNotWorking.zip
and here is an image showing the issue, as you can see Summary Item is "NULL" but it should contain the NAME from Task.
First off, always use Mapper.AssertConfigurationIsValid(); to make sure your mapping configuration is valid. I added it to your code and it immediately highlighted the problem: You didn't tell Automapper what to do with the Summary property. Since Task doesn't contain a property called Summary, Automapper needs to know what to do with it.
So the problem isn't really how to map a nested class, you just need to tell Automapper what to do with Summary. Here's the Automapper configuration that works for your example:
Mapper.CreateMap<Task, Output>()
.ForMember(d => d.Summary, o => o.MapFrom(t => new Output.Details {Item = t.Name}));
Mapper.AssertConfigurationIsValid();
That's all you need.
for the new version, it can be performed as follow:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<UdtDevQuestion, QuestionViewModel>();
});
config.AssertConfigurationIsValid();

AutoMapper - How to pass parameters into a Custom Resolver using ConstructedBy method?

In my ASP.NET MVC 2 (RC) project - I'm using AutoMapper to map between a Linq to Sql class (Media) and a view model (MediaVM). The view model has a SelectList property for a drop down in the view. I have a custom value resolver to populate the SelectList property items from the db, but am wondering if there's a way to pass a couple values from the source model into the resolver (using ConstructedBy method?) to a) define the selected item and b) filter the items from the db. The source object gets passed into the custom resolver - but the resolver is used on several different view models with different types of source objects, so would rather define where to get the values from in my mapping config. Here is my view model:
public class MediaVM
{
public bool Active { get; set; }
public string Name { get; set; }
[UIHint("DropDownList")]
[DisplayName("Users")]
public SelectList slUsers { get; private set; }
}
The automapper mapping config:
Mapper.CreateMap<Media, MediaVM>()
.ForMember(dest => dest.slUsers, opt => opt.ResolveUsing<UsersSelectListResolver>());
It would be nice to be able to do something like this on the .ForMember mapping clause:
.ConstructedBy(src => new UsersSelectListResolver(src.UserID, src.FilterVal))
Is there a way to accomplish this?
I like that idea as a feature request. You can do something like that right now, with MapFrom:
ForMember(dest => dest.slUsers, opt => opt.MapFrom(src => new UsersSelectListResolver(src).Resolve(src));
I found your posting trying to do the same thing. I decided on a simple approach and skip trying to map to my select list directly via AutoMaper. I simply return an array into my ViewModel and reference that object for my select list. The array gets mapped, select list object does not. Simple, effective. And, IMHO each is doing it's intended task - the mapper maps, the ViewModel does the layout
View Model code:
[DisplayName("Criterion Type")]
public virtual CriterionType[] CriterionTypes { get; set; }
[DisplayName("Criterion Type")]
public SelectList CriterionTypeList
{
get
{
return new SelectList(CriterionTypes, "Id", "Key");
}
}
my mapper:
Mapper.CreateMap<Criterion, CriterionForm>()
.ForMember(dest => dest.CriterionTypeList, opt => opt.Ignore());

Categories