• Kyle Hill

Kylie Bot Part 6 - Creating the Web Api Project

Connecting our Kylie Bot to a Dynamics 365 Customer Engagement instance is perhaps one of the most vital steps in this project as it will provide us with loads of additional functionality that we can leverage through our Kylie Bot.

Firstly, a little bit on the architecture I have chosen for this project. I am a believer in separating functionality out into separate projects where possible. This is good in theory but there are some limitations, mainly around the use of Nuget packages in the solution as a whole. As this post is not focusing on these limitations, I will just provide a brief description of the problems and solution I have chosen.

Problem 1: the packages that we need in our KylieBot project requires a version of a specific dll that is different to the version required in our project for connecting to Dynamics 365. Running the projects under the same solution causes this dll mismatch and ultimately the functionality to break.

Problem 2: the token that we obtained through our authentication step from the previous posts, will not be valid for a connection to Dynamics 365. This is because Dynamics 365 requires a user license that our authenticated user will not have.

Solution: while we can still have the projects in the same solution, we will deploy the project for connecting to our Dynamics 365 instance to the Azure Web Api. This Api will then be able to execute under service account credentials which will different to those of the authenticated user. Simple in theory, so let's get it done.

*If you haven't already, you will need to provision an Azure trial, typically in the same tenant as the Dynamics 365 Customer Engagement trial that we spun up previously. I have posted about this process here if you need it.

The first thing we need to do is add a new project to our KylieBot solution. To do this, right click on the solution and select 'Add->Project' like this:

You should then be presented with a project templates screen like this:

Make sure you have chosen the 'Web' item from the navigation tree on the left hand side of the screen. Enter in the Name field and then click 'OK'. You should then be presented with an Api configuration screen like this:

Make sure that you select the 'Empty' template and check the 'Web API' check box like the image above. Click 'OK' to complete the creation of the project. Next we are going to add 2 additional packages which will give us the required Dynamics 365 Customer Engagement SDKs that we will need in future posts. These packages are:

  • Microsoft.CrmSdk.CoreAssemblies

  • Microsoft.CrmSdk.XrmTooling.CoreAssembly

To add these packages, right click on the CRMApi project and select 'Manage Nuget Packages...'. Next, we need to add a controller to our project by right clicking on the Controllers folder in the CRMApi project and selecting 'Add->Controller' like this:

Next, select the type of controller we want, like in the below image:

Make sure you select 'Web API 2 Controller - Empty' and then click on 'Add' in the bottom right of the screen to progress. You should now be presented with a screen where you can name you controller like this:

Update the name to 'CRMController' and the click 'Add' to complete the addition of the controller. Next, add the following code to the controller:

namespace CRMApi.Controllers { public class CRMController : ApiController { List<CRMContact> contacts = new List<CRMContact>(); Dynamics365Helper CRMConnection = new Dynamics365Helper();

private void connectCRM() { CRMConnection.ConnectCRM(ConfigurationManager.AppSettings["D365.Username"], ConfigurationManager.AppSettings["D365.Password"], Dynamics365Helper.CRMRegions.NorthAmerica.ToString(), ConfigurationManager.AppSettings["D365.Uri"], true); }


[Route("CRM/GetContact/{email}/")] [HttpGet] public CRMContact GetContact(string email) { if (CRMConnection != null) { connectCRM(); }

string fetchXML = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false' returntotalrecordcount='true' > <entity name='contact'> <attribute name='contactid' /> <attribute name='fullname' /> <attribute name='emailaddress1' /> <filter type = 'and'> <condition attribute = 'emailaddress1' value = " + email + @" operator = 'eq' /> </filter> </entity> </fetch>";


EntityCollection collection = Dynamics365Helper.RetrieveXML(fetchXML);

CRMContact contact = null; if (collection.Entities != null) { var entity = collection.Entities.FirstOrDefault(); contact = new CRMContact(); contact.Email = entity.Attributes["emailaddress1"].ToString(); contact.FullName = entity.Attributes["fullname"].ToString(); contact.ContactId = new Guid(entity.Attributes["contactid"].ToString()); }

return contact; }


[Route("CRM/CreateBotChat")] [HttpPost] public Tuple<bool, Guid> CreateBotChat(JObject transcript) { BotChat chat; Tuple<bool, Guid> result = null;

if (transcript != null) { chat = JsonConvert.DeserializeObject<BotChat>(transcript.ToString());

if (CRMConnection != null) { connectCRM(); }

if (CRMConnection.IsConnectionReady) { try { result = Dynamics365Helper.CreateCRMBotChat(chat); } catch (Exception e) { } } } return result; } } }

You will notice that there are two statements before each method signature. The Route statement lets the controller know how the method can be accessed through the Api and the next statement indicates to the controller what types oh messages the method will accept. For more information on this, see here.

Next, let's add a reference in our main KylieBot project so that we can use some of the assets from the CRMApi project. To do this, right click on the 'references' item in the KylieBot project and select 'Add Reference' like this:

You should then be presented with this:

Next, check the checkbox next to the CRMApi project and click 'OK' to complete the addition of the reference. Next, we are going to add a Helpers folder to our CRMApi project by right clicking on the project and selecting 'Add->New Folder' like this:

Next, rename the folder to 'Helpers' and add a Dynamics365Helper class like this:

