Skip to main content

Command Palette

Search for a command to run...

Displaying ZohoBooks Data on Your Website using Oauth2 and .NetCore 8.0

Updated
6 min read

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