1

I've created an API with ASPNET Core 2 that has a BlogPost service. The service handles creating blog posts and tags. The "Tags" are linked to the BlogPosts (as their children, by blogpostid), but i can't find a way to display those tags when i'm retrieving blogposts.

This is what i'm getting at the moment.

{
    "id": 3,
    "title": "Im Just Testing",
    "body": "Esta Bien senor",
    "type": "News",
    "dateCreated": "9/20/2018 12:00:00 AM",
    "author": "Dante",
    "tags": []
}

This is my controller ->

[HttpGet("posts/{id}")]
public IActionResult GetBlogPostById(int id, bool includePostTags = true)
{
    var blogPost = _blogPostService.GetBlogPostById(id, includePostTags);

    if(blogPost == null)
    {
        return NotFound();
    }

    var blogPostDto = _mapper.Map<BlogPostDto>(blogPost);
    return Ok(blogPostDto);
}

This is my BlogPostDTO

public class BlogPostDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public string Type { get; set; }
    public string DateCreated { get; set; }
    public string Author { get; set; }

    public ICollection<BlogPostTagDto> Tags { get; set; }
        = new List<BlogPostTagDto>();
}

And finally this is my BlogPostTag Entity ->

public class BlogPostTag
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    [MaxLength(50)]
    public string TagName { get; set; }

    [MaxLength(200)]
    public string Description { get; set; }

    [ForeignKey("BlogPostId")]
    public BlogPost BlogPost { get; set; }
    public int BlogPostId { get; set; }
}

And my BlogPostTagDto

public class BlogPostTagDto
{
    public int Id { get; set; }
    [Required(ErrorMessage = "You should provide a Tag Name value.")]
    [MaxLength(50)]
    public string TagName { get; set; }

    [MaxLength(200)]
    public string Description { get; set; }
}

TLDR: The BlogPostTags are created (i checked the DB), but i can't retrieve them in the "Get All Posts" call.

BlogPostService ->

public BlogPost GetBlogPostById(int id, bool includePostTags)
{
    if(includePostTags)
    {
        return _context.BlogPosts.Include(c => c.BlogPostTags).Where(c => c.Id == id).FirstOrDefault();
    }
    return _context.BlogPosts.Where(c => c.Id == id).FirstOrDefault();
}

And DataContext (perhaps it helps) ->

public class DataContext : DbContext
{
    public DataContext(DbContextOptions<DataContext> options) : base(options)
    {
        Database.Migrate();
    }

    public DbSet<User> Users { get; set; }
    public DbSet<BlogPost> BlogPosts { get; set; }
    public DbSet<BlogPostTag> BlogPostTags { get; set; }
}

BloGPost ENTITY

public class BlogPost
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    [MaxLength(100)]
    public string Title { get; set; }

    [Required]
    [MaxLength(500)]
    public string Body { get; set; }
    public string Type { get; set; }
    public string DateCreated { get; set; }
    public string Author { get; set; }

    public ICollection<BlogPostTag> BlogPostTags { get; set; }
        = new List<BlogPostTag>();
}

Added Mapper profile ->

    public AutoMapperProfile()
    {
        CreateMap<User, UserDto>();
        CreateMap<UserDto, User>();
        CreateMap<BlogPost, BlogPostDto>();
        CreateMap<BlogPostDto, BlogPost>();
        CreateMap<BlogPostTag, BlogPostTagForCreationDto>();
        CreateMap<BlogPostTagForCreationDto, BlogPostTag>();
       // CreateMap<BlogPostTagForCreationDto, BlogPostTag>();
    }

SOLVED:

Apparently in my BlogPostDto i had to retrieve "BlogPostTagForCreationDto" instead of the normal Dto.

    public ICollection<BlogPostTagForCreationDto> BlogPostTags { get; set; }
        = new List<BlogPostTagForCreationDto>();

This PLUS changing the name from "Tags" to "BlogPostTags" seems to have fixed my issue. I've got to research more on why this happened.

