2

Let's say I am modeling blogging REST API which has resources Blog, Post and Comment. Then I add following URIs for Blog and Post resources:

/api/blogs
/api/blogs/{blogId}
/api/blogs/{blogId}/posts

and since deep nesting should be avoided I create separate endpoint for all Posts in order to get their Comment`s:

/api/posts
/api/posts/{postId}
/api/posts/{postId}/comments

Now, since Post resource can be accessed from two different URIs like this:

  • /api/posts?blogId=123
  • /api/blogs/123/posts

how should I implement this in ASP.NET Core API project without unnecessary code duplication?

Eg. should I implement both of these endpoints in the same action of the same controller, or should I separate this in two controllers (eg. handle /api/posts?blogId=123 in PostsController and /api/blogs/123/posts in BlogsController)? Also, should I implement POST, PUT and DELETE actions on both endpoints or just choose one as the primary URI?

After I understand how to do this, is it ok to generalize the same approach for other resources with the same kind of relationship (eg. Post and Comment)?

2

3 Answers 3

7

You should make separate controllers for each resource. What those two controllers may share is potentially a PostService or other mechanism for getting posts. Also keep in mind if you are using Entity Framework or some other ORM, the Posts may be exposed on the Blog object through a relationship. Therefore your action might be as simple as:

public async Task<IActionResult> GetPostsForBlog(int blogId)
{
    return Ok(_context.Blogs.Find(blogId).Posts);
}
Sign up to request clarification or add additional context in comments.

4 Comments

In which controller should I put this GetPostsForBlog(int blogId) method? Should it go to PostsController or BlogsController (since both of them can return array of posts)?
surely in post controller
You should also consider we've only talked about [HttpGet] requests to this point. Consider where the [HttpPost] or [HttpDelete] methods may go. For instance, it would be more likely to me that the [HttpPost] method would be on the posts controller because it would be a little strange (but not impossible) to operate on the Posts collections as part of a call to the Blogs controller like that.
@diesel.coder could you please provide minimal but complete example code for both controllers so that I can select your answer. Thanks!
1

I would choose one of both ways and stick with it.

For example, if you are going to pass parameters in the path:

BlogsController

  • /api/blogs
  • /api/blogs/{blogId}

PostsController

  • /api/posts
  • /api/posts/{postId}
  • /api/blogs/{blogId}/posts

CommentsController

  • /api/comments
  • /api/comments/{commentId}
  • /api/posts/{postId}/comments

1 Comment

The standard is for the CommentsController to have the subroute of /api/comments. You would have to overwrite the convention for this methodology. Not necessarily a bad thing, but I wanted to make a note that this deviates from the standard.
0

There is a difference between two approaches and it's about performance issues on database side.

When use /api/blogs/123/posts you need to include Posts in Blog object like this:

 _dbContext.Blogs
 .AsNoTracking()
 .Include(x => x.Posts)
 .Where(x => x.Id == 123)
 .FirstOrDefaultAsync();

And this will be translated to T-Sql and use Inner Join or Left Join (based on required relations) between blog table and post table some thing like this:

Select * from (Select top 1 * from dbo.Blogs where Id = 123) b 
Left Join dbo.Posts p on b.Id = p.BlogId 

But when call /api/posts?blogId=123 you can simply query on Post object like this:

_dbContext.Posts
.AsNoTracking()
.Where(x => x.BlogId == 123)
.ToListAsync();

And this will be translated to T-Sql like this:

Select * from dbo.Posts where BlogId=123

As you may be guessed the second query is faster than first.

If you want to follow Rest Api standard you should follow the first one but for get better performance use second query for get Posts items and no need implement two approaches, just pick one because with implement two approaches already you implement logic in two places and if logic changed you should change two place of your code.

2 Comments

Sidenote: FirstOrDefaultAsync() results in SELECT TOP(1) ...
@momo yes you right. But I just show what happens in database and I know that sql select top one from blog table and get related posts with join. Thanks for your point.

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.