Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core

Introduction

 
Your application may have functionality like uploading or downloading files like PDF, JPEG, PNG etc. into/from a document db like CouchDB. This article demonstrates how to handle attachments / files into CouchDB via HTTP-based REST API in an ASP.NET Core application.
 
Prerequisites
 
The CouchDB Server is properly configured and running at http://<<serveripaddress>>:5789/
 
Database is present in server where we perform CRUD operations, in my case it is “test”
 

Attachments/Files CRUD Operation to CouchDB In ASP.NET Core

 
To demonstrate end-to-end functionality of file/attachment upload, I created a sample web application in ASP.NET MVC where files will be uploaded into CouchDB. Sample application contains add, edit, delete and download features of a file along with some other user contents like name, email etc. Let us understand the step-by-step workflow for each operation.
 
Here is a quick glance at a  sample UI application once it is loaded to browser.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
Case 1 - Upload attachment
 
Let’s upload a PDF file with some other details like Name, Email address, Course name, comments etc. In the UI, entering sample info with a file to perform upload activity.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
Once file upload is successful, let us validate all the information against CouchDB.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
The below code snippet contains models, action method and Rest Call details for this file upload operation,
 
Models
 
SaveAttachment.cs
  1. public class SaveAttachment {  
  2.     [Json Ignore]  
  3.     public string Id {  
  4.         get;  
  5.         set;  
  6.     }  
  7.     [Json Ignore]  
  8.     public string Rev {  
  9.         get;  
  10.         set;  
  11.     }  
  12.     public string Name {  
  13.         get;  
  14.         set;  
  15.     }  
  16.     public string EmailAddress {  
  17.         get;  
  18.         set;  
  19.     }  
  20.     public string CourseName {  
  21.         get;  
  22.         set;  
  23.     }  
  24.     public string Comments {  
  25.         get;  
  26.         set;  
  27.     }  
  28.     [Json Ignore]  
  29.     public IFormFile File {  
  30.         get;  
  31.         set;  
  32.     }  
  33.     public string FileName {  
  34.         get;  
  35.         set;  
  36.     }  
  37.     public string FileExtension {  
  38.         get;  
  39.         set;  
  40.     }  
  41.     [Json Ignore]  
  42.     public byte[] AttachmentData {  
  43.         get;  
  44.         set;  
  45.     }  
  46.     public string CreatedOn {  
  47.         get;  
  48.         set;  
  49.     }  
  50. }  
Action Method
 
HomeController.cs
  1. private readonly ILogger < HomeController > _logger;  
  2. private readonlystring _couchDbUrl;  
  3. private readonlystring _couchDbName;  
  4. private readonlystring _couchDbUser;  
  5. private readonly IConfiguration _configuration;  
  6. private readonly IHttpClientFactory _clientFactory;  
  7. public Home Controller(ILogger < HomeController > logger, IConfiguration configuration, IHttpClientFactory clientFactory) {  
  8.         _logger = logger;  
  9.         _configuration = configuration;  
  10.         _clientFactory = clientFactory;  
  11.         _couchDbUrl = this._configuration["CouchDB:URL"];  
  12.         _couchDbName = this._configuration["CouchDB:DbName"];  
  13.         _couchDbUser = this._configuration["CouchDB:User"];  
  14.     }  
  15.     [HttpPost]  
  16. public async Task < IActionResult > SaveAttachment([FromForm] SaveAttachment attachment) {  
  17.     if (attachment.Id == null) {  
  18.         // Insert new attachment  
  19.         if (attachment == null || attachment.File.Length == 0) return Content("file not selected");  
  20.         var ms = new MemoryStream();  
  21.         attachment.File.OpenReadStream().CopyTo(ms);  
  22.         byte[] fileBytes = ms.ToArray();  
  23.         attachment.FileName = attachment.File.FileName;  
  24.         attachment.AttachmentData = fileBytes;  
  25.         attachment.FileExtension = System.IO.Path.GetExtension(attachment.File.FileName).Replace(".""").ToUpper();  
  26.         attachment.CreatedOn = DateTime.Now.ToString();  
  27.         var result = await AddAttachment(attachment);  
  28.         if (result.IsSuccess) {  
  29.             return RedirectToAction("Index");  
  30.         }  
  31.     } else {  
  32.         // Update existing attachment  
  33.         var docData = await GetAttachmentByteArray(attachment.Id, attachment.FileName);  
  34.         attachment.AttachmentData = docData.Result;  
  35.         attachment.CreatedOn = DateTime.Now.ToString();  
  36.         var result = await UpdateAttachment(attachment);  
  37.         if (result.IsSuccess) {  
  38.             return RedirectToAction("Index");  
  39.         }  
  40.     }  
  41.     return View();  
  42. }  
