How to get started with Couchbase with ASP.NET Core 3 using Linq2Couchbase AND Dependency Injection
Introduction
This totorial will create a sample repo for using Couchbase with ASP.NET Core 3 with Linq2Couchbase AND Dependency Injection.
How to re-create this repo?
Bucket setup
Assuming you already have an up and running Couchbase server running. If not, chcek-out official Couchbase docs and come back.
Create a new bucket
named contacts add a few documents like this
{
"name": "Satish K. Yadav",
"number": "9876543210",
"type": "Contact"
}
Since this
bucket
may contain any type of document, an optional propertytype
has been added to filtercontact
objects easily.
New Peoject
Create a new Project with ASP.NET Core - Web Application (MVC).
Installing Couchbase packages
Install following nuget packages.
CouchbaseNetClient
- Couchbase .NET SDKCouchbase.Extensions.DependencyInjection
- Dependency Injection extensionsLinq2Couchbase
- Linq-to-Couchbase provider for accessing database like other ORMs e.g.EntityFramework
.Couchbase Server Configuration
Keeping Couchbase configuration in ASP.NET Configuration
Add Couchbase Database Server configuration in
appsettings.json
e.g.{ "Couchbase": { "Servers": [ "http://localhost" ], "Username": "USERNAME", "Password": "PASSWORD", "UseSsl": false } }
For Production, you may want to set
UseSsl
astrue
depending on your server configuration.
Couchbase configuration with ASP.NET Core Dependency Injection
Add configuration to ConfigureServices
method
public void ConfigureServices(IServiceCollection services)
{
//Other configurations
//CouchBase configuration using Dependency Injection
services.AddCouchbase(Configuration.GetSection("Couchbase"));
}
Accessing buckets
Instead of hardcoding bucket names all over the project, use buckets with named providers.
Create a named bucket provider
Create new folder Data
in the root, and add an interface
named IContactsBucketProvider
. It should implement INamedBucketProvider
. Leave it blank. It should look like this.
public interface IContactsBucketProvider: INamedBucketProvider
{
}
Register bucket provider with Dependency Injection
Register named bucket provider with AddCouchbaseBucket
by chaining it to AddCouchbase
call, so it looks like this:
public void ConfigureServices(IServiceCollection services)
{
//Other configurations
//CouchBase configuration using Dependency Injection
services
.AddCouchbase(Configuration.GetSection("Couchbase"))
.AddCouchbaseBucket<IContactsBucketProvider>("contacts");
}
Couchbase clean-up after application stops
Add a IHostApplicationLifetime
parameter to Configure
method and code to clean-up Couchbase once the application stops, so it looks like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime hostApplicationLifetime)
{
//Other configurations
hostApplicationLifetime.ApplicationStopped.Register(() =>{
//Cleaning up using using Dependency Injection
app.ApplicationServices.GetRequiredService<ICouchbaseLifetimeService>().Close();
});
}
Context using Linq2Couchbase
Register a BucketContext
to be used for accessging documents from datbase. In ConfigureServices
method add following:
public void ConfigureServices(IServiceCollection services)
{
//Other configurations
//CouchBase configuration using Dependency Injection
services
.AddCouchbase(Configuration.GetSection("Couchbase"))
.AddCouchbaseBucket<IContactsBucketProvider>("contacts");
services.AddTransient(x =>
{
var contactsBucket = x.GetRequiredService<IContactsBucketProvider>();
return new BucketContext(contactsBucket.GetBucket());
});
}
POCO classes for accessing Couchbase
Add a Contact
class in Data
folder for accessing Couchbase documents.
public class Contact
{
public string Name { get; set; }
public string Number { get; set; }
public string Type => typeof(Contact).Name;
}
Now create a Model class under Models
folder for controllers.
public class ContactDto
{
[Key]
public string Id { get; set; }
public string Name { get; set; }
public string Number { get; set; }
}
Controllers
Add a new ContactsController
and add all CRUD
methods, which looks like this:
public class ContactsController : Controller
{
private BucketContext _bucketContext;
public ContactsController(BucketContext bucketContext)
{
_bucketContext = bucketContext;
}
// GET: Contacts
public ActionResult Index()
{
var allContacts = _bucketContext
.Query<Contact>()
.Where(x => x.Type == "Contact")
.Select(x => new ContactDto
{
Id = N1QlFunctions.Meta(x).Id,
Name = x.Name,
Number = x.Number
})
.ToList();
return View(allContacts);
}
// GET: Contacts/Details/5
public ActionResult Details(string id)
{
var contact = _bucketContext
.Query<Contact>()
.Select(x => new ContactDto
{
Id = N1QlFunctions.Meta(x).Id,
Name = x.Name,
Number = x.Number
})
.FirstOrDefault(x => x.Id == id);
return View(contact);
}
// GET: Contacts/Create
public ActionResult Create()
{
return View();
}
// POST: Contacts/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ContactDto contactDto)
{
try
{
// TODO: Add insert logic here
//contactsModel.Id = Guid.NewGuid().ToString();
var contact = new Contact { Name = contactDto.Name, Number = contactDto.Number };
_bucketContext.Save(contact);
return RedirectToAction(nameof(Index));
}
catch (Exception e)
{
ModelState.TryAddModelException("exception", e);
return View();
}
}
// GET: Contacts/Edit/5
public ActionResult Edit(string id)
{
var contact = _bucketContext
.Query<Contact>()
.Select(x => new ContactDto
{
Id = N1QlFunctions.Meta(x).Id,
Name = x.Name,
Number = x.Number
})
.FirstOrDefault(x => x.Id == id);
return View(contact);
}
// POST: Contacts/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, ContactDto contact)
{
try
{
// TODO: Add update logic here
_bucketContext.Save(new Contact { Name = contact.Name, Number = contact.Number });
return RedirectToAction(nameof(Index));
}
catch (Exception e)
{
ModelState.TryAddModelException("exception", e);
return View();
}
}
// GET: Contacts/Delete/5
[HttpGet(Name = "Delete")]
public ActionResult ConfirmDelete(string id)
{
var contact = _bucketContext
.Query<Contact>()
.Select(x => new ContactDto
{
Id = N1QlFunctions.Meta(x).Id,
Name = x.Name,
Number = x.Number
})
.FirstOrDefault(x => x.Id == id);
return View(contact);
}
// POST: Contacts/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(string id)
{
try
{
// TODO: Add delete logic here
var contact = _bucketContext.Query<Contact>().FirstOrDefault(x => N1QlFunctions.Meta(x).Id == id);
if (contact == null)
return NotFound();
_bucketContext.Remove(contact);
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
Views
Add Index
, Create
, Edit
, Details
, and Delete
views, under Views
-> Contacts
folder. Alternatively generate these views by scaffolding. Views should look something like these:
Create
@model YOUR_NAMESPACE_HERE.Models.ContactDto
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>ContactsModel</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Number" class="control-label"></label>
<input asp-for="Number" class="form-control" />
<span asp-validation-for="Number" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Delete
@model YOUR_NAMESPACE_HERE.Models.ContactDto
@{
ViewData["Title"] = "Delete";
}
<h1>Delete Contact</h1>
<h3>Are you sure you want to delete this?</h3>
<div>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Number)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Number)
</dd>
</dl>
<form asp-action="Delete">
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-action="Index">Back to List</a>
</form>
</div>
Details
@model YOUR_NAMESPACE_HERE.Models.ContactDto
@{
ViewData["Title"] = "Details";
}
<h1>Contact Details</h1>
<div>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Number)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Number)
</dd>
</dl>
</div>
<div>
@Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
<a asp-action="Index">Back to List</a>
</div>
Edit
@model YOUR_NAMESPACE_HERE.Models.ContactDto
@{
ViewData["Title"] = "Edit";
}
<h1>Edit Contact</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="All" class="text-danger"></div>
<input type="hidden" readonly asp-for="Id" class="form-control" />
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Number" class="control-label"></label>
<input asp-for="Number" class="form-control" />
<span asp-validation-for="Number" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Index
@model IEnumerable<YOUR_NAMESPACE_HERE.Models.ContactDto>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Number)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Number)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
@Html.ActionLink("Details", "Details", new { id = item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</tbody>
</table>
jQuery
validation library
_ValidationScriptsPartial.cshtml
looks like this
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
Run
Thant’s it! Run and your application is ready to be served.
Edit: If you’ve run into an error
If you’ve run into an error, which looks something like this:
CouchbaseQueryException: No index available on keyspace contacts that matches your query. Use CREATE INDEX or CREATE PRIMARY INDEX to create an index, or check that your expected index is online.
You need to create an index before querying. For simplicity create a Primary Index
on type
field with following script.
CREATE PRIMARY INDEX ON contacts