Introduction to CouchDB with .NET part 25: connecting to CouchDB from .NET
July 21, 2017 4 Comments
Introduction
In the previous post we looked at how cookie based authentication works in the CouchDB API. This type of authentication follows a popular model in APIs. The user of the API will first need to acquire a temporary authentication cookie or token. This token must then be attached to the subsequent calls to the API as a means of authentication without sending the username and password in the request. Authentication cookies typically have an expiration date of some minutes. In CouchDB this is set to 10 minutes by default.
In this post we’ll look at how to connect to CouchDB from a .NET project. This is also the final post in this introductory series.
Connecting via the HTTP client object
The most straightforward way of communicating with the CouchDB database via C# is using one of the objects designed to handle web requests and responses. We saw how CouchDB exposes its functions via a HTTP API. The same API can be invoked from any programming language that can handle web requests. .NET exposes a number of different objects for this purposes and HttpClient in the System.Net.Http namespace. We’ll use that object throughout this post. It’s only a matter of dressing up the various ingredients of HttpClient to mirror our previous demo examples of the CouchDB API we did through a client like Postman or Fiddler.
We’ll work with a console application to keep things simple. Go ahead and create a new console application in Visual Studio as the first step. In addition, install the JSON.NET package via NuGet:
I’ll assume that you have had at least some exposure to both the HttpClient object and the immensely popular NewtonSoft JSON serialiser library in the world of .NET.
Getting the authentication cookie
Our CouchDB installation now has a number of users and roles so we must start with the authentication step. Recall that we’ll need to send a payload with the username and password to the POST /_session endpoint. Let’s create a simple class for the payload called Credentials.cs:
using Newtonsoft.Json; namespace CouchDbClient { public class Credentials { [JsonProperty(PropertyName ="username")] public string Username { get; set; } [JsonProperty(PropertyName = "password")] public string Password { get; set; } } }
We need to declare that our C# property names be serialised to lower case letters otherwise CouchDB won’t understand what we want.
Here’s a code example of how to read the authentication cookie from the CouchDB _session endpoint:
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; namespace CouchDbClient { class Program { private static string BaseCouchDbApiAddress = "http://localhost:5984"; private static HttpClient Client = new HttpClient() { BaseAddress = new Uri(BaseCouchDbApiAddress) }; private static string AuthCouchDbCookieKeyName = "AuthSession"; static void Main(string[] args) { Credentials couchDbCredentials = new Credentials() { Username = "olivia", Password = "secret" }; string authCookie = GetAuthenticationCookie(couchDbCredentials); Console.ReadLine(); } private static string GetAuthenticationCookie(Credentials credentials) { string authPayload = JsonConvert.SerializeObject(credentials); var authResult = Client.PostAsync("/_session", new StringContent(authPayload, Encoding.UTF8, "application/json")).Result; if (authResult.IsSuccessStatusCode) { var responseHeaders = authResult.Headers.ToList(); string plainResponseLoad = authResult.Content.ReadAsStringAsync().Result; Console.WriteLine("Authenticated user from CouchDB API:"); Console.WriteLine(plainResponseLoad); var authCookie = responseHeaders.Where(r => r.Key == "Set-Cookie").Select(r => r.Value.ElementAt(0)).FirstOrDefault(); if (authCookie != null) { int cookieValueStart = authCookie.IndexOf("=") + 1; int cookieValueEnd = authCookie.IndexOf(";"); int cookieLength = cookieValueEnd - cookieValueStart; string authCookieValue = authCookie.Substring(cookieValueStart, cookieLength); return authCookieValue; } throw new Exception("There is auth cookie header in the response from the CouchDB API"); } throw new HttpRequestException(string.Concat("Authentication failure: ", authResult.ReasonPhrase)); } } }
First we declare a couple of static fields such as the CouchDB API base address. The GetAuthenticationCookie function reaches out to the POST /_session endpoint and tries to extract the header called Set-Cookie from the response. We also print the string response from the API just to see what it looks like. The actual value of the cookie is extracted using a couple of lines of string manipulation. The full cookie value looks like this:
AuthSession=b2xpdmlhOjU5NzBDNUEzOuXWH8Mh_WcqL_MbY-9sgtO4cPq1; Version=1; Path=/; HttpOnly
…and we only need the portion between the equal sign and the first semi-colon.
If you run this code then the GetAuthenticationCookie function will print a response similar to the following:
Authenticated user from CouchDB API: {"ok":true,"name":"olivia","roles":["database-read-write"]}
Attaching the cookie to a HTTP request
The System.Net.CookieContainer class comes very handy when attaching a cookie to a HttpClient object. The following function and some additional static fields show how to attach the AuthSession cookie to the HTTP request to get some information about the restaurants database:
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; namespace CouchDbClient { class Program { private static string BaseCouchDbApiAddress = "http://localhost:5984"; private static CookieContainer CookieContainer = new CookieContainer(); private static HttpClientHandler ClientHandler = new HttpClientHandler() { CookieContainer = CookieContainer }; private static HttpClient Client = new HttpClient(ClientHandler) { BaseAddress = new Uri(BaseCouchDbApiAddress) }; private static string AuthCouchDbCookieKeyName = "AuthSession"; static void Main(string[] args) { Credentials couchDbCredentials = new Credentials() { Username = "olivia", Password = "secret" }; string authCookie = GetAuthenticationCookie(couchDbCredentials); GetRestaurantDbInformation(authCookie); Console.ReadLine(); } private static void GetRestaurantDbInformation(string authCookie) { CookieContainer.Add(new Uri(BaseCouchDbApiAddress), new Cookie(AuthCouchDbCookieKeyName, authCookie)); var result = Client.GetAsync("/restaurants").Result; if (result.IsSuccessStatusCode) { Console.WriteLine(result.Content.ReadAsStringAsync().Result); } else { Console.WriteLine(result.ReasonPhrase); } } private static string GetAuthenticationCookie(Credentials credentials) { string authPayload = JsonConvert.SerializeObject(credentials); var authResult = Client.PostAsync("/_session", new StringContent(authPayload, Encoding.UTF8, "application/json")).Result; if (authResult.IsSuccessStatusCode) { var responseHeaders = authResult.Headers.ToList(); string plainResponseLoad = authResult.Content.ReadAsStringAsync().Result; Console.WriteLine("Authenticated user from CouchDB API:"); Console.WriteLine(plainResponseLoad); var authCookie = responseHeaders.Where(r => r.Key == "Set-Cookie").Select(r => r.Value.ElementAt(0)).FirstOrDefault(); if (authCookie != null) { int cookieValueStart = authCookie.IndexOf("=") + 1; int cookieValueEnd = authCookie.IndexOf(";"); int cookieLength = cookieValueEnd - cookieValueStart; string authCookieValue = authCookie.Substring(cookieValueStart, cookieLength); return authCookieValue; } throw new Exception("There is auth cookie header in the response from the CouchDB API"); } throw new HttpRequestException(string.Concat("Authentication failure: ", authResult.ReasonPhrase)); } } }
We attach the cookie using the cookie container. If everything goes fine then the restaurant information should be returned from the CouchDB API:
{ "db_name":"restaurants", "update_seq":"59-g1AAAAFTeJzLYWBg4MhgTmEQTM4vTc5ISXLIyU9OzMnILy7JAUoxJTIkyf___z8rUQCPoiQFIJlkD1bHgk-dA0hdPFgdOz51CSB19WB1nHjU5bEASYYGIAVUOj8rkY2g2gUQtfuzElkJqj0AUXufGLUPIGpB_soCAP0db2k", "sizes":{ "file":226787, "external":7535, "active":13276 }, "purge_seq":0, "other":{ "data_size":7535 }, "doc_del_count":2, "doc_count":24, "disk_size":226787, "disk_format_version":6, "data_size":13276, "compact_running":false, "instance_start_time":"0" }
Getting a restaurant by ID
Communicating with the CouchDB API involves a lot of these HTTP calls, checking the response and converting it to our custom objects. We’ll typically want to represent our database objects in C# code so that we can easily serialise and deserialise to and from JSON. A restaurant in CouchDB can be represented by the following 3 C# classes:
using Newtonsoft.Json; namespace CouchDbClient { public class RestaurantGradeDb { [JsonProperty(PropertyName = "grade")] public string Grade { get; set; } [JsonProperty(PropertyName = "score")] public int Score { get; set; } } }
using Newtonsoft.Json; namespace CouchDbClient { public class RestaurantAddressDb { [JsonProperty(PropertyName = "building")] public string Building { get; set; } [JsonProperty(PropertyName = "coord")] public double[] Coordinates { get; set; } [JsonProperty(PropertyName = "street")] public string Street { get; set; } [JsonProperty(PropertyName = "zipcode")] public string ZipCode { get; set; } } }
using Newtonsoft.Json; using System.Collections.Generic; namespace CouchDbClient { public class RestaurantDb { [JsonProperty(PropertyName = "_id")] public string DatabaseId { get; set; } [JsonProperty(PropertyName = "_rev")] public string RevisionId { get; set; } [JsonProperty(PropertyName = "address")] public RestaurantAddressDb Address { get; set; } [JsonProperty(PropertyName = "borough")] public string Borough { get; set; } [JsonProperty(PropertyName = "cuisine")] public string Cuisine { get; set; } [JsonProperty(PropertyName = "grades")] public List<RestaurantGradeDb> Grades { get; set; } [JsonProperty(PropertyName = "name")] public string Name { get; set; } } }
You’ll recognise the various building blocks and property names of a Restaurant object in the CouchDB database. Here’s an example:
{ "_id": "dfd33c43e5c559ed6f2343d6f903c164", "_rev": "1-15df18a3f71b20cb837094f25784fa6f", "address": { "building": "2780", "coord": [ -73.98241999999999, 40.579505 ], "street": "Stillwell Avenue", "zipcode": "11224" }, "borough": "Brooklyn", "cuisine": "American ", "grades": [ { "grade": "A", "score": 5 }, { "grade": "A", "score": 7 }, { "grade": "A", "score": 12 }, { "grade": "A", "score": 12 } ], "name": "Riviera Caterer" }
The following function will look for a Restaurant with a specific ID:
private static void FindRestaurantById(string authCookie, string id) { CookieContainer.Add(new Uri(BaseCouchDbApiAddress), new Cookie(AuthCouchDbCookieKeyName, authCookie)); var result = Client.GetAsync(string.Concat("/restaurants/", id)).Result; if (result.IsSuccessStatusCode) { var stringContent = result.Content.ReadAsStringAsync().Result; RestaurantDb restaurant = JsonConvert.DeserializeObject<RestaurantDb>(stringContent); } else { Console.WriteLine(result.ReasonPhrase); } }
We read the JSON body from the response and convert it to the corresponding C# object. Here’s an example of the usage:
FindRestaurantById(authCookie, "dfd33c43e5c559ed6f2343d6f903c164");
You can test the code with an ID from your instance if the Restaurant DB.
There’s really not much more to communicating with the CouchDB HTTP API. We set up the endpoints and the payload where appropriate and then analyse the HTTP response. All CRUD operations are performed like that. Get hold of the correct CouchDB endpoint and payload and translate it into C# code.
You can view all posts related to data storage on this blog here.
Pingback: CouchDB Weekly News, July 27, 2017 – CouchDB Blog
Or you can use MyCouch async .Net library.
https://github.com/danielwertheim/mycouch
Nice article, how to insert documents in couchdb
You know this article would be better if you could show the case of how to insert the objects class you created as documents into the database.