This is from a lab from the soon to be released Nintex End User Training courses. Its the first of its kind and I'll simply say, you will learn some fun stuff!
- Login to
Azure, or create a trial for Azure Functions - Trial
– https://functions.azure.com/try - Portal
– - Select
your subscription - For
the function name, type “NintexExternalStart” - Select
a region - Click
“Create + get started”, this can take a few seconds - Click the
“New Function” button - Select the
“HttpTrigger-CSharp” template: - Click
“Create"
Copy the following to your azure function:
#r "Newtonsoft.Json"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
string url = "https://run.nintex.io/x-start/YourEndPoint";
string securityKey = "YourSecurityKey";
var variableValues = new Dictionary<string, object>();
var service = new ExternalStartApiClient();
try
{
log.Info("C# HTTP trigger function processed a request.");
var correlationId = service.StartWorkflow(url, securityKey, variableValues, log);
}
catch
{
}
string name = "Chris";
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
public class ExternalStartApiClient
{
public RestClient Client { get; internal set; }
#region Constructors
public ExternalStartApiClient()
{
this.Client = new RestClient();
}
public ExternalStartApiClient(RestClient client)
{
this.Client = client;
}
#endregion
/// <summary>
/// Retrieves information about the workflow variables available for the Nintex workflow associated with the
/// specified endpoint URL.
/// </summary>
/// <param name="endpointUrl">The External Start endpoint URL associated with a Nintex workflow.</param>
/// <returns>A List object that contains a WorkflowVariable object for each available workflow variable.</returns>
public List<WorkflowVariable> GetWorkflowVariables(string endpointUrl)
{
// Send a GET request to the specified endpoint URL. No authorization or authentication is
// required for this request.
HttpResponseMessage response = this.Client.Invoke(HttpMethod.Get, endpointUrl, null, null);
// If a response is returned, check the HTTP status code.
if (response != null)
{
switch (response.StatusCode)
{
case HttpStatusCode.OK:
// Success – deserialize and return the list of workflow variables.
return this.Client.DeserializeJson<List<WorkflowVariable>>(response);
case HttpStatusCode.BadRequest:
// Failure – the endpoint URL could not access a workflow.
throw new ArgumentException("The request could not be processed.");
default:
throw new Exception("An unexpected error has occurred.");
}
}
else
{
return null;
}
}
/// <summary>
/// Sends a message to start the Nintex workflow associated with the specified endpoint URL, optionally specifying
/// values for workflow variables defined by that workflow
/// </summary>
/// <param name="endpointUrl">
/// The External Start endpoint URL associated with a Nintex workflow.
/// </param>
/// <param name="securityKey">
/// The security key associated with the endpoint URL.
/// </param>
/// <param name="workflowVariables">
/// The workflow variable values to use when starting the workflow.
/// </param>
/// <returns>
/// The <see cref="string"/>.
/// The X-CorrelationId: {GUID} of the request
/// </returns>
public string StartWorkflow(string endpointUrl, string securityKey, Dictionary<string, object> workflowVariables, TraceWriter log)
{
// If no workflow variable values are provided, send an empty request body; otherwise, send
// a serialized JSON object, in which each workflow variable value is represented as a property value.
string requestBody = "";
if (workflowVariables != null)
{
JObject associationData = new JObject();
foreach (string key in workflowVariables.Keys)
{
JProperty value = new JProperty(key, workflowVariables[key]);
associationData.Add(value);
}
requestBody = associationData.ToString();
}
// Retrieve and configure the values used to calculate the digest value for the request.
var path = new Uri(endpointUrl).AbsolutePath.ToLower();
var httpMethod = HttpMethod.Post.ToString().ToLower();
var nonce = Guid.NewGuid().ToString();
var timestamp = DateTime.UtcNow.ToString("O");
// Calculate and return the digest value for the request.
var digest = CalculateDigest(securityKey, httpMethod, path, nonce, timestamp, requestBody);
log.Info(digest);
log.Info(timestamp);
log.Info(nonce);
// Specify the header values for the request.
var headerValues = new Dictionary<string, string>();
headerValues.Add("X-Api-Digest", digest);
headerValues.Add("X-Api-Timestamp", timestamp);
headerValues.Add("X-Api-Nonce", nonce);
headerValues.Add("X-Api-Source", "ExternalStart");
// Send the request to the endpoint URL.
HttpResponseMessage response = this.Client.Invoke(HttpMethod.Post, endpointUrl, headerValues,
new StringContent(requestBody, Encoding.UTF8, "application/json"));
if (response != null)
{
log.Info(response.StatusCode.ToString());
switch (response.StatusCode)
{
case HttpStatusCode.OK:
// Success – the message was successfully sent.
IEnumerable<string> correlationIds;
response.Headers.TryGetValues("X-CorrelationId", out correlationIds);
return correlationIds.FirstOrDefault();
break;
case HttpStatusCode.BadRequest:
// Failure – the endpoint URL could not access a workflow.
throw new ArgumentException("The request could not be processed.");
default:
throw new Exception("An unexpected error has occurred.");
}
}
else
{
throw new Exception("An unexpected error has occurred.");
}
}
/// <summary>
/// Calculate the digest value used to authenticate requests for the External Start feature.
/// </summary>
/// <param name="securityKey">The security key associated with an External Start endpoint URL.</param>
/// <param name="httpMethod">The HTTP method name, in lower case.</param>
/// <param name="path">The absolute URI of the External Start endpoint URL, in lower case.</param>
/// <param name="nonce">The nonce value.</param>
/// <param name="timestamp">The date and time of the request, in ISO 8601 format.</param>
/// <param name="requestBody">The serialized body of the request.</param>
/// <returns>A keyed hash value, using the SHA-256 function, to be used as the Hash-based Authentication Code (HMAC) value for a request.</returns>
public string CalculateDigest(string securityKey, string httpMethod, string path, string nonce,
string timestamp, string requestBody)
{
// The data values are concatenated into a single string, in which each data value is delimited by
// a colon (:) character, which is then encoded as a UTF-8 byte array.
var dataBytes = Encoding.UTF8.GetBytes(String.Join(":", httpMethod, path, nonce, timestamp, requestBody));
// The security key is encoded as a UTF-8 byte array.
var keyBytes = Encoding.UTF8.GetBytes(securityKey);
// Using the HMACSHA256 object provided by .NET Framework, the
// data values are hashed, using the security key, and any dashes are removed.
using (var hasher = new HMACSHA256(keyBytes))
{
var hashBytes = hasher.ComputeHash(dataBytes);
return BitConverter.ToString(hashBytes).Replace("-", "");
}
}
}
public class RestClient
{
public NetworkCredential Credential { get; set; }
#region Constructors
public RestClient()
{
}
public RestClient(NetworkCredential credential)
{
this.Credential = credential;
}
#endregion
#region Deserialization
/// <summary>
/// Deserialize the body of a specified HTTP response as an instance of a specified type.
/// </summary>
/// <typeparam name="T">The type into which to deserialize.</typeparam>
/// <param name="restResponse">The HTTP response from which to deserialize.</param>
/// <param name="jsonConverters">If needed, any custom JSON converters with which to deserialize.</param>
/// <returns>An instance of the specified type, deserialized from the body of the specified HTTP response.</returns>
internal T DeserializeJson<T>(HttpResponseMessage restResponse, JsonConverter[] jsonConverters = null)
{
return this.DeserializeJson<T>(restResponse.Content.ReadAsStringAsync().Result, jsonConverters);
}
/// <summary>
/// Deserialize a specified JSON-encoded string as an instance of a specified type.
/// </summary>
/// <typeparam name="T">The type into which to deserialize.</typeparam>
/// <param name="jsonString">The string from which to deserialize.</param>
/// <param name="jsonConverters">If needed, any custom JSON converters with which to deserialize.</param>
/// <returns>An instance of the specified type, deserialized from the body of the specified string.</returns>
internal T DeserializeJson<T>(string jsonString, JsonConverter[] jsonConverters = null)
{
if (jsonConverters != null)
return JsonConvert.DeserializeObject<T>(jsonString, jsonConverters);
else
return JsonConvert.DeserializeObject<T>(jsonString);
}
#endregion
#region Serialization
/// <summary>
/// Serialize the specified object as a JSON-encoded string.
/// </summary>
/// <param name="value">The object from which to serialize.</param>
/// <param name="jsonConverters">If needed, any custom JSON converters with which to serialize.</param>
/// <returns>A JSON-encoded string that contains the serialization of the specified object.</returns>
internal string SerializeJson(object value, JsonConverter[] jsonConverters = null)
{
if (jsonConverters != null)
{
return JsonConvert.SerializeObject(value, Formatting.Indented, jsonConverters);
}
return JsonConvert.SerializeObject(value, Formatting.Indented);
}
#endregion
#region REST invocation
/// <summary>
/// Invokes the specified REST resource, using the specified HTTP method, optionally providing any specified header values and request content.
/// </summary>
/// <param name="operationMethod">The HTTP method with which to invoke the REST resource.</param>
/// <param name="operationUrl">The operation URL with which to invoke the REST resource.</param>
/// <param name="operationHeaders">A collection of header names and values to include when invoking the REST resource.</param>
/// <param name="operationContent">The HTTP content to include when invoking the REST resource.</param>
/// <returns></returns>
internal HttpResponseMessage Invoke(HttpMethod operationMethod,
string operationUrl,
Dictionary<string, string> operationHeaders,
HttpContent operationContent)
{
HttpResponseMessage response = null;
try
{
// Instantiate a new HttpClientHandler object and, if credentials are provided,
// configure and include them.
var clientHandler = new HttpClientHandler { PreAuthenticate = true };
if (this.Credential == null)
{
clientHandler.UseDefaultCredentials = true;
}
else
{
clientHandler.Credentials = this.Credential;
}
// Instantiate a new HttpRequestMessage, using the specified HTTP method and operation URL,
// for the request.
&n
bsp; var request = new HttpRequestMessage(operationMethod, operationUrl);
bsp; var request = new HttpRequestMessage(operationMethod, operationUrl);
// If header values are provided, add them to the request.
// NOTE: The implementation is not optimal, but suffices for the sample.
if (operationHeaders != null)
{
foreach (var key in operationHeaders.Keys)
{
request.Headers.Add(key, operationHeaders[key]);
}
}
if (operationContent != null)
{
request.Content = operationContent;
}
// Instantiate a new HttpClient, asynchronously invoke the REST resource,
// and await the result.
using (var client = new HttpClient(clientHandler))
{
response = client.SendAsync(request).GetAwaiter().GetResult();
}
}
catch (Exception)
{
response = null;
}
// Return the resulting HttpResponseMessage.
return response;
}
#endregion
}
public enum VariableTypes
{
Text,
TextMultipleLine,
Number,
DateTime,
Boolean
}
/// <summary>
/// Models the information returned for workflow variables by the External Start feature.
/// This class is used to deserialize that information for the purposes of the sample.
/// </summary>
public class WorkflowVariable
{
[JsonProperty("Name"), JsonRequired]
public string Name { get; internal set; }
[JsonProperty("Type"), JsonRequired]
public VariableTypes Type { get; internal set; }
[JsonProperty("Required"), JsonRequired]
public bool Required { get; internal set; }
}
And then you'll need a project.json file:
{
"frameworks": {
"net46":{
"dependencies": {
"Newtonsoft.Json": "8.0.3"
}
}
}
}
"frameworks": {
"net46":{
"dependencies": {
"Newtonsoft.Json": "8.0.3"
}
}
}
}