Cloud Services Example 5: Get Interview Files

To display correctly, the HotDocs Interview requires additional Interview Files when the interview is initialised. These include:

  • Interview Definition Files – a JavaScript file that defines the structures and content of the interview
  • Image Files – any image files used in Image-style Dialog Elements

This example takes you through the process of retrieving Interview Files from a HotDocs Template Package File uploaded to Cloud Services.

In this example you will:

  • Create a new MVC project in Visual Studio
  • Retrieve an Interview Definition File from a HotDocs Template Package File in Cloud Services

Full source code for this example is available at the bottom of the page.

Example Source Code on GitHub

The CloudServicesAPIExample5InterviewFile example project is available on GitHub, in the HotDocs-Samples repository.

Requirements

Displaying an interview in Cloud Services requires that a HotDocs Template Package File is uploaded to the service. For information on uploading a Template Package File, see Example 1: Uploading a Template.

1. Create a new MVC Project in Visual Studio

Before you start, create a new MVC Application project for this example named CloudServicesAPIExample5InterviewFile. You will gradually add code to this project until you have a working example.

2. Reference the DLLs

In the project, edit Program.cs. Add the following using statements at the beginning of the file:

  • using System.Net.Http;

3. Add Subscriber ID and Other Information

In the Main method of Program.cs, add variables with your Cloud Services Subscriber information:

var subscriberId = "example-subscriber-id";
var signingKey = "example-signing-key"; 

These items are:

  • Subscriber ID – your ID for signing in to Cloud Services. This is typically the name of your organisation.
  • Signing Key – the unique key associated with your user account. This is used for signing requests made to Cloud Services.

The Subscriber ID and Signing Key are provided when you sign up for Cloud Services. If you do not have this information, please contact your HotDocs Sales Representative.

You will use these items later when calculating the HMAC and creating the HTTP request to access Cloud Services.

3.2 Add Data Required to Retrieve the Package File

Next, add a Package ID variable:

var packageId = "ed40775b-5e7d-4a51-b4d1-32bf9d6e9e29";

This is the unique ID (in this case, a GUID) that identifies the HotDocs Template Package File which contains the required file.

Add the required Filename:

var fileName = "HelloWorld.docx.js";

This is the name of the file requested by the interview. In this example, it is an Interview Definition File. The Interview Definition File is a JavaScript file used when rendering the interview. It defines the interview layout and content.

Finally, add the following variables and values:

var timestamp = DateTime.UtcNow;
var billingRef = "ExampleBillingRef";

These items are:

  • Timestamp (DateTime) – the time at which the assembly was started. This is used by Cloud Services to check that an assembly request has not expired.
  • Billing Reference (string) – a custom logging message. This optional parameter is typically used to identify the user making the request, for billing purposes.

You will use these items later when calculating the HMAC and creating the HTTP request to access Cloud Services.

4. Calculate the HMAC

Every request made to Cloud Services must use an HMAC to verify the request. To calculate the HMAC, you must add two new methods to Program.cs:

  • CalculateHMAC – performs the HMAC calculation and returns the HMAC string.
  • CanonicalizeParameters – takes the parameters for the request and converts them into the standardised format required to calculate the HMAC.

4.1 Create the CalculateHMAC method

To calculate the HMAC, add a new method, CalculateHMAC:

public static string CalculateHMAC(string signingKey, params object[] parametersList)
{
        var key = Encoding.UTF8.GetBytes(signingKey);
        var parameterStringToSign = Canonicalize(parametersList);
        var parameterBytesToSign = Encoding.UTF8.GetBytes(parameterStringToSign);
        byte[] signature;
        using (var hmac = new System.Security.Cryptography.HMACSHA1(key))
        {
                signature = hmac.ComputeHash(parameterBytesToSign);
        }
        return Convert.ToBase64String(signature);
}