You can also go ahead and add the following code to the Dynamics365Helper class you just created:

namespace CRMApi.Helpers { public class Dynamics365Helper { private Boolean isConnectionReady = false; public bool IsConnectionReady { get => isConnectionReady; set => isConnectionReady = value; }

private static CrmServiceClient crmSvc; public static CrmServiceClient CrmSvc { get => crmSvc; set => crmSvc = value; }

private string connectionError; public string ConnectionError { get => connectionError; set => connectionError = value; } public enum CRMRegions { NorthAmerica, EMEA, APAC, SouthAmerica, Oceania, JPN, CAN, IND, NorthAmerica2 };


public void ConnectCRM(string crmUsername, string crmPassword, string crmRegion, string crmOrgId, bool isO365) { try { CrmSvc = new CrmServiceClient(crmUsername, CrmServiceClient.MakeSecureString(crmPassword), crmRegion, crmOrgId, true, true, null, isO365); IsConnectionReady = CrmSvc.IsReady; } catch (Exception e) { ConnectionError = e.Message.ToString(); } }


public static EntityCollection RetrieveXML(string XML) { EntityCollection queryResult = null;

if (CrmSvc != null && CrmSvc.IsReady && XML != null) { return queryResult = CrmSvc.GetEntityDataByFetchSearchEC(XML); } else { return queryResult; } }


public static Tuple<bool, Guid> CreateCRMBotChat(BotChat transcript) { bool success = false; Guid chatId = new Guid();

if (CrmSvc != null && CrmSvc.IsReady && transcript != null) { if (transcript.existingChatID == Guid.Empty) { Dictionary<string, CrmDataTypeWrapper> inData = new Dictionary<string, CrmDataTypeWrapper>(); inData.Add("subject", new CrmDataTypeWrapper("Bot chat: " + DateTime.Now.ToString() + " - " + transcript.channel.ToString(), CrmFieldType.String)); if (transcript.regardingId != Guid.Empty) { inData.Add("regardingobjectid", new CrmDataTypeWrapper(transcript.regardingId, CrmFieldType.Lookup, "contact")); } inData.Add("kbot_transcript", new CrmDataTypeWrapper("From: " + transcript.chatUser + Environment.NewLine + "Message: " + transcript.chatMessage + Environment.NewLine + "Time: " + transcript.timeStamp + Environment.NewLine + "Channel: " + transcript.channel + Environment.NewLine + Environment.NewLine, CrmFieldType.String));

try { chatId = CrmSvc.CreateNewRecord("kbot_botchat", inData); success = true; } catch (Exception e) { success = false; } } else { chatId = transcript.existingChatID;

Dictionary<string, object> data = CrmSvc.GetEntityDataById("kbot_botchat", chatId, new List<string> { "kbot_transcript", "regardingobjectid" }); Dictionary<string, CrmDataTypeWrapper> updateData = new Dictionary<string, CrmDataTypeWrapper>(); if(!data.ContainsKey("regardingobjectid")) { if (transcript.regardingId != Guid.Empty) { updateData.Add("regardingobjectid", new CrmDataTypeWrapper(transcript.regardingId, CrmFieldType.Lookup, "contact")); } }

foreach (var pair in data) { switch (pair.Key) { case "kbot_transcript": string original = (string)pair.Value; updateData.Add("kbot_transcript", new CrmDataTypeWrapper(original + "From: " + transcript.chatUser + Environment.NewLine + "Message: " + transcript.chatMessage + Environment.NewLine + "Time: " + transcript.timeStamp + Environment.NewLine + "Channel: " + transcript.channel + Environment.NewLine + Environment.NewLine, CrmFieldType.String)); break; default: break; } } success = crmSvc.UpdateEntity("kbot_botchat", "activityid", chatId, updateData); } } return new Tuple<bool, Guid>(success, chatId); } } }

You will notice from the code that we are using the new XRM.Tooling method to connect to our Dynamics 365 instance in the ConnectCRM method. You will also notice that there are two other methods for data manipulating. The first of these is a generic RetrieveXML method that accepts a parameter of the XML to execute and returns an EntityCollection of the results (we will use it to see if our Kylie Bot authenticated user is in our Dynamics 365 instance). The second is a CreateCRMBotChat method that accepts a BotChat object that we will write to Dynamics 365. The methods returns a Tuple of whether the action was successful and the Guid of the record if is was successful.

Next, we will need to add two classes to the Models folder of the CRMApi project. These classes will represent the following objects: CRMContact, BotChat.

Finally, lets add some configuration lines to the web.config file of our CRMApi project like this:

Good job! Lookout for the next post where we will deploy the Api and link to it from our Kylie Bot project.

*Don't forget to check in you code!

#Azure #WebApi #KylieBot #XRMTooling #Dynamics365Connection

As an innovative technology pioneer, I focus on the Microsoft Business Applications platform and on creating value-generating solutions, which also include IoT, AI and Mixed Reality.

See this blog and more on the Dynamics Community and my MVP profile

  • RSS Social Icon
  • Facebook Social Icon
  • LinkedIn Social Icon
  • Twitter Social Icon
  • Instagram Social Icon

© 2019 by Kyle Hill

London, UK

kyle@daringdynamics.co.uk