SDK Example 10: 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
  • Create a new method to handle processing of answers after the interview is completed

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

Example Source Code on GitHub

The SdkExample10WebServiceInterviewDisplay 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 SdkExample10WebServiceInterviewDisplay. You will gradually add code to this project until you have a working example.

2. Reference the HotDocs DLLs

Before you can start developing with the Web Service, you must add references to the HotDocs Server and Web Service from the HotDocs Open SDK DLLs. At the start of your project, add the lines:

using HotDocs.Sdk;
using HotDocs.Sdk.Server;
using HotDocs.Sdk.Server.Contracts;
using HotDocs.Sdk.Server.WebService;

3. Create the Interview HTML Fragment

In this step, you will use the InterviewSettings to retrieve the InterviewResult, an object that allows you to interact with a HotDocs interview.

First, create a new method, GetInterviewFragment, that returns a string:

private static string GetInterviewFragment()
{   
}

Next, use the CreateTemplate created in the previous example.

var template = CreateTemplate();

The template will be used when creating the interview. Before that, you must retrieve the file location using the Template's CreateLocator method. This is used when creating the interview settings in the next step.

string templateLocation = template.CreateLocator();

3.2 Create the Interview Settings

Before the interview fragment can be created, you must create the InterviewSettings. These settings control the behaviour of the interview. Add the following line to GetInterviewFragment:

var interviewSettings = GetInterviewSettings(templateLocation);

The GetInterviewSettings method does not yet exist. You will create this method in the next step, after GetInterviewFragment is completed.

3.3 Retrieving the Interview HTML Fragment

To retrieve the Interview HTML fragment, first create the Service:

var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/hdcs");

Then use the Service's GetInterview method to return the interview:

var interview = service.GetInterview(template, null, interviewSettings, null, "ExampleLogReference");

In the returned Interview object, there is a HtmlFragment property containing the HTML definition of the interview. It is this that you need to pass to the view to render the interview. Return the HtmlFragment property:

return interview.HtmlFragment;

The full method looks as follows:

private static string GetInterviewFragment()
{            
        var template = CreateTemplate();            
        string templateLocation = template.CreateLocator();
        var interviewSettings = GetInterviewSettings(templateLocation);
        var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/hdcs");
        var interview = service.GetInterview(template, null, interviewSettings, null, "ExampleLogReference");
        return interview.HtmlFragment;
}

4. Create the GetInterviewSettings method

In the previous step, you added a reference to a GetInterviewSettings method that returns an InterviewSettings object. The InterviewSettings object defines:

  • The location of the HotDocs JavaScript and Stylesheet files
  • How the Interview should behave

In this step, you will create the GetInterviewSettings method.

4.1 CreateInterviewSettings method

The first step is to create a new method, CreateInterviewSettings:

private static InterviewSettings CreateInterviewSettings() 
{    
} 

4.2 The HotDocs File URLs

Next, you need to supply several parameters to create a new InterviewSettings object. These are:

  • Post-Interview URL the URL to which data from the interview is sent. This will usually be a controller in your web application. Handling post-interview processing is covered later in this example.
  • Interview Runtime URL the URL from which the browser interview will request the script files used by the interview.
  • Stylesheet URL the URL where the CSS stylesheets for the interview are stored.
  • Interview File URL the URL from which the browser interview will request template-specific files required by the interview at runtime.

For more information about these parameters, see InterviewSettings Constructor(String, String, String, String).

In this example, you will use URLs for a typical HotDocs Server installation, where the interviewRuntimeUrl and interviewStylesheetUrl use the default HDServerFiles IIS application. This is set up by default when installing HotDocs Server. By default, the files are located at the following URLs on a local machine:

  • Runtime files http://localhost/HDServerFiles/js
  • Stylesheet files http://localhost/HDServerFiles/stylesheets

4.3 The Interview File URL

In this example, you create the InterviewFileUrl like this:

string interviewFileUrl = HttpUtility.UrlEncode("Home/GetInterviewFile?templatelocater=" + templateLocation));

The templateLocation parameter passed into the method is used in the URL to pass the template location to the interview. This allows the interview to retrieve pass the template location to the GetInterviewFile method during the interview load.

4.4 Post-Interview URL

In this example, the post-interview URL points to an action in the HomeController that you will create later in the example. Use the following URL in GetInterviewSettings, where Home is the Controller and PostInterviewProcessing is the action:

string postInterviewUrl =  "Home/PostInterviewProcessing";

4.5 Create the InterviewSettings

