all posts

How to use Kigg as a service from other sites

Published to Blog on 14 Jul 2008

AspDotNetMVCGetsKigg

In yesterday’s post I started providing some detail about my use of Kigg to add rating functionality to the AspDotNetMVC site. Because I wanted to integrate the ASP.NET Membership store between the two sites into an external database I started by decoupling the membership data from the Kigg database, model and code.

My end goal was to allow visitors to the AspDotNetMVC site to view the number of existing votes for each article (blog post, buzz, news, or video) and to vote for an article if they were logged in without having to jump over to the Kigg site to do so. Because each of the items listed on the AspDotNetMVC are basically just pointers to some content on the Internet at a unique URL I needed a way to request the rating count from Kigg by URL. I also needed a way to vote for an article by passing some (url, title, description, category, tags and user) from the main site to Kigg. Of course as a web developer my first thought was to create a web service in Kigg with two methods, GetVoteCount(url) and SubmitOrVote(url, title, desc…). Since I had never previously created a WCF web service I chose to go that route rather than the asmx approach.

YMMV, but the first thing I needed was a new method in my Kigg data model to allow for retrieving the “story details” by url rather than id. In Kigg, like Digg, a story is an item submitted to the site. Since I’d be using an external site I wouldn’t know the ID of a story (or care what the id was) I wanted to be able to perform lookups by the story’s URL. I added the following to the KiggDataContext model (and the signature to the IDataContext interface):

public StoryDetailItem GetStoryDetailByUrl(Guid userId, string url) {
  return Stories
    .Where(s => s.Url.ToLower() == url.ToLower())
    .Select(s =>
      new StoryDetailItem
      {
        ID = s.ID,
        Title = s.Title,
        Description = s.Description,
        Url = s.Url,
        Category = s.Category.Name,
        Tags = s.StoryTags.Select(st => st.Tag.Name).ToArray(),
        PostedBy = new UserItem(s.PostedBy),
        PostedOn = s.PostedOn,
        PublishedOn = s.PublishedOn,
        VoteCount = s.Votes.Count(),
        HasVoted = (s.Votes.Count(v => v.UserID == userId) > 0),
        VotedBy = s.Votes
          .OrderBy(v => v.Timestamp)
          .Select(v => new UserItem(v.UserID)).ToArray(),
        Comments = s.Comments
          .OrderBy(c => c.PostedOn)
          .Select(c =>
            new CommentItem
            {
              PostedBy = new UserItem(c.PostedBy),
              PostedOn = c.PostedOn,
              Content = c.Content
            }
          ).ToArray()
      }
    )
  .FirstOrDefault();
}

This is very much just a copy and paste of the existing GetStoryDetailById method, just querying by URL rather than ID.

Next I needed to create the web service and two new methods in it. I created the service at /services/KiggService.svc and the two methods look like the following:

[WebGet(ResponseFormat = WebMessageFormat.Xml)]
[OperationContract]
public string GetVoteCount(string url) {
  //MembershipID doesn't matter because we're just getting the vote count.
  Guid _currentUserID = new Guid();
  IDataContext db = new KiggDataContext();
  var story = db.GetStoryDetailByUrl(_currentUserID, url);
  if (story != null)
    return story.VoteCount.ToString();
  return "0";
}
[WebGet(ResponseFormat = WebMessageFormat.Xml)]
[OperationContract]
public string SubmitOrVote(string userid, string url, string title, string desc, string categoryName, string tag)
{
  if (string.IsNullOrEmpty(userid))
    return("You must be authenticated prior hitting this url");
  Guid _currentUserID = new Guid(userid);
  Category[] _categories;
  IDataContext db = new KiggDataContext();
  _categories = db.GetCategories();
  try {
    Category cat = _categories.First(c => c.Name == "General");
    if (!string.IsNullOrEmpty(categoryName)) {
      cat = _categories.First(c => c.Name.ToLower() == categoryName.ToLower());
    }
    var categoryId = cat.ID;
    var story = db.GetStoryDetailByUrl(_currentUserID, url);
    if (story == null) {
      db.SubmitStory(url, title, categoryId, desc, tag, _currentUserID);
      return "1";
    } else {
      db.KiggStory(story.ID, _currentUserID, 3);
      var story2 = db.GetStoryDetailById(_currentUserID, story.ID);
      return story2.VoteCount.ToString();
    }
  } catch (Exception e) {
    return "error" \+ e.Message;
  }
}

There’s not much to the GetVoteCount method, it just checks to see if a story already exists in Kigg for the given URL, if it does then it returns the vote count, otherwise it returns 0.

The second method SubmitOrVote probably needs a little more explanation. It allows for a user to click a vote button on the AspDotNetMVC site and if the story already exists then it adds an additional vote. If the story does not already exist in Kigg then it submits it. Sounds simple enough. Since the category the story belongs in is passed in by a string I need to check the categories that exist in Kigg to find a match for that passed in categoryName string. First I set “cat” to the default category, “General”, and then try to find the match. If there is a match then I use the Kigg category that I found, otherwise I stick with General. Next I just lookup the story and then give it the additional vote or create it if it doesn’t exist and then return the appropriate new vote count.

You’ll notice that I’m passing the userId across the wire. In my case it’s not a big deal because my calls to the service are done server side in my client application and the Kigg site is not publicly accessible. Since the Kigg site is only accessible by the AspDotNetMVC site I also don’t have to worry about authentication or authorization either. Again YMMV. Also you may not want to expose your exception like I have to callers of your Kigg service. In my case it’s fine because I’m the only one calling it and I wanted to be able to log any exceptions in the client application for later review.

You’ll have to add appropriate error handling around the calls to these web service service and it would be a good idea to cache the votes for each article/url/story in your consuming application so you aren’t calling that method every time you need to show the votecount.

That’s all the adjustments that you need to make to Kigg to allow it act as a service for other applications. I’ll admit that it did take me a while to get the web.config setup properly to allow for the WCF services, but that has more to do with my first time doing doing anything with WCF than it does with the Kigg updates. If you have any WCF experience at all I’m sure it will take you much less time than the 30 minutes I spent doing so (mostly spent searching for answers on Google).

Hopefully these last two posts will provide you with a jumpstart for integrating Digg-style ratings (um… I mean Kigg-style) into your existing or new web applications. Have fun with those new mashups.


Dan Hounshell
Web geek, nerd, amateur maker. Likes: apis, node, motorcycles, sports, chickens, watches, food, Nashville, Savannah, Cincinnati and family.
Dan Hounshell on Twitter