A MongoDB Tutorial using C# and ASP.NET MVC

In this post I’m going to create a simple ASP.NET MVC website for a simple blog that uses MongoDB and the offical 10gen C# driver.

MongoDB is no NOSQL database that stores information as Binary JSON (BSON) in documents. I have been working with it now for around 6 months on an enterprise application and so far am loving it. Our application is currently in alpha phase but should be public early next year! If you are used to working with an RDBMS, it takes a little bit of getting used to as generally you work with a denormalized schema. This means thinking about things quite differently to how you would previously; you’re going to have repeating data which is a no-no in a relational database, but it’s going to give you awesome performance, sure you may need an offline process that runs nightly and goes and cleans up your data, but for the real time performance gains it’s worth it.

Download source

Our reasons for choosing MongoDB were performance and scalability. The application is dealing with a lot of data where a single page load would require many joins and become a very expesive query. Sure we could cache the result, but we really want our data in real time, and MongoDB allowed us to do this.

Another reason was scalibility; MongoDB supports automatic sharding, where once set up your data can be scaled horizontally across multiple machines (we’re hosting on Amazon EC2), so for a very large collection (table), you can split the data based on a key so that when making your query MongoDB knows which machine your data is stored on and can go straight there.

Replica Sets is also an important feature for us to manage redundancy and failover. We can have multiple MongoDB instances running which essentially mirror each other. If one node goes down another one takes over and the application continues to perform.

MongoDB is also the only NOSQL database I’m aware of that has commerical support from its creators, 10gen.

Anyway, on with the tutorial… I’d suggest reading the documentation on the MongoDB website and also the books by Kristina Chodorow. Also if you want a visual representation of your data I’d suggest having a look at MongoVue.

The first step is to get MongoDB installed on your machine, follow the quickstart guides on the MongoDB website. If you’re running windows, which you probably are as this blog is primarily about Microsoft technologies, you may also want to install MongoDB as a windows service.

Okay so once it’s installed lets fire up Visual Studio and create a new ASP.NET MVC 3 web project. The first thing we want to do is add a reference to the 10gen C# driver which you can do via nuget. Right click on the libaries folder under the web project and choose Add Libary Package Reference, then search online for ‘mongo’ and add a reference to the offical 10gen driver.

As mentioned MongoDB stores its data in documents. A simple C# POCO can be serialized as a document and stored by MongoDB. Documents can also contain other embedded documents or arrays of documents. Lets start by looking at my Post object that I will use for this blog tutorial. I have added a new class library to my soultion called Core where I will put my domain objects and services.

public class Post
{
    [ScaffoldColumn(false)]
    [BsonId]
    public ObjectId PostId { get; set; }

    [ScaffoldColumn(false)]
    public DateTime Date { get; set; }

    [Required]
    public string Title { get; set; }

    [ScaffoldColumn(false)]
    public string Url { get; set; }

    [Required]
    public string Summary { get; set; }

    [UIHint("WYSIWYG")]
    [AllowHtml]
    public string Details { get; set; }

    [ScaffoldColumn(false)]
    public string Author { get; set; }

    [ScaffoldColumn(false)]
    public int TotalComments { get; set; }

    [ScaffoldColumn(false)]
    public IList<Comment> Comments { get; set; }
}

As you can see above I’m also going to be using this domain model directly in my MVC views. Normally you’d want to separate your domain model and view model and use something like AutoMapper to map between then, but for simplicity in this tutorial I’m just going to use my domain model.

All in all a pretty simple object, but you’ll notice my PostId property has a type of ObjectId. Most collections in MongoDB have a unique identifier which is stored in the field ‘_id’. A unique index is automatically created on this field which cannot be removed. MongoDB has a special BSON type called ObjectId which is a 12-byte values made up of a time stamp, the machine id, the process id and a sequence number. This should give a unique Id that can be used on your documents. If I named my property ‘Id’ it would automatically become the ‘_id’ element in the document, but as I decided to call it ‘PostId’ I must specify it’s the Id by using the BsonId attribute. You don’t have to use the ObjectId type, but you do need to ensure that whatever you choose to use is unique. In the above example I could have used Url as my ‘_id’ field by adding the BsonId attribute to that field. It’s worth noting that when serialized the field names will match the POCO properties except for the PostId which will actually be stored as ‘_id’.

So now I have my document I want to be able to store it. To do this I need to create a connection to my MongoDB server, then choose my database and collection I want the document to belong to. Using the C# driver I can do this with the following code.

var server = MongoServer.Create("mongodb://127.0.0.1");
var db = server.GetDatabase("blog");
var collection = db.GetCollection<Post>("post");