This method calculates the HMAC by:

  • Converting the signing key to a byte array
  • Canonicalizing the parameters
  • Converting the canonicalized parameters to a byte array
  • Creating a new HMAC using the converted signing key and converted canonicalized parameters.
  • Returning the new HMAC signature as a string.

For this method to work, you must create a second method to canonicalize the parameters used to create the HMAC, before the HMAC is calculated.

4.2 Create the Canonicalize method

The canonicalize method takes all of the parameters required to calculate the HMAC, converts them to appropriately-formatted strings, and joins the strings together. The complete method looks as follows:

public static string Canonicalize(params object[] paramList)
{
        if (paramList == null)
        {
                throw new ArgumentNullException();
        }
        var strings = paramList.Select(param =>
        {
                if (param is string || param is int || param is Enum || param is bool)
                {
                        return param.ToString();
                }
                if (param is DateTime)
                {
                        DateTime utcTime = ((DateTime)param).ToUniversalTime();
                        return utcTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
                }
          
                if (param is Dictionary<string, string>)
                {
                        var sorted = ((Dictionary<string, string>)param).OrderBy(kv => kv.Key);
                        var stringified = sorted.Select(kv => kv.Key + "=" + kv.Value).ToArray();
                        return string.Join("\n", stringified);
                }
        
                return "";
        });
        return string.Join("\n", strings.ToArray());
}  

4.3 Retrieve the CalculateHMAC method

Add the following line to the Main method, to retrieve the calculated HMAC:

var hmac = CalculateHMAC(signingKey, timestamp, subscriberId, packageId, "", false, "", format, "");

To calculate the HMAC for retrieving an interview file, you must pass in the following parameters:

  • signingKey (string) – the unique key associated with your user account, used for signing requests to Cloud Services.
  • timestamp (DateTime) – the time at which the assembly was started. This is used by Cloud Services to check that an assembly request has not expired.
  • subscriberId (string) – your ID for signing in to Cloud Services.
  • packageId (string) –  the unique ID of the Template Package used for the request.
  • fileName (string) – the name of the file to retrieve from the HotDocs Template Package File (.hdpkg) stored in Cloud Services.
  • billingRef (string) – a custom logging message. This is typically used to identify the user making the request, for billing purposes.

Once the HMAC has been calculated, you can use it when retrieving the interview file from Cloud Services.

5. Create the Interview File Request

5.1 Create the CreateHttpRequestMessage method

Create a new method, CreateHttpRequestMessage:

private static HttpRequestMessage CreateHttpRequestMessage(string hmac, string subscriberId, string packageId, string fileName, DateTime timestamp, string billingRef)
{
}

This method takes the following parameters:

  • hmac – the HMAC calculated in the step above.
  • subscriberId – your ID for signing in to Cloud Services.
  • packageId – the unique ID of the Template Package used for the request.
  • fileName – the name of the Interview File to be retrieved from the Template Package File,
  • timestamp – the time at which the assembly was started.

5.2 Create the Get Interview File URL

The URL for the GetInterviewFile request uses the following format:

http://{endpoint}/hdcs/interviewfile/{subscriberID}/{packageID}?filename={fileName}&billingRef={billingRef}

The placeholders values in this URL are:

  • endpoint – the appropriate Cloud Services instance for your account. Either:
    • USA – cloud.hotdocs.ws
    • EU – europe.hotdocs.ws

See Cloud Services REST API Endpoints for more information.

  • subscriberID – your ID for signing in to Cloud Services.
  • packageID – the unique ID of the Template Package used for the request.
  • fileName – the name of the Interview File to be retrieved from the Template Package File,

Add the following line to the CreateHttpRequestMessage method:

var interviewFileUrl = string.Format("https://cloud.hotdocs.ws/hdcs/interviewfile/{0}/{1}?filename={2}&billingRef={3}", subscriberId, packageId, fileName, billingRef);

5.3 Create the Request

Next, you create the request, using a HttpRequestMessage:

var request = new HttpRequestMessage { }

The request must have the following properties:

  • RequestUri – the URI to which the request is sent. This is the Get Interview File URL, created above.
  • Method – the method used to send the request. Retrieving an Interview File requires the GET method.

Add the properties to the request, like this:

RequestUri = new Uri(interviewFileUrl),
Method = HttpMethod.Get

Finally, add the following content headers to the request:

request.Headers.TryAddWithoutValidation("x-hd-date", timestamp.ToString("yyyy-MM-ddTHH:mm:ssZ"));
request.Headers.TryAddWithoutValidation("Authorization", hmac);
request.Headers.Add("Keep-Alive", "false");

You can now return the request message. The full method looks as follows:

private static HttpRequestMessage CreateHttpRequestMessage(string hmac, string subscriberId, string packageId, string fileName, DateTime timestamp, string billingRef)
{
        var interviewFileUrl = string.Format("https://cloud.hotdocs.ws/hdcs/interviewfile/{0}/{1}?filename={2}&billingRef={3}", subscriberId, packageId, fileName, billingRef);
        var request = new HttpRequestMessage
        {
                RequestUri = new Uri(interviewFileUrl),
                Method = HttpMethod.Get
        };
        // Add request headers
        request.Headers.TryAddWithoutValidation("x-hd-date", timestamp.ToString("yyyy-MM-ddTHH:mm:ssZ"));
        request.Headers.TryAddWithoutValidation("Authorization", hmac);
        request.Headers.Add("Keep-Alive", "false");  
         
        return request;
}

6. Send the Request

The request can be sent to Cloud Services. In the Main method, you will now send the request and retrieve the response.

6.1 Create the request

First, create the request message, using the CreateHttpRequestMessage from above:

var request = CreateHttpRequestMessage(hmac, subscriberId, packageId, fileName, timestamp);

6.2 Send the Request using HttpClient

In Main, create a new HttpClient:

var client = new HttpClient();    

This is used to send the request. Next, to send the request, pass the request message to the HttpClient method, SendAsync:

var response = client.SendAsync(request);

6.3 Read the Response

Once the Cloud Services returns a response, extract the result:


var attachmentContent = response.Result.Content.ReadAsStringAsync();
attachmentContent.Wait();

In this example, the result is read as a string, as it is a JavaScript file that is returned from Cloud Services. If the file returned was an image, you would write the content as a stream.

7. Save the Interview File

Once Cloud Services has retrieved the Interview File, the file can be saved. To do this, you will create a new method, SaveInterviewFilesToTempDirectory, that will write the file to disk.

7.1 Create the SaveInterviewFilesToTempDirectory method

Create a new method, SaveInterviewFilesToTempDirectory:

static async Task SaveInterviewFilesToTempDirectory(string filename, string interviewFileContent)
{
}

You will pass in the filename of the Interview File and the file content sent back from Cloud Services.

7.2 Save the Interview File

First, create an appropriate file path for the interview file. In this example, you will save the interview file in a temp folder in your host application:

var filePath = String.Format(@"C:\examplehostapplication\temp\{0}", filename);

Next, write the interview file content to the file:

File.WriteAllText(filePath, interviewFileContent);

The complete method appears as follows:

private static void SaveInterviewFilesToTempDirectory(string filename, string interviewFileContent)
{
        var filePath = String.Format(@"C:\examplehostapplication\temp\{0}", filename);            File.WriteAllText(filePath, interviewFileContent);
}

7.3 Call the SaveInterviewFilesToTempDirectory method

In the Main method, add a call to SaveInterviewFilesToTempDirectory. Pass in the file name and the content returned from Cloud Services:

SaveInterviewFilesToTempDirectory(fileName, attachmentContent.Result);

When the project is run, the interview file content will be saved to the temp folder. From there, it can be accessed by call-backs from the HotDocs Interview.

Testing

