Cloud Services Example 1: Upload a Template Package
You must first upload a HotDocs Template Package file to Cloud Services before users can interact with HotDocs interviews and assemble documents. In this example, you will learn how to:
- Create an HMAC to authenticate requests to the Cloud Services API.
- Create a request to upload a HotDocs Template Package file to the service.
- Send the upload request and receive a response.
Full source code for this example is available at the bottom of the page.
Example Source Code on GitHub
The CloudServicesAPIExample1Upload example project is available on GitHub, in the HotDocs-Samples repository.
1. Create a new Console Application Project in Visual Studio
To begin, create a new Visual Studio Solution and Console Application project. Name it CloudServicesAPIExample1Upload. You will use this solution and project to create the first 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 Information and Other Data
In the Main method of the class, 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.
4. Add Data Required for Uploading the Template
In the Main method, add the following three variables:
var timestamp = DateTime.UtcNow;
var packageId = "ed40775b-5e7d-4a51-b4d1-32bf9d6e9e29";
string templateName = "";
var sendPackage = true;
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.
- Package ID (string) – a unique identifier for the HotDocs Template Package File uploaded. A GUID is recommended.
- Template Name (null) – must be null or an empty string when uploading a package file.
- Send Package (boolean) – must be true when uploading a package file. Indicates that a package is being sent with a request.
- Billing Reference (string) – a custom logging message. This 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.
5. 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.
5.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.
5.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()); }
5.3 Retrieve the Calculated HMAC
Add the following line to the Main method, to retrieve the calculated HMAC:
var hmac = CalculateHMAC(signingKey, timestamp, subscriberId,
packageId, templateName, sendPackage, billingRef);
To calculate the HMAC for uploading a template, 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.
- templateName (string) – must be null or an empty string when uploading a package.
- sendPackage (boolean) – must be true when uploading a package.
- 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 uploading the HotDocs Template Package file to Cloud Services.
6. Create the Upload Request
6.1 Create the CreateHttpRequestMessage method
Create a new method, CreateHttpRequestMessage:
private static HttpRequestMessage CreateHttpRequestMessage(string hmac, string subscriberId, string packageId, 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 name of the Template Package used for the assembly.
- timestamp – the time at which the assembly was started.
6.2 Create the Upload URL
To connect to the Cloud Services REST service, you use the following address:
- https://cloud.hotdocs.ws/hdcs/
The URL used when uploading templates to Cloud Services must also contain specific parameters:
- SubscriberId
- packageId
To the Cloud Services REST service address, the parameters above are added. The complete upload URL is formatted as follows:
- https://cloud.hotdocs.ws/hdcs/subscriberId/packageId
You can also optionally add a billing reference, like this:
- https://cloud.hotdocs.ws/hdcs/subscriberId/packageId?billingRef={billing-reference-value}
In your project, add the following line to create the upload URL:
var uploadUrl = string.Format("https://cloud.hotdocs.ws/hdcs/{0}/{1}?billingRef={2}",
subscriberId, packageId, billingRef);
6.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 Upload URL, created above.
- Method – the method used to send the request. Uploading a template package requires the PUT method.
- Content – the content to be sent with the request. In this example, the content is the Template Package file.
Add the properties to the request, like this:
RequestUri = new Uri(uploadUrl),
Method = HttpMethod.Put,
Content = CreateFileContent()
For the content data to be added to the request, you must create a new method, CreateFileContent, to retrieve the data. You will create this method in the next step.
Finally, add the following content headers Content headers to the request:
request.Headers.TryAddWithoutValidation("x-hd-date", timestamp.ToString("yyyy-MM-ddTHH:mm:ssZ"));
request.Content.Headers.TryAddWithoutValidation("Content-Type", "application/binary");
request.Headers.TryAddWithoutValidation("Authorization", hmac);
You can now return the request message. The full method looks as follows:
private static HttpRequestMessage CreateHttpRequestMessage(string hmac, string subscriberId, string packageId, DateTime timestamp, string billingRef) { var uploadUrl = string.Format("https://cloud.hotdocs.ws/hdcs/{0}/{1}?billingRef={2}", subscriberId, packageId, billingRef); var request = new HttpRequestMessage { RequestUri = new Uri(uploadUrl), Method = HttpMethod.Put, Content = CreateFileContent() }; request.Content.Headers.TryAddWithoutValidation("x-hd-date", timestamp.ToString("yyyy-MM-ddTHH:mm:ssZ")); request.Content.Headers.TryAddWithoutValidation("Content-Type", "application/binary"); request.Headers.TryAddWithoutValidation("Authorization", hmac); return request; }
7. Retrieve the File Content
For the Template Package to be uploaded, it must be added to the request message created above. You will do this with a new method, CreateFileContent. This method will retrieve a HotDocs Template Package File from disk and create a content stream that can be added to the request message.
7. 1 Create the CreateFileContent method
First create a new method to return a StreamContent object, CreateFileContent:
private static StreamContent CreateFileContent()
{
}
7.2 Read the HotDocs Package File
The content to be retrieved comes from a HotDocs Template Package File. In this example, you will use the HelloWorld.hdpkg file in the hotdocs-examples project. Copy the package file to a stream:
var filePath = @"C:\temp\Test.hdpkg";
var stream = File.OpenRead(filePath);
7.3 Create the StreamContent
First, copy the stream into a new Stream Content object:
var fileContent = new StreamContent(stream);
To the stream content object content disposition header, you must add two properties:
- Name – the name of the content body part.
- FileName – the name of the package file.
You can now return the StreamContent. The full method looks as follows:
private static StreamContent CreateFileContent()
{
var filePath = @"C:\temp\Test.hdpkg";
var stream = File.OpenRead(filePath);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "\"files\"",
FileName = "\"" + Path.GetFileName(filePath) + "\""
};
return fileContent;
}
8. Send the Request
Now that the file content has been added to the request message, the request can be sent to Cloud Services. In the Main method, you will now send the request and retrieve the response.
8.1 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);
8.2 Write the Response to Console
Finally, to see if the upload has been successful, write the status code sent back from Cloud Services to the console:
Console.WriteLine("Upload:" + response.Result.StatusCode);
Console.ReadKey();
Testing
To test uploading the Template Package File to Cloud Services:
- Set the current project as the Startup Project (Right-click the CloudServicesAPIExample1Upload project in Visual Studio and select Startup Project from the drop-down menu)
- Press F5 to run the Project. The console opens. If the test has been successful, the message Upload: Created message appears.
Source Code (C#)
using System; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.IO; using System.Collections.Generic; using System.Text; namespace CloudServicesExample1Upload { // Upload a HotDocs Package File to Cloud Services using an HMAC for authentication class Program { static void Main(string[] args) { // Cloud Services Subscription Details var subscriberId = "example-subscriber-id"; var signingKey = "example-signing-key"; // HMAC calculation data var timestamp = DateTime.UtcNow; var packageId = "ed40775b-5e7d-4a51-b4d1-32bf9d6e9e29"; string templateName = ""; var sendPackage = true; var billingRef = "ExampleBillingRef"; // Generate HMAC using Cloud Services signing key var hmac = CalculateHMAC(signingKey, timestamp, subscriberId, packageId, templateName, sendPackage, billingRef); // Create upload request var request = CreateHttpRequestMessage(hmac, subscriberId, packageId, timestamp); //Send upload request to Cloud Service var client = new HttpClient(); var response = client.SendAsync(request); Console.WriteLine("Upload:" + response.Result.StatusCode); Console.ReadKey(); } private static HttpRequestMessage CreateHttpRequestMessage(string hmac, string subscriberId, string packageId, DateTime timestamp, string billingRef) { var uploadUrl = string.Format("https://cloud.hotdocs.ws/hdcs/{0}/{1}?billingRef={2}", subscriberId, packageId, billingRef); var request = new HttpRequestMessage { RequestUri = new Uri(uploadUrl), Method = HttpMethod.Put, Content = CreateFileContent() }; // Add request headers request.Content.Headers.Add("x-hd-date", timestamp.ToString("yyyy-MM-ddTHH:mm:ssZ")); request.Content.Headers.Add("Content-Type", "application/binary"); request.Headers.TryAddWithoutValidation("Authorization", hmac); return request; } //Create a stream of a HotDocs Template Package file private static StreamContent CreateFileContent() { var filePath = @"C:\temp\HelloWorld.hdpkg"; var stream = File.OpenRead(filePath); var fileContent = new StreamContent(stream); fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "\"files\"", FileName = "\"" + Path.GetFileName(filePath) + "\"" }; return fileContent; } public static string CalculateHMAC(string signingKey, params object[] paramList) { byte[] key = Encoding.UTF8.GetBytes(signingKey); string stringToSign = Canonicalize(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); } 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()); } } }