This is the last blog post in this series introducing Couchbase to ASP.NET developers (or vice-versa, depending on your point of view :). I’ll be rounding out the sample app that I’ve been building with a full suite of CRUD functionality. The app already shows a list of people. After this post, you’ll be able to add a new person via the web app (instead of directly in Couchbase Console), edit a person, and delete a person.

Before I start, a disclaimer. I’ve made some modeling decisions in this sample app. I’ve decided that keys to Person documents should be of the format “Person::{guid}”, and I’ve decided that I will enforce the “Person::” prefix at the repository level. I’ve also made a decision not to use any intermediate view models or edit models in my MVC app, for the purposes of a concise demonstration. By no means do you have to make the same decisions I did! I encourage you to think through the implications for your particular use case, and please feel free to discuss the merits and trade-offs in the comments.

Adding a new person document

In the previous blog posts, I added new documents through the Couchbase Console. Now let’s make it possible via a standard HTML form on an ASP.NET page.

First, I need to make a slight change to the Person class:

[DocumentTypeFilter("Person")] public class Person { public Person() { Type = "Person"; } [Key] public string Id { get; set; } public string Type { get; set; } public string Name { get; set; } public string Address { get; set; } } 1 2 3 4 5 6 7 8 9 10 11 12 [ DocumentTypeFilter ( "Person" ) ] public class Person { public Person ( ) { Type = "Person" ; } [ Key ] public string Id { get ; set ; } public string Type { get ; set ; } public string Name { get ; set ; } public string Address { get ; set ; } }

I added an “Id” field, and marked it with the [Key] attribute. This attribute comes from System.ComponentModel.DataAnnotations, but Linq2Couchbase interprets it to mean “use this field for the Couchbase key”.

Now, let’s add a very simple new action to HomeController:

public ActionResult Add() { return View("Edit", new Person()); } 1 2 3 4 5 public ActionResult Add ( ) { return View ( "Edit" , new Person ( ) ) ; }

And I’ll link to that with the bootstrap navigation (which I snuck in previously, and by no means are you required to use):

@model CouchbaseAspNetExample3.Models.Person @{ <span class="Apple-converted-space"> </span>ViewBag.Title = "Add : Couchbase & ASP.NET Example"; } @using (Html.BeginForm("Save", "Home", FormMethod.Post)) { <span class="Apple-converted-space"> </span><p> <span class="Apple-converted-space"> </span>@Html.LabelFor(m => m.Name) <span class="Apple-converted-space"> </span>@Html.TextBoxFor(m => m.Name) <span class="Apple-converted-space"> </span></p> <span class="Apple-converted-space"> </span><p> <span class="Apple-converted-space"> </span>@Html.LabelFor(m => m.Address) <span class="Apple-converted-space"> </span>@Html.TextBoxFor(m => m.Address) <span class="Apple-converted-space"> </span></p> <span class="Apple-converted-space"> </span><input type="submit" value="Submit" /> } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @ model CouchbaseAspNetExample3 . Models . Person @ { < span class = "Apple-converted-space" > < / span > ViewBag . Title = "Add : Couchbase & ASP.NET Example" ; } @ using ( Html . BeginForm ( "Save" , "Home" , FormMethod . Post ) ) { < span class = "Apple-converted-space" > < / span > < p > < span class = "Apple-converted-space" > < / span > @ Html . LabelFor ( m = > m . Name ) < span class = "Apple-converted-space" > < / span > @ Html . TextBoxFor ( m = > m . Name ) < span class = "Apple-converted-space" > < / span > < / p > < span class = "Apple-converted-space" > < / span > < p > < span class = "Apple-converted-space" > < / span > @ Html . LabelFor ( m = > m . Address ) < span class = "Apple-converted-space" > < / span > @ Html . TextBoxFor ( m = > m . Address ) < span class = "Apple-converted-space" > < / span > < / p > < span class = "Apple-converted-space" > < / span > < input type = "submit" value = "Submit" / > }

Since that form will be POSTing to a Save action, that needs to be created next:

[HttpPost] public ActionResult Save(Person model) { _personRepo.Save(model); return RedirectToAction("Index"); } 1 2 3 4 5 6 7 [ HttpPost ] public ActionResult Save ( Person model ) { _personRepo . Save ( model ) ; return RedirectToAction ( "Index" ) ; }

Notice that the Person type used in the parameter is the same type as before. Here is where a more complex web application would probably want to use an edit model, validation, mapping, and so on. I’ve omitted all of that, and I send the model straight to a new method in PersonRepository:

public void Save(Person person) { // if there is no ID, then assume this is a "new" person // and assign an ID if (string.IsNullOrEmpty(person.Id)) person.Id = "Person::" + Guid.NewGuid(); _context.Save(person); } 1 2 3 4 5 6 7 8 9 10 public void Save ( Person person ) { // if there is no ID, then assume this is a "new" person // and assign an ID if ( string . IsNullOrEmpty ( person . Id ) ) person . Id = "Person::" + Guid . NewGuid ( ) ; _context . Save ( person ) ; }

This repository method will set the ID, if one isn’t already set (it will be, when we cover ‘Edit’ later). The “Save” method on IBucketContext is from Linq2Couchbase. It will add a new document if the key doesn’t exist, or update an existing document if it does. It’s known as an “upsert” operation. In fact, I can do nearly the same thing without Linq2Couchbase:

var doc = new Document { Id = "Person::" + person.Id, Content = person }; _bucket.Upsert(doc); 1 2 3 4 5 6 7 var doc = new Document { Id = "Person::" + person . Id , Content = person } ; _bucket . Upsert ( doc ) ;

Editing an existing person document

Now, I want to be able to edit an existing person document in my ASP.NET site. First, let’s add an edit link to each person, by making a change to _person.cshtml partial view.

@Model.Name @Html.ActionLink("[Edit]", "Edit", new {id = Model.Id.Replace("Person::", "")}) @Html.ActionLink("[Delete]", "Delete", new {id = Model.Id.Replace("Person::", "")}, new { @class="deletePerson"}) 1 2 3 @ Model . Name @ Html . ActionLink ( "[Edit]" , "Edit" , new { id = Model . Id . Replace ( "Person::" , "" ) } ) @ Html . ActionLink ( "[Delete]" , "Delete" , new { id = Model . Id . Replace ( "Person::" , "" ) } , new { @ class = "deletePerson" } )

Again, I’m using bootstrap here, which is not required. I also added a “delete” link while I was in there, which we’ll get to later. One more thing to point out: when creating the routeValues, I stripped out “Person::” from the Id. If I don’t do this, ASP.NET will complain about a potentially malicious HTTP request. It would probably be better to give each person document a more friendly “slug” to use in the URL, and maybe to use that as the document key too. That’s going to depend on you and your use case.

Now I need an Edit action in HomeController:

public ActionResult Edit(Guid id) { var person = _personRepo.GetPersonByKey(id); return View("Edit", person); } 1 2 3 4 5 6 public ActionResult Edit ( Guid id ) { var person = _personRepo . GetPersonByKey ( id ) ; return View ( "Edit" , person ) ; }

I’m reusing the same Edit.cshtml view, but now I need to add a hidden field to hold the document ID.

<input name="Id" type="hidden" value="@Model.Id" /><input <span class="hljs-built_in">type</span>=<span class="hljs-string">"hidden"</span> name=<span class="hljs-string">"Id"</span> value=<span class="hljs-string">"@Model.Id"</span>/> 1 2 < input name = "Id" type = "hidden" value = "@Model.Id" / > < input < span class = "hljs-built_in" > type < / span > = < span class = "hljs-string" > "hidden" < / span > name = < span class = "hljs-string" > "Id" < / span > value = < span class = "hljs-string" > "@Model.Id" < / span > / >

Voila! Now you can add and edit person documents.

This may not be terribly impressive to those of you already comfortable with ASP.NET MVC. So, next, let’s look at something cool that a NoSQL database like Couchbase brings to the table.

Iterating on the data stored in the person document

I want to collect more information about a Person. Let’s say I want to get a phone number, and a list of that person’s favorite movies. With a relational database, that means that I would need to add at least two columns, and more likely, at least one other table to hold the movies, with a foreign key.

With Couchbase, there is no explicit schema. So instead, all I have to do is add a couple more properties to the Person class.

[DocumentTypeFilter("Person")] public class Person { public Person() { Type = "Person"; } [Key] public string Id { get; set; } public string Type { get; set; } public string Name { get; set; } public string Address { get; set; } public string PhoneNumber { get; set; } public List FavoriteMovies { get; set; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [ DocumentTypeFilter ( "Person" ) ] public class Person { public Person ( ) { Type = "Person" ; } [ Key ] public string Id { get ; set ; } public string Type { get ; set ; } public string Name { get ; set ; } public string Address { get ; set ; } public string PhoneNumber { get ; set ; } public List FavoriteMovies { get ; set ; } }

I also need to add a corresponding UI. I used a bit of jQuery to allow the user to add any number of movies. I won’t show the code for it here, because the implementation details aren’t important. But I have made the whole sample available on Github, so you can check it out later if you’d like.

I also need to make changes to the _person.cshtml to (conditionally) display the extra information:

<span class="hljs-tag"><<span class="hljs-title">div</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"panel-body"</span>></span> @Model.Address @if (!string.IsNullOrEmpty(Model.PhoneNumber)) { <span class="hljs-tag"><<span class="hljs-title">br</span> /></span> @Model.PhoneNumber } @if (Model.FavoriteMovies != null && Model.FavoriteMovies.Any()) { <span class="hljs-tag"><<span class="hljs-title">br</span>/></span> <span class="hljs-tag"><<span class="hljs-title">h4</span>></span>Favorite Movies<span class="hljs-tag"></<span class="hljs-title">h4</span>></span> <span class="hljs-tag"><<span class="hljs-title">ul</span>></span> @foreach (var movie in Model.FavoriteMovies) { <span class="hljs-tag"><<span class="hljs-title">li</span>></span>@movie<span class="hljs-tag"></<span class="hljs-title">li</span>></span> } <span class="hljs-tag"></<span class="hljs-title">ul</span>></span> } <span class="hljs-tag"></<span class="hljs-title">div</span>></span> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 < span class = "hljs-tag" > < < span class = "hljs-title" > div < / span > < span class = "hljs-attribute" > class < / span > = < span class = "hljs-value" > "panel-body" < / span > > < / span > @ Model . Address @ if ( ! string . IsNullOrEmpty ( Model . PhoneNumber ) ) { < span class = "hljs-tag" > < < span class = "hljs-title" > br < / span > / > < / span > @ Model . PhoneNumber } @ if ( Model . FavoriteMovies ! = null & & Model . FavoriteMovies . Any ( ) ) { < span class = "hljs-tag" > < < span class = "hljs-title" > br < / span > / > < / span > < span class = "hljs-tag" > < < span class = "hljs-title" > h4 < / span > > < / span > Favorite Movies < span class = "hljs-tag" > < / < span class = "hljs-title" > h4 < / span > > < / span > < span class = "hljs-tag" > < < span class = "hljs-title" > ul < / span > > < / span > @ foreach ( var movie in Model . FavoriteMovies ) { < span class = "hljs-tag" > < < span class = "hljs-title" > li < / span > > < / span > @ movie < span class = "hljs-tag" > < / < span class = "hljs-title" > li < / span > > < / span > } < span class = "hljs-tag" > < / < span class = "hljs-title" > ul < / span > > < / span > } < span class = "hljs-tag" > < / < span class = "hljs-title" > div < / span > > < / span >

And here’s how that would look (this time with two Person documents):

I didn’t have to migrate a SQL schema. I didn’t have to create any sort of foreign key relationship. I didn’t have to setup any OR/M mappings. I simply added a couple of new fields, and Couchbase turned it into a corresponding JSON document.

Deleting a person document

I already added the “Delete” link, so I just need to create a new Controller action…

public ActionResult Delete(Guid id) { _personRepo.Delete(id); return RedirectToAction("Index"); } 1 2 3 4 5 6 public ActionResult Delete ( Guid id ) { _personRepo . Delete ( id ) ; return RedirectToAction ( "Index" ) ; }

…and a new repository method:

public void Delete(Guid id) { _bucket.Remove("Person::" + id); } 1 2 3 4 5 public void Delete ( Guid id ) { _bucket . Remove ( "Person::" + id ) ; }

Notice that this method is not using Linq2Couchbase. It’s using the Remove method on IBucket. There is a Remove available on IBucketContext, but you need to pass it an object, and not just a key. I elected to use the IBucket, but there’s nothing inherently superior about it.

Wrapping up

Thanks for reading through this blog series. Hopefully, you’re on your way to considering or even including Couchbase in your next ASP.NET project. Here are some more interesting links for you to continue your Couchbase journey:

You might be interested in the ASP.NET Identity Provider for Couchbase (github). If you want to store identity information in Couchbase, this is one way you could do it. At the time of this blog post, it’s an early developer preview, and is missing support for social logins.

Linq2Couchbase is a great project with a lot of features and documentation, but it’s still a work in progress. If you are interested, I suggest visiting Linq2Couchbase on Github. Ask questions on Gitter, and feel free to submit issues or pull requests.

Right now, Couchbase is giving away a $500 prize for a creative use of Couchbase. Iterate on this ASP.NET example? Create a plugin for your favorite IDE? Implement that missing functionality in the ASP.NET Identity Provider? You’ll get some swag just for entering.

Conclusion

I’ve put the full source code for this example on Github.

What did I leave out? What’s keeping you from trying Couchbase with ASP.NET today? Please leave a comment, ping me on Twitter, or email me (matthew.groves AT couchbase DOT com). I’d love to hear from you.