7

Given these two objects

public class UserModel
{
    public string Name {get;set;}
    public IList<RoleModel> Roles {get;set;}
}

public class UserViewModel 
{
    public string Name {get;set;}
    public IList<RoleViewModel> Roles {get;set;} // notice the ViewModel
}

Is this the most optimal way to do the mapping, or is AutoMapper capable of mapping Roles to Roles on its own?

App Config

Mapper.CreateMap<UserModel, UserViewModel>()
    .ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles));
Mapper.CreateMap<UserViewModel, UserModel>()
    .ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles));

Implementation

_userRepository.Create(Mapper.Map<UserModel>(someUserViewModelWithRolesAttached);
Chase Florell
  • 46,378
  • 57
  • 186
  • 376

4 Answers4

16

Is this the most optimal way to do the mapping, or is AutoMapper capable of mapping Roles to Roles on its own?

If the property names are identical, you should not have to manually provide a mapping:

Mapper.CreateMap<UserModel, UserViewModel>();
Mapper.CreateMap<UserViewModel, UserModel>();

Just make sure the inner types are mapped as well (RoleViewModelRoleModel)

What this means, however, is that if you change a source or destination property name, AutoMapper mappings can fail silently and cause hard to track down problems (e.g., if you changed UserModel.Roles to UserModel.RolesCollection without changing UserViewModels.Roles).

AutoMapper provides a Mapper.AssertConfigurationIsValid() method that will check all of your mappings for errors and catch misconfigured mappings. It's useful to have a unit test that runs with the build that validates your mappings for this kind of problem.

Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
15

You don't need to map the properties. Just make sure that the property names match and there is a mapping defined between them.

Mapper.CreateMap<UserModel, UserViewModel>();
Mapper.CreateMap<UserViewModel, UserModel>();
Mapper.CreateMap<RoleModel, RoleViewModel>();
Mapper.CreateMap<RoleViewModel, RoleModel>();

Or with the cooler way I just found out:

Mapper.CreateMap<UserModel, UserViewModel>().ReverseMap();
Mapper.CreateMap<RoleModel, RoleViewModel>().ReverseMap();
Ufuk Hacıoğulları
  • 37,978
  • 12
  • 114
  • 156
0

All the other answers, are much better (which I gave an upvote to each).

But what I wanted to post here is a quick playground that you could copy and past right into LinqPad in C# program mode and play your idea's without messing with your actual code.

Another awesome thing about moving all your conversions into a TyperConverter class is that your conversions are now Unit Testable. :)

Here you will notice that the model and viewmodel are almost identical except for one property. But through this process the right property is converted to the correct property in the destination object.

Copy this code into LinqPad and you can run it with the play button after switching to C# Program mode.

void Main()
{
    AutoMapper.Mapper.CreateMap<UserModel, UserViewModel>().ConvertUsing(new UserModelToUserViewModelConverter());
    AutoMapper.Mapper.AssertConfigurationIsValid();

    var userModel = new UserModel
    {
        DifferentPropertyName = "Batman",
        Name = "RockStar",
        Roles = new[] {new RoleModel(), new RoleModel() }
    };

    var userViewModel = AutoMapper.Mapper.Map<UserViewModel>(userModel);
    Console.WriteLine(userViewModel.ToString());
}

// Define other methods and classes here
public class UserModel
{
    public string Name {get;set;}
    public IEnumerable<RoleModel> Roles { get; set; }
    public string DifferentPropertyName { get; set; }
}

public class UserViewModel 
{
    public string Name {get;set;}
    public IEnumerable<RoleModel> Roles { get; set; } // notice the ViewModel
    public string Thingy { get; set; }

    public override string ToString()
    {
        var sb = new StringBuilder();
        sb.AppendLine(string.Format("Name: {0}", Name));
        sb.AppendLine(string.Format("Thingy: {0}", Thingy));
        sb.AppendLine(string.Format("Contains #{0} of roles", Roles.Count()));

        return sb.ToString();
    }
}

public class UserModelToUserViewModelConverter : TypeConverter<UserModel, UserViewModel>
{
    protected override UserViewModel ConvertCore(UserModel source)
    {
        if(source == null)
        {
            return null;
        }

        //You can add logic here to deal with nulls, empty strings, empty objects etc
        var userViewModel = new UserViewModel
        {
             Name = source.Name,
             Roles = source.Roles, 
             Thingy = source.DifferentPropertyName
        };
        return userViewModel;
    }
}

public class RoleModel
{
    //no content for ease, plus this has it's own mapper in real life
}

Result from the Console.WriteLine(userViewModel.ToString());:

Name: RockStar
Thingy: Batman
Contains #2 of roles
Cubicle.Jockey
  • 3,288
  • 1
  • 19
  • 31
0

Inside the Startup.cs in the Configure() method:

Mapper.Initialize(config => {
                    config.CreateMap<UserModel, UserViewModel>().ReverseMap();
                    // other maps you want to do.
                });
Fernando de Bem
  • 116
  • 1
  • 6