skip to Main Content

The Data Model

enter image description here

The Entities

public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string UrlSlug { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string UrlSlug { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string UrlSlug { get; set; }
    public string TnImage { get; set; }
    public string Author { get; set; }
    public List<Tag> Tags { get; set; }
    public Category Category { get; set; }
    public string DatePublished { get; set; }
    public string DateCreated { get; set; }
    public string DateModified { get; set; }
    public string Description { get; set; }
    public string ArticleBody { get; set; }
}

I’m using Entity-Framework against Sql Server Compact embedded DB, and have written a Generic Repository that does CRUD operations against the individual entities like Post, Category, Tag etc.

All that works fine.

I’m writing an ASP.Net Web API to expose the CRUD via a REST-ful API.

The REST API End Points

╔═══════╦═══════════════════════╦════════════════════════════════════╗
║ VERB  ║ End Point             ║  Description                       ║ 
╠═══════╬═══════════════════════╬════════════════════════════════════╣
║ GET   ║ /api/tags             ║ Returns all Tags                   ║
║ GET   ║ /api/tags/tips        ║ Returns single Tag (name=tips)     ║
║ POST  ║ /api/tags             ║ Creates new Tag                    ║
║ GET   ║ /api/categories       ║ Returns all Categories             ║
║ GET   ║ /api/categories/news  ║ Returns single Category (name=news)║
║ POST  ║ /api/categories       ║ Creates new Category               ║
║ GET   ║ /api/posts            ║ Returns all Post                   ║
║ GET   ║ /api/posts/51         ║ Returns single Post (Id=51)        ║
║ GET   ║ /api/posts/2/Tags     ║ Returns Tags for Post w/ Id=2      ║
║ GET   ║ /api/posts/2/Category ║ Returns Category for Post w/ Id=2  ║
║ POST  ║ /api/posts            ║ Creates new Post                   ║
╚═══════╩═══════════════════════╩════════════════════════════════════╝

The Situation

When I create a Post entity by doing a POST Request against the /api/posts endpoint and I have the Post entities data in the body as JSON, it could potentially have 1 or more tag instances, and 0 or 1 category.

Sample POST Body for a Blog Post

{
  "title": "How to get 6 Pack Abs in 6 days",
  "urlSlug": "6-pack-abs-6-days",
  "tnImage": "http://example.com/image.jpg",
  "author": "John Doe",
  "tags": [
    {
      "name": "6 pack abs tips"
    },
    {
      "name": "exercise tips"
    },
    {
      "name": "workout videos"
    }
  ],
  "category": {
    "name": "fitness"
  },
  "datePublished": "2017-04-01",
  "dateCreated": "2015-01-20",
  "dateModified": "2017-04-01",
  "description": "SEO keyword stuffed description for fake tips to get abs here",
  "articleBody": "full post body containing fake tips to get 6 packs. html"
}

Of these tags, some may exist, and some may not. The ones that don’t exist need to be created. And for the tags that need to be created, the UrlSlug will be generated using a helper method.

The category, if filled, should be inserted if it doesn’t exist.

The question

Given this scenario, how do I had my POST request against the /api/posts endpoint, and ensure that the related Tag and Category entities are added if they don’t exist?

If the sample post shown above is posted to this endpoint, how do I handle adding tags that don’t exist, category if it doesn’t exist, and then adding the new post?

Also, am I approaching this wrong in terms of REST design? If yes, what is the recommended approach to ensure that data isn’t inconsistent, i.e. some tags added then error occurs, so there are orphans now.

public class PostsController : ApiController
{

    [Route("api/posts")]
    [HttpPost]
    public IHttpActionResult Post([FromBody]PostDto post)
    {
        try
        {
            if (post == null)
            {
                return BadRequest();
            }

            // what goes in here, so that I add tags that don't exist, 
            // category if it doesn't exist
            // and then create the post. All ADD methods will call repo methods
            // 
            // Also is there some other way, i.e. am I approaching this wrong?
        }
        catch (Exception)
        {
            return InternalServerError();
        }
    }

2

Answers


  1. I’ve done similar things in the past and where I did them was in the database. When calling a generic proc like “SetTag” it would take the entityid and the tag and then do a check query first like:

    DECLARE @TagID INT
    SELECT @TagID = TagID FROM TagTable WHERE Tag = @TagPassedIn
    IF @TagID IS NULL 
    BEGIN
    INSERT INTO TagTable (Tag) VALUES (@TagPassedIn)
    SELECT @TagID = SCOPE_INDENTITY()
    END
    INSERT INTO AttTable (EntityID,TagID) VALUES (@EntityIDPassedIn,@TagID)
    
    Login or Signup to reply.
  2. A sample solution can be here:

    using (var context = new YourContext())
                {
                    //Find or Create Category
                    Category category = null;
                    if (context.Categories.Any(cat => cat.Name == post.category.Name))
                    {
                        category = context.Categories.FirstOrDefault(cat => cat.Name == post.category.Name);
                    }
                    else
                    {
                        category = new Category
                        {
                            Name = post.category.Name
                        };
                        context.Categories.Add(category);
                    }
    
                    //Find or Create Tags
                    var tags = new List<Tag>();
                    foreach (var tag in post.tags)
                    {
                        Tag targetedTag;
                        if (context.Tags.Any(t => t.Name == tag.Name))
                        {
                            targetedTag = context.Tags.FirstOrDefault(t => t.Name == tag.Name);
                        }
                        else
                        {
                            targetedTag = new Tag
                            {
                                Name = tag.Name,
                                // UrlSlug = use helper as you've said 
                            };
                            context.Tags.Add(targetedTag);
                        }
                        tags.Add(targetedTag);
                    }
    
                    var targetedPost = new Post
                    {
                        Category = category,
                        Tags = tags,
                        ArticleBody = post.articleBody,
                        Author = post.author,
                        DateCreated = post.dateCreated,
                        DateModified = post.dateModified,
                        DatePublished = post.datePublished,
                        Description = post.description,
                        Title = post.title,
                        TnImage = post.tnImage,
                        UrlSlug = post.urlSlug
                    };
    
                    context.Posts.Add(targetedPost);
    
                    context.SaveChanges();
    
                }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search