Displaying ZohoBooks Data on Your Website using Oauth2 and .NetCore 8.0
Prerequisites
You already have a ZohoBooks account and Organization ID
You have your ZohoBooks OAuth2 Client ID and Client Secret
You have generated your ZohoBooks Grant Token
You have generated your ZohoBooks Refresh Token (and initial Access Token)
You are displaying the ZohoBooks data on your private (logged in) website, not a public website.
Create and Populate a New Database Table for OAuth2 Token Values
There will be only 1 record in this table. The ACCESSTOKENTIMESTAMP will be populated whenever the ACCESSTOKEN is updated. The ACCESSTOKENEXPIRATION will be used to determine if the current ACCESSTOKEN has expired, so that a new token is requested and obtained.
CREATE TABLE "zohotokenstore" (
"ID" INTEGER NOT NULL UNIQUE,
"ACCESSTOKEN" TEXT,
"ACCESSTOKENTIMESTAMP" TEXT,
"ACCESSTOKENEXPIRATION" TEXT,
"ORGANIZATIONID" TEXT,
"REFRESHTOKEN" TEXT,
"CLIENTID" TEXT,
"CLIENTSECRET" TEXT,
"REDIRECTURL" TEXT,
"APIBASEURL" TEXT,
"REFRESHTOKENBASEURL" TEXT,
PRIMARY KEY("ID")
)
Create Token Store Model Class
The model class should have all of the above database columns defined within it. This model class will be referenced in the next step by the token store helper class.
using System.ComponentModel.DataAnnotations;
namespace Ecd.Web.Models.Zoho
{
public class TokenStore
{
[Key]
public int ID { get; set; }
public string? ACCESSTOKEN { get; set; }
public DateTime ACCESSTOKENTIMESTAMP { get; set; }
public DateTime ACCESSTOKENEXPIRATION { get; set; }
public string? ORGANIZATIONID { get; set; }
public string? REFRESHTOKEN { get; set; }
public string? CLIENTID { get; set; }
public string? CLIENTSECRET { get; set; }
public string? REDIRECTURL { get; set; }
public string? APIBASEURL { get; set; }
public string? REFRESHTOKENBASEURL { get; set; }
}
}
Add New Model to DB Context Class
This will allow reference to it within other classes including the helper and controller classes.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
......
//Zoho token store
modelBuilder.Entity<Ecd.Web.Models.Zoho.TokenStore>().ToTable("zohotokenstore");
....
}
''''
public DbSet<Ecd.Web.Models.Zoho.TokenStore> TokenStore { get; set; }
....
Create Token Store Helper Class
This helper will be called in the controller class that is retrieving data using the OAuth2 integration and generates a new access token with each call.
using Ecd.Web.Data;
using Ecd.Web.Models.Zoho;
using RestSharp;
using System.Text.Json;
namespace Ecd.Web.Helpers
{
public class ZohoTokenStoreHelper
{
public TokenStore getAccessToken(MainDbContext _context)
{
var grantType = "refresh_token";
string accessToken;
var model = new TokenStore();
model = _context.TokenStore.First(a => a.ID == 1);
if (DateTime.UtcNow >= model.ACCESSTOKENEXPIRATION)
{
var requestUrl = "/token?refresh_token=" + model.REFRESHTOKEN + "&client_id=" + model.CLIENTID + "&client_secret=" + model.CLIENTSECRET + "&redirect_uri=" + model.REDIRECTURL + "&grant_type=" + grantType;
var client = new RestClient("https://accounts.zoho.com/oauth/v2"); // Zoho Books API base URL
var request = new RestRequest(requestUrl, Method.Post); // Specify the endpoint and HTTP method
request.AddHeader("Content-Type", "application/data"); // Add authorization header with the access token
var response = client.Execute(request);
using (JsonDocument document = JsonDocument.Parse(response.Content))
{
JsonElement root = document.RootElement;
accessToken = "Zoho-oauthtoken " + root.GetProperty("access_token").GetString();
Console.WriteLine($"access_token: {accessToken}");
}
model.ACCESSTOKEN = accessToken;
model.ACCESSTOKENTIMESTAMP = DateTime.UtcNow;
model.ACCESSTOKENEXPIRATION = model.ACCESSTOKENTIMESTAMP.AddSeconds(3600);
;
_context.Update(model);
_context.SaveChangesAsync();
Console.WriteLine(response.Content);
}
return model;
}
}
}
Create Controller Class to Pass Zoho Item List to View
This helper will be called in all of the controller class that is retrieving data using the OAuth2 integration and generates a new access token with each call.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using RestSharp;
using Newtonsoft.Json;
using Ecd.Web.Data;
using Ecd.Web.Models.Zoho;
using Ecd.Web.Helpers;
using System.Text.Json.Nodes;
using System.Text.Json;
using System.Text;
namespace Ecd.Web.Controllers
{
[Authorize]
public class ZohoItemsController : Controller
{
private readonly MainDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public ZohoItemsController(MainDbContext context, IHttpContextAccessor httpContextAccessor)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
}
public IActionResult Index(string itemName)
{
if (!string.IsNullOrWhiteSpace(itemName))
{
ViewData["filter"] = itemName;
}
return View();
}
public async Task<IActionResult> LoadData(string itemName, int totalPages, int currentPage = 1)
{
var tokenHelper = new ZohoTokenStoreHelper();
var tokenStore = tokenHelper.getAccessToken(_context);
var access_token = tokenStore.ACCESSTOKEN;
var organization_id = tokenStore.ORGANIZATIONID;
string url_page_context;
string url_data;
if (!string.IsNullOrWhiteSpace(itemName))
{
url_page_context = "/items?organization_id=" + organization_id + "&status=active&name_contains=" + itemName + "&response_option=2";
url_data = "https://www.zohoapis.com/books/v3/items?organization_id=" + organization_id + "&status=active&name_contains=" + itemName + "&page=";
}
else
{
url_page_context = "/items?organization_id=" + organization_id + "&status=active&response_option=2";
url_data = "https://www.zohoapis.com/books/v3/items?organization_id=" + organization_id + "&status=active&page=";
}
Console.WriteLine($"itemName: {itemName}");
Console.WriteLine($"url_page_context: {url_page_context}");
Console.WriteLine($"url_data: {url_data}");
JsonNode jsonObject;
JsonArray? jsonArray;
Console.WriteLine($"tokenStore: {tokenStore}");
Console.WriteLine($"access_token: {access_token}");
Console.WriteLine($"organization_id: {organization_id}");
////////////////GET TOTAL PAGE COUNT/////////////////////////////////////////////////////
var client = new RestClient("https://www.zohoapis.com/books/v3"); // Zoho Books API base URL
// var request = new RestRequest("/items?organization_id=" + organization_id + "&status=active&name_contains="+ itemName + "&response_option=2", Method.Get); // Specify the endpoint and HTTP method
var request = new RestRequest(url_page_context, Method.Get); // Specify the endpoint and HTTP method
Console.WriteLine($"request: {request}");
request.AddHeader("Authorization", access_token);
request.AddHeader("Content-Type", "application/json");
var response = client.Execute(request);
dynamic responseData = JsonConvert.DeserializeObject(response.Content);
totalPages = responseData.page_context.total_pages;
Console.WriteLine($"response option 2 data: {responseData}");
Console.WriteLine($"totalPages: {totalPages}");
////////////////////////////GET FIRST PAGE DATA/////////////////////////////////////////
using (var httpClient = new HttpClient())
{
var httpRequest = new HttpRequestMessage(HttpMethod.Get, url_data + currentPage);
Console.WriteLine($"httpRequest: {httpRequest}");
httpRequest.Headers.Add("Authorization", access_token);
var httpResponse = await httpClient.SendAsync(httpRequest);
string content = await httpResponse.Content.ReadAsStringAsync();
JsonNode jsonObject1 = JsonObject.Parse(content)["items"];
JsonArray? jsonArray1 = jsonObject1?.AsArray();
////////////////GET REMAINING PAGES DATA/////////////////////////////////////////////////
while (currentPage < totalPages)
{
currentPage = currentPage + 1;
// httpClient = new HttpClient();
httpRequest = new HttpRequestMessage(HttpMethod.Get, url_data + "&page=" + currentPage);
httpRequest.Headers.Add("Authorization", access_token);
httpResponse = await httpClient.SendAsync(httpRequest);
content = await httpResponse.Content.ReadAsStringAsync();
jsonObject = JsonObject.Parse(content)["items"];
jsonArray = jsonObject?.AsArray();
////////////////ADD NEXT PAGE DATA TO FIRST PAGE DATA/////////////////////////////////
foreach (var element in jsonArray)
{
jsonArray1.Add(element?.DeepClone()); // DeepClone to avoid parent issues
}
}
return Json(new { data = jsonArray1 });
}
}
}
}
Create View to Display the Zoho Item List
The view will display the listing of Zoho Items using datatables. I have included several datatables style and jscript file references below, which normally are in my layout page.
@{
ViewData["Title"] = "Zoho Items";
Layout = "_Layout_logged_in";
}
<!-- Datatables -->
<link href="https://cdn.datatables.net/2.0.3/css/dataTables.bootstrap4.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.datatables.net/2.0.3/css/dataTables.dataTables.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.datatables.net/autofill/2.7.0/css/autoFill.dataTables.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.datatables.net/buttons/3.0.1/css/buttons.dataTables.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.datatables.net/colreorder/2.0.0/css/colReorder.dataTables.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.datatables.net/select/2.0.0/css/select.dataTables.min.css" rel="stylesheet" type="text/css" />
<!-- For the DataTables grid current version-->
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/2.0.3/js/dataTables.js"></script>
<script src="https://cdn.datatables.net/2.0.3/js/dataTables.bootstrap4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-ajax-unobtrusive/3.2.6/jquery.unobtrusive-ajax.min.js"></script>
<script src="https://cdn.datatables.net/select/2.0.0/js/dataTables.select.min.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.1/js/dataTables.buttons.min.js"></script>
<br />
<div id="container">
<div>
<h3>Zoho Books Items</h3>
</div>
<table id="itemsTable" class="table table-striped table-condensed table-bordered data-tables responsive" style="width:100%">
<thead>
<tr>
<th>ITEMID</th>
<th>SKU</th>
<th>NAME</th>
<th>SALESDESCRIPTION</th>
<th>PURCHASEDESCRIPTION</th>
<th>SALESRATE</th>
<th>PURCHASERATE</th>
<th>SOH</th>
</tr>
</thead>
</table>
</div>
<partial name="editSalesRate" />
<script>
$(document).ready(function () {
var itemsTable;
var successMessage = "The database has been updated successfully!";
var url = "/ZohoItems/LoadData";
itemsTable = $('#itemsTable').DataTable({
dom: 'Bpflrtipl',
retrieve: true,
processing: true, // for show progress bar
language: { processing: "<font style=color:#fa5729;font-weight:bold;font-size:x-large;>PROCESSING....</font>" },
//serverSide: true, // for process server side
filter: true, // this is for disable filter (search box)
ajax: {
url: url,
type: "POST",
datatype: "json"
},
columns: [
{ data: "item_id", name: "item_id", autoWidth: true, visible: false, "defaultContent": "---" },
{ data: "sku", name: "sku", autoWidth: true, "defaultContent": "---" },
{ data: "name", name: "name", autoWidth: true, "defaultContent": "---" },
{ data: "description", name: "description", autoWidth: true, "defaultContent": "---" },
{ data: "purchase_description", name: "purchase_description", autoWidth: true, "defaultContent": "---" },
{ data: "rate", name: "rate", autoWidth: true, "defaultContent": "---" },
{ data: "purchase_rate", name: "purchase_rate", autoWidth: true, "defaultContent": "---" },
{ data: "available_stock", name: "available_stock", autoWidth: true, "defaultContent": 0 }
]
});
});
</script>
Sample Website Display
/ZohoItems/Index