10
  • 1
    check out this link for swagger if you don't already have it installed. it will help to show you how the api is expecting the data to be constructed. Commented Sep 20, 2018 at 11:58
  • could you try not to use DTO first? return direct blogPost Commented Sep 20, 2018 at 11:59
  • 1
    I blame var blogPostDto = _mapper.Map<BlogPostDto>(blogPost);. Most likely you didn't set up AutoMapper correctly Commented Sep 20, 2018 at 12:01
  • Could you provide us the BlogPost entity? If you've not defined any specific mapping profile for your entity, it will just make them match by name. If the collection is different in both, you won't get the data you expect. Commented Sep 20, 2018 at 12:01
  • 1
    Yeah, so that's your problem. You need to take control of the mapping as you have different names. There's tonnes of information on how to do that here on SO and in the docs. It's ForMember and MapFrom you're most interested in here (covered in the docs I linked). If you struggle with this and would like a specific answer, I'll be happy to post one. Commented Sep 20, 2018 at 12:11

3 Answers 3

1

After confirming in the comments that the problem is in the difference between the names BlogPostTags and Tags, it's evident that AutoMapper needs to be instructed to map from BlogPostTags to Tags when mapping from a BlogPost to a BlogPostDto. This can be configured inside of your AutoMapperProfile, like this:

public AutoMapperProfile()
{
    // ...

    CreateMap<BlogPost, BlogPostDto>()
        .ForMember(x => x.Tags, opt => opt.MapFrom(x => x.BlogPostTags));
}

All we're doing here is creating an explicit map from BlogPost to BlogPostDto, which allows us to take control of specific properties as they get mapped. In this case, we're simply stating that we want to MapFrom BlogPostTags to Tags, accordingly.

Sign up to request clarification or add additional context in comments.

2 Comments

Yep that seems to have fixed my BlogPostTags naming problem. I've updated mapper and tried to remove the other mappings, but that throws errors for me. For example if i remove the map for "BlogPost, BlogPostDto" and "BlogPostDto, BlogPost" i can't POST to create a new blogpost, it throws an error without info "AutoMapper.AutoMapperConfigurationException". Since this problem wasn't the point of my question i'll accept this one as an answer, many thanks ^^.
That’s for letting me know. I’ve removed the suggestion about the inline mapping - easier to just do that than try and figure out why it’s not working in your case. As you say, the original problem is solved.
0

It looks to me that you are missing a mapping:

CreateMap<BlogPostTag, BlogPostTagDto>();

1 Comment

I was only mapping "forcreationdto", but adding another map does nothing unfortunately.
0

Return BlogPostDto instead of BlogPost from your GetBlogPostById method in BlogPostService as follows:

public BlogPostDto GetBlogPostById(int id, bool includePostTags)
{
    if(includePostTags)
    {
        return _context.BlogPosts.Where(c => c.Id == id).Select(bp =>
                new BlogPostDto
                {
                    Id = bp.Id,
                    Title = bp.Title,
                    .......
                    Tags = bp.BlogPostTags.Select(t => new BlogPostTagDto {
                             Id = t.Id,
                             ....
                           }).ToList()
                }).FirstOrDefault();
    }

    return _context.BlogPosts.Where(c => c.Id == id).Select(bp =>
                new BlogPostDto
                {
                    Id = bp.Id,
                    Title = bp.Title,
                    .......
                }).FirstOrDefault();
}

Then in the controller:

[HttpGet("posts/{id}")]
public IActionResult GetBlogPostById(int id, bool includePostTags = true)
{
    var blogPost = _blogPostService.GetBlogPostById(id, includePostTags);

    if(blogPost == null)
    {
        return NotFound();
    }

    return Ok(blogPostDto);
}

3 Comments

While this might work (haven't tested it yet), its not really what i'm looking for. It kind of defeats the purpose of using Mapper if i'm going to map them myself. I fixed the problem tho', just need to find out what it really means.
Well based on how i fixed it i'm wondering why i had to use the Dto for "Creation" on BlogPostTags instead of the normal Dto that's like the DB and also why name matters so much. If i just changed the name from "Tags" to "BlogPostTags" and used "BlogPostTagDto" instead of "BlogPostTagForCreationDto" i'd just get a 500 internal error.
Well if you have a better way than my newbie fix then please do. While i got it working i might not have the best practices since i'm still learning.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.