TLDR :Full code is Running Sandbox is .
Writing a crappy, subpar clone of seems to have become the latest FizzBuzz fad.
In the last week, I've seen:
And the list goes. All of these implement simply implement posting and voting, and with the exception of the PHP one, don't use any form of persistance (The perl one might, I can't read it).
I wanted to play, but I also wanted to provide a little more features, as I don't believe any of those actually count as a "Reddit Clone". I decided on the following features:
- View Links ordered by score
- Submit Links
- Vote on links
- Comment on links in typical nested reddit fashion
- Vote on comments
- Order comments by score
- Use a real database as persistance
These features make my project a bit more ambitious than the others, but also makes it a nice technical showcase for how concise a program can be.
Since Java was already done, I decided to use C# and ASP.NET MVC as the language and framework for my clone.
The first order of busines is building the data model. Because I am going for simplicity I am going to define the two tables I need using C# classes and Linq to SQL attributes. Using this Linq to SQL will generate the database for me the first time the application is ran.
My model is simple:
The submissions class:
1 [Table(Name = "Submissions")]
2 public class Submission
3 {
4 private EntitySet<Comment> _comments
5 = new EntitySet<Comment>();
6
7 public Submission()
8 {
9 this.Score = 1;
10 }
11
12 [Column
13 (IsPrimaryKey = true,
14 IsDbGenerated = true,
15 DbType = "INT NOT NULL IDENTITY")]
16 public int ID { get; set; }
17
18 [Column]
19 public string Title { get; set; }
20
21 [Column]
22 public string Link { get; set; }
23
24 [Column]
25 public int Score { get; set; }
26
27 [Association(Storage = "_comments", OtherKey = "SubmissionID")]
28 public EntitySet<Comment> Comments
29 {
30 get { return this._comments; }
31 set { this._comments.Assign(value); }
32 }
33 }
And the Comment class:
1 public class Comment
2 {
3 private EntitySet<Comment> _childComments
4 = new EntitySet<Comment>();
5
6 public Comment()
7 {
8 this.Score = 1;
9 }
10
11 [Column
12 (IsPrimaryKey = true,
13 IsDbGenerated = true,
14 DbType = "INT NOT NULL IDENTITY")]
15 public int ID { get; set; }
16
17 [Column]
18 public Nullable<int> ParentCommentID { get; set; }
19
20 [Column]
21 public int SubmissionID { get; set; }
22
23 [Column]
24 public string Text { get; set; }
25
26 [Column]
27 public int Score { get; set; }
28
29 [Association(Storage = "_childComments", OtherKey="ParentCommentID", ThisKey = "ID")]
30 public EntitySet<Comment> ChildComments
31 {
32 get { return this._childComments; }
33 set { this._childComments.Assign(value); }
34 }
35 }
With those two classes defined, all I need to do now is create a DataContext:
1 [Database(Name = "RedditClone")]
2 public class RedditModel : DataContext
3 {
4 private static string _connString =
5 ConfigurationManager.ConnectionStrings["RedditClone"].ConnectionString;
6
7 public RedditModel() : base(_connString)
8 {
9 if (!this.DatabaseExists())
10 this.CreateDatabase();
11 }
12
13 public Table<Submission> Submissions
14 {
15 get { return this.GetTable<Submission>();}
16 }
17
18 public Table<Comment> Comments
19 {
20 get { return this.GetTable<Comment>(); }
21 }
22 }
The first time a RedditModel class is instanciated, Linq to SQL will build a database with the following schema:

