I'm fairly new to the DocuSign API, and have been messing with the samples with success - for single documents. My issue is that I need to send Multiple document sin a single package, and I do not want to use JSON formatting at this particular moment (maybe as I become more familiar with it).
I've read the post here, but it doesn't solve my issue.: Trying to create DocuSign envelope with multiple documents
I have modified the example found here (for C#) and sending single documents works fine. http://iodocs.docusign.com/APIWalkthrough/requestSignatureFromDocument
NOTE: The code below works for a SINGLE document, just as the example. However, I need to be able to send multiple documents, and have been banging my head for a more than a few hours now. My issue is that I think I've got the format down for the multiple documents (as described in the stackoverflow post above), however, I cannot seem to get it to work properly.
So, modifying the example for C#, I've come up with the following:
public static void configureMultiPartFormDataRequest(HttpWebRequest request, string xmlBody, string docName, string contentType, string[] documents)
{
string boundaryName = string.Empty;
string boundaryDocsName = string.Empty;
boundaryName = "AAA";
boundaryDocsName = "BBB";
bool multipleDocs = documents.Length > 1;
// start building the multipart request body
string requestBodyStart = "\r\n\r\n--" + boundaryName + "\r\n" + "Content-Type: application/xml\r\n" +
"Content-Disposition: form-data\r\n\r\n" +
xmlBody;
string requestBodyEnd = "\r\n--" + boundaryName + "--\r\n\r\n";
string requestBodyDocsEnd = "\r\n--" + boundaryDocsName + "--";
if (multipleDocs)
{
requestBodyStart = requestBodyStart + "\r\n--" + boundaryName + "\r\n" + "Content-Disposition: form-data\r\n" + "Content-Type: multipart/mixed; boundary=" + boundaryDocsName + "\r\n";
}
else
{
// overwrite the default content-type header and set a boundary marker
request.ContentType = string.Format("multipart/form-data; boundary={0}", boundaryName);
boundaryDocsName = boundaryName;
requestBodyStart = requestBodyStart;
}
byte[] bodyStart = System.Text.Encoding.UTF8.GetBytes(requestBodyStart.ToString());
byte[] bodyEnd = System.Text.Encoding.UTF8.GetBytes(requestBodyEnd.ToString());
byte[] bodyDocsEnd = System.Text.Encoding.UTF8.GetBytes(requestBodyDocsEnd.ToString());
Stream dataStream = request.GetRequestStream();
dataStream.Write(bodyStart, 0, requestBodyStart.ToString().Length);
// Do documents
addRequestDocs(dataStream, contentType, documents, boundaryDocsName);
if (multipleDocs)
{
// Write Document End
dataStream.Write(bodyDocsEnd, 0, requestBodyDocsEnd.ToString().Length);
}
// Write Stream end
dataStream.Write(bodyEnd, 0, requestBodyEnd.ToString().Length);
string textDataStream = dataStream.ToString();
dataStream.Close();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
public static void addRequestDocs(Stream dataStream, string contentType, string[] documents, string boundaryName)
{
int counter = 0;
foreach (string x in documents)
{
counter = counter + 1;
string docBodyStart = string.Empty;
string myDocName = GetPhysicalFileName(x);
string requestDocumentBodyStart = "\r\n--" + boundaryName + "\r\n" + "Content-Type: " + contentType + "\r\n" +
"Content-Disposition: file; filename=\"" + x + "\"; documentId=" + counter + "\r\n\r\n";
byte[] bodyStart = System.Text.Encoding.UTF8.GetBytes(requestDocumentBodyStart.ToString());
dataStream.Write(bodyStart, 0, requestDocumentBodyStart.ToString().Length);
byte[] docContents = File.ReadAllBytes(myDocName);
// read contents of provided document(s) into the stream
dataStream.Write(docContents, 0, docContents.Length);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
public static string GetPhysicalFileName(string relativeFileName)
{
string virtualFileName = string.Format("{0}{1}", HttpRuntime.AppDomainAppVirtualPath, relativeFileName);
string physicalFileName = HttpContext.Current.Request.MapPath(virtualFileName);
return physicalFileName;
}
Calling code looks similar to the sample, but I've added a second document, and a location for signature in each document.
public string RequestEmailSignature()
{
//---------------------------------------------------------------------------------------------------
// ENTER VALUES FOR THE FOLLOWING 6 VARIABLES:
//---------------------------------------------------------------------------------------------------
string username = "***";
string password = "***";
string integratorKey = "***";
bool returnPrettyXML = false;
string recipientName = "John Smith"; // recipient (signer) name
string recipientEmail = "john.smith@place.com"; // recipient (signer) email
string recipientName1 = "John Smith Jr."; // recipient (signer) name
string recipientEmail1 = "john.smith.jr@place.com"; // recipient (signer) email
string documentName = "IRS_4506_T.pdf"; // copy document with same name and extension into project directory (i.e. "test.pdf")
string documentName1 = "Doc2"; // copy document with same name and extension into project directory (i.e. "test.pdf")
string contentType = "application/pdf"; // default content type is PDF
//---------------------------------------------------------------------------------------------------
// additional variable declarations
//string response = string.Empty;
//string retResult = string.Empty;
string baseURL = string.Empty; // - we will retrieve this through the Login API call
string response = string.Empty;
string retResult = string.Empty;
// Endpoint for Login api call (in demo environment):
string url = "https://demo.docusign.net/restapi/v2/login_information";
try
{
//============================================================================
// STEP 1 - Login API Call (used to retrieve your baseUrl)
//============================================================================
// set request url, method, and headers. No body needed for login api call
HttpWebRequest request = initializeRequest(url, "GET", null, username, password, integratorKey);
// read the http response
response = getResponseBody(request);
// parse baseUrl from response body
baseURL = parseDataFromResponse(response, "baseUrl");
if (!returnPrettyXML)
{
retResult = baseURL;
}
else
{
retResult = prettyPrintXml(response);
}
//--- display results
//Console.WriteLine("\nAPI Call Result: \n\n" + prettyPrintXml(response));
//}
//try
//{
// Endpoint for Login api call (in demo environment):
//string url = "https://demo.docusign.net/restapi/v2/login_information";
//============================================================================
// STEP 2 - Send Signature Request from Template
//============================================================================
/*
This is the only DocuSign API call that requires a "multipart/form-data" content type. We will be
constructing a request body in the following format (each newline is a CRLF):
--AAA
Content-Type: application/xml
Content-Disposition: form-data
<XML BODY GOES HERE>
--AAA
Content-Type:application/pdf
Content-Disposition: file; filename="document.pdf"; documentid=1
<DOCUMENT BYTES GO HERE>
--AAA--
*/
// append "/envelopes" to baseURL and use for signature request api call
url = baseURL + "/envelopes";
// construct an outgoing XML formatted request body (JSON also accepted)
// .. following body adds one signer and places a signature tab 100 pixels to the right
// and 100 pixels down from the top left corner of the document you supply
string xmlBody =
"<envelopeDefinition xmlns=\"http://www.docusign.com/restapi\">" +
"<emailSubject>DocuSign API - Signature Request on Document</emailSubject>" +
"<status>sent</status>" + // "sent" to send immediately, "created" to save as draft in your account
// add document(s)
"<documents>" +
// Doc 1
"<document>" +
"<documentId>1</documentId>" +
"<name>" + documentName + "</name>" +
"</document>" +
// Doc 2
"<document>" +
"<documentId>2</documentId>" +
"<name>" + documentName1 + "</name>" +
"</document>" +
"</documents>" +
// add recipient(s)
"<recipients>" +
"<signers>" +
// Signer 1
"<signer>" +
"<recipientId>1</recipientId>" +
"<email>" + recipientEmail + "</email>" +
"<name>" + recipientName + "</name>" +
"<tabs>" +
"<signHereTabs>" +
// Doc 1
"<signHere>" +
"<xPosition>80</xPosition>" + // default unit is pixels
"<yPosition>620</yPosition>" + // default unit is pixels
"<documentId>1</documentId>" +
"<pageNumber>1</pageNumber>" +
"</signHere>" +
// Doc 2
"<signHere>" +
"<xPosition>20</xPosition>" + // default unit is pixels
"<yPosition>674</yPosition>" + // default unit is pixels
"<documentId>2</documentId>" +
"<pageNumber>4</pageNumber>" +
"</signHere>" +
"</signHereTabs>" +
"</tabs>" +
"</signer>" +
"</signers>" +
"</recipients>" +
"</envelopeDefinition>";
// set request url, method, headers. Don't set the body yet, we'll set that separelty after
// we read the document bytes and configure the rest of the multipart/form-data request
request = initializeRequest(url, "POST", null, username, password, integratorKey);
// DOC 1 and 2
string[] docArray = new string[] { documentName, documentName1 };
// DOC 1 only <-- WORKS!!!
//string[] docArray = new string[] { documentName};
configureMultiPartFormDataRequest(request, xmlBody, documentName, contentType, docArray);
// read the http response
response = getResponseBody(request);
if (!returnPrettyXML)
{
retResult = response;
}
else
{
retResult = prettyPrintXml(response);
}
//--- display results
//Console.WriteLine("\nAPI Call Result: \n\n" + prettyPrintXml(response));
}
catch (WebException e)
{
using (WebResponse callResponse = e.Response)
{
HttpWebResponse httpResponse = (HttpWebResponse)callResponse;
//Console.WriteLine("Error code: {0}", httpResponse.StatusCode);
using (Stream data = callResponse.GetResponseStream())
{
string text = new StreamReader(data).ReadToEnd();
retResult = text;
// Console.WriteLine(prettyPrintXml(text));
}
}
}
return retResult;
}
This results in the following result for 1 document:
Accept: application/xml
Content-Type: multipart/form-data; boundary=AAA
Host: demo.docusign.net
Content-Length: 92530
Expect: 100-continue
--AAA
Content-Type: application/xml
Content-Disposition: form-data
<envelopeDefinition xmlns="http://www.docusign.com/restapi"><emailSubject>DocuSign API - Signature Request on Document</emailSubject><status>sent</status><documents><document><documentId>1</documentId><name>IRS_4506_T.pdf</name></document></documents><recipients><signers><signer><recipientId>1</recipientId><email>troy.munford@sba.gov</email><name>Troy Munford</name><routingOrder>1</routingOrder><tabs><signHereTabs><signHere><xPosition>80</xPosition><yPosition>620</yPosition><documentId>1</documentId><pageNumber>1</pageNumber></signHere></signHereTabs></tabs></signer></signers></recipients></envelopeDefinition>
--AAA
Content-Type: application/pdf
Content-Disposition: file; filename="IRS_4506_T.pdf"; documentId=1
<PDF REMOVED>
--AAA--
This results in the following for 2 documents:
Accept: application/xml
Content-Type: application/xml
Host: demo.docusign.net
Content-Length: 694790
Expect: 100-continue
--AAA
Content-Type: application/xml
Content-Disposition: form-data
<envelopeDefinition xmlns="http://www.docusign.com/restapi"><emailSubject>DocuSign API - Signature Request on Document</emailSubject><status>sent</status><documents><document><documentId>1</documentId><name>IRS_4506_T.pdf</name></document><document><documentId>2</documentId><name>Loan Application Home (Form 5C).pdf</name></document></documents><recipients><signers><signer><recipientId>1</recipientId><email>troy.munford@sba.gov</email><name>Troy Munford</name><routingOrder>1</routingOrder><tabs><signHereTabs><signHere><xPosition>80</xPosition><yPosition>620</yPosition><documentId>1</documentId><pageNumber>1</pageNumber></signHere><signHere><xPosition>30</xPosition><yPosition>685</yPosition><documentId>2</documentId><pageNumber>4</pageNumber></signHere></signHereTabs></tabs></signer></signers></recipients></envelopeDefinition>
--AAA
Content-Disposition: form-data
Content-Type: multipart/mixed; boundary=BBB
--BBB
Content-Type: application/pdf
Content-Disposition: file; filename="IRS_4506_T.pdf"; documentId=1
<PDF REMOVED>
--BBB
Content-Type: application/pdf
Content-Disposition: file; filename="Loan Application Home (Form 5C).pdf"; documentId=2
<PDF REMOVED>
--BBB--
--AAA--
FYI: I've tried a multitude of things (including moving the --AAA and --BBB directly under the PDF, or header data), but nothing seems to allow a second document to be added.