0

I have the following Entity-Models

public class Blog 
{
    public int Id { get; set;}
    public string Title { get; set; }
    public string Body { get; set; }

    [ForeignKey("Category")]
    public int? CategoryId { get; set; }
    public virtual Category Category { get; set; }
    public virtual ICollection<Comment> Comments { get; set; }
}

public class Category
{
    public int Id { get; set;}

    public string Name { get; set; }
}

public class Comment
{
    public int Id { get; set;}

    public string Title { get; set; }
    public string Body { get; set; }
    [ForeignKey("Blog")]
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; }
}

Then I have the following view-model in which I like to tell AutoMapper to map the Blog object into the BlogViewModel notice the CategoryName property will need to come from Blog.Category.Name and each Comment in the Blog.Comments need to be converter to CommentViewModel using the organic convention.

I currently set the mapping at run time using reflection for any class that implements the ICustomMap interface. Please read the comment in the code over the Transfer(IMapper mapper) method.

public class BlogViewModel : ICustomMapFrom 
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public string MyCatName { get; set; }
    public IEnumerable<CommentViewModel> Comments { get; set; }

    // **IMPORTANT NOTE**
    // This method is called using reflection when the on Application_Start() method.
    // If IMapper is the wrong Interface to pass, I can change
    // the implementation of ICustomMap
    // I assumed that `IMapper` is what is needed to add configuration at runtime.
    public void Transfer(IConfigurationProvider config)
    {
        // How to I do the custom mapping for my MyCatName and Comments?
        // I need to use the config to create the complex mapping
        // AutoMapper.Mapper.Map(typeof(Blog), typeof(BlogViewModel));
    }
}

Finally here is my CommentViewModel

public class CommentViewModel : IMapFrom<Comment>
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
}

How can I tell AutoMapper how to map the CategoryName and the Comments?

Updated

Here is how I would create the mapping. I would have the following 3 interfaces

public interface IMap
{
}

public interface IMapFrom<T> : IMap
{
}

public interface ICustomMapFrom : IMap
{
    void Map(IConfigurationProvider config);
}

Then in the Global.cs file

I would execute the Run method on startup. Basically this method will scan assemblies and register the classes that I would want to register using the interfaces.

public class ConfigureAutoMapper 
{
    public void Run()
    {
        var types = AssemblyHelpers.GetInternalAssemblies()
                                   .SelectMany(x => x.GetTypes())
                                   .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && typeof(IMap).IsAssignableFrom(x))
                                   .ToList();


        RegisterStandardMappings(types);
        RegisterCustomMappings(types);
    }

    private static void RegisterStandardMappings(IEnumerable<Type> types)
    {
        foreach (Type type in types)
        {
            if(type.IsGenericType && typeof(IMapFrom<>).IsAssignableFrom(type))
            {
                AutoMapper.Mapper.Map(type.GetGenericArguments()[0], type);
            }
        }
    }

    private static void RegisterCustomMappings(IEnumerable<Type> types)
    {
        foreach (Type type in types)
        {
            if (typeof(ICustomMapFrom).IsAssignableFrom(type))
            {
                ICustomMapFrom map = (ICustomMapFrom)Activator.CreateInstance(type);
                var t = AutoMapper.Mapper.Configuration;

                map.Map(Mapper.Configuration);
            }
        }
    }
}
InteXX
  • 6,135
  • 6
  • 43
  • 80
Junior
  • 11,602
  • 27
  • 106
  • 212

1 Answers1

0

I wrote an NUnit test which sets up AutoMapper with your classes. AutoMapper supports the mapping of CategoryName out of the box.

[TestFixture]
public class TestClass
{
    [Test]
    public void Test1()
    {
        var config = new MapperConfiguration(cfg =>
        {
            cfg.CreateMap<Blog, BlogViewModel>();
        });

        config.AssertConfigurationIsValid();

        var blog = new Blog()
        {
            Body = "Blog body",
            Category = new Category { Name = "My Category" },
            Comments = new List<Comment>() {
                new Comment { Body = "Comment body 1" },
                new Comment { Body = "Comment body 2" }
            }
        };

        var mapper = config.CreateMapper();
        var result = mapper.Map<Blog, BlogViewModel>(blog);

        Assert.AreEqual(blog.Body, "Blog body");
        Assert.AreEqual(blog.Category.Name, result.CategoryName);
        List<CommentViewModel> comments = result.Comments.ToList();
        Assert.That(comments.Any(c => c.Body == "Comment body 1"));
        Assert.That(comments.Any(c => c.Body == "Comment body 2"));
    }
}
Marius
  • 1,529
  • 6
  • 21
  • Thank for for this. The answer does not show how to map the CategoryName also how can I use my Transfer method to create the mapping at runtime? – Junior Mar 02 '18 at 02:53
  • You only need to setup the MapperConfiguration as shown in the answer and then AutoMapper automatically maps `CategoryName` correctly as verified by the test. It is not clear to me what you intend to do in the `Transfer()` method. Do you mean by 'create the mapping at runtime' to setup the mapping configuration or to map an instance? In order to setup the mapping you need to use `MapperConfiguration`. If you want to map a `Blog` instance to a `BlogViewModel` instance then you need to use the `IMapper.Map` method where you pass in a `Blog` instance (not `typeof(Blog)`). – Marius Mar 02 '18 at 03:52
  • Can you please help me understand how to use the MapperConfiguration can be used to do complex mapping? Also, how does AutoMapper known to map Blog.Category.Name to BlogViewModel.CategoryName ? – Junior Mar 02 '18 at 04:03
  • AutoMapper applys [Flattening](http://automapper.readthedocs.io/en/latest/Flattening.html) to automatically convert complex object structures to simpler structures that's why it knows how to map `Blog.Category.Name` to `BlogViewModel.CategoryName`. – Marius Mar 02 '18 at 04:12
  • As for complex mappings: See the [documentation](http://automapper.readthedocs.io/en/latest/index.html). What kind of complex mapping would you like to do? – Marius Mar 02 '18 at 04:14
  • By complex I mean a mapping that does not for the standard convention. For example what if CategoryName property was called CatName instead. I would need a way to tell the mapper how to map it. Instead of configuring this relation in the Global.cs file, I want to be able to do that directly in the view move so it is easy to know the mapping directly within the ViewModel. So any ViewModel that Ned’s to define complex/non-standard mapping, I need to define the mapping in the Transfer method in the view model – Junior Mar 02 '18 at 04:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/166070/discussion-between-marius-and-mike-a). – Marius Mar 02 '18 at 04:25
  • Please check my updated question for how I want to map my types. – Junior Mar 02 '18 at 04:37