Btoast
Btoast

Reputation: 21

How to use Automapper to map an object to an interface at a different level?

I'm new to Automapper and am trying to map the data from an object in a list to an interface in a list. Add to that the lists are in different levels of the containing objects:

// class definitions
public class MyViewModel
{
    public int ViewModelId { get; set; }
    public IList<ViewItem> Page1Selections { get; set; }
    public IList<ViewItem> Page2Selections { get; set; }
}

public class ViewItem
{
    public int ItemId { get; set; }
    public bool IsSelected { get; set; }
}

public class MyDbModel
{
    public int DbModelId { get; set; }
    public IPageSelection Page1Selections { get; set; }
    public IPageSelection Page2Selections { get; set; }
}

public interface IPageSelection
{
    int PageNumber { get; set; }
    IList<IMyDbItem> PageSelections { get; set; }
}

public class PageSelection : IPageSelection
{
    public int PageNumber { get; set; }
    public IList<IMyDbItem> PageSelections { get; set; }
}

public interface IMyDbItem
{
    int ItemId { get; set; }
    bool IsSelected { get; set; }
}

public class MyDbItem : IMyDbItem
{
    public int ItemId { get; set; }
    public bool IsSelected { get; set; }
}


    // mapping code
    MyViewModel myVm = new MyViewModel();
    myVm.ViewModelId = 123;
    myVm.Page1Selections = new List<ViewItem>();
    myVm.Page1Selections.Add(new ViewItem() { ItemId = 1, IsSelected = true });
    myVm.Page2Selections = new List<ViewItem>();
    myVm.Page2Selections.Add(new ViewItem() { ItemId = 2, IsSelected = true });

    Mapper.Initialize(cfg =>
    {
        cfg.BindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
        cfg.CreateMap<MyViewModel, MyDbModel>();
        cfg.CreateMap<ViewItem, IMyDbItem>();
    });

    MyDbModel myDb = new MyDbModel();
    myDb = Mapper.Map<MyDbModel>(myVm); //<== exception here

Sorry for the long attachment. But I'm fighting both an object to interface and hierarchical mismatch mapping problem.

Upvotes: 2

Views: 2209

Answers (1)

Andrew Whitaker
Andrew Whitaker

Reputation: 126072

Here's one way you could accomplish this:

cfg.CreateMap<MyViewModel, MyDbModel>()
    .ForMember(dest => dest.DbModelId, opt => opt.MapFrom(src => src.ViewModelId));

cfg.CreateMap<ViewItem, IMyDbItem>()
    .ConstructUsing((ViewItem src) => new MyDbItem());

cfg.CreateMap<IList<ViewItem>, IPageSelection>()
    .ConstructUsing((IList<ViewItem> src) => new PageSelection())
    .ForMember(dest => dest.PageSelections, opt => opt.MapFrom(src => src))
    .ForMember(dest => dest.PageNumber, opt => opt.Ignore());

The error you were getting is because no mapping existed from IList<ViewItem> to IPageSelection. I've created that mapping above.

An important bit is the ConstructUsing calls. I added those here because I assumed you wanted to use the concrete types that implement the interfaces you're mapping to. Otherwise, AutoMapper will create a proxy class that implements the destination interface.

The part that enables mapping to the IPageSelection.PageSelections from IList<ViewItem> is the opt.MapFrom(src => src) part. This essentially tells AutoMapper to map from the list to the inner PageSelections property, ultimately using the mapping from ViewItem to IMyDbItem.

Example: https://dotnetfiddle.net/DW9dfO

Upvotes: 1

Related Questions