Scheduling Dynamics 365 workflows with Azure Functions and C#
Over the past few days, I've shared two approaches for scheduling Dynamics 365 workflows using Azure Functions and the Dynamics 365 Web API. One uses Node.js, and the other uses Python. Because most Dynamics CRM developers are probably more familiar with C# than Node.js or Python, I also created an equivalent C# version. Just like with my previous examples, this version calls the Web API directly instead of using any SDK assemblies.
Here's my code. It does the following:
- Request an OAuth token using a username and password.
- Query the Dynamics 365 Web API for accounts with names that start with the letter "F."
- Execute a workflow for each record that was retrieved in the previous step. The workflow that I am executing is the same workflow I used in my Node.js example to create a note on an account.
#r "Newtonsoft.Json"
using System;
using System.Net;
using System.IO;
using Newtonsoft.Json;
//set these values to retrieve the oauth token
static string crmorg = "https://CRMORG.crm.dynamics.com";
static string clientid = "00000000-0000-0000-0000-000000000000";
static string username = "xxxxxx@xxxxxxxx";
static string userpassword = "xxxxxxxx";
static string tokenendpoint = "https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/token";
//set these values to query your crm data
static string crmwebapihost = "https://CRMORG.api.crm.dynamics.com/api/data/v8.2";
static string crmwebapipath = "/accounts?$select=name,accountid&$filter=startswith(name,'F')";
static string workflowid = "DC8519EC-F3CE-4BC9-BB79-DF2AD70217A1";
public static void Run(TimerInfo myTimer, TraceWriter log)
{
//build the authorization request
var reqstring = "client_id=" + clientid;
reqstring += "&resource=" + Uri.EscapeUriString(crmorg);
reqstring += "&username=" + Uri.EscapeUriString(username);
reqstring += "&password=" + Uri.EscapeUriString(userpassword);
reqstring += "&grant_type=password";
WebRequest req = WebRequest.Create(tokenendpoint);
req.ContentType = "application/x-www-form-urlencoded";
req.Method = "POST";
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(reqstring);
req.ContentLength = bytes.Length;
System.IO.Stream os = req.GetRequestStream();
os.Write(bytes, 0, bytes.Length);
os.Close();
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
StreamReader tokenreader = new StreamReader(resp.GetResponseStream());
string responseBody = tokenreader.ReadToEnd();
tokenreader.Close();
var tokenresponse = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(responseBody);
var token = tokenresponse["access_token"];
log.Info("got token");
WebRequest crmreq = WebRequest.Create(crmwebapihost + crmwebapipath);
crmreq.Headers = new WebHeaderCollection();
crmreq.Headers.Add("Authorization", "Bearer " + token);
crmreq.Headers.Add("OData-MaxVersion", "4.0");
crmreq.Headers.Add("OData-Version", "4.0");
crmreq.Headers.Add("Prefer", "odata.maxpagesize=500");
crmreq.Headers.Add("Prefer", "odata.include-annotations=OData.Community.Display.V1.FormattedValue");
crmreq.ContentType = "application/json; charset=utf-8";
crmreq.Method = "GET";
HttpWebResponse crmresp = (HttpWebResponse)crmreq.GetResponse();
StreamReader crmreader = new StreamReader(crmresp.GetResponseStream());
string crmresponseBody = crmreader.ReadToEnd();
crmreader.Close();
var crmresponseobj = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(crmresponseBody);
foreach(var row in crmresponseobj["value"].Children())
{
log.Info(row["name"].ToString());
runWorkflow(token.ToString(), new Guid(row["accountid"].ToString()), log);
}
Console.ReadLine();
}
static void runWorkflow(string token, Guid entityid, TraceWriter log)
{
var crmwebapiworkflowpath = "/workflows(" + workflowid + ")/Microsoft.Dynamics.CRM.ExecuteWorkflow";
WebRequest req = WebRequest.Create(crmwebapihost + crmwebapiworkflowpath);
log.Info(" calling workflow for " + entityid);
string reqobject = "{ \"EntityId\": \"" + entityid + "\"}";
req.Headers.Add("Authorization", "Bearer " + token);
req.Headers.Add("OData-MaxVersion", "4.0");
req.Headers.Add("OData-Version", "4.0");
req.Headers.Add("Prefer", "odata.maxpagesize=500");
req.Headers.Add("Prefer", "odata.include-annotations=OData.Community.Display.V1.FormattedValue");
req.ContentType = "application/json; charset=utf-8";
req.Method = "POST";
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(reqobject);
req.ContentLength = bytes.Length;
System.IO.Stream os = req.GetRequestStream();
os.Write(bytes, 0, bytes.Length);
os.Close();
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
StreamReader reader = new StreamReader(resp.GetResponseStream());
string responseBody = reader.ReadToEnd();
reader.Close();
var responseobj = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(responseBody);
if(resp.StatusCode == HttpStatusCode.OK)
{
log.Info(" success " + entityid.ToString());
}
else
{
log.Info(" error " + entityid.ToString());
}
}
To set this up in your Azure tenant, set up a Functions App and a new C# timer trigger function like I described in the Node.js example. Copy the C# code from above and paste it into the function editor window. Set any specifics relative to your Dynamics 365 organization and click save. That's all there is to it.
*If you're wondering about that #r "Newtonsoft.Json"
at the top of the C# code, take a look here.