Rest Call to CouchDB
  1. private HttpClient DbHttpClient() {  
  2.     var httpClient = this._clientFactory.CreateClient();  
  3.     httpClient.DefaultRequestHeaders.Accept.Clear();  
  4.     httpClient.DefaultRequestHeaders.Clear();  
  5.     httpClient.BaseAddress = new Uri(_couchDbUrl);  
  6.     var dbUserByteArray = Encoding.ASCII.GetBytes(_couchDbUser);  
  7.     httpClient.DefaultRequestHeaders.Add("Authorization""Basic " + Convert.ToBase64String(dbUserByteArray));  
  8.     return httpClient;  
  9. }  
Add Attachment Rest Call
  1. private async Task < dynamic > AddAttachment(SaveAttachment attachInfo) {  
  2.     var dbClient = DbHttpClient();  
  3.     var jsonData = JsonConvert.SerializeObject(attachInfo);  
  4.     var httpContent = new StringContent(jsonData, Encoding.UTF8, "application/json");  
  5.     var postResult = await dbClient.PostAsync(_couchDbName, httpContent).ConfigureAwait(true);  
  6.     var result = await postResult.Content.ReadAsStringAsync();  
  7.     var savedInfo = JsonConvert.DeserializeObject < SavedResult > (result);  
  8.     var requestContent = new ByteArrayContent(attachInfo.AttachmentData);  
  9.     requestContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");  
  10.     var putResult = await dbClient.PutAsync(_couchDbName + "/" + savedInfo.Id + "/" + attachInfo.FileName + "?rev=" + savedInfo.Rev, requestContent);  
  11.     if (putResult.IsSuccessStatusCode) {  
  12.         return new {  
  13.             IsSuccess = true, Result = await putResult.Content.ReadAsStringAsync()  
  14.         };  
  15.     }  
  16.     return new {  
  17.         IsSuccess = false, Result = putResult.ReasonPhrase  
  18.     };  
  19. }  
Update attachment Rest Call
  1. public async Task < dynamic > UpdateAttachment(SaveAttachment attachInfo) {  
  2.     var dbClient = DbHttpClient();  
  3.     var jsonData = JsonConvert.SerializeObject(attachInfo);  
  4.     var httpContent = new StringContent(jsonData, Encoding.UTF8, "application/json");  
  5.     var postResult = await dbClient.PutAsync(_couchDbName + "/" + attachInfo.Id + "?rev=" + attachInfo.Rev, httpContent);  
  6.     var result = await postResult.Content.ReadAsStringAsync();  
  7.     var savedInfo = Json Convert.DeserializeObject < SavedResult > (result);  
  8.     attachInfo.Id = savedInfo.Id;  
  9.     attachInfo.Rev = savedInfo.Rev;  
  10.     var requestContent = new ByteArrayContent(attachInfo.AttachmentData);  
  11.     requestContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");  
  12.     var putResult = await dbClient.PutAsync(_couchDbName + "/" + savedInfo.Id + "/" + attachInfo.FileName + "?rev=" + savedInfo.Rev, requestContent);  
  13.     if (putResult.IsSuccessStatusCode) {  
  14.         return new {  
  15.             IsSuccess = true, Result = await putResult.Content.ReadAsStringAsync()  
  16.         };  
  17.     }  
  18.     return new {  
  19.         IsSuccess = false, Result = putResult.ReasonPhrase  
  20.     };  
  21. }  