Using the URLs defined above, create a InterviewSettings object:

var interviewSettings = new InterviewSettings(postInterviewUrl, interviewRuntimeUrl, interviewStylesheetUrl, interviewFileUrl);

The interview settings object can now be returned. The final method looks like this:

private static InterviewSettings GetInterviewSettings(){ 
        string postInterviewUrl =  "Home/PostInterviewProcessing"; 
        string interviewRuntimeUrl = "http://localhost/HDServerFiles/js"; 
        string interviewStylesheetUrl = "http://localhost/HDServerFiles/stylesheets"; 
        string interviewFileUrl = HttpUtility.UrlEncode(String.Format("Home/GetInterviewFile?templatelocater={0}&filename=", templateLocation));
        var interviewSettings = new InterviewSettings(postInterviewUrl, interviewRuntimeUrl, interviewStylesheetUrl, interviewFileUrl);
        return interviewSettings; 
} 

4.6 Change the interview behaviour (Optional)

The InterviewSettings object also allows you to change the default behaviour of the interview. For example, you can set InterviewSettings properties to define:

  • DisableAnswerSummary disable the answer summary page.
  • UnansweredFormat specify the text to merge into a document when an answer is missing.
  • TemplateTitleOverride change the title of the template displayed in the interview.

For a full list of interview settings options, see InterviewSettings Members.

5. Create a new Interview ViewModel

In MVC, a ViewModel structures the data used to populate the content on a page. To display the interview to end users, you must create a ViewModel that contains the interview definition. This definition can then be passed to the page and the interview rendered on-screen. 

5.1 Create the ViewModel

To create a new ViewModel,

  1. In Visual Studio, expand the current project folder.
  2. Right-click the Models folder and select Add > Class. The Add New Item dialog appears.
  3. Select Class from the list and enter InterviewModel.cs in the Name field.
  4. Click Add. A new Class is added to the Models folder. This will become our new ViewModel.

The next step is to define the data that is passed to the page.

5.2 Define the ViewModel data

In the InterviewModel created above, you now need to include the Interview fragment, so it can be displayed in the view. As the Interview Fragment is a string of HTML, add a new string property to the model named InterviewFragment:

public string InterviewFragment { get; set; }

The ViewModel is now ready to use in the View.

5.3 Pass the Interview HTML Fragment to a View

Return to the HomeController. In the Index ActionResult method, you can now add the interview fragment to the model:

var model = new InterviewModel { InterviewFragment = interviewFragment };

Once the model has been created and data assigned to it, you can pass it to the Index View for the HomeController:

return View(model);

The entire Index method looks like this:

public ActionResult Index() 
{ 
        var interviewFragment = GetInterviewFragment(); 
        var model = new InterviewModel { InterviewFragment = interviewFragment }; 
        return View(model); 
} 

6. Display the HotDocs Interview in the View

A View for the application was automatically created when the MVC project was created. In the Project, navigate to Views > Home and edit Index.cshtml. It is this page to which you will add the interview.

6.1 Add the View Model Reference

At the top of the page, add a reference to the ViewModel:

@model SdkExample12.Models.InterviewModel

This will allow us to use the data contained within the ViewModel elsewhere on the page.

6.2 Insert the Interview HTML Fragment

To insert the HTML fragment, add the following line to the page:

@Html.Raw(Model.InterviewFragment)

This line of code is comprised of two items:

  • Model.InterviewFragment the reference to the interview fragment data that you added to the model in step 3.3. This is the HTML that is passed to the page to embed the interview.
  • Html.Raw a MVC HtmlHelper method that ensures that the characters in the HTML fragment are not encoded, so the interview renders correctly.

The full model looks as follows:

@model SdkExample12.Models.InterviewModel 
@Html.Raw(Model.InterviewFragment) 

7. Add a GetInterviewFile method

Before you can run the interview, you need to supply a method that will return any files required by the interview as it loads. This method will return a FileContentResult, a .Net MVC class that sends a binaryfile as a response to a request.

7.1 Create the GetInterviewFile method

Create a new method, GetInterviewFile, to return a FileStreamResult:

public FileContentResult GetInterviewFile(string filename, string templateLocation)
{ 
} 
The interview automatically passes the required parameters into the method when calling the method.

7.2 Get the Interview File

To retrieve the requested interview file from the service, you must first connect to the service:

var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/hdcs");

Next, retrieve the requested template from its location:

var template = Template.Locate(templateLocation);

To retrieve the file, use the Service's GetInterviewFile method:

var interviewFile = service.GetInterviewFile(template, filename, "");

Before the file can be passed to the interview, it must be converted to a stream:

var stream = new MemoryStream();            
interviewFile.CopyTo(stream);

and returned as part of a FileContentResult object:

var fileContentResult = new FileContentResult(stream.ToArray(), Path.GetExtension(filename));

stream.Close();

return fileContentResult;

The complete method looks like this:

public FileContentResult GetInterviewFile(string filename, string templateLocation)
{
        var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/hdcs");
        var template = Template.Locate(templateLocation);
        var interviewFile = service.GetInterviewFile(template, filename, "");
        var stream = new MemoryStream();            
        interviewFile.CopyTo(stream);
        var fileContentResult = new FileContentResult(stream.ToArray(), Path.GetExtension(filename));
        stream.Close();
        return fileContentResult;
}

8. Test

To test the interview:

  1. Set the current project as the Startup Project (right-click the SdkExample10WebServiceInterviewDisplay 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.

If you click the Finish button on the interview, an error page appears, as you have not yet created a method to handle the post-assembly action. You will do this in the next step.

9. Post-Interview Processing

Now that the interview is working, you need to create a new method to handle the answer data posted back when an interview finishes. Generally, a post-interview processing method will need to handle at least the first two of the following:

  • Retrieve the answer data
  • Assemble a document using the answer data
  • Save the answer data

When you create the HotDocs InterviewSettings, one of the parameters is a post-interview URL. In the example above, on clicking Finish, the interview looks for Home/PostInterviewProcessing, which results in the Interview posting back to the HomeController, looking for a method, PostInterviewProcessing. In this example you create this new method and implement answer data retrieval and document assemby.

9.1 Assemble the Document

In this step, you will modify the AssembleDocument method created in example 2 to assemble the document using the answers retrieved from the interview.

First, copy the AssembleDocument method from example 2. It looks like this:

private static AssembleDocumentResult AssembleDocument()
{                                           
        var template = GetTemplate();            
        var answers = GetAnswers();
        var assembleDocumentSettings = new AssembleDocumentSettings();
        
        var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/HDCS");
        return service.AssembleDocument(template, answers, assembleDocumentSettings, "ExampleLogRef");
}

Edit the constructor to accept the answer XML string received from the interview:

private static void AssembleDocument(string encodedAnswers)

Next, remove the call to the GetAnswers method and replace it with a new StringReader that converts the encodedAnswers string into a StringReader:

var answers = new StringReader(encodedAnswers);

In the method, delete the return statement. Instead of returning the AssembledDocumentResult, you will pass it to a new method, SaveAssembledDocumentResult, which writes the assembled document file to disk:

var assembledDocumentResult = service.AssembleDocument(template, answers, assembleDocumentSettings, "ExampleLogRef");
SaveAssembledDocument(assembledDocumentResult);  

The completed method looks like this:

private static void AssembleDocument(string encodedAnswers)
{            
        var template = CreateTemplate();
        var answers = new StringReader(encodedAnswers);
        var assembleDocumentSettings = new AssembleDocumentSettings();
        var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/hdcs");
        var assembledDocumentResult = service.AssembleDocument(template, answers, assembleDocumentSettings, "ExampleLogRef");
        SaveAssembledDocument(assembledDocumentResult);                       
}

9.2 Save the Assembled Document

Create a new method, SaveAssembledDocumentResult, and save it to disk as you did in example 2:

private static void SaveAssembledDocument(AssembleDocumentResult assembledDocumentResult)
{
        var fileStream = System.IO.File.Create(@"C:\temp\output" + assembledDocumentResult.Document.FileExtension);
        stream.Close();
        assembledDocumentResult.Document.Content.CopyTo(fileStream);
}

9.3 Create the PostInterviewProcessing Method

Finally, create a new method to handle the data posted back from the interview. Create a new ActionResult method, PostInterviewProcessing:

[HttpPost] 
public ActionResult PostInterviewProcessing() 
{   
} 

This will handle the data send back from the interview. Note the HttpPost attribute added before the method definition. This is added so that the ActionResult will accept POST requests, as you are receiving data that is posted back from the interview.

9.4 Retrieving the answer data

To retrieve the answer data posted from the interview, using the InterviewResponse.GetAnswers method to extract the answers from the Request Form:

var encodedAnswers = InterviewResponse.GetAnswers(Request.Form);            

9.5 Assemble the Document

With the encoded answers from the interview, you can assemble the document using the AssembleDocument method created above:

AssembleDocument(encodedAnswers);

To exit the method, return a null value.

return null;

At this stage in your own host application, you would typically redirect the user to a disposition page from where they can download the assembled document. The application is now ready to test.

10. Test

To run the project and test the interview post-back:

  1. Set the current project as the Startup Project (Right-click the SdkExample10WebServiceInterviewDisplay project, select Startup Project from the drop-down menu)
  2. Press F5 to run the Project. The interview loads in the browser.
  3. When the interview has finished loading, enter answer data into the interview fields.
  4. Click Finish. The Interview finishes. An completed document is written to C:\temp\output.docx.

Example Source Code (C#)

In the HomeController:

using System;
using System.IO;
using System.Web;
using System.Web.Mvc;
using HotDocs.Sdk;
using HotDocs.Sdk.Server;
using HotDocs.Sdk.Server.OnPremise;
using SdkExample10WebServiceInterviewDisplay.Models;
namespace SdkExample10WebServiceInterviewDisplay.Controllers
{
        public class HomeController : Controller
    {              
                public ActionResult Index()
                {
                        var interviewFragment = GetInterviewFragment();
                        var model = new InterviewModel { InterviewFragment = interviewFragment };
                        return View(model);
                }
                private static string GetInterviewFragment()
                {            
                        var template = CreateTemplate();            
                        string templateLocation = template.CreateLocator();
                        var interviewSettings = GetInterviewSettings(templateLocation);
                        
                        var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/hdcs");
                        var interview = service.GetInterview(template, null, interviewSettings, null, "ExampleLogReference");
                        return interview.HtmlFragment;
                }
                private static InterviewSettings GetInterviewSettings(string templateLocation)
                {
                        string postInterviewUrl = "Home/PostInterviewProcessing";
                        string interviewRuntimeUrl = "http://localhost/HDServerFiles/js";
                        string interviewStylesheetUrl = "http://localhost/HDServerFiles/stylesheets";
                        string interviewFileUrl = HttpUtility.UrlEncode("/Home/GetInterviewFile?templatelocater="+ templateLocation);
                        var interviewSettings = new InterviewSettings(postInterviewUrl, interviewRuntimeUrl, interviewStylesheetUrl, interviewFileUrl) { RoundTripUnusedAnswers = true };
                        return interviewSettings;
                }
                [HttpPost]
                public ActionResult PostInterviewProcessing()
                {
                        var encodedAnswers = InterviewResponse.GetAnswers(Request.Form);            
                        AssembleDocument(encodedAnswers);
                        return null;
                }
                public FileContentResult GetInterviewFile(string filename, string templateLocation)
                {
                        var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/hdcs");
                        var template = Template.Locate(templateLocation);
                        var interviewFile = service.GetInterviewFile(template, filename, "");
                        
                        var stream = new MemoryStream();            
                        interviewFile.CopyTo(stream);
                        var fileContentResult = new FileContentResult(stream.ToArray(), Path.GetExtension(filename));
                        return fileContentResult;
                }
                private static Template CreateTemplate()
                {            
                        var templateLocation = new PackagePathTemplateLocation("7A7BF8B9-C895-4BC9-BC1A-44E61D6008A2", @"C:\Temp\HelloWorld.hdpkg");
                        var template = new Template(templateLocation);
                        return template;
                }
                private static void AssembleDocument(string encodedAnswers)
                {            
                        var template = CreateTemplate();
                        var answers = new StringReader(encodedAnswers);
                        var assembleDocumentSettings = new AssembleDocumentSettings();
                        var service = new OnPremiseServices("http://localhost:80/hdswebapi/api/hdcs");
                        var assembledDocumentResult = service.AssembleDocument(template, answers, assembleDocumentSettings, "ExampleLogRef");
                        SaveAssembledDocument(assembledDocumentResult);                       
                }
                private static void SaveAssembledDocument(AssembleDocumentResult assembledDocumentResult)
                {
                        var fileStream = System.IO.File.Create(@"C:\temp\output" + assembledDocumentResult.Document.FileExtension);
                        assembledDocumentResult.Document.Content.CopyTo(fileStream);
                }
        
        }
}

In the Index View:

@model SdkExample10WebServiceInterviewDisplay.Models.InterviewModel
<div>
        @Html.Raw(Model.InterviewFragment)
</div>

 

In the Model:

namespace SdkExample10WebServiceInterviewDisplay.Models
{
        public class InterviewModel
        {
                public string InterviewFragment { get; set; }
        }
}