Command Palette
Search for a command to run...
Search for a command to run...
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.
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")
)
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; }
}
}
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; }
....
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;
}
}
}
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 });
}
}
}
}
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>
/ZohoItems/Index