Case 2 - Find attachments
 
Once file is uploaded, we need to find those files based on the some selector query into CouchDB.
 
Here, in the UI all the files that uploaded can be retrieved and displayed.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
If we run the selector query into CouchDB, will get the same result.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
The below code snippet contains models, action method and Rest Call details for this find attachment operation,
 
Models
  1. public class GetAttachments {  
  2.     [Json Property("Docs")]  
  3.     public List < AttachmentInfo > Docs {  
  4.         get;  
  5.         set;  
  6.     }  
  7. }  
  8. public class AttachmentInfo {  
  9.     [Json Property("_id")]  
  10.     public string Id {  
  11.         get;  
  12.         set;  
  13.     }  
  14.     [Json Property("_rev")]  
  15.     public string Rev {  
  16.         get;  
  17.         set;  
  18.     }  
  19.     public string Name {  
  20.         get;  
  21.         set;  
  22.     }  
  23.     public string EmailAddress {  
  24.         get;  
  25.         set;  
  26.     }  
  27.     public string FileExtension {  
  28.         get;  
  29.         set;  
  30.     }  
  31.     public string FileName {  
  32.         get;  
  33.         set;  
  34.     }  
  35.     public string CourseName {  
  36.         get;  
  37.         set;  
  38.     }  
  39.     public string Comments {  
  40.         get;  
  41.         set;  
  42.     }  
  43. }  
Action Method
  1. public async Task < IActionResult > Index() {  
  2.     GetAttachments getInfo = new GetAttachments();  
  3.     var findResult = await FindAttachments();  
  4.     if (findResult.IsSuccess && findResult.Result != null) {  
  5.         getInfo = JsonConvert.DeserializeObject < GetAttachments > (findResult.Result);  
  6.     }  
  7.     return View(getInfo.Docs);  
  8. }  
Rest Call to CouchDB
  1. private async Task < dynamic > FindAttachments() {  
  2.     var dbClient = DbHttpClient();  
  3.     var docSelector = AttachmentsSelector();  
  4.     var jsonQuery = JsonConvert.SerializeObject(docSelector);  
  5.     var httpContent = new StringContent(jsonQuery, Encoding.UTF8, "application/json");  
  6.     var dbResult = await dbClient.PostAsync(_couchDbName + "/_find", httpContent);  
  7.     if (dbResult.IsSuccessStatusCode) {  
  8.         return new {  
  9.             IsSuccess = true, Result = await dbResult.Content.ReadAsStringAsync()  
  10.         };  
  11.     }  
  12.     return new {  
  13.         IsSuccess = false, Result = dbResult.ReasonPhrase  
  14.     };  
  15. }  
  16. private dynamic AttachmentsSelector() {  
  17.     FileTypeIn typeList = new FileTypeIn() {  
  18.         TypesIn = new List < string > () {  
  19.             "PDF",  
  20.             "PNG",  
  21.             "JPEG",  
  22.             "JPG"  
  23.         }  
  24.     };  
  25.     AttachmentExists isExists = new AttachmentExists() {  
  26.         Exists = true  
  27.     };  
  28.     var selector = new {  
  29.         selector = new {  
  30.                 FileExtension = typeList,  
  31.                     _attachments = isExists  
  32.             },  
  33.             fields = new ArrayList {  
  34.                 "_id",  
  35.                 "_rev",  
  36.                 "Name",  
  37.                 "EmailAddress",  
  38.                 "FileName",  
  39.                 "CourseName",  
  40.                 "Comments"  
  41.             }  
  42.     };  
  43.     return selector;  
  44. }  
  45. internal class AttachmentExists {  
  46.     [Json Property("$exists")]  
  47.     public bool Exists {  
  48.         get;  
  49.         set;  
  50.     }  
  51. }  
  52. internal class FileTypeIn {  
  53.     [Json Property("$in")]  
  54.     public List < String > TypesIn {  
  55.         get;  
  56.         set;  
  57.     }  
  58. }  
