Search:

Search all manuals
Search this manual
Manual
Couchbase Client Library: .NET (C#) 1.1
Community Wiki and Resources
Wiki: .NET Client Library
Download Client Library
.NET Client Library
SDK Forum
Additional Resources
Community Wiki
Community Forums
Couchbase SDKs
Parent Section
2 Couchbase and ASP.NET MVC 3 Tutorial
Chapter Sections
Chapters

2.7. Stage 6: Adding diary entries

First, add a new model class called DiaryEntry with the contents from Listing 13.

Listing 13, DiaryEntry.cs

using System;

namespace CouchbaseTutorial.Models
{
    [Serializable]
    public class DiaryEntry
    {
        public Guid DiaryEntryId { get; set; }
        public Guid UserId { get; set; }
        public String Title { get; set; }
        public String Text { get; set; }
        public DateTime CreationDate { get; set; }
    }
}

Next add MakeEntry action methods (Listing 14) in HomeController.

Listing 14: MakeEntry actions.

/// <summary>
        /// GET: /Home/MakeEntry
        /// </summary>
        /// <returns></returns>
        public ActionResult MakeEntry()
        {
            var client = MvcApplication.CouchbaseClient;

            User user;
            if (!IsAuthenticated(client, out user))
                return RedirectToAction("Login");

            return View();
        }

        /// <summary>
        /// POST: /Home/MakeEntry
        /// </summary>
        /// <param name="collection"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult MakeEntry(FormCollection collection)
        {
            var client = MvcApplication.CouchbaseClient;

            User user;
            if (!IsAuthenticated(client, out user))
                RedirectToAction("Login");

            var title = collection["EntryTitle"];
            var text = collection["EntryText"];

            // Persist these in the database

            var entry = new DiaryEntry
            {
                DiaryEntryId = Guid.NewGuid(),
                Text = text,
                Title = title,
                CreationDate = DateTime.Now,
                UserId = user.UserId
            };

            client.Store(StoreMode.Set, entry.DiaryEntryId.ToString(), entry);

            // Now we need to keep track of the entries a user has.
            var entriesKey = user.UserId + ".DiaryEntries";
            AddToListWithCas(client, entriesKey, entry.DiaryEntryId);

            return RedirectToAction("Index");
        }

You can see that this method uses another method called AddToListWithCas to make sure that the list of diary entries for each user is added to the database. Add this method to HomeController.cs from Listing 15.

Listing 15, AddToListWithCas

/// <summary>
        /// Adds a value to a list of type T associated with the given Key using a
        /// CAS operation and retries.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="client"></param>
        /// <param name="key"></param>
        /// <param name="entry"></param>
        private static void AddToListWithCas<T>(CouchbaseClient client, string key,
                                                T entry)
        {
            CasResult<bool> casResult;
            do
            {
                var result = client.GetWithCas<List<T>>(key);
                var list = result.Result ?? new List<T>();
                var cas = result.Cas;

                // Avoid duplicates
                if (list.Contains(entry)) return;

                list.Add(entry);
                casResult = client.Cas(StoreMode.Set, key, list, cas);

            } while (casResult.Result == false);
        }

A CAS (Check And Set) operation is the correct way to ensure that operations happen without data loss without requiring distributed locking protocols or transactions to be handled. The way it works is you get the value of something with its cas. This is a monotonically increasing number that can be thought of as the version number of the object in the database. You mutate the object (change it in some way) and use a Cas operation to write it back to the database. This will fail (a casResult whose Result property is set to false) if some other client had modified the value in the database in the meantime. Notice the use of the '??' operator. This says assign to the list variable result.Result if it is not null; otherwise create an empty list.

So, in this case, the loop is repeated until the operation succeeds. This is a simplified example of how to do this. You may want to limit the number of retries performed before failing.

Also note that in this case it is highly unlikely that another user will be modifying the same list, so the Cas operation is probably overkill, but we will need this operation later in situations where the database could indeed be modified at the same time by another user.

Next add the form for creating a new diary entry by right clicking on the Home folder under Views, and choosing Add | View ... and naming it MakeEntry. See Listing 16 for the contents of this file.

Listing 16, MakeEntry.cshtml

@{
    ViewBag.Title = "Diary Entry";
}

<h2>Diary Entry</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Enter your diary entry:</legend>

        <p>
            Title: @Html.TextBox("EntryTitle")
        </p>

        <p>
            @Html.TextArea("EntryText", "", 20, 80, null)
        </p>

        <p>
            <input type="submit" value="Done" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Go back...", "Index")
</div>

You will need a place to display the diary entries too. We'll just display them as a bulleted list in the Index view. Edit Index.cshtml and add the content in Listing 17 before the Logout link.

Listing 17, an update to Index.cshtml

<p>@Html.ActionLink("Make diary entry...", "MakeEntry")</p>

@if (ViewData.ContainsKey("DiaryEntries")) {
    <ul>
    @foreach (var entry in ((IList<DiaryEntry>)ViewData["DiaryEntries"]))
    {
        <li>@entry.CreationDate.ToShortDateString()
            @entry.CreationDate.ToShortTimeString() -
            @entry.Title <p>@entry.Text</p>
       </li>
    }
    </ul>
}

Finally you need to make an update (Listing 18) to the Index action in HomeController.cs to generate the view data that the view is trying to display. Insert these lines just before the return at the end of the method.

Listing 18, Update Index method of HomeController.cs

var entriesKey = user.UserId + ".DiaryEntries";
            var result = client.Get<List<Guid>>(entriesKey);

            if (result != null)
            {
                // First we want to display all of a user's diary entries

                // Convert all of the Guids to strings to do a multi-get with
                var keys = result.Select(key => key.ToString()).ToList();
                var entryDictionary = client.Get(keys);
                var diaryEntries = new List<DiaryEntry>(
                    entryDictionary.Values.Cast<DiaryEntry>());
                diaryEntries.Sort((a, b) => a.CreationDate.CompareTo(b.CreationDate));
                ViewData["DiaryEntries"] = diaryEntries;
            }

The list of diary entry keys is retrieved from the database first. If the list exists, we convert these to a list of strings to use in a multi-get operation. The multi-get operation returns an entry dictionary but we are only interested in the fact that the values are all DiaryEntry objects, so we typecast them all using a quick LINQ expression. Finally the entries are sorted by creation date, and stored into the ViewData under the DiaryEntries key.

Now you can compile the application and have some fun writing diary entries. You will also be able to log out, create other users, and make more diary entries. Don't get too carried away.

That's a lot of fun, but what if we tried to make this a more social operation? Imagine if you could be told the names of the top three users who are making diary entries containing many of the same words that you are. The final stage of this tutorial will discuss doing just that.