Tip #794: Connect to Dynamics 365 in Azure Functions

Something usefulDelegating some work from plugins to Azure Functions turned out to be not as complex as we expected. Making function to do something useful (like updating Dynamics 365 records) was entirely different story.

After running some NEL (naked eye learning) algorithms against our sales pipeline we calculated that an average opportunity value is inflated by 20%. What would be a better candidate for a function than our freshly baked AutOpAd™ (Automatic Opportunity Adjuster). We have the basic plumbing worked out, these are the steps to turn it into the BS (Business Solutions) sales machine.

Opportunity Helper

In Azure UI for the function click View Files, then +Add, name the file opportunity.csx and add the following content:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Tooling.Connector;

public static void ChangeOpportunity(CrmServiceClient crmSvc, Entity opportunity, decimal howmuch, TraceWriter log)
{
  log.Info($"Changing opportunity {opportunity.GetAttributeValue<string>("name")} ({opportunity.Id}) by {howmuch}");
  
  var estvalue = opportunity.GetAttributeValue<Money>("estimatedvalue");

  if(estvalue == null)
  {
    log.Info("Opportunity is priceless - nothing to do");
  }
  else
  {
    Entity oppUpd = new Entity("opportunity");
    oppUpd["opportunityid"] = opportunity.Id;
    oppUpd["estimatedvalue"] = new Money(estvalue.Value * (1m + howmuch));
    crmSvc.Update(oppUpd);
    
    if(howmuch != 0m) 
    {
      log.Info($"Opportunity value {(howmuch < 0m ? "de" : "in")}creased");
    }
  }
}

Don’t worry about any compilation errors just yet – we are not done.

Main code

Let’s now use the above soul of AutOpAd™ and change the heart, i.e. run.csx to match it:

#r "Microsoft.ServiceBus"

#load "opportunity.csx"

using System;
using System.Threading.Tasks;
using Microsoft.ServiceBus.Messaging;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Tooling.Connector;

public static void Run(BrokeredMessage myQueueItem, TraceWriter log)
{
  log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
  log.Info($"Message ID: {myQueueItem.CorrelationId}");

  var rec = myQueueItem.GetBody<RemoteExecutionContext>();
  if(rec.PrimaryEntityName == "opportunity")
  {
    Entity entity = rec.InputParameters["Target"] as Entity;
    var name = entity.GetAttributeValue<string>("name");
    var est = entity.GetAttributeValue<Money>("estimatedvalue");
    string value = est == null ? "unknown value": string.Format("{0:C2}", est.Value);
    log.Info($"Opportunity {name} for {value} was created");

	// New stuff!
    log.Info($"Let's decrease the opportunity value by 20%");

    CrmServiceClient crmSvc = new CrmServiceClient(
      "foo@barbaz.onmicrosoft.com",
      CrmServiceClient.MakeSecureString("pass@word1 is not a strong password"),
      "NorthAmerica", "barbaz",
      useSsl: true, isOffice365: true);

    log.Info($"Connection result: {crmSvc.IsReady}");
    if(crmSvc.IsReady)
    {
      log.Info($"User id: {crmSvc.GetMyCrmUserId()}");
      ChangeOpportunity(crmSvc, entity, -0.2m, log);
    }
    else 
    {
      log.Info("Couldn't connect to CRM!");
    }
  }
}

Painful stuff

Now, the important stuff, otherwise it wouldn’t be a tip, just a bunch code, right? We need to get a reference for Microsoft.CrmSdk.XrmTooling.CoreAssembly into project.json file, right? Sounds easy but, as it turned out, due to the way SDK assemblies refer to each other combined with some idiosyncrasies of Azure Function runtime binding, referring to the assemblies neither by the full version as 8.2.0.1, nor as a short 8.2 would work. Only 8.2.0 and nothing else. Go figure. Content of project.json is as following:

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.CrmSdk.CoreAssemblies": "8.2.0",
        "Microsoft.CrmSdk.XrmTooling.CoreAssembly": "8.2.0"
      }
    }
  }
}

Now your AutOpAd™ should compile successfully and when new opportunity gets created, you’ll see the following output:

2016-12-12T23:07:26.773 Function started (Id=50a61122-a8f4-4822-8648-d5638385b88e)
2016-12-12T23:07:27.196 C# ServiceBus queue trigger function processed message: Microsoft.ServiceBus.Messaging.BrokeredMessage{MessageId:e0df93163d334e8dbe055e2ed816ba12}
2016-12-12T23:07:27.196 Message ID: {3b36858e-b709-4e0c-8dde-b2a0df0c89ba}
2016-12-12T23:07:27.196 Opportunity I am a salesperson of the year for $100.00 was created
2016-12-12T23:07:27.196 Let's decrease the opportunity value by 20%
2016-12-12T23:07:27.196 Connection result: True
2016-12-12T23:07:27.196 User id: d17c298b-e7bb-e611-80ff-c4346bac3ac4
2016-12-12T23:07:27.213 Changing opportunity I am a salesperson of the year (d3d576bd-bfc0-e611-8101-c4346bac8af8) by -0.2
2016-12-12T23:07:27.337 Opportunity value decreased
2016-12-12T23:07:27.337 Function completed (Success, Id=50a61122-a8f4-4822-8648-d5638385b88e)

While you are at it, in Function App UI, click Function app settings on the left, then click Configure app settings, and change Platform to 64-bit. We ain’t doing no 32-bit with Dynamics 365 no more.

2 thoughts on “Tip #794: Connect to Dynamics 365 in Azure Functions

  1. Georgy says:

    what are the options in Azure functions to handle unresponsive CRM/errors? I see that you are not doing “message.Complete()” does the environment handles that for you?

Leave a Reply

Your email address will not be published. Required fields are marked *