To test retrieving an Interview File from a Template Package in Cloud Services:

  1. Set the current project as the Startup Project (Right-click the CloudServicesAPIExample5InterviewFile project in Visual Studio and select Startup Project from the drop-down menu)
  2. Press F5 to run the Project.
  3. Navigate to the location of the downloaded Interview File, C:\temp\. You will see the HelloWorld.docx.js file in the folder.

Source Code (C#)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
namespace CloudServicesAPIExample5InterviewFile
{
    class Program
    {
        static void Main(string[] args)
        {
            // Cloud Services Subscription Details
            string subscriberId = "example-subscriber-id";
            string signingKey = "example-signing-key";
            // HMAC calculation data      
            var timestamp = DateTime.UtcNow;
            var packageId = "ed40775b-5e7d-4a51-b4d1-32bf9d6e9e29";
            var fileName = "HelloWorld.docx.js";
            var billingRef = "ExampleBillingRef";
            // Generate HMAC using Cloud Services signing key            
            string hmac = CalculateHMAC(signingKey, timestamp, subscriberId, packageId, null, true, billingRef);
            // Create assemble request            
            var request = CreateHttpRequestMessage(hmac, subscriberId, packageId, fileName, timestamp, billingRef);
            //Send assemble request to Cloud Services
            var client = new HttpClient();
            var response = client.SendAsync(request);
            // Read Interview File content from Response
            var attachmentContent = response.Result.Content.ReadAsStringAsync();
            attachmentContent.Wait();            
            // Save Interview File
            SaveInterviewFilesToTempDirectory(fileName, attachmentContent.Result);
        }
        private static void SaveInterviewFilesToTempDirectory(string filename, string interviewFileContent)
        {
            var filePath = String.Format(@"C:\examplehostapplication\temp\{0}", filename);
            File.WriteAllText(filePath, interviewFileContent);
        }
        private static HttpRequestMessage CreateHttpRequestMessage(string hmac, string subscriberId, string packageId, string fileName, DateTime timestamp, string billingRef)
        {
            var interviewFileUrl = string.Format("https://test.hotdocs.ws/hdcs/interviewfile/{0}/{1}?filename={2}&billingRef={3}", subscriberId, packageId, fileName, billingRef);            var request = new HttpRequestMessage
            {
                RequestUri = new Uri(interviewFileUrl),
                Method = HttpMethod.Get
            };
            // Add request headers
            request.Headers.Add("x-hd-date", timestamp.ToString("yyyy-MM-ddTHH:mm:ssZ"));
            request.Headers.TryAddWithoutValidation("Authorization", hmac);
            request.Headers.Add("Keep-Alive", "false");
            return request;
        }
        private static string CalculateHMAC(string signingKey, params object[] paramList)
        {
            byte[] key = Encoding.UTF8.GetBytes(signingKey);
            string stringToSign = CanonicalizeParameters(paramList);
            byte[] bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
            byte[] signature;
            using (var hmac = new System.Security.Cryptography.HMACSHA1(key))
            {
                signature = hmac.ComputeHash(bytesToSign);
            }
            return Convert.ToBase64String(signature);
        }
        private static string CanonicalizeParameters(params object[] paramList)
        {
            if (paramList == null)
            {
                throw new ArgumentNullException();
            }
            var strings = paramList.Select(param =>
            {
                if (param is string || param is int || param is Enum || param is bool)
                {
                    return param.ToString();
                }
                if (param is DateTime)
                {
                    DateTime utcTime = ((DateTime)param).ToUniversalTime();
                    return utcTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
                }
                if (param is Dictionary<string, string>)
                {
                    var sorted = ((Dictionary<string, string>)param).OrderBy(kv => kv.Key);
                    var stringified = sorted.Select(kv => kv.Key + "=" + kv.Value).ToArray();
                    return string.Join("\n", stringified);
                }
                return "";
            });
            return string.Join("\n", strings.ToArray());
        }
    }
}