First I create an instance of the MongoServer object using a MongoDB connection string for my local instance of the server. Second I get an instance of MongoDatabase for my blog database from the server. Lastly I get the MongoCollection object for the collection I want to use. MongoCollection is the object you use to insert, update and query that collection. I’m using the generic version of MongoCollection which speficies the domain object y0u will using for the document.

If the database or collection do not exist, then they will be created for you automatically.

The C# driver also has a class called MongoConnectionStringHelper that you can use to easily get the connection string from your web.config file. I could add my connection string as follows.

<connectionStrings>
  <add name="MongoDB" connectionString="server=127.0.0.1;database=blog" />
</connectionStrings>

I can then change my code to look like this.

var con = new MongoConnectionStringBuilder(ConfigurationManager.ConnectionStrings["MongoDB"].ConnectionString);

var server = MongoServer.Create(con);
var db = server.GetDatabase(con.DatabaseName);
var collection = db.GetCollection<Post>("post");

Now the server and database name are coming from the web.config and can be changed at any time without having to touch the code.

In my Core project I have created a PostService class which has the following Create method.

public void Create(Post post)
{
    var con = new MongoConnectionStringBuilder(
        ConfigurationManager.ConnectionStrings["MongoDB"].ConnectionString);

    var server = MongoServer.Create(con);
    var db = server.GetDatabase(con.DatabaseName);
    var collection = db.GetCollection<Post>("post");

    post.Comments = new List<Comment>();

    collection.Save(post);
}

The method accepts an instance of my post object as a parameter that I will save to MongoDB. I can do that easily by calling the Save method of MongoCollection passing it the object. MongoCollection also has Insert and Update methods which Save calls internally depending on the value of the _id field.

The only other thing I’m doing in this method is initializing my Comments property as a new list, which will create an empty array in MongoDB. Later I’ll explain how to push comment objects into this array, but if I didn’t initialize it the value would be null in the document and I couldn’t push to it.

The connection logic is simple, but I don’t want to have to repeat it in every service method, so I like to create a generic helper that wraps it up. Below is a class that does that called MongoHelper.

public class MongoHelper<T> where T : class
{
    public MongoCollection<T> Collection { get; private set; }

    public MongoHelper()
    {
        var con = new MongoConnectionStringBuilder(
            ConfigurationManager.ConnectionStrings["MongoDB"].ConnectionString);

        var server = MongoServer.Create(con);
        var db = server.GetDatabase(con.DatabaseName);
        Collection = db.GetCollection<T>(typeof(T).Name.ToLower());
    }
}

The helper can be used by giving the domain object type, which it also uses to derive the collection name. I’m using ToLower as I like my collection names to be lower case. I can then put the following variable and constructor in my PostService.

private readonly MongoHelper<Post> _posts;

public PostService()
{
    _posts = new MongoHelper<Post>();
}

My Create method is now simplified to this.

public void Create(Post post)
{
    post.Comments = new List<Comment>();
    _posts.Collection.Save(post);
}

For the rest of the tutorial I will use this helper in my services. Next I need a page to add new blog posts. In the MVC project I’ve added a PostController with a Create method.

[HttpGet]
public ActionResult Create()
{
    return View(new Post());
}

This is just rendering a simple view shown below, and passing through a new instance of the Post object.

@model Core.Domain.Post

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm())
{
    @Html.EditorForModel()

    <p>
        <input type="submit" value="Create" />
    </p>
}

This gives me a form to create a new Post that looks like this.

The WYSIWYG editor appears due to the UIHint attribute in my Post object. I have an editor template that renders a text area with a specific class which I then use in my layout page to hook up to CKEditor.

My post action for creating looks like this.

[HttpPost]
public ActionResult Create(Post post)
{
    if (ModelState.IsValid)
    {
        post.Url = post.Title.GenerateSlug();
        post.Author = User.Identity.Name;
        post.Date = DateTime.Now;

        _postService.Create(post);

        return RedirectToAction("Index");
    }

    return View();
}

Here I set the Url, Author and Date properties which are not set by the form, then call Create on my PostService. GenerateSlug is an extension method that I got from here to slugify my title which I’ll use in routing to display the post.

Now I have successfully saved a document with MongoDB, easy!

So the next step would be display a list of posts which can be done with the following service method.

public IList<Post> GetPosts()
{
    return _posts.Collection.FindAll()
        .SetFields(Fields.Exclude("Comments"))
        .SetSortOrder(SortBy.Descending("Date"))
        .ToList();
}

Here I am using the FindAll method of MongoCollection which returns all documents in the collection. It’s probably not wise to use this method without paging in a production environment as it could be a very expensive query. I am also using the SetFields method which can be used to either include or exlude fields, which is analogous to choosing fields in a SELECT statement in SQL.

