Web Service Example 3: Display the HotDocs Interview using the HotDocs Web Service

A core function of HotDocs is the Interview, a series of questions displayed to users in order to gather answer data that HotDocs can use to assemble a completed document. You can use the Web Service to provide a fragment of HTML that allows you to embed the HotDocs Interview in a web page, where users can interact with it.

This example takes you through the process of creating an interview and retrieving the HTML fragment used to display the interview to users.

In this example you will:

  • Create a new MVC project in Visual Studio
  • Retrieve a HotDocs Interview HTML fragment
  • Use the HTML fragment to display a HotDocs Interview to the user

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

Example Source Code on GitHub

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

1. Create a new MVC Project in Visual Studio

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

2. Create a new Interview Request class

To retrieve the required files and render the interview, create a new class:

  1. In Visual Studio, right-click the WebServiceExample3InterviewDisplay project and select Add > Class.
  2. Enter InterviewRequest.cs in the Name field.
  3. Click OK. The new InterviewRequest class is added to the project.

You will now add methods to this class to request the interview.

3. Add Subscriber ID and Other Information

Create a new method, GetInterview:

private HttpResponseMessage GetInterview()
{
}

This method connects to the Web Service, sends the interview request, and receives the response message.

3.1 Add Subscriber ID and Other Information

To retrieve the interview, the request must use appropriate subscriber information and other request information.

In the GetInterview method, 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 interview request.

Add the required Interview Format:

var format = "Native";

The format can be:

  • JavaScript – the interview is rendered using JavaScript.
  • Silverlight – the interview is rendered using Silverlight.
  • Native – selects an interview format based on the user's browser

Add the Temporary Image URL:

var tempImageUrl = "http://localhost/HDServerFiles/temp";

This is the location where image files are temporarily stored when required by the interview.

 

Finally, add the required interview settings:

var settings = new Dictionary<string, string>
{
        {"HotDocsJsUrl", "http://localhost/HDServerFiles/js/"},
        {"HotDocsCssUrl", "http://localhost/HDServerFiles/stylesheets/"},
        {"FormActionUrl", "InterviewFinish"},
};

The Interview Request requires three interview settings:

  • HotDocsJsUrl – the URL where the HotDocs JavaScript files are located. In Web Services, this will be the JavaScript files from the local installation of HotDocs Server.
  • HotDocsCssUrl – the URL where the HotDocs Server CSS files are located. In Web Services, this will be the CSS files from the local installation of HotDocs Server.
  • FormActionUrl – the URL of the page to which the user is sent when the click the Finish button in the interview. Typically, this is a page from which the user can download the assembled document.

You will use these items later when retrieving the interview from Web Services.

3.2 Create the Request message

Once the setting above are added, they can be used to create the Interview URL and Request message. For now, add the following lines to the GetInterview method:

var interviewUrl = CreateInterviewUrl(subscriberId, packageId, format, tempImageUrl, settings);
var request = CreateHttpRequestMessage(interviewUrl);

You will add the CreateHttpRequestMessage method later in this example.

3.3 Send the Interview Request message

Once the Interview request message is created, send it to the Web Service using HttpClient:

var client = new HttpClient();
var response = client.SendAsync(request);

Once the response is received from the Web Service, return the result:

return response.Result;

3.4 Completed GetInterview method

The completed GetInterview looks like this:

