Web Service Example 2: Assemble a Document using the HotDocs Web Service
HotDocs creates a document by merging answer data with a HotDocs template. The following example demonstrates using the HotDocs Web Service API to assemble a completed document from a HotDocs template, using existing answer data. In this example, you will:
- Create a WebServiceTemplateLocation object
- Retrieve a Template using the WebServiceTemplateLocation
- Assemble a completed document using the template and answer data
- Save the completed document to disk
Full source code for this example is available at the bottom of the page.
Example Source Code on GitHub
The WebServiceExample2DocumentAssembly example project is available on GitHub, in the HotDocs-Samples repository.
Requirements
Assembling a document 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 Console project for this example named WebServiceExample2DocumentAssembly. 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 the class, add a subscriber ID variable:
var subscriberId = "0";
The Subscriber ID used to connect to local Web Services is always "0".
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 used for the assembly.
Finally, add two values used for document assembly:
var format = "Native";
Dictionary<string, string> settings = new Dictionary<string, string>{{"UnansweredFormat", "[Variable]"}};
These items are:
- Format (string) – the format of the document produced by the assembly. This can be:
- Native – assembles to the same format as the template in the Template Package File. For example, a DOCX template will produce a DOCX document.
- PDF – creates a PDF of the assembled document.
- settings – a dictionary of all additional assembly settings. In this case, the UnansweredFormat setting is used to change the default placeholder for unanswered variables.
You will use these items later when assembling the document with Web Services.
6. Create the Assemble URL
6.1 Create the CreateAssembleUrl method
The URL to which the request is sent contains multiple parameters. To create the URL with all the required parameters. create a new method, CreateAssembleUrl:
private static string CreateAssembleUrl(string subscriberId, string packageId, string format, Dictionary<string, string> settings)
{
}
6.2 Create the Assemble URL
To assemble a document using the Cloud Services REST service, you use the following address:
- http://{MachineName}:{PortNumber}/hdswebapi/api/hdcs/assemble
Where MachineName is the name of the server where the Web Service is installed and PortNumber is the appropriate port for the Web Service in IIS.
The URL used when uploading templates to the Web Service must also contain specific parameters:
- SubscriberId
- packageId
format
To the Web Services REST service address, the parameters above are added. The complete upload URL is formatted as follows:
- https://localhost:80/HDSWEBAPI/api/hdcs/assemble/subscriberId/packageId?format=format
In the method, add the following line to create the upload URL:
var assembleUrl = string.Format("http://localhost:80/HDSWEBAPI/api/hdcs/assemble/{0}/{1}?format={2}", subscriberId, packageId, format);
Next, you will add the settings parameters to the URL.
6.3 Append Settings to the URL
Each of the assembly settings must be appended to the URL as individual query string parameters. To do this, you iterate through the settings and append the key name and its value.
First, use the URL create in the previous to create a new StringBuilder:
var assembleUrlWithSettings = new StringBuilder(assembleUrl);
Next, iterate through the settings and append the key and key value to the string builder:
foreach (var kv in settings)
{
assembleUrlWithSettings.AppendFormat("&{0}={1}", kv.Key, kv.Value ?? "");
}
Once the settings parameters have been appended, you can return the URL as a string:
return assembleUrlWithSettings.ToString();
You will use this method later, when creating the request.
6.4 Completed CreateAssembleUrl Method
The completed CreateAssembleUrl method looks as follows:
private static string CreateAssembleUrl(string subscriberId, string packageId, string format, Dictionary<string, string> settings)
{
var assembleUrl = string.Format("http://localhost:80/HDSWEBAPI/api/HDCS/assemble/{0}/{1}?format={2}", subscriberId, packageId, format);
var assembleUrlWithSettings = new StringBuilder(assembleUrl);
foreach (var kv in settings)
{
assembleUrlWithSettings.AppendFormat("&{0}={1}", kv.Key, kv.Value ?? "");
}
return assembleUrlWithSettings.ToString();
}
7. Create the Upload Request
7.1 Create the CreateHttpRequestMessage method
Create a new method, CreateHttpRequestMessage:
private static HttpRequestMessage CreateHttpRequestMessage(string subscriberId, string packageId, string format, Dictionary<string, string> settings)
{
}
This method takes the following parameters:
- subscriberId – your ID for signing in to Cloud Services.
- packageId – the name of the Template Package used for the assembly.
- format – the format of the assembled document.
- settings – any additional assembly settings used.
7.2 Create the Assembly URL
Use the CreateAssembleUrl method created above to create the URL used in the request:
var assembleUrl = CreateAssembleUrl(subscriberId, packageId, format, settings);
7.2 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. Assembling a document requires the POST 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(assembleUrl), Method = HttpMethod.Post, Content = GetAnswers()
For the content data to be added to the request, you must create a new method, GetAnswers, to retrieve the answer data used to assemble the document. You will create this method in the next step.
Finally, add the following content headers Content headers to the request:
request.Headers.TryAddWithoutValidation("Content-Type", "text/xml");
request.Headers.Add("Keep-Alive", "false");
You can now return the request message. The full method looks as follows:
private static HttpRequestMessage CreateHttpRequestMessage(string subscriberId, string packageId, string format, Dictionary<string, string> settings) { var assembleUrl = CreateAssembleUrl(subscriberId, packageId, format, settings); var request = new HttpRequestMessage { RequestUri = new Uri(assembleUrl), Method = HttpMethod.Post, Content = GetAnswers() }; // Add request headers request.Headers.TryAddWithoutValidation("Content-Type", "text/xml"); request.Headers.Add("Keep-Alive", "false"); return request; }
7. Retrieve the File Content
For the document to be assembled, answer XML should be provided with the assembly request. You will do this with a new method, GetAnswers:
private static StringContent GetAnswers()
{
return new StringContent(@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><AnswerSet version=""1.1""><Answer name=""TextExample-t""><TextValue>Hello World</TextValue></Answer></AnswerSet>");
}
This method will retrieves HotDocs answer XML as StringContent. It can now be added to the request message.
8. Send the Request
Now that the file content has been added to the request message, the request can be sent to the Web Service. 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
To see if the upload has been successful, write the status code sent back from Cloud Services to the console:
Console.WriteLine("Assemble:" + response.Result.StatusCode);
Console.ReadKey();
9. Save Assembled Documents
Once Cloud Services has completed the Assembly, the documents sent back can be saved. To do this, you will create a new method, SaveAssembledDocuments, that will asynchronously save the documents.
9.1 Create the SaveAssembledDocuments method
Create a new method, SaveAssembledDocuments:
static async Task SaveAssembledDocuments(HttpResponseMessage response)
{
}
You will pass in the response message sent back from Cloud Services. Data from the response will be saved as a document.
9.2 Read data from the Response Message
A single Assembly can produce multiple documents. As such, the response content from Cloud Services is a multi-part stream. To separate the multiple streams, so they can be processed later, convert the content into a MultipartStreamProvider object:
MultipartStreamProvider multipartStream = await response.Content.ReadAsMultipartAsync();
9.3 Save the Response Contents
Next, iterate through the contents of the multipart stream:
foreach (var attachment in multipartStream.Contents)
{
}
Inside the foreach loop, read the response contents to a stream:
Stream writeAttachmentStream = await attachment.ReadAsStreamAsync();
Next, create a new FileStream and copy the contents of the attachment stream to the Filestream:
using (FileStream output = new FileStream(@"C:\temp\" + attachment.Headers.ContentDisposition.FileName, FileMode.Create))
{
writeAttachmentStream.CopyTo(output);
}
The complete method appears as follows:
static async Task SaveAssembledDocuments(HttpResponseMessage response)
{
MultipartStreamProvider multipartStream = await response.Content.ReadAsMultipartAsync();
foreach (var attachment in multipartStream.Contents)
{
Stream writeAttachmentStream = await attachment.ReadAsStreamAsync();
using (FileStream output = new FileStream(@"C:\temp\" + attachment.Headers.ContentDisposition.FileName, FileMode.Create))
{
writeAttachmentStream.CopyTo(output);
}
}
}
9.4 Using SaveAssembledDocuments
To use SaveAssembledDocuments to save the documents sent back from Cloud Services, you must add a call to SaveAssembledDocuments in the Main method. As SaveAssembledDocuments is an asynchronous method, it must be run as a Task:
var saveDocumentsTask = Task.Run(async () => { await SaveAssembledDocuments(response.Result); });
saveDocumentsTask.Wait();
Testing
To test assembling a document using Web Services:
- Set the current project as the Startup Project (Right-click the SdkExample17 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 Assemble: OK message appears.
- Navigate to the location of the assembled document, C:\temp\.
Source Code (C#)
using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Net.Http; using System.Threading.Tasks; namespace WebServiceExample2DocumentAssembly { internal class WebServiceExample2DocumentAssembly { static void Main(string[] args) { // Web Services Subscriber Details var subscriberId = "0"; // Assembly Request Parameters var packageId = "ed40775b-5e7d-4a51-b4d1-32bf9d6e9e29"; var format = "Native"; Dictionary<string, string> settings = new Dictionary<string, string> { {"UnansweredFormat", "[Variable]"} }; // Create assemble request var request = CreateHttpRequestMessage(subscriberId, packageId, format, settings); // Send assemble request to Web Services var client = new HttpClient(); var response = client.SendAsync(request); Console.WriteLine("Assemble:" + response.Result.StatusCode); // Save Assembled Documents var saveDocumentsTask = Task.Run(async () => { await SaveAssembledDocuments(response.Result); }); saveDocumentsTask.Wait(); Console.ReadKey(); } private static HttpRequestMessage CreateHttpRequestMessage(string subscriberId, string packageId, string format, Dictionary<string, string> settings) { var assembleUrl = CreateAssembleUrl(subscriberId, packageId, format, settings); var request = new HttpRequestMessage { RequestUri = new Uri(assembleUrl), Method = HttpMethod.Post, Content = GetAnswers() }; // Add request headers request.Headers.TryAddWithoutValidation("Content-Type", "text/xml"); request.Headers.Add("Keep-Alive", "false"); return request; } private static string CreateAssembleUrl(string subscriberId, string packageId, string format, Dictionary<string, string> settings) { var assembleUrl = string.Format("http://localhost:80/HDSWEBAPI/api/hdcs/assemble/{0}/{1}?format={2}", subscriberId, packageId, format); var assembleUrlWithSettings = new StringBuilder(assembleUrl); foreach (var kv in settings) { assembleUrlWithSettings.AppendFormat("&{0}={1}", kv.Key, kv.Value ?? ""); } return assembleUrlWithSettings.ToString(); } static async Task SaveAssembledDocuments(HttpResponseMessage response) { MultipartStreamProvider multipartStream = await response.Content.ReadAsMultipartAsync(); foreach (var attachment in multipartStream.Contents) { Stream writeAttachmentStream = await attachment.ReadAsStreamAsync(); using (FileStream output = new FileStream(@"C:\temp\" + attachment.Headers.ContentDisposition.FileName, FileMode.Create)) { writeAttachmentStream.CopyTo(output); } } } private static StringContent GetAnswers() { return new StringContent(@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><AnswerSet version=""1.1""><Answer name=""TextExample-t""><TextValue>Hello World</TextValue></Answer></AnswerSet >"); } } }