You probably heard of the new kid on the Azure block: Azure Functions, that allow serverless execution of code. Wouldn’t that be nice to be able to trigger this serverless execution from Dynamics 365? I’m glad you asked! Since functions are very new, some plumbing is required but not as much as one would think.
Without further ado:
- Create Azure queue and register service endpoint as documented in Walkthrough: Configure Microsoft Azure (SAS) for integration with Dynamics 365. The only difference in our sample is .NET Binary serialization format instead of JSON.
- Register new asynchronous step on create message for the opportunity.
- Create new function app in Azure. For some reason Azure folks couldn’t decide on the category so you need to go to Everything and search for Function App.
Note the best part – Consumption Plan. That means you don’t have to worry about the plan, scaling out and scaling up – it’s all taken care of and you only pay for what you use. - Add new function, select ServiceBusQueueTrigger-CSharp template. Enter details of the queue that you created earlier. Since we are not doing anything fancy, Listen access rights are sufficient.
- Click on View Files, click Add, name file project.json. Enter this context:
{ "frameworks": { "net46":{ "dependencies": { "WindowsAzure.ServiceBus": "3.4.3", "Microsoft.CrmSdk.CoreAssemblies": "8.2.0.1", "Microsoft.CrmSdk.Deployment": "8.2.0.1", "Microsoft.CrmSdk.Workflow": "8.2.0.1" } } } }
This instructs Azure to pull these assemblies from nuget to compile the function.
- Switch to run.csx and replace the code with the following:
using System; using System.Threading.Tasks; using Microsoft.ServiceBus.Messaging; using Microsoft.Xrm.Sdk; using System.Globalization; public static void Run(BrokeredMessage myQueueItem, TraceWriter log) { log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}"); log.Info($"CorrelationId: {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"); } }
Note how we replaced string input with BrokeredMessage. That’s the beauty of the functions – they are flexible and will take almost anything you throw at them (read documentation for the accepted types).
That’s it! Now you can create an opportunity in Dynamics 365 (enter name and estimated value) and, if everything is wired correctly, the function log will display something like:
2016-12-11T16:58:30.324 Script for function 'CrmTipOfTheDay' changed. Reloading. 2016-12-11T16:58:30.638 Compilation succeeded. 2016-12-11T16:58:57.540 Function started (Id=8b02256d-11cd-4f00-bf5e-512558a91edb) 2016-12-11T16:58:57.883 C# ServiceBus queue trigger function processed message: Microsoft.ServiceBus.Messaging.BrokeredMessage{MessageId:59326b1a62c64b20b5261abc667b4816} 2016-12-11T16:58:57.883 CorrelationId: {196dd84c-6b4c-4024-85eb-e50e2586c0e5} 2016-12-11T16:58:57.899 Opportunity Huge one for $9.95 was created 2016-12-11T16:58:57.899 Function completed (Success, Id=8b02256d-11cd-4f00-bf5e-512558a91edb)
So here you have it: we just remoted our plugin execution into a functional abyss running on consumption plan that will scale to consume whatever our Dynamics 365 can throw at it.
Big thanks to Matt Barbour who’s taken time from his busy schedule at eXtremeCRM to look into few stumbling blocks I encountered along the way.