Reputation: 673
I am creating a webhook in a .Net Core 3 Web API for DocuSign Connect to invoke and provide me status updates + signed documents from envelopes my app has created. The C# example at https://www.docusign.com/blog/dsdev-adding-webhooks-application was very helpful in getting me almost to my goal. The code from the example is:
[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook(HttpRequestMessage request)
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(request.Content.ReadAsStreamAsync().Result);
var mgr = new XmlNamespaceManager(xmldoc.NameTable);
mgr.AddNamespace("a", "http://www.docusign.net/API/3.0");
XmlNode envelopeStatus = xmldoc.SelectSingleNode("//a:EnvelopeStatus", mgr);
XmlNode envelopeId = envelopeStatus.SelectSingleNode("//a:EnvelopeID", mgr);
XmlNode status = envelopeStatus.SelectSingleNode("./a:Status", mgr);
var targetFileDirectory = @"\\my-network-share\";
if (envelopeId != null)
{
System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{status.InnerText}_.xml", xmldoc.OuterXml);
}
if (status.InnerText == "Completed")
{
// Loop through the DocumentPDFs element, storing each document.
XmlNode docs = xmldoc.SelectSingleNode("//a:DocumentPDFs", mgr);
foreach (XmlNode doc in docs.ChildNodes)
{
string documentName = doc.ChildNodes[0].InnerText; // pdf.SelectSingleNode("//a:Name", mgr).InnerText;
string documentId = doc.ChildNodes[2].InnerText; // pdf.SelectSingleNode("//a:DocumentID", mgr).InnerText;
string byteStr = doc.ChildNodes[1].InnerText; // pdf.SelectSingleNode("//a:PDFBytes", mgr).InnerText;
System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{documentId}_{documentName}", byteStr);
}
}
}
For testing purposes, my Web API is allowing all origins and exposed to the outside world via NGROK, and I can hit other test endpoints (both GET and POST), but for some reason this webhook is not being hit by Connect when there is a notification-worthy event on my envelope.
I can see in the DocuSign Admin portal logs that Connect invoked my webhook but got The remote server returned an error: (415) Unsupported Media Type.. This led me to add the [FromBody]
attribute to my method signature like so but I still get the same error when my webhook is invoked by Connect.
[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook([FromBody] HttpRequestMessage request)
{
// ... rest of the method was unchanged, removed for brevity
}
I have never used HttpRequestMessage
before but it looks straightforward enough. I noticed in the DocuSign Admin portal logs that the data that Connect tried to send to the webhook is just XML. I could try to change the webhook's signature to look for an XmlDocument
instead of an HttpRequestMessage
but I am not sure what, if anything, I will be missing out on.
Has anyone else integrated with Connect via a webhook recently? And were you able to make the HttpRequestMessage
work for you?
Added on 10/18/2019:
DocuSign mentions that the content type is XML. Here is what the content looks like:
<DocuSignEnvelopeInformation
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.docusign.net/API/3.0">
<EnvelopeStatus>...</EnvelopeStatus>
<DocumentPDFs>...</DocumentPDFs>
</DocuSignEnvelopeInformation>
I have added AddXmlSerializerFormatters()
to the ConfigureServices
method in Startup.cs
. This being .Net Core 3, I had to set it up like services.AddControllers().AddXmlSerializerFormatters()
instead of services.AddMVC().AddXmlSerializerFormatters()
per https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio.
With that change, I have now tried using [FromForm]
like so and my webhook IS being hit, but the request
input parameter is essentially empty ... request.Content = null
:
[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook([FromForm] HttpRequestMessage request)
{
// ... rest of the method was unchanged, removed for brevity
}
Since the request is being sent from DocuSign Connect, I have no control over the headers/format/content. As far as I can tell, they are not submitting an XML object, not a form, so [FromForm]
is probably not the way to go.
Upvotes: 4
Views: 3133
Reputation: 247591
That linked example is not for .net core. HttpRequestMessage
is no longer a first class citizen in asp.net-core framework and will treated as a normal model.
Just extract the content directly from the Request's body and the rest should be able to remain the same as in the example.
[HttpPost("api/[controller]/ConnectWebHook")]
public IActionResult ConnectWebHook() {
Stream stream = Request.Body;
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(stream);
var mgr = new XmlNamespaceManager(xmldoc.NameTable);
mgr.AddNamespace("a", "http://www.docusign.net/API/3.0");
XmlNode envelopeStatus = xmldoc.SelectSingleNode("//a:EnvelopeStatus", mgr);
XmlNode envelopeId = envelopeStatus.SelectSingleNode("//a:EnvelopeID", mgr);
XmlNode status = envelopeStatus.SelectSingleNode("./a:Status", mgr);
var targetFileDirectory = @"\\my-network-share\";
if (envelopeId != null) {
System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{status.InnerText}_.xml", xmldoc.OuterXml);
}
if (status.InnerText == "Completed") {
// Loop through the DocumentPDFs element, storing each document.
XmlNode docs = xmldoc.SelectSingleNode("//a:DocumentPDFs", mgr);
foreach (XmlNode doc in docs.ChildNodes) {
string documentName = doc.ChildNodes[0].InnerText; // pdf.SelectSingleNode("//a:Name", mgr).InnerText;
string documentId = doc.ChildNodes[2].InnerText; // pdf.SelectSingleNode("//a:DocumentID", mgr).InnerText;
string byteStr = doc.ChildNodes[1].InnerText; // pdf.SelectSingleNode("//a:PDFBytes", mgr).InnerText;
System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{documentId}_{documentName}", byteStr);
}
}
return Ok();
}
Upvotes: 1