0

I have one base class and 3 sub classes and single viemodel with all properties. I want in my controller create action to bind this viewmodel to concrete sub type.

Here is my create action which doesn't work (I'm getting Error mapping types):

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(AdViewModel vm)
{
    if (ModelState.IsValid)
    {
         var ad = Mapper.Map<Ad>(vm);
        _context.Ads.Add(ad);
        _context.SaveChanges();
         return RedirectToAction("Index");
    }
    return View();
}

This is automapper configuration:

Mapper.Initialize(config =>
        {
            config.CreateMap<AdViewModel, Ad>().ReverseMap();
            config.CreateMap<AdViewModel, Realty>().ReverseMap();
            config.CreateMap<AdViewModel, Auto>().ReverseMap();
            config.CreateMap<AdViewModel, Service>().ReverseMap();
        });

And this is working code, but I doubt to use it:

public IActionResult Create(AdViewModel vm)
{
    if (ModelState.IsValid)
    {
        if (vm.RealtyType != null)
        {
            var ad = Mapper.Map<Realty>(vm);
            _context.Add(ad);
        }
        else if (vm.AutoType != null)
        {
            var ad = Mapper.Map<Auto>(vm);
            _context.Add(ad);
        }
        else
        {
            var ad = Mapper.Map<Service>(vm);
            _context.Add(ad);
        }
        _context.SaveChanges();
        return RedirectToAction("Index");
    }
    return View();
}
ekad
  • 14,436
  • 26
  • 44
  • 46

1 Answers1

1

You have to put the logic to create the correct subclass somewhere. If you want to hide it in the Automapper config, you can achieve this by defining a constructor function. This will only work if the target entities are related (I will assume that Ad is the baseclass of Realty, Auto and Service).

// construct correct subtype of entity, depending on ViewModel state
var ctorFunc = new Func<AdViewModel, Ad>(vm => {
    if (vm.RealtyType != null) {
        return new Realty();
    }
    // etc.
});

// create correct subclass, map common properties of base class, 
// then dispatch to map properties of child class
CreateMap<AdViewModel, Ad>()
    // construct correct subclass of target entity
    .ConstructUsing(ctorFunc)
    // map common members to base class
    .ForMember(ent => ent.CommonField, o => o.MapForm(vm => vm.CommonField)
    // dispatch to mapping of child class
    // NOTE: this assumes that this profile was used to configure the static AutoMapper
    .AfterMap((vm, ent) => Mapper.Map(vm, ent, vm.GetType(), ent.GetType()));

// define rules for special members of child classes
CreateMap<AdViewModel, Realty>().ReverseMap();
// etc.

Then you should be able to call var ad = Mapper.Map<Ad>(vm); successfully.

Georg Patscheider
  • 9,357
  • 1
  • 26
  • 36
  • Hi Georg, could you please help me with defining constructor? Where is should be stored, in my viewmodel or create it in separate file? Thanks. – Азамат Исмагулов Aug 23 '16 at 15:35
  • I put the constructor function into the Automapper Profile class that also contains the CreateMap definitions that use it. `public class MyProfile : Profile { protected override void Configure() { /* ctor func */ */ CreateMap */ } }`. – Georg Patscheider Aug 24 '16 at 07:25