1

I'm having a problem trying to use complex types with AutoMapper.

I have two objects, a domain and a ViewModel that utlize in my View;

ViewModel

public class RegisterCoupleModel
{
    [Required(ErrorMessage = "campo obrigatório")]
    [Display(Name = "Primeiro nome", Order = 0, GroupName = "Noivo")]
    [StringLength(60)]
    public string FistNameGroom
    {
        get;
        set;
    }

    [Required(ErrorMessage = "campo obrigatório")]
    [Display(Name = "Último nome", Order = 1, GroupName = "Noivo")]
    [StringLength(60)]
    public string LastNameGroom
    {
        get;
        set;
    }

    [Required(ErrorMessage = "campo obrigatório")]
    [RegularExpression(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$", ErrorMessage = "Formato de email inválido.")]
    [DataType(DataType.EmailAddress)]
    [Remote("IsEmailAvailable", "Validation", "", ErrorMessage = "este email já está sendo usado por outro usuário.")]
    [Display(Name = "Email", Order = 2, GroupName = "Noivo")]
    [StringLength(180)]
    public string EmailGroom
    {
        get;
        set;
    }

    [Required(ErrorMessage = "campo obrigatório")]
    [Display(Name = "Primeiro nome", Order = 0, GroupName = "Noiva")]
    [StringLength(60)]
    public string FistNameBride
    {
        get;
        set;
    }

    [Required(ErrorMessage = "campo obrigatório")]
    [Display(Name = "Último nome", Order = 1, GroupName = "Noiva")]
    [StringLength(60)]
    public string LastNameBride
    {
        get;
        set;
    }

    [Required(ErrorMessage = "campo obrigatório")]
    [RegularExpression(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$", ErrorMessage = "Formato de email inválido.")]
    [DataType(DataType.EmailAddress)]
    [Remote("IsEmailAvailable", "Validation", "", ErrorMessage = "este email já está sendo usado por outro usuário.")]
    [Display(Name = "Email", Order = 2, GroupName = "Noiva")]
    [StringLength(180)]
    public string EmailBride
    {
        get;
        set;
    }

    [Required(ErrorMessage = "campo obrigatório")]
    [Display(Name = "Url")]
    [RegularExpression(@"^[a-zA-Z0-9_\-\.\+]{5,22}$", ErrorMessage = "Chave inválida")]
    [StringLength(22, MinimumLength = 5, ErrorMessage = "A chave deve ter entre {0} e {1} caracteres.")]
    [Remote("IsKeywordAvaliable", "Validation")]
    public string UrlKeyword
    {
        get;
        set;
    }

    [Display(Name = "Voce é humano?")]
    [UIHint("ReCaptcha")]
    public string ReCaptcha
    {
        get;
        set;
    }
}

Domain Object

public class Couple
{
    [Key]
    public Guid Id { get; set; }
    public string UrlKeyword { get; set; }
    public virtual Partner Groom { get; set; }
    public virtual Partner Bride { get; set; }
    public DateTime Marriage { get; set; }
    public DateTime Dating { get; set; }
    public DateTime Engagement { get; set; }
    public virtual ICollection<User> Users { get; set; }
}

If you look you will see that my object Couple domain has the properties has Bride and Groom which are actually the same type.

How can I map my domain object Couple with the RegisterCoupleModel?

Here I made it as far as:

In settings automapper tried to do something like:

CreateMap<RegisterCoupleModel, Couple>()
    .ForMember(dest => dest.Bride.User.FirstName, opt => opt.MapFrom(source => source.FistNameBride))
    .ForMember(dest => dest.Bride.User.LastName, opt => opt.MapFrom(source => source.LastNameBride))
    .ForMember(dest => dest.Bride.User.Email, opt => opt.MapFrom(source => source.EmailBride))
    .ForMember(dest => dest.Groom.User.FirstName, opt => opt.MapFrom(source => source.FistNameGroom))
    .ForMember(dest => dest.Groom.User.LastName, opt => opt.MapFrom(source => source.LastNameGroom))
    .ForMember(dest => dest.Groom.User.Email, opt => opt.MapFrom(source => source.EmailGroom));

But the error below is displayed:

Expression 'dest => dest.Bride.User.FirstName' must resolve to top-level member. Parameter name: lambdaExpression

I know this takes the act of trying to use to map properties with nested types.

But how can I map RegisterCoupleModel to Couple and the two properties Bride and Groom are the same type?

I found a question here on StackOverflow that looks like this, but it helped me.

Community
  • 1
  • 1
ridermansb
  • 10,779
  • 24
  • 115
  • 226

1 Answers1

2

What i would do is encapsulate the properties for FirstNameBride, LastNameBride, EmailBride, FirstNameGroom, LastNameGroom, EmailGroom into a nested viewmodel type, let's say PersonDetails:

public class PersonDetails
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Email { get; set; }
}

And update your parent VM accordingly:

public class RegisterCoupleModel
{
   public PersonDetails GroomDetails { get; set; }
   public PersonDetails BrideDetails { get; set; }
}

Then you can provide the mapping from PersonDetails to User (or whatever type Groom.User is):

Mapper.CreateMap<PersonDetails,User>();

Note how i've got so explict property mapping -> because the fields have the same name in the source and destination, so no explicit mapping is required. Always try and do this where possible. Less code -> better code.

Then in addition to the above mapping, simply do this:

Mapper.CreateMap<RegisterCoupleModel, Couple>();

And AutoMapper will see that RegisterCoupleModel has two PersonDetails objects, see it already has a mapping definition, then automatically use that.

That should work (i've done it before).

You shouldn't always use flat viewmodels, nest them where necessary (such as reusing fields).

RPM1984
  • 72,246
  • 58
  • 225
  • 350
  • But how would the attributes? If encapsulate these fields, the settings of the attributes are the same for the bride and groom. So for example I can not change the display text or GrupName for Display attribute. – ridermansb Sep 30 '11 at 00:25
  • 1
    @Riderman - use some OO tricks, for example make `PersonDetails` abstract, then create derived classes for `BrideDetails` and `GroomDetails`. Set the common attributes in the base, then override the different ones. – RPM1984 Sep 30 '11 at 00:32
  • I use the helper: `@Html.EditorForModel()` to generate the form, but it does not generate nested types. How can I encapsulate these three properties and make the helper continues to display them in the form? – ridermansb Sep 30 '11 at 00:43
  • @Riderman - create a custom editor template for `PersonDetails` – RPM1984 Sep 30 '11 at 00:45