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.
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.
Once file upload is successful, let us validate all the information against CouchDB.
The below code snippet contains models, action method and Rest Call details for this file upload operation,
Models
SaveAttachment.cs
- public class SaveAttachment {
- [Json Ignore]
- public string Id {
- get;
- set;
- }
- [Json Ignore]
- public string Rev {
- get;
- set;
- }
- public string Name {
- get;
- set;
- }
- public string EmailAddress {
- get;
- set;
- }
- public string CourseName {
- get;
- set;
- }
- public string Comments {
- get;
- set;
- }
- [Json Ignore]
- public IFormFile File {
- get;
- set;
- }
- public string FileName {
- get;
- set;
- }
- public string FileExtension {
- get;
- set;
- }
- [Json Ignore]
- public byte[] AttachmentData {
- get;
- set;
- }
- public string CreatedOn {
- get;
- set;
- }
- }
Action Method
HomeController.cs
- private readonly ILogger < HomeController > _logger;
- private readonlystring _couchDbUrl;
- private readonlystring _couchDbName;
- private readonlystring _couchDbUser;
- private readonly IConfiguration _configuration;
- private readonly IHttpClientFactory _clientFactory;
- public Home Controller(ILogger < HomeController > logger, IConfiguration configuration, IHttpClientFactory clientFactory) {
- _logger = logger;
- _configuration = configuration;
- _clientFactory = clientFactory;
- _couchDbUrl = this._configuration["CouchDB:URL"];
- _couchDbName = this._configuration["CouchDB:DbName"];
- _couchDbUser = this._configuration["CouchDB:User"];
- }
- [HttpPost]
- public async Task < IActionResult > SaveAttachment([FromForm] SaveAttachment attachment) {
- if (attachment.Id == null) {
-
- if (attachment == null || attachment.File.Length == 0) return Content("file not selected");
- var ms = new MemoryStream();
- attachment.File.OpenReadStream().CopyTo(ms);
- byte[] fileBytes = ms.ToArray();
- attachment.FileName = attachment.File.FileName;
- attachment.AttachmentData = fileBytes;
- attachment.FileExtension = System.IO.Path.GetExtension(attachment.File.FileName).Replace(".", "").ToUpper();
- attachment.CreatedOn = DateTime.Now.ToString();
- var result = await AddAttachment(attachment);
- if (result.IsSuccess) {
- return RedirectToAction("Index");
- }
- } else {
-
- var docData = await GetAttachmentByteArray(attachment.Id, attachment.FileName);
- attachment.AttachmentData = docData.Result;
- attachment.CreatedOn = DateTime.Now.ToString();
- var result = await UpdateAttachment(attachment);
- if (result.IsSuccess) {
- return RedirectToAction("Index");
- }
- }
- return View();
- }
Rest Call to CouchDB
- 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;
- }
Add Attachment Rest Call
- private async Task < dynamic > AddAttachment(SaveAttachment attachInfo) {
- var dbClient = DbHttpClient();
- var jsonData = JsonConvert.SerializeObject(attachInfo);
- var httpContent = new StringContent(jsonData, Encoding.UTF8, "application/json");
- var postResult = await dbClient.PostAsync(_couchDbName, httpContent).ConfigureAwait(true);
- var result = await postResult.Content.ReadAsStringAsync();
- var savedInfo = JsonConvert.DeserializeObject < SavedResult > (result);
- var requestContent = new ByteArrayContent(attachInfo.AttachmentData);
- requestContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
- var putResult = await dbClient.PutAsync(_couchDbName + "/" + savedInfo.Id + "/" + attachInfo.FileName + "?rev=" + savedInfo.Rev, requestContent);
- if (putResult.IsSuccessStatusCode) {
- return new {
- IsSuccess = true, Result = await putResult.Content.ReadAsStringAsync()
- };
- }
- return new {
- IsSuccess = false, Result = putResult.ReasonPhrase
- };
- }
Update attachment Rest Call
- public async Task < dynamic > UpdateAttachment(SaveAttachment attachInfo) {
- var dbClient = DbHttpClient();
- var jsonData = JsonConvert.SerializeObject(attachInfo);
- var httpContent = new StringContent(jsonData, Encoding.UTF8, "application/json");
- var postResult = await dbClient.PutAsync(_couchDbName + "/" + attachInfo.Id + "?rev=" + attachInfo.Rev, httpContent);
- var result = await postResult.Content.ReadAsStringAsync();
- var savedInfo = Json Convert.DeserializeObject < SavedResult > (result);
- attachInfo.Id = savedInfo.Id;
- attachInfo.Rev = savedInfo.Rev;
- var requestContent = new ByteArrayContent(attachInfo.AttachmentData);
- requestContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
- var putResult = await dbClient.PutAsync(_couchDbName + "/" + savedInfo.Id + "/" + attachInfo.FileName + "?rev=" + savedInfo.Rev, requestContent);
- if (putResult.IsSuccessStatusCode) {
- return new {
- IsSuccess = true, Result = await putResult.Content.ReadAsStringAsync()
- };
- }
- return new {
- IsSuccess = false, Result = putResult.ReasonPhrase
- };
- }
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.
If we run the selector query into CouchDB, will get the same result.
The below code snippet contains models, action method and Rest Call details for this find attachment operation,
Models
- public class GetAttachments {
- [Json Property("Docs")]
- public List < AttachmentInfo > Docs {
- get;
- set;
- }
- }
- public class AttachmentInfo {
- [Json Property("_id")]
- public string Id {
- get;
- set;
- }
- [Json Property("_rev")]
- public string Rev {
- get;
- set;
- }
- public string Name {
- get;
- set;
- }
- public string EmailAddress {
- get;
- set;
- }
- public string FileExtension {
- get;
- set;
- }
- public string FileName {
- get;
- set;
- }
- public string CourseName {
- get;
- set;
- }
- public string Comments {
- get;
- set;
- }
- }
Action Method
- public async Task < IActionResult > Index() {
- GetAttachments getInfo = new GetAttachments();
- var findResult = await FindAttachments();
- if (findResult.IsSuccess && findResult.Result != null) {
- getInfo = JsonConvert.DeserializeObject < GetAttachments > (findResult.Result);
- }
- return View(getInfo.Docs);
- }
Rest Call to CouchDB
- private async Task < dynamic > FindAttachments() {
- var dbClient = DbHttpClient();
- var docSelector = AttachmentsSelector();
- var jsonQuery = JsonConvert.SerializeObject(docSelector);
- var httpContent = new StringContent(jsonQuery, Encoding.UTF8, "application/json");
- var dbResult = await dbClient.PostAsync(_couchDbName + "/_find", httpContent);
- if (dbResult.IsSuccessStatusCode) {
- return new {
- IsSuccess = true, Result = await dbResult.Content.ReadAsStringAsync()
- };
- }
- return new {
- IsSuccess = false, Result = dbResult.ReasonPhrase
- };
- }
- private dynamic AttachmentsSelector() {
- FileTypeIn typeList = new FileTypeIn() {
- TypesIn = new List < string > () {
- "PDF",
- "PNG",
- "JPEG",
- "JPG"
- }
- };
- AttachmentExists isExists = new AttachmentExists() {
- Exists = true
- };
- var selector = new {
- selector = new {
- FileExtension = typeList,
- _attachments = isExists
- },
- fields = new ArrayList {
- "_id",
- "_rev",
- "Name",
- "EmailAddress",
- "FileName",
- "CourseName",
- "Comments"
- }
- };
- return selector;
- }
- internal class AttachmentExists {
- [Json Property("$exists")]
- public bool Exists {
- get;
- set;
- }
- }
- internal class FileTypeIn {
- [Json Property("$in")]
- public List < String > TypesIn {
- get;
- set;
- }
- }
Case 3 - Download attachment
To download an uploaded file, on clicking of file name it will auto download in browser.
Below code snippet contains action method and Rest Call details for this download attachment operation,
Action Method
- public async Task < FileResult > DownloadFile(string id, string fileName) {
- var docData = await GetAttachmentByteArray(id, fileName);
- byte[] fileBytes = docData.Result;
- return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
- }
Rest Call to CouchDB
- public async Task < dynamic > GetAttachmentByteArray(string DocId, string AttName) {
- var dbClient = DbHttpClient();
- var dbResult = await dbClient.GetAsync(_couchDbName + "/" + DocId + "/" + AttName);
- if (dbResult.IsSuccessStatusCode) {
- return new {
- IsSuccess = true, Result = await dbResult.Content.ReadAsByteArrayAsync()
- };
- }
- return new {
- IsSuccess = false, Result = dbResult.ReasonPhrase
- };
- }
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.
Once update is successful, let us validate all the information against CouchDB.
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.
Once delete is successful, let us run selector query and check file is being deletedin CouchDB.
Below code snippet contains action method and Rest Call details for this delete attachment operation,
Action Method
- public async Task < IActionResult > DeleteAttachment(string id) {
- var httpClientResponse = await GetDocumentAsync(id);
- if (httpClientResponse.IsSuccess) {
- AttachmentInfo sResult = JsonConvert.DeserializeObject < AttachmentInfo > (httpClientResponse.Result);
- httpClientResponse = await DeleteDocumentAsync(id, sResult.Rev);
- if (httpClientResponse.IsSuccess) {
- ViewBag.DeleteMessage = sResult.FileName + " deleted successfully!";
- return RedirectToAction("Index");
- }
- return new NotFoundObjectResult("Failed to delete the file!");
- }
- return View();
- }
Rest Call to CouchDB
- private async Task < object > DeleteDocumentAsync(string id, string rev) {
- var dbClient = DbHttpClient();
- var dbResult = await dbClient.DeleteAsync(_couchDbName + "/" + id + "?rev=" + rev);
- if (dbResult.IsSuccessStatusCode) {
- return new {
- IsSuccess = true, Result = await dbResult.Content.ReadAsStringAsync()
- };
- }
- return new {
- IsSuccess = false, Result = dbResult.ReasonPhrase
- };
- }
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!