Case 3 - Download attachment
 
To download an uploaded file, on clicking of file name it will auto download in browser.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
Below code snippet contains action method and Rest Call details for this download attachment operation,
 
Action Method
  1. public async Task < FileResult > DownloadFile(string id, string fileName) {  
  2.     var docData = await GetAttachmentByteArray(id, fileName);  
  3.     byte[] fileBytes = docData.Result;  
  4.     return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);  
  5. }  
Rest Call to CouchDB
  1. public async Task < dynamic > GetAttachmentByteArray(string DocId, string AttName) {  
  2.     var dbClient = DbHttpClient();  
  3.     var dbResult = await dbClient.GetAsync(_couchDbName + "/" + DocId + "/" + AttName);  
  4.     if (dbResult.IsSuccessStatusCode) {  
  5.         return new {  
  6.             IsSuccess = true, Result = await dbResult.Content.ReadAsByteArrayAsync()  
  7.         };  
  8.     }  
  9.     return new {  
  10.         IsSuccess = false, Result = dbResult.ReasonPhrase  
  11.     };  
  12. }  
Case 4 - Update document that contains attachment
 
Let’s update the document to CouchDB which contains attachment. Here, I am updating email and course name; i.e. enroll to on clicking on Edit button from grid.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
Once update is successful, let us validate all the information against CouchDB.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
The corresponding action method and Rest Call details for this update operation are already mentioned on Case 1 - Upload attachment.
 
Case 5 - Delete attachment
 
As you have seen we have two records, one is being deleted on clicking on Delete button in the grid.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
Once delete is successful, let us run selector query and check file is being deletedin CouchDB.
 
Handling Attachments/Files Into CouchDB Via Rest API In ASP.NET Core 
 
Below code snippet contains action method and Rest Call details for this delete attachment operation,
 
Action Method
  1. public async Task < IActionResult > DeleteAttachment(string id) {  
  2.     var httpClientResponse = await GetDocumentAsync(id);  
  3.     if (httpClientResponse.IsSuccess) {  
  4.         AttachmentInfo sResult = JsonConvert.DeserializeObject < AttachmentInfo > (httpClientResponse.Result);  
  5.         httpClientResponse = await DeleteDocumentAsync(id, sResult.Rev);  
  6.         if (httpClientResponse.IsSuccess) {  
  7.             ViewBag.DeleteMessage = sResult.FileName + " deleted successfully!";  
  8.             return RedirectToAction("Index");  
  9.         }  
  10.         return new NotFoundObjectResult("Failed to delete the file!");  
  11.     }  
  12.     return View();  
  13. }  
Rest Call to CouchDB
  1. private async Task < object > DeleteDocumentAsync(string id, string rev) {  
  2.     var dbClient = DbHttpClient();  
  3.     var dbResult = await dbClient.DeleteAsync(_couchDbName + "/" + id + "?rev=" + rev);  
  4.     if (dbResult.IsSuccessStatusCode) {  
  5.         return new {  
  6.             IsSuccess = true, Result = await dbResult.Content.ReadAsStringAsync()  
  7.         };  
  8.     }  
  9.     return new {  
  10.         IsSuccess = false, Result = dbResult.ReasonPhrase  
  11.     };  
  12. }  

Conclusion

 
In this article, we have seen how to upload an attachment like PDF, JPEG, PNG into CouchDB via REST API. In addition, we have seen how to find attachments using selector based on certain conditions. We have performed update and delete operations on file attachments associated with the document. A sample application is attached to this article for reference purposes. I hope you find this information useful! Happy Learning!