bevacqua
bevacqua

Reputation: 48516

AutoMapper Map to different type based on an enum?

I'm starting to implement AutoMapper, first I managed to integrate it with Castle.Windsor, which I'm already using. Now I have a Post entity which I want to map to either a LinkPostModel or an ImagePostModel. Both inherit from PostModel

1) This is what I have so far:

public class PostModelFromPostEntityConverter : ITypeConverter<Post, PostModel>
{
    private readonly IPostService postService;

    public PostModelFromPostEntityConverter(IPostService postService)
    {
        if (postService == null)
        {
            throw new ArgumentNullException("postService");
        }
        this.postService = postService;
    }

    public PostModel Convert(ResolutionContext context)
    {
        Post post = (Post)context.SourceValue;
        Link link = post.Link;
        if (link.Type == LinkType.Html)
        {
            return new LinkPostModel
            {
                Description = link.Description,
                PictureUrl = link.Picture,
                PostId = post.Id,
                PostSlug = postService.GetTitleSlug(post),
                Timestamp = post.Created,
                Title = link.Title,
                UserMessage = post.UserMessage,
                UserDisplayName = post.User.DisplayName
            };
        }
        else if (link.Type == LinkType.Image)
        {
            return new ImagePostModel
            {
                PictureUrl = link.Picture,
                PostId = post.Id,
                PostSlug = postService.GetTitleSlug(post),
                Timestamp = post.Created,
                UserMessage = post.UserMessage,
                UserDisplayName = post.User.DisplayName
            };
        }
        return null;
    }
}

Obviously the point in implementing AutoMapper is removing repeat code like this, so how am I supposed to map the common stuff, before adding my custom rules (such as the if-clause)

Ideally I'd want this to be something like:

public class PostModelFromPostEntityConverter : ITypeConverter<Post, PostModel>
{
    [...]

    public PostModel Convert(ResolutionContext context)
    {
        Post post = (Post)context.SourceValue;
        Link link = post.Link;
        if (link.Type == LinkType.Html)
        {
            return Mapper.Map<Post, LinkPostModel>(post);
            // and a few ForMember calls?
        }
        else if (link.Type == LinkType.Image)
        {
            return Mapper.Map<Post, ImagePostModel>(post);
            // and a few ForMember calls?
        }
        return null;
    }
}

2) After this mapping is complete. I have a "parent" mapping, where I need to map an IEnumerable<Post> the following model:

public class PostListModel : IHasOpenGraphMetadata
{
    public OpenGraphModel OpenGraph { get; set; } // og:model just describes the latest post
    public IList<PostModel> Posts { get; set; }
}

So basically I'd need another TypeConverter (right?), which allows me to map the posts list first, and then create the og:model

I have this, but it feels kind of clunky, I feel it could be better:

public class PostListModelFromPostEntityEnumerableConverter : ITypeConverter<IEnumerable<Post>, PostListModel>
{
    public PostListModel Convert(ResolutionContext context)
    {
        IEnumerable<Post> posts = (IEnumerable<Post>)context.SourceValue;
        PostListModel result = new PostListModel
        {
            Posts = posts.Select(Mapper.Map<Post, PostModel>).ToList()
        };
        Post first = posts.FirstOrDefault();
        result.OpenGraph = Mapper.Map<Post, OpenGraphModel>(first);
        return result;
    }
}

3) I didn't actually run the code yet, so another question comes to mind, and that is why aren't mappings strongly typed in converters?

IEnumerable<Post> posts = (IEnumerable<Post>)context.SourceValue;

where it could actually be

IEnumerable<Post> posts = context.SourceValue;

Upvotes: 6

Views: 1570

Answers (2)

Moniker
Moniker

Reputation: 185

For me, this other SO post worked.

It uses Automapper's ConstructUsing to return context.Mapper.Map statements based on the predicate. Leaving this here in-case this is the first search result like it was for me

Upvotes: 0

ASpirin
ASpirin

Reputation: 3651

Trying to get Necromancer badge.
Nowadays this task can be solved much easier with using ConstructUsing function specifc fields should be filled in the provided action, but all the common fields will go to ForMember execution of the mapping. Collections in this case doesn't requires any additional logic/mapping configurations. Classes that has a property of type collection as well.

cfg.CreateMap<Post, PostModel>()
    .ConstructUsing(p =>
    {
        switch (p.Type)
        {
            case LinkType.Html: return new LinkPostModel
            {
                Title = p.Description
                // other specific fields
            };
            case LinkType.Image: return new ImagePostModel
            {
                // other specific fields
            };
        }
        return null;
    })
    .ForMember(x => x.PostId, m => m.MapFrom(p => p.Id));
cfg.CreateMap<PostList, PostListModel>();

Upvotes: 1

Related Questions