This approach works very well for a simple project, as it makes installation very simple. All you have to do is drop the files onto a server and browse to it and you have the full database built and ready.
At this point, I have a full persistance layer completely finished and have used roughly 50 lines of code...Not bad.
However, I can't do much with a persistance layer without a frontend. So I need to define some MVC Controllers to handle requests. My design calls for two controllers:
- SubmissionController
- CommentController
SubmissionController will perform the following tasks:
- List all submissions
- Load a single submission
- Submit a submission
- Vote (Up and Down) on submissions
CommentController will perform the following tasks:
- Create a commment
- Vote (Up and down) on comments
Because of how I structured the data model, I don't need a action to request comments, they lazily load when you access a submission.
I'll start with the "Index" action on SubmissionController. This action responds to GET requests to the Submissions controller and will return a list of submisssions ordered by score:
1 public ActionResult Index()
2 {
3 RedditModel model = new RedditModel();
4 return View(
5 "Index",
6 model.Submissions
7 .OrderByDescending(s => s.Score)
8 .ToList());
9 }
As you can see, the heavily lifting of data manipulation is easily abstracted into the model. All this actually has to do is grab the data and it needs and pass it to the view (Which I have yet to define).
The "View" action is equally simple:
1 public ActionResult View(int id)
2 {
3 RedditModel model = new RedditModel();
4 return View(
5 "Comments",
6 model.Submissions
7 .Where(s => s.ID == id)
8 .FirstOrDefault());
9 }
With these two actions defined, I have handled the read-only aspect of my Reddit Clone. However, I want the user to be able to submit new links, as well as vote. So I'll add three more actions:
1 [ValidateInput(false)]
2 [AcceptVerbs(HttpVerbs.Post)]
3 public ActionResult Submit(FormCollection collection)
4 {
5 RedditModel model = new RedditModel();
6 Submission newSub = new Submission();
7
8 UpdateModel(newSub, new[] { "Title", "Link" });
9
10 model.Submissions.InsertOnSubmit(newSub);
11 model.SubmitChanges();
12
13 return RedirectToAction("Index");
14 }
The "Submit" action responds to a POST verb and creates a new submission. Due to the nice MVC UpdateModel method, it is very easy to map a POST to my model class.
Finally, I need to add voting. To do this I define a "VoteUp" action and a "VoteDown" action. Both of these call a private method that adjusts the vote count for the submission:
1 public ActionResult VoteUp(int id)
2 {
3 vote(id, 1);
4 return RedirectToAction("Index");
5 }
6
7 public ActionResult VoteDown(int id)
8 {
9 vote(id, -1);
10 return RedirectToAction("Index");
11 }
12
13 private void vote(int id, int direction)
14 {
15 RedditModel model = new RedditModel();
16
17 Submission voteSub = model.Submissions.Where(s => s.ID == id).FirstOrDefault();
18 voteSub.Score += direction;
19
20 model.SubmitChanges();
21 }
With this done, I'm at about 100 lines of C#, and I have a more functional RedditClone than the other examples.
I still need to add the CommentsController, which is very similar to the SubmissionController:
1 public class CommentsController : Controller
2 {
3 [ValidateInput(false)]
4 [AcceptVerbs(HttpVerbs.Post)]
5 public ActionResult Create(FormCollection collection)
6 {
7 RedditModel model = new RedditModel();
8 Comment newComment = new Comment();
9
10 UpdateModel(newComment, new[] { "SubmissionID", "ParentCommentID", "Text" });
11
12 model.Comments.InsertOnSubmit(newComment);
13 model.SubmitChanges();
14
15 return RedirectToRoute(new { controller = "Submissions", action = "View", id = newComment.SubmissionID });
16 }
17
18 public ActionResult VoteUp(int id, int submissionID)
19 {
20 vote(id, 1);
21 return RedirectToRoute(new { controller = "Submissions", action = "View", id = submissionID });
22 }
23
24 public ActionResult VoteDown(int id, int submissionID)
25 {
26 vote(id, -1);
27 return RedirectToRoute(new { controller = "Submissions", action = "View", id = submissionID });
28 }
29
30 private void vote(int id, int direction)
31 {
32 RedditModel model = new RedditModel();
33
34 Comment voteComment = model.Comments.Where(c => c.ID == id).FirstOrDefault();
35 voteComment.Score += direction;
36
37 model.SubmitChanges();
38 }
39 }
This wraps up all of the code needed. All that is left is to create views that actually display the data.
I'm a fan of the opensource project Spark, and will use it instead of the conventional ASP.NET view engine. I find the syntax from Spark a lot easier to read and less "tag-soup" than the ASPX views.
Because I have two different views (All submissions, and comments for a submission), I'll implement a master layout that they both share. Spark makes this easy:
1 "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 "http://www.w3.org/1999/xhtml">
3
4
5
6
7
8
9
Hi Jonathan,
Very nice to see a C# contribution! Just counting lines in the .cs files I see about 270 lines, making this 50% shorter than the ASM version, very nice :) Unfortunately, you are missing quite a few features of the original post, see my follow-up here:
http://www.bestinclass.dk/index.php/2010/02/reddit-clone-with-user-registration/
The database is a nice touch, but wouldn't add more lines to the Clojure version at least, since ClojureQL offers much the same access to a database as to a native datastructures.
Ps: Have you tried ClojureCLR ?
Keep up the good work, Lau