I am excluding the Comments array as this service method is used for listing posts where I don’t care about the comments, I’ll show those on the detail page. When writing queries it’s always worth thinking about how you are going to use the data so you can make choices on structuring them to give maximum performance. I am also using SetSortOrder which as it suggests allows you to choose which field the documents returned are ordered by. I’m sorting by Date descending so that the latest Posts will be at the top.

Now back to MVC. I’m going to display my posts on my index page so my action will look like this.

public ActionResult Index()
{
    return View(_postService.GetPosts());
}

And my view like this.

@model IList<Core.Domain.Post>

@{
    ViewBag.Title = "Index";
}

<h2>Posts</h2>

<p>
    @Html.ActionLink("New Post", "Create")
</p>

@Html.DisplayForModel()

I’ve added a link to my page to create new posts and then used DisplayForModel to show my display template that looks like this.

@model Core.Domain.Post

<div>
    <h3>@Html.ActionLink(Model.Title, "Detail", new { id = Model.Url })</h3>

    <p>
        <em>Posted at @Model.Date.ToLocalTime().ToString() by @Model.Author</em>
    </p>

    <p>
        @Model.Summary
    </p>

    <p>
        (@Model.TotalComments comments)
    </p>

    <p>
        @Html.ActionLink("Delete Post", "Delete", new { id = Model.PostId })
        |
        @Html.ActionLink("Edit Post", "Update", new { id = Model.Url })
    </p>
</div>

The model for my view was a list of Posts, so this display template is repeated for each item. It shows a summary of the post as well as having links to the full post when clicking the title and links for delete and edit which I’ll implement next. My list of posts now looks like this.

For editing a post, if you’re used to Linq2Sql or Entity Framework you may do something like this.

public void Edit(Post post)
{
    var originalPost = GetPost(post.PostId);
    originalPost.Title = post.Title;
    originalPost.Url = post.Url;
    originalPost.Summary = post.Summary;
    originalPost.Details = post.Details;

    _posts.Collection.Save(originalPost);
}

Here I’m passing an updated Post object into the method, I query the database to get the most up to date document, update some of the properties to the new values, then save the updated document back to the database. In doing this the database is being hit twice. Another way to update MongoDB in one query is like this:

public void Edit(Post post)
{
    _posts.Collection.Update(
        Query.EQ("_id", post.PostId),
        Update.Set("Title", post.Title)
            .Set("Url", post.Url)
            .Set("Summary", post.Summary)
            .Set("Details", post.Details));
}

Here I’m using the Update method of MongoCollection. The first argument is a MongoDB query which allows you to specify what documents should be matched. With the C# driver most operations can be done using the query builder as above. I’m using Query.EQ which matches the given value against the specified field. The second argument is an update document which can also be created using the Update builder object. This objects wraps up the various MongoDB update methods. Above I am using Update.Set which simply sets a new value for the given field. Each update method returns another instance of the UpdateBuilder object so you can chain different methods together.

If you want to update more than one document you need to use one of the overloads that takes the UpdateFlags enum and use UpdateFlags.Multi, otherwise it will it will only update the first document matched by the query.

MongoDB also supports the FindAndModify command which the C# driver wraps up with a FindAndModify method on MongoCollection. This command allows you to find a document, update it, then return the document either updated or before the update, in a single operation. My edit service method could be amended to return the updated post like so.

public Post Edit(Post post)
{
    var updatedPost =
        _posts.Collection.FindAndModify(
            Query.EQ("_id", post.PostId),
            null,
            Update.Set("Title", post.Title)
                .Set("Url", post.Url)
                .Set("Summary", post.Summary)
                .Set("Details", post.Details),
            true).GetModifiedDocumentAs<Post>();

    return updatedPost;
}

FindAndModify returns a FindAndModiftyResult which has a ModifiedDocument which is a BsonDocument. Above I’m using the GetModifiedDocumentAs method which deserializes the BsonDocument to the correct type. By choosing the overload with returnNew and setting it to true I get the updated document, otherwise it would be the document before it was updated.

Another cool feature of MongoDB is upserts. This is where if the document exists it is updated, otherwise a new document is inserted. Upserts can be done using the FindAndModify method and using the overload with the upsert argument and setting it to true.

Getting back to MVC again; the action methods for my edit page looks like this.

[HttpGet]
public ActionResult Update(string id)
{
    return View(_postService.GetPost(id));
}