private HttpResponseMessage GetInterview()
{
            // Web Services Subscription Details
            string subscriberId = "0";            
            // HMAC calculation data            
            var packageId = "ed40775b-5e7d-4a51-b4d1-32bf9d6e9e29";
            var format = "Unspecified";
            var tempImageUrl = "http://localhost/HDServerFiles/temp";
            var settings = new Dictionary<string, string>
            {
                {"HotDocsJsUrl", "https://localhost/HDServerFiles/js/"},
                {"HotDocsCssUrl", "https://localhost/HDServerFiles/stylesheets/"},
                {"FormActionUrl", "InterviewFinish"},
            };                     
            // Create assemble request
var interviewUrl = CreateInterviewUrl(subscriberId, packageId, format, tempImageUrl, settings); var request = CreateHttpRequestMessage(interviewUrl);             //Send assemble request to Cloud Services             var client = new HttpClient();             var response = client.SendAsync(request);             return response.Result; }

 

4. Create the Interview Request URL

The URL required to send the request to the Web Service must incorporate all of the appropriate information from the GetInterview method, including subscriber ID, package ID, format, temp image URL and interview settings. In this step, you will create a method to create and return a correctly-formatted URL.

4.1 Create the Interview Request URL method

First, create a new method, CreateInterviewUrl:

private string CreateInterviewUrl(string subscriberId, string packageId, string format, string tempImageUrl, Dictionary<string, string> settings)
{ 
}

This method returns the URL used to access the Web Service. It is used in the GetInterview method, created above.

4.2 Create the Request URL

The URL used to request the Interview from the Web Service has the following format:

http://{MachineName}:{PortNumber}/HDSWebAPI/api/hdcs/interview/{SubscriberID}/{TemplateID}?format={Format}&tempimageurl={TempImageUrl}

The Machine Name and Port Number values are the name of the server where HotDocsWeb Services is installed and the appropriate port number for accessing the Web Service. The other values are taken from the parameters passed to the method, as below.

4.2.1 Create URL with Interview Query String

First, create a new string with the Subscriber ID, Package ID, Format, and Temporary Image URL in the appropriate places:

var partialInterviewUrl = string.Format("http://localhost:80/HDSWebAPI/api/hdcs/interview/{0}/{1}?format={2}&tempimageurl={3}", subscriberId, packageId, format, tempImageUrl);

The Interview Settings must now be added to the URL.

4.2.2 Add Interview Settings to Query String

The Interview Settings are stored in a Dictionary object, created in the GetInterview method. To add these to the interview URL, you must iterate through the Dictionary and append the key/value pairs from the dictionary.

First, use the URL above to create a new StringBuilder:

var completedInterviewUrlBuilder = new StringBuilder(partialInterviewUrl);

Next, iterate through the Dictionary and append the key/value pairs to the URL:

foreach (var kv in settings)
{
        completedInterviewUrlBuilder.AppendFormat("&{0}={1}", kv.Key, kv.Value ?? "");
}

Once all of the Interview Settings values are appended to the URL, return the URL as a String:

return completedInterviewUrlBuilder.ToString();

4.3 Completed CreateInterviewUrl method

The completed CreateInterviewUrl method looks as follows:

private string CreateInterviewUrl(string subscriberId, string packageId, string format, string tempImageUrl, Dictionary<string, string> settings)
{
        var partialInterviewUrl = string.Format("http://localhost:80/HDSWebAPI/api/hdcs/interview/{0}/{1}?format={2}&tempimageurl={3}", subscriberId, packageId, format, tempImageUrl);  var completedInterviewUrlBuilder = new StringBuilder(partialInterviewUrl);
        foreach (var kv in settings)
        {
                completedInterviewUrlBuilder.AppendFormat("&{0}={1}", kv.Key, kv.Value ?? "");
        }
        return completedInterviewUrlBuilder.ToString();
}

5. Create the Interview Request Message

To get the Interview from the Web Service, you must first create a HttpRequest message to request the interview. This method is used in the GetInterview method created above.

5.1 Create the CreateHttpRequestMessage Method

Create a new method, CreateHttpRequestMessage:

private HttpRequestMessage CreateHttpRequestMessage(string interviewUrl)
{ 
}

This method returns a HttpRequest message that can then be sent to the Web Service.

5.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 Interview URL, created above.
  • Method – the method used to send the request. Retrieving an interview requires the POST method.
  • Content – the content to be sent with the request. In this example, the content is answer XML.

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.

 

5.3 Complete CreateHttpRequestMessage method

The completed CreateHttpRequestMessage method looks as follows:

private HttpRequestMessage CreateHttpRequestMessage(string interviewUrl)
{                       
            var request = new HttpRequestMessage
            {
                RequestUri = new Uri(interviewUrl),
                Method = HttpMethod.Post,
                Content = GetAnswers()
            };
            // Add request headers
            request.Content.Headers.Add("Content-Type", "text/xml");            
            request.Headers.Add("Keep-Alive", "false");
            return request;
}

6. Retrieve the File Content

For the answers to appear in the interview fields, answer XML should be provided with the interview request.

This is an optional step. Interviews may be loaded without answer XML.

First, create a new method, GetAnswers:

private 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.

7. Extract Interview Files From Response

The interview files used to render the HotDocs interview are returned from the Web Service as a multi-part stream. Once the Response message is returned by GetInterview, these files must be extracted.

7.1 Create the GetMultipartStream method

First, create a new method, GetMultipartStream:

private async Task<IEnumerable<HttpContent>> GetMultipartStream(HttpResponseMessage response)
{
}

This method asynchronously reads the multiple streams from the response message and returns them as a collection.

7.2 Read the Multipart Stream from the Response

First, create a new IEnumerable collection to store the HttpContent from the response:

var individualFileStreams = Enumerable.Empty<HttpContent>();

Next, start a new Task to read the multipart streams:

Task.Factory.StartNew(
).Wait();

Inside the Task, read the HttpContent from the response using ReadAsMultipartAsync:

() => individualFileStreams = response.Content.ReadAsMultipartAsync().Result.Contents,
    CancellationToken.None,
    TaskCreationOptions.LongRunning,
    TaskScheduler.Default

The full task looks like this:

Task.Factory.StartNew(
    () => individualFileStreams = response.Content.ReadAsMultipartAsync().Result.Contents,
        CancellationToken.None,
        TaskCreationOptions.LongRunning,
        TaskScheduler.Default
).Wait();

The collection of streams can now be returned:

return individualFileStreams; 

7.3 Complete GetMultipartStream method

The completed GetMultipartStream method looks like this:

private async Task<IEnumerable<HttpContent>> GetMultipartStream(HttpResponseMessage response)
{
    var individualFileStreams = Enumerable.Empty<HttpContent>();
    Task.Factory.StartNew(
        () => individualFileStreams = response.Content.ReadAsMultipartAsync().Result.Contents,
            CancellationToken.None,
            TaskCreationOptions.LongRunning,
            TaskScheduler.Default
    ).Wait();
    return individualFileStreams;            
}

8. Get the Interview Files from the Stream

Once the multipart stream is received, you must extract the interview files and return the appropriate file to render the interview. This is the Interview HTML Fragment.

8.1 Create the GetInterviewFiles method

First, create a new method, GetInterviewFiles:

private async Task<string> GetInterviewFiles(IEnumerable<HttpContent> multipartStream)
{
}

This method returns the Interview HTML Fragment string once it is extracted from the multipart stream collection.

8.2 Extract the Interview Files from the Stream

Next, you must iterate through the attachments in the response, read the interview files, and return the appropriate file as a string.

First, iterate through the collection of streams:

foreach (var attachment in multipartStream)
{
}

Inside the loop, check if the attachment has the text/plain content type.

if(attachment.Headers.ContentType.ToString() == "text/plain") 
{
}

This is the Interview HTML Fragment, which is the file you need to render the interview.

Inside the condition, read the attachment as a string and return it:

var writeAttachmentStream = await attachment.ReadAsStringAsync();
return writeAttachmentStream;

The InterviewFragment string can then be passed to a View and rendered in the browser.

8.3 Completed GetInterviewFiles method

The completed GetInterviewFiles method looks like this:

private async Task<string> GetInterviewFiles(IEnumerable<HttpContent> multipartStream)
{            
        foreach (var attachment in multipartStream)
        {
                if(attachment.Headers.ContentType.ToString() == "text/plain") {
                        var writeAttachmentStream = await attachment.ReadAsStringAsync();
                        return writeAttachmentStream;
                }
        }
        return null;
}

9. Retrieve the Interview Fragment

Using the methods created above, you can now generate the Interview HTML Fragment used to render the interview. The Interview HTML Fragment is a string of HTML and JavaScript that displays the HotDocs Interview when embedded in a web page.

9.1 Create the GetInterviewFragment method

Create a new method, GetInterviewFragment:

public string GetInterviewFragment()
{
}

This method returns the interview HTML fragment string.

9.2 Generate the Interview Fragment

Generating the interview fragment takes several steps:

9.2.1 Get the Interview from the Web Service

First, use the GetInterview method created above to generate the interview using the Web Service.

var interviewResult = GetInterview();   

9.2.2 Retrieve the Multipart Stream from the Response

Using the response from the Web Service, use the GetMultipartStream method to retrieve the multipart stream:

var multipartStream = GetMultipartStream(interviewResult);
multipartStream.Wait();

9.2.3 Get the Interview Files from the Stream

Using the multipart stream, use the GetInterviewFiles method to extract the interview HTML fragment:

var interviewFiles = GetInterviewFiles(multipartStream.Result);

9.2.4 Return the Interview HTML Fragment

Once the interview files have been extracted, return the result containing the interview HTML fragment:

return interviewFiles.Result;

9.3 Complete GetInterviewFragment method

The completed GetInterviewFragment method looks like this:

public string GetInterviewFragment()
{
        var interviewResult = GetInterview();                                   
        var multipartStream = GetMultipartStream(interviewResult);
        multipartStream.Wait();
        var interviewFiles = GetInterviewFiles(multipartStream.Result);
        return interviewFiles.Result;
}

10. Use the Interview Fragment in the View

Once the interview HTML fragment is returned from the GetInterviewFragment method, it can be passed to a view and rendered in the browser.

10.1 Edit the HomeController

When the MVC project was created, a Controller class was created at the same time. You will use this class, HomeController, to request the interview HTML fragment and pass it to the view.

To edit the HomeController:

  1. In Visual Studio, expand the WebServiceExample3InterviewDisplay > Controllers folder.
  2. Double-click the HomeController.cs file.

10.2 Request the Interview HTML Fragment

In HomeController, there are several ActionResult methods, which are used when the user performs certain actions in the browser. For this example, you will edit the Index ActionResult method.

Inside the Index method, create a new InterviewRequest object:

var interviewRequest = new InterviewRequest();

Next, use the GetInterviewFragment public method to retrieve the Interview HTML Fragment:

var interviewFragment = interviewRequest.GetInterviewFragment();

Once the interview HTML fragment is received, assign it to a ViewBag property, which can then be displayed in the View:

ViewBag.InterviewFragment = interviewFragment;

Finally, return the View:

ViewBag.InterviewFragment = interviewFragment;

The completed Index method looks like this:

public ActionResult Index()
{
        var interviewRequest = new InterviewRequest();
        var interviewFragment = interviewRequest.GetInterviewFragment();
        ViewBag.InterviewFragment = interviewFragment;
        return View();
}  

10.3 Add the Interview HTML Fragment to the View

Once the Interview HTML Fragment  is received, it must be added to the View. When the MVC project was created, a View page for the HomeController Index was created at the same time. You will use this view, to request the interview HTML fragment and pass it to the view.

10.3.1 Edit the View

To edit the HomeController Index View:

  1. In Visual Studio, expand the WebServiceExample3InterviewDisplay > Views > Home folder.
  2. Double-click the Index.cshtml file.

10.3.2 Add the Interview HTML Fragment

In the Index.cshtml file, add the following lines to render the Interview HTML Fragment as raw HTML:

<div>
    @Html.Raw(ViewBag.InterviewFragment)
</div>

The Interview can now be displayed in the browser and the application is ready to test.

11. Test

To test the interview:

  1. Set the current project as the Startup Project (right-click the WebServiceExample3InterviewDisplay project in the Solution Explorer and select Startup Project from the drop-down menu)
  2. Press F5 to run the MVC application. The browser opens.
  3. When the browser opens, a HotDocs interview appears.

Source Code (C#)

In the HomeController:

using System.Web.Mvc;

namespace WebServiceExample3InterviewDisplay.Controllers
{
    public class HomeController : Controller
    {                
        public ActionResult Index()
        {
            var interviewRequest = new InterviewRequest();
            var interviewFragment = interviewRequest.GetInterviewFragment();
            ViewBag.InterviewFragment = interviewFragment;
            
            return View();
        }        
    }
}

In the Index View:

<div>
        @Html.Raw(ViewBag.InterviewFragment)
</div>

In InterviewRequest.cs:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace WebServiceExample3InterviewDisplay
{
    public class InterviewRequest
    {      
        public string GetInterviewFragment()
        {
            // Get Interview from Web Service
            var interviewResult = GetInterview(); 
                                   
            // Get interview file stream from response
            var multipartStream = GetMultipartStream(interviewResult);
            multipartStream.Wait();
            // Get Interview HTML Fragment from file stream
            var interviewFiles = GetInterviewFiles(multipartStream.Result);
            return interviewFiles.Result;
        }
        private async Task<IEnumerable<HttpContent>> GetMultipartStream(HttpResponseMessage response)
        {
            var individualFileStreams = Enumerable.Empty<HttpContent>();
            Task.Factory.StartNew(
                () => individualFileStreams = response.Content.ReadAsMultipartAsync().Result.Contents,
                    CancellationToken.None,
                    TaskCreationOptions.LongRunning,
                    TaskScheduler.Default
            ).Wait();
            return individualFileStreams;            
        }
        private async Task<string> GetInterviewFiles(IEnumerable<httpcontent> multipartStream)
        {
            Dictionary<string, string> s = new Dictionary<string, string>();
            foreach (var attachment in multipartStream)
            {
                var writeAttachmentStream = await attachment.ReadAsStringAsync();
                s.Add(attachment.Headers.ContentType.ToString(), writeAttachmentStream);
            }
            return s["text/plain"];
        }
        private HttpResponseMessage GetInterview()
        {
            // Web Services Subscription Details
            string subscriberId = "0";            
            // Request data            
            var packageId = "ed40775b-5e7d-4a51-b4d1-32bf9d6e9e29";
            var format = "Unspecified";
            var tempImageUrl = "http://localhost/HDServerFiles/temp";
            var settings = new Dictionary<string, string>
            {
                {"HotDocsJsUrl", "http://localhost/HDServerFiles/js/"},
                {"HotDocsCssUrl", "http://localhost/HDServerFiles/stylesheets/"},
                {"FormActionUrl", "Disposition.aspx"},
            };                     
            // Create interview request  
            var interviewUrl = CreateInterviewUrl(subscriberId, packageId, format, tempImageUrl, settings);
            var request = CreateHttpRequestMessage(interviewUrl);
            //Send interview request to Cloud Services
            var client = new HttpClient();
            var response = client.SendAsync(request);
            return response.Result;
        }
        private HttpRequestMessage CreateHttpRequestMessage(string interviewUrl)
        {                       
            var request = new HttpRequestMessage
            {
                RequestUri = new Uri(interviewUrl),
                Method = HttpMethod.Post,
                Content = GetAnswers()
            };
            // Add request headers
            request.Content.Headers.Remove("Content-Type");
            request.Content.Headers.Add("Content-Type", "text/xml");            
            request.Headers.Add("Keep-Alive", "false");
            return request;
        }
        private string CreateInterviewUrl(string subscriberId, string packageId, string format, string tempImageUrl, Dictionary<string, string> settings)
        {
            var partialInterviewUrl = string.Format("http://localhost:80/HDSWebAPI/api/hdcs/interview/{0}/{1}?format={2}&tempimageurl={3}", subscriberId, packageId, format, tempImageUrl);
            var completedInterviewUrlBuilder = new StringBuilder(partialInterviewUrl);
            foreach (var kv in settings)
            {
                completedInterviewUrlBuilder.AppendFormat("&{0}={1}", kv.Key, kv.Value ?? "");
            }
            return completedInterviewUrlBuilder.ToString();
        }
        private 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>");
        }
        
    }
}