Introduction
This article demonstrates CRUD operation to CouchDB via HTTP-based REST API in an ASP.NET Core application.
CouchDB is an open source NoSQL document database where data are stored in JSON based document format in simple key/value pair manner.
CouchDB falls into the AP category of CAP theorem (Availability and Partition Tolerance), whereas MongoDB falls into the CP category of CAP theorem (Consistency and Partition Tolerance).
Each document in CouchDB has a document-level unique identifier (_id) as well as a revision (_rev) number for each change that is made into the database.
Prerequisites
- The CouchDB Server is properly configured and running at http://<<dbserveripaddress>>:5789/.
- Database is present in server where we will perform CRUD operation, in my case it is “my-couch-db”.
Performing CRUD operation to Couch DB from a ASP.NET Core application
I created ASP.NET Core Web API project and created an API controller called “CourseContoller”. From controller I am calling repository class to perform DB operation. Here, I will perform operation on course enrollment as an example.
The Approach
- Add couch db configuration into appsettings.json and install NuGet package.
- Create Models for handling input from User.
- Create ICouchRepository.cs and implement it to CouchRepository.cs
- Do Rest Call to CouchDB from CouchRepository.cs.
- Register Repository into stratup.cs.
- Modify/Create api controller to take the input from user and call repository method to perform CRUD activity.
- Test from Postman and validate against CouchDB.
Step 1 - Modify appsettings.json
Before performing any operation, let's added my couch db configuration into appsettings.json.
- "CouchDB": {
- "URL": "http://127.0.0.1:5984",
- "DbName": "my-couch-db",
- "User": "Username:Password"
- }
Install below NuGet package.
- <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
Step 2 - Create Models
EnrollCourse.cs
- public class EnrollCourse
- {
- public string Name { get; set; }
- public string EmailAddress { get; set; }
- public string CourseName { get; set; }
- public DateTime EnrolledOn { get; set; }
- public DateTime UpdatedOn { get; set; }
- }
EnrollInfo.cs
- public class EnrollInfo
- {
- [JsonProperty("_id")]
- public string Id { get; set; }
- [JsonProperty("_rev")]
- public string Rev { get; set; }
- public string Name { get; set; }
- public string EmailAddress { get; set; }
- public string CourseName { get; set; }
- public DateTime EnrolledOn { get; set; }
- public DateTime UpdatedOn { get; set; }
- }
UpdateEnroll.cs
- public class UpdateEnroll
- {
- public string Id { get; set; }
- [JsonIgnore]
- public string Rev { get; set; }
- public string Name { get; set; }
- public string EmailAddress { get; set; }
- public string CourseName { get; set; }
- public DateTime EnrolledOn { get; set; }
- public DateTime UpdatedOn { get; set; }
- }
HttpClientResponse.cs
- public class HttpClientResponse
- {
- public bool IsSuccess { get; set; }
- public dynamic SuccessContentObject { get; set; }
- public string FailedReason { get; set; }
- }
- public class SavedResult
- {
- public string Id { get; set; }
- public string Rev { get; set; }
- }
Step 3
Create ICouchRepository.cs and implement it to CouchRepository.cs
Step 4 - Rest Call to CouchDB from CouchRepository.cs
Let's create DBContext folder and add ICouchRepository.cs and CouchRepository.cs into it. From CouchRepository , we will do REST call to CouchDB to perform CRUD operation.
- GET http://{CouchDB_hostname_or_IP}:{Port}/{couchDbName}/{_id}
- POST http://{CouchDB_hostname_or_IP}:{Port}/{couchDbName}
- PUT http://{CouchDB_hostname_or_IP}:{Port}/{couchDbName}/{_id}/?rev={_rev}
- DELETE http://{CouchDB_hostname_or_IP}:{Port}/{couchDbName}/{_id}/?rev={_rev}
ICouchRepository.cs
- public interface ICouchRepository
- {
- Task<HttpClientResponse> PostDocumentAsync(EnrollCourse enrollCourse);
- Task<HttpClientResponse> PutDocumentAsync(UpdateEnroll update);
- Task<HttpClientResponse> GetDocumentAsync(string id);
- Task<HttpClientResponse> DeleteDocumentAsync(string id, string rev);
- }
CouchRepository.cs
- public class CouchRepository : ICouchRepository
- {
- private readonly string _couchDbUrl;
- private readonly string _couchDbName;
- private readonly string _couchDbUser;
- private readonly IConfiguration _configuration;
- private readonly IHttpClientFactory _clientFactory;
- public CouchRepository(IConfiguration configuration, IHttpClientFactory clientFactory)
- {
-
- _configuration = configuration;
- _clientFactory = clientFactory;
- _couchDbUrl = this._configuration["CouchDB:URL"];
- _couchDbName = this._configuration["CouchDB:DbName"];
- _couchDbUser = this._configuration["CouchDB:User"];
- }
-
- public async Task<HttpClientResponse> DeleteDocumentAsync(string id, string rev)
- {
- HttpClientResponse response = new HttpClientResponse();
- var dbClient = DbHttpClient();
-
-
- var dbResult = await dbClient.DeleteAsync(_couchDbName + "/" + id + "?rev=" + rev);
-
- if (dbResult.IsSuccessStatusCode)
- {
- response.IsSuccess = true;
- response.SuccessContentObject = await dbResult.Content.ReadAsStringAsync();
- }
- else
- {
- response.IsSuccess = false;
- response.FailedReason = dbResult.ReasonPhrase;
- }
- return response;
- }
-
- public async Task<HttpClientResponse> GetDocumentAsync(string id)
- {
- HttpClientResponse response = new HttpClientResponse();
- var dbClient = DbHttpClient();
-
-
- var dbResult = await dbClient.GetAsync(_couchDbName + "/" + id);
-
- if (dbResult.IsSuccessStatusCode)
- {
- response.IsSuccess = true;
- response.SuccessContentObject = await dbResult.Content.ReadAsStringAsync();
- }
- else
- {
- response.IsSuccess = false;
- response.FailedReason = dbResult.ReasonPhrase;
- }
- return response;
- }
-
- public async Task<HttpClientResponse> PostDocumentAsync(EnrollCourse enrollCourse)
- {
- HttpClientResponse response = new HttpClientResponse();
- var dbClient = DbHttpClient();
- var jsonData = JsonConvert.SerializeObject(enrollCourse);
- var httpContent = new StringContent(jsonData, Encoding.UTF8, "application/json");
-
-
- var postResult = await dbClient.PostAsync(_couchDbName, httpContent).ConfigureAwait(true);
-
- if (postResult.IsSuccessStatusCode)
- {
- response.IsSuccess = true;
- response.SuccessContentObject = await postResult.Content.ReadAsStringAsync();
- }
- else
- {
- response.IsSuccess = false;
- response.FailedReason = postResult.ReasonPhrase;
- }
- return response;
- }
-
- public async Task<HttpClientResponse> PutDocumentAsync(UpdateEnroll update)
- {
- HttpClientResponse response = new HttpClientResponse();
- var dbClient = DbHttpClient();
- var updateToDb = new
- {
- update.Name,
- update.EmailAddress,
- update.CourseName,
- update.EnrolledOn,
- update.UpdatedOn
- };
- var jsonData = JsonConvert.SerializeObject(updateToDb);
- var httpContent = new StringContent(jsonData, Encoding.UTF8, "application/json");
-
-
- var putResult = await dbClient.PutAsync(_couchDbName + "/" +
- update.Id +
- "?rev=" + update.Rev, httpContent).ConfigureAwait(true);
-
- if (putResult.IsSuccessStatusCode)
- {
- response.IsSuccess = true;
- response.SuccessContentObject = await putResult.Content.ReadAsStringAsync();
- }
- else
- {
- response.IsSuccess = false;
- response.FailedReason = putResult.ReasonPhrase;
- }
- return response;
- }
-
- private HttpClient DbHttpClient()
- {
- var httpClient = this._clientFactory.CreateClient();
- httpClient.DefaultRequestHeaders.Accept.Clear();
- httpClient.DefaultRequestHeaders.Clear();
-
- httpClient.BaseAddress = new Uri(_couchDbUrl);
- var dbUserByteArray = Encoding.ASCII.GetBytes(_couchDbUser);
- httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(dbUserByteArray));
- return httpClient;
- }
- }
Step 5 - Register Repository into stratup.cs
Add below code into ConfigureServices method.
- services.AddHttpClient();
- services.AddTransient<ICouchRepository, CouchRepository>();
Step 6 - Modify API Controller to take the input from user and call respective repository methods
Let's modify the controller code as like below.
- [Route("api/[controller]")]
- [ApiController]
- public class CourseController : ControllerBase
- {
- private readonly ILogger<CourseController> _logger;
- private readonly ICouchRepository _couchRepository;
- public CourseController(ILogger<CourseController> logger, ICouchRepository couchRepository)
- {
- _logger = logger;
- _couchRepository = couchRepository;
- }
-
- [HttpGet("{id}")]
- public async Task<IActionResult> Get(string id)
- {
- var result = await _couchRepository.GetDocumentAsync(id);
- if (result.IsSuccess)
- {
- var sResult = JsonConvert.DeserializeObject<EnrollInfo>(result.SuccessContentObject);
- return new OkObjectResult(sResult);
- }
- return new NotFoundObjectResult("NotFound");
- }
-
- [HttpPost]
- public async Task<IActionResult> PostAsync([FromBody] EnrollCourse enrollCourse)
- {
- enrollCourse.EnrolledOn = DateTime.Now;
- var result = await _couchRepository.PostDocumentAsync(enrollCourse);
- if (result.IsSuccess)
- {
- var sResult = JsonConvert.DeserializeObject<SavedResult>(result.SuccessContentObject);
- return new CreatedResult("Success", sResult);
- }
-
- return new UnprocessableEntityObjectResult(result.FailedReason);
- }
-
- [HttpPut]
- public async Task<IActionResult> PutAsync([FromBody] UpdateEnroll enrollCourse)
- {
- var httpClientResponse = await _couchRepository.GetDocumentAsync(enrollCourse.Id);
-
- if (httpClientResponse.IsSuccess)
- {
- EnrollInfo existingInfo = JsonConvert.DeserializeObject<EnrollInfo>(httpClientResponse.SuccessContentObject);
- enrollCourse.Rev = existingInfo.Rev;
- enrollCourse.Name = String.IsNullOrEmpty(enrollCourse.Name) ? existingInfo.Name : enrollCourse.Name;
- enrollCourse.CourseName = String.IsNullOrEmpty(enrollCourse.CourseName) ? existingInfo.CourseName : enrollCourse.CourseName;
- enrollCourse.EmailAddress = String.IsNullOrEmpty(enrollCourse.EmailAddress) ? existingInfo.EmailAddress : enrollCourse.EmailAddress;
- enrollCourse.EnrolledOn = enrollCourse.EnrolledOn == DateTime.MinValue ? existingInfo.EnrolledOn : enrollCourse.EnrolledOn;
- enrollCourse.UpdatedOn = enrollCourse.UpdatedOn == DateTime.MinValue ? DateTime.Now : enrollCourse.UpdatedOn;
-
- var result = await _couchRepository.PutDocumentAsync(enrollCourse);
- if (httpClientResponse.IsSuccess)
- {
- var sResult = JsonConvert.DeserializeObject<SavedResult>(result.SuccessContentObject);
- return new CreatedResult("Success", sResult);
- }
- return new UnprocessableEntityObjectResult(result.FailedReason);
- }
-
- return new UnprocessableEntityObjectResult(httpClientResponse.FailedReason);
- }
-
- [HttpDelete("{id}")]
- public async Task<IActionResult> DeleteAsync(string id)
- {
- var httpClientResponse = await _couchRepository.GetDocumentAsync(id);
-
- if (httpClientResponse.IsSuccess)
- {
- EnrollInfo sResult = JsonConvert.DeserializeObject<EnrollInfo>(httpClientResponse.SuccessContentObject);
- httpClientResponse = await _couchRepository.DeleteDocumentAsync(id, sResult.Rev);
- if (httpClientResponse.IsSuccess)
- {
- return new OkObjectResult("Success");
- }
- return new NotFoundObjectResult("Failed to delete the document");
- }
-
- return new NotFoundObjectResult("Document not exists.");
- }
- }
Step 7 - Test from Postman and validate against CouchDB
Excellent! Now we are ready to test from Postman and validate the same against CouchDB to see the changes are reflecting or not.
I logged in into my couch db server http://127.0.0.1:5984/_utils/#login and check the document.
Create Document
Postman vs CouchDB Snapshot
Get Document
Postman Snapshot
Update Document
Postman vs CouchDB Snapshot
Delete Document
Postman vs CouchDB Snapshot
Conclusion
In this article, we have seen how to do CRUD operations for a document into CouchDB via Rest Call from a .NET core application. Hope you find this information useful!
Happy Learning!