[HttpPost]
public ActionResult Update(Post post)
{
    if (ModelState.IsValid)
    {
        post.Url = post.Title.GenerateSlug();

        _postService.Edit(post);

        return RedirectToAction("Index");
    }

    return View();
}

All pretty simple; I’m generating the slug from the title again incase it was updated. The view is also very simple.

@model Core.Domain.Post

@{
    ViewBag.Title = "Update";
}

<h2>Update</h2>

@using (Html.BeginForm())
{
    @Html.HiddenFor(m => m.PostId);
    @Html.EditorForModel()

    <p>
        <input type="submit" value="Update" />
    </p>
}

I then get an edit page that looks very similar to the create post page.

My list of posts also has a link to delete a post, so let’s looks at the service method for that.

public void Delete(ObjectId postId)
{
    _posts.Collection.Remove(Query.EQ("_id", postId));
}

It takes the ID of the post to delete and uses the Remove method of MongoCollection with a query that matches the ID. When deleting I’m redirecting to a page to allow the user to confirm the delete. The action methods used look like this.

[HttpGet]
public ActionResult Delete(ObjectId id)
{
    return View(_postService.GetPost(id));
}

[HttpPost, ActionName("Delete")]
public ActionResult ConfirmDelete(ObjectId id)
{
    _postService.Delete(id);

    return RedirectToAction("Index");
}

And the view.

@model Core.Domain.Post

@{
    ViewBag.Title = "Delete";
}

<h2>Delete</h2>

<p>
    Are you sure you want to delete this post?
</p>

<h2>@Model.Title</h2>

@using (Html.BeginForm())
{
    <input type="submit" value="Delete" />
}

@Html.ActionLink("Back to posts", "Index")

Next let’s do the detail page that displays the full post. On this page I’m going to allow adding comments as well as displaying the current comments and loading more via ajax. Here is the service method that gets a single post via the Url.

public Post GetPost(string url)
{
    var post = _posts.Collection.Find(Query.EQ("Url", url)).SetFields(Fields.Slice("Comments", -5)).Single();
    post.Comments = post.Comments.OrderByDescending(c => c.Date).ToList();
    return post;
}

In this method I use the Find method of MongoCollection which takes a query object just like the Update method used previously. Here I’m matching the Url field against the url parameter passed into the service method.

The for the full post I want to see comments, but I only want the latest 5, and I’ll load the rest via ajax. To do this I’m using the SetFields method again, but this time using Fields.Slice which returns a subset of the documents in an array. There are two overloads of the Slice method, one allowing to select first or last x number of documents by using a positive or negative value, and the other allowing you to do paging with skip/limit.

I’m getting the last 5 comments, but I want them ordered with the latest comment as the top; at the beginning of my list. For this reason I’m sorting the comments descending by date. I’m doing this after querying MongoDB as at the time of writing this article there is no way to sort documents in a nested array using MongoDB.

The action method to display my detail page looks like this.

[HttpGet]
public ActionResult Detail(string id)
{
    var post = _postService.GetPost(id);
    ViewBag.PostId = post.PostId;

    return View(post);
}

You can see I’m adding the PostId to the ViewBag; I’ll use this later when I add a comment so that I know which post the comment is against. The view for the detail page looks like this.

@model Core.Domain.Post

@{
    ViewBag.Title = "Detail";
}

<h2>@Model.Title</h2>

<p>
    <em>Posted at @Model.Date.ToLocalTime().ToString() by @Model.Author</em>
</p>

<p>
    @Html.Raw(Model.Details)
</p>

<div id="add-comment">
    @Html.Partial("AddComment", new Core.Domain.Comment())
</div>

<h3>Comments</h3>
<div id="comment-list">
    @if (Model.Comments != null)
    {
        Html.RenderPartial("CommentList", Model.Comments);
    }
</div>

I’m using two partials in this view, one to allow adding a new comment and one to display the list of comments for the post. Below is the comment domain object.

public class Comment
{
    [BsonId]
    public ObjectId CommentId { get; set; }

    public DateTime Date { get; set; }

    public string Author { get; set; }

    [Required]
    public string Detail { get; set; }
}

The object contains the date the comment was made, the person who made it, and the comment itself. When adding a comment I create a new instance of the Comment object and pass that as the model to the partial. In doing this I can utilise the validation attributes in the model.

@model Core.Domain.Comment

@using (Html.BeginForm("AddComment", "Post", FormMethod.Post))
{
    <input name="postId" type="hidden" value="@ViewBag.PostId" />
    <div>
        Add Comment
        <br />
        @Html.TextAreaFor(m => m.Detail)
        @Html.ValidationMessageFor(m => m.Detail)
        <br />
        <input type="submit" value="Add Comment" />
    </div>
}

You can see I’m putting the PostId into a hidden input field which will then be posted with the  rest of the form data allowing me to store the comment against the correct post. The service method to add a comment is shown below.

public void AddComment(ObjectId postId, Comment comment)
{
    _posts.Collection.Update(Query.EQ("_id", postId),
        Update.PushWrapped("Comments", comment).Inc("TotalComments", 1));
}

When I did the update query for a post I used Update.Set to set a particular field. Here I am using Update.PushWrapped. In MongoDB push appends to an exisiting array within a document (remember when creating the post I initialized the comment collection to be a new list). The C# driver has the helper methods Push, which pushes a BsonDocument, or PushWrapped, which allows you to push an instance of the object used when instanciating the MongoCollection. Here I am adding the new comment object to the Comments field on the post, which is an array. Another useful function for dealing with arrays in AddToSet which only adds the document to the array if it doesn’t already exist.

In the same update I’m also using Inc which increments a number field; here the total number of comments the post has. To decrement you can use a negative value. I could perform a count on the number of documents in the post collection, but this could have a negative effect on performance. If I had millions of documents in the collection it would have to touch each one to work out the count. Performance wise it’s better to maintain the count in a field yourself, and just query that.

To submit the comment, I’m going to hijax the form submission and perform the addition using an ajax call. Due to this my action method for adding a comment returns JSON data. Normally if you want to return the rendered HTML for a partial you can use the PartialViewResult, but here if the comment textbox is empty I need to return an error; I can do this easily by rending the AddComment partial again using the invalid comment object as it’s model.

[HttpPost]
public ActionResult AddComment(ObjectId postId, Comment comment)
{
    if (ModelState.IsValid)
    {
        var newComment = new Comment()
                                {
                                    CommentId = ObjectId.GenerateNewId(),
                                    Author = User.Identity.Name,
                                    Date = DateTime.Now,
                                    Detail = comment.Detail
                                };

        _commentService.AddComment(postId, newComment);

        ViewBag.PostId = postId;
        return Json(
            new
                {
                    Result = "ok",
                    CommentHtml = RenderPartialViewToString("Comment", newComment),
                    FormHtml = RenderPartialViewToString("AddComment", new Comment())
                });
    }

    ViewBag.PostId = postId;
    return Json(
        new
            {
                Result = "fail",
                FormHtml = RenderPartialViewToString("AddComment", comment)
            });
}

As you can see if the model state is valid I create a new comment object with the current user and date and save it to MongoDB. I put the PostId in ViewBag again so it’s ready for another comment to be added, then I return a JsonResult containing a string stating everything was ‘ok’, the HTML from the Comment partial view rendered against the new comment and the HTML from the AddComment partial for a new comment. In the callback for my ajax call I check the result string, and if everything is okay I replace the add comment form and append the new comment to the list of comments.

If the model state is invalid the JsonResult contains the result string ‘fail’ as well as the rendered HTML from the AddComment partial, but using the comment object which has an invalid state, which will therefore render the required error message due to the required validation attribute on the model.

The RenderPartialViewToString method came from here and renders a partial to string using the Razor view engine.

Here is the jQuery that hijaxes the form and updates the DOM.

$("form[action$='AddComment']").live("submit", function () {
    $.post(
        $(this).attr("action"),
        $(this).serialize(),
        function (response) {
            if (response.Result == "ok") {
                $(response.CommentHtml).hide().prependTo("#comment-list").fadeIn(1000);
                $("#add-comment").html(response.FormHtml);
                $("#Detail").val("");
            }
            else {
                $("#add-comment").html(response.FormHtml);
            }
        });
    return false;
});

And now I can add comments to my posts using ajax.

Here is the partial view that’s used for rendering a comment.

@model Core.Domain.Comment
<div><strong>By @Model.Author at @Model.Date.ToLocalTime().ToString()</strong>
(<a class="remove-comment" href="javascript:void()" data-id="@Model.CommentId">Remove</a>)@Model.Detail

</div>

You can see that it has a remove link to delete a comment. Lets look at the service method used to do this.

public void RemoveComment(ObjectId postId, ObjectId commentId)
{
    _posts.Collection.Update(Query.EQ("_id", postId),
        Update.Pull("Comments", Query.EQ("_id", commentId)).Inc("TotalComments", -1));
}

The method is similar to adding a comment but I use Update.Pull which will remove matching documents from the Comments array. Update.Pull allows you to use a mongo query to select the documents to remove, or you can use Update.PullWrapped if you have an instance of the object you want to remove. Im also decrementing the TotalComments field using the Inc method with a negative number.

I will also use ajax to remove a comment from MongoDB and the DOM. My action method looks like this.

public ActionResult RemoveComment(ObjectId postId, ObjectId commentId)
{
_commentService.RemoveComment(postId, commentId);
return new EmptyResult();
}

As this is just a tutorial, I’m being a bit lazy and returning an EmptyResult. If I had proper error handling in place I’d probably want to return a JsonResult that indicates whether the remove was successful or not.

The jQuery to call this action and remove the comment from the DOM looks like this.

$(".remove-comment").live("click", function () {
    var comment = $(this).parent();
    $.post(
        '@Url.Action("RemoveComment")',
        { postId : '@Model.PostId', commentId : $(this).data("id") },
        function () {
            comment.fadeOut(1000, function() { $(this).remove(); });
        }
    );
});

The only part left to do on this page is to load more comments via an ajax call. If you remember when I initially load the post for the detail page I only load the latest 5 comments, so I want to be able to load more and add them to do the DOM. This is quite straight forward using SetFields and Slice with skip/limit as I mentioned earlier.

The only tricky part is that new documents may have been added to the end of the array since the page loaded which could result in duplicate comments being displayed. Let’s say I have 20 comments in my array, and I want the latest documents first, I use -5 for the limit on the initial slice to give me comments 16, 17, 18, 19 and 20. For the next page I’d want to use -5 for skip and -5 for limit to get the next set of 5 comments. This should give me 11, 12, 13, 14 and 15, but what happens if since loading the page somebody else added a comment to the same post? When I go to get the next page there would be 21 comments in total, and my query doing a skip -5 limit -5 would actually return me comments 12, 13, 14, 15 and 16, so comment 16 would be returned twice.

To resolve this I put the total number of comments in the ViewBag when rendering the page, then when loading the next page of comments I compare this against the actual number of comments at that point and adjust the skip with the offset. This does mean that to load a page of comments I hit MongoDB twice, but both calls are very small so it won’t really add much overhead.

My Detail action method has been amended to look like this.

[HttpGet]
public ActionResult Detail(string id)
{
    var post = _postService.GetPost(id);
    ViewBag.PostId = post.PostId;

    ViewBag.TotalComments = post.TotalComments;
    ViewBag.LoadedComments = 5;

    return View(post);
}

As you can see the TotalComments is added to ViewBag, as well as the number of comments I currently have loaded which I will use for the initial skip value.

My comments get rendered by the CommentList partial view that looks like this.

@model IList<Core.Domain.Comment>

@foreach (var comment in Model)
{
    Html.RenderPartial("Comment", comment);
}

@if (ViewBag.TotalComments > ViewBag.LoadedComments)
{
    <div style="margin-bottom: 20px;">
        <a id="load-more" data-loadedComments="@ViewBag.LoadedComments" href="javascript:void(0)">Load more...</a>
    </div>
}

The view renders each comment, then if the number of TotalComments is greater than the LoadedComments, ie. there are more comments to load, it renders a ‘Load more’ link which will load more comments using ajax. Here I’m using a data attribute in the anchor tag which contains the number of loaded comments. This is so I can easily get this number from my jQuery method to load the  next page.

$("#load-more").live("click", function () {
    $.post(
        '@Url.Action("CommentList")',
        { postId: '@Model.PostId', skip : $(this).data("loadedComments"), limit : 5, totalComments: @ViewBag.TotalComments },
        function (response) {
            $("#comment-list").find("#load-more").parent().replaceWith($(response).fadeIn(1000));
        }
    );
});

The method above calls an action, CommentList passing through the PostId, the number of comments current loaded, the limit of 5, which is how many comments I want per page, and the total number of comments from when the page was first rendered. The action method returns HTML rendered using the CommentList partial used above which gets added to the DOM, replacing the load more link.

[HttpPost]public ActionResult CommentList(ObjectId postId, int skip, int limit, int totalComments)
{
    ViewBag.TotalComments = totalComments;
    ViewBag.LoadedComments = skip + limit;
    return PartialView(_commentService.GetComments(postId, ViewBag.LoadedComments, limit, totalComments));
}

I need to put the TotalComments in ViewBag again, as well as the  new value for LoadedComments. I then return a PartialViewResult from the result of the GetComments service method.

public IList<Comment> GetComments(ObjectId postId, int skip, int limit, int totalComments)
{
    var newComments = GetTotalComments(postId) - totalComments;
    skip += newComments;

    var post = _posts.Collection.Find(Query.EQ("_id", postId)).SetFields(Fields.Exclude("Date", "Title", "Url", "Summary", "Details", "Author", "TotalComments").Slice("Comments", -skip, limit)).Single();
    return post.Comments.OrderByDescending(c => c.Date).ToList();
}

public int GetTotalComments(ObjectId postId)
{
    var post = _posts.Collection.Find(Query.EQ("_id", postId)).SetFields(Fields.Include("TotalComments")).Single();
    return post.TotalComments;
}

The GetComments method first calls GetTotalComments to work out of any new comments have been added and adjusts the skip parameter accordingly. It then queries the post collection excluding all fields except the Comments array which it then performs a Slice on to get the next page of comments. Again I then reorder those comments at the client before returning them.

I think that’s about it for this tutorial; there is a  lot more I want to write about with MongoDB but this post seems to have got fairly large, so I think the other subjects will have to go into seperate posts. At least I’ve actually finished this post which I started about a month ago; keeping my blog up to date is getting increasingly hard with a 6 month old baby!

I hope this helps follow Microsoft developers who are looking into MongoDB!

Download source

Posted on by Joe in ASP.NET, C#, MongoDB, MVC

45 Responses to A MongoDB Tutorial using C# and ASP.NET MVC

  1. Liam

    Thanks a lot, just getting started with MongoDB, and this will help me out a bunch.

  2. sina

    Thanks…
    But what can we do for edit a Comment ?!!!

  3. Joe

    Sina

    You should be able to easily adapt the code in this article to allow you so edit a comment. Just take the code for updating a post and change it.

    Cheers
    Joe

  4. fairy

    Hello

    Thank you for your tutorial!

    very helpful and in detail

    many useful resource link!

    Thank you so much , best wishes to you!

  5. fairy

    hello thank you for your wonderful tutorial,

    I have a question about ckeditor and searching it for 2 days..

    I can create editor textarea, but when render it , even use html.raw, there are still html tags..

    I think I followed your code for ckeditor,
    but for “The WYSIWYG editor appears due to the UIHint attribute in my Post object. ”
    is but just put [UIHint("WYSIWYG")] on top of attribute or need to do more ?

    thank you so much !

  6. fairy

    hello

    I found without save like

    ” mytext ”
    but in database it saved as”& l t ; p & g t ; mytext”,

    may I ask your advice?

    I put [UIHint("WYSIWYG")] but look like does not work

  7. fairy

    hello I found it out

    using like
    @Html.Raw(HttpUtility.HtmlDecode(question.FaqAnswer))

    and it works..but still do not know how your way works exactly,..

  8. Joe

    Hi Fairy

    Your HTML is being encoded somewhere along the line before being put in the database.

    Did you use the AllowHTML attribute?

    If you want me to take a look put your code somewhere I can download it.

    Cheers
    Joe

  9. Joe

    Also be aware that using HTML.Raw with user generated content is a security risk. If it’s just content you are creating then you can manage it, otherwise you should use an Anti XSS library.

  10. fairy

    Hello Joe

    thank you for your help and advice.

    maybe My problem is razor automatically encode that property when saving in database..then I need to decode it..I used [AllowHtml] but not seemed working on my case,BTW, the project mongoDB model previous designed by others is not exactly like your project..

    I searched about Anti XSS library..

    seems people said maybe for razor, don’t need it?

    http://stackoverflow.com/questions/5649018/is-use-of-antixss-library-necessary-recommended-in-mvc-3-razor-application

    http://stackoverflow.com/questions/2022289/why-use-microsoft-antixss-library

    Thanks so much, I learned a lot from you, thanks

  11. Denis

    Very good article! Thx!

  12. jun1st

    Good Introduction!

  13. RJ

    Very Helpful! Thanks for posting.

  14. Roni

    This is an amazing detailed post! Congratulation.

  15. Joe

    Thanks Roni. Hope it helped.

  16. Fredrik

    Big Thanks!

  17. Harvey

    Excellent tutorial.
    I am using VS2010, MVC .NET, C#
    But I can not run the Post Create, apparently my localhost using Mongo port refuses the connection as I get error destination computer actively refused connection for 127.0.0.1:27017

    Is it a Windows Firewall issue or ?

    It is not clear how to include the WYSIWG editor. I looked through your files and besides a property with attribute and template I don’t see any references to CKEditor.NET or a ckeditor folder in web project.

    This of course is really outside the scope of the tutorial on Mongo, still any assistance on my 2 questions would be greatly appreciated. Thanx

  18. Harvey

    Solved the connection refused :-( Had to start mongoserver!

  19. JC

    ObjectId does not get populated when I update a post. It seems like MVC does not know how to serialize and deserialize ObjectId?

  20. Joe
  21. Nitin

    Joe – Everytime i save a comment, either using the PushWrapped or just a simple save from the post object, the ObjectId is all zeros. I’ve got the attribute marked as BsonId and the type is ObjectId. Any thoughts?

  22. westtiger

    Thank you for your introduction~
    It is very helpful
    Thank you again.

  23. Steven

    Excellent information here. Thanks

  24. Richard

    Hi Joe
    Thanks so much for this article. It is one of the best I have seen on using the mongodb c# driver.

    The question that I still have is how to prevent 2 users from editing the same record. For example, if somebody clicked Edit on the Post and then a second user clicked Edit on the same Post. I don’t wan’t user 2 to be able to overwrite what the first user saved. I want user 2 to essentially fail.

    I saw a javascript post of how to do this, but I am failing in understanding how to translate it to the c# driver.

    https://groups.google.com/forum/?fromgroups=#!topic/mongodb-user/iI9eFBRdnww

    Any help, or a possible update to this tutorial would be amazing.

    Thanks.

  25. Joe

    Hi Richard

    As they say in that article you’d just need to add some sort of version number to your document which get incremented on a successful update. When you retrieve the post for editing you keep hold of that version number, then you use it again when saving to make sure that it’s the same, therefore knowing that nobody else has modified the document.

    I’ve not done this myself but it wouldn’t be difficult. I’ll perhaps look into adding it to the solution soon.

    Cheers
    Joe

  26. Attila

    Thanks for the great posts, couple of days ago I started experimenting with MVC and Mongodb, this helps alot.

  27. Joe

    Richard

    I created a separate post that answers your question. It uses the same technique as the article you provided.

    http://www.joe-stevens.com/2013/01/31/optimistic-concurrency-with-mongodb-c-driver-and-asp-net-mvc-prevent-multiple-users-updating-the-same-record/

    Cheers
    Joe

  28. Pingback: MongoDb + MVC | Gordon's Blog

  29. David

    Fantastic tutorial. Thanks for the step-by-step real world scenario! I even submitted a pull request to add it to the list of tutorials here: http://docs.mongodb.org/ecosystem/drivers/csharp-community-projects/

  30. Joe

    Thanks David, glad you liked it.

    Cheers
    Joe

  31. Hello VNP

    I’m new begginner learn MongoDB.
    Great article, but I have proplem.
    1. How to edit one “Comments” in the “Posts”.?
    If find one “Comment”, then must get parent “Post” of this. All data of post(comment collection is loaded) will load???

    Post: Comments[Array]
    2. How to get all comments?
    3. How to move comment to other post?
    Many Thank!

  32. anil

    Hi Please give me Simple Login Example on MongoDb with ASP.Net webpages
    please help me

  33. Pingback: MongoDb + MVC | Gordon Durgha - Professional Web Developer

  34. Joe

    Hi Anil

    For WebForms you have two options; create your own login control, or create a custom MembershipProvider and use the built in controls. The latter would also give you access to all the other Membership API functions.

    There should be enough code and information in this tutorial to help you do this.

    Cheers
    Joe

  35. Rozy

    hi,
    please tell where should i see the stored ..i mean in c:data/db i can see the database name.but i want to see physical stored data.

  36. leslie

    I love it

  37. leslie

    performance and scalability,scalability is so important for large db

  38. PAM

    I like this practical tutorial. Thank you!

  39. Robin Minnaard

    Hi Joe,
    Looks like you know about this stuff. I have a small challenge.

    Via this call i retrieve the OrderBy parameter.

    string sortField = docQueryHelp.GetSortField(filter.OrderBy);
    bool ascending = filter.IsAsc ?? false;

    In “filter” the OrderBy value is “EndTime” (field in MongoDB).

    This is set via a php script that talks to the windows server.

    $live_specification_filter->OrderBy = ‘EndTime’;
    $live_specification_filter->IsAsc = true;

    Now i tried to have to fields to sort on by passing this in the php script:

    $live_specification_filter->OrderBy = ‘EndTime, Specification.Category’;
    $live_specification_filter->IsAsc = true;

    So first sort on EndTime then Specification.Category.

    Using only 1 OrderBy value works, but how can set/format the above OrderBy
    in the .Net code?

    string sortField = docQueryHelp.GetSortField(filter.OrderBy);

    Would be great if you could help me.

    Thanx.

    Robin

  40. Ravi Lele

    Awesome sample, Happy coding

  41. arun

    Great! An awespme start to the mongodb backend for asp.net mvc.

  42. Pingback: [RESOLVED]Need Database deign and fields | ASP Questions & Answers

  43. Charles

    THank you very much,

  44. Dinesh

    very useful for a beginer

  45. Mike k

    Incredible Tutorial Joe, i dig the MongoHelper.

Add a Comment