In the previous post we created a CRMApi project. To deliver the value of the this project though, we need to publish the Api and connect to it from our Kylie Bot. Let's get started. First, right click on the CRMApi project and select 'Publish' like this:
You should then be presented with this:
Select the 'Microsoft Azure App Service' option and then the 'Create New' radio button and then click on the 'Publish' button. You should then see this screen:
Note that credentials are required for an Azure subscription (hopefully you completed the Azure trial signup from a previous post). Sign in with these credentials in the top right of the screen. Next, complete the details for the App as above and then click on the 'Create' button at the bottom of the screen. You will then be presented with a confirmation like this:
Confirm this profile by clicking on the 'Publish' button. This will then deploy your Web Api to Azure and present the following screen once complete:
Perfect! Let's now go and implement this in our main Kylie Bot project. The first thing we will need to do is add a BotLogger class to our Helpers folder in our KylieBot project like this:
You can then specify the name of the class like this:
And finally, add the following code to the class:
namespace KylieBot.Helpers { public static class BotLogger { public static async Task Log(Activity activity) { var x = activity; BotChat chat = new BotChat(x.From.Name.ToString() + "(" + x.From.Id.ToString() + ")", x.Text, x.ChannelId.ToString(), x.Timestamp.Value);
if (MessagesController.LocalUser.existingChatID!= null) { chat.existingChatID = MessagesController.LocalUser.existingChatID; }
if (MessagesController.LocalUser.CRMContactId != null) { chat.regardingId = MessagesController.LocalUser.CRMContactId; }
HttpClient cons = new HttpClient(); cons.BaseAddress = new Uri("http://YOUR WEB API ADDRESS.net/"); cons.DefaultRequestHeaders.Accept.Clear(); cons.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
using (cons) { var content = new StringContent(JsonConvert.SerializeObject(chat), Encoding.UTF8, "application/json"); HttpResponseMessage res = await cons.PostAsync("CRM/CreateBotChat", content); if (res.IsSuccessStatusCode) { Tuple<bool, Guid> result = await res.Content.ReadAsAsync<Tuple<bool, Guid>>(); if (MessagesController.LocalUser.existingChatID== Guid.Empty && result.Item1 && result.Item2 != Guid.Empty) { MessagesController.LocalUser.existingChatID= result.Item2; } } } } } }
*Make sure to update the 'YOUR WEB API ADDRESS' text in the above with the address of your specific app that you published above. If you can't remember this, you can always check for in by opening the publishing profile in your CRMApi project like this:
Opening the profile will present you with a screen like this where you can get the address:
Next, let's add a BotChat class to our Models folder. This class may be different to the class we have in our CRMApi project so make sure that you do not duplicate it, rather copy the code from here:
namespace KylieBot.Models { public class BotChat { public string chatUser { get; set; } public string chatMessage { get; set; } public string channel { get; set; } public DateTime timeStamp { get; set; } public Guid regardingId { get; set; } public Guid existingChatID { get; set; } public BotChat(string user, string message, string comChannel, DateTime stamp) { chatUser = user; chatMessage = message; channel = comChannel; timeStamp = stamp; } } }
We will also need to need to add two properties to our User model that represent our existingChatID (the Guid of the conversation record from Dynamics 365) and CRMContactId (the Guid of the record representing User in Dynamics 365 if they have registered through the portal from previous posts) like this:
public Guid existingChatID { get; set; } public Guid CRMContactId { get; set; }
Next, lets call our functionality from the RootDialog by adding the following line to the ResumeAfterAuth method:
await getCRMContact();
and then specifying the corresponding method like this:
public async Task getCRMContact() { if (MessagesController.LocalUser != null) { HttpClient cons = new HttpClient(); cons.BaseAddress = new Uri("http://crmapi-kyliebot.azurewebsites.net/"); cons.DefaultRequestHeaders.Accept.Clear(); cons.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
using (cons) { HttpResponseMessage res = await cons.GetAsync("CRM/GetContact/'" + MessagesController.LocalUser.AADEmail.ToString() + "'/"); if (res.IsSuccessStatusCode) { CRMContact contact = await res.Content.ReadAsAsync<CRMContact>(); MessagesController.LocalUser.CRMContactId = contact.ContactId; } } } }
This will pass a request to our CRMApi attempting to match the user that is logged into our Kylie Bot application, to a Contact in Dynamics 365. If a match is found, we will update the CRMContactId property of our user object that is stored globally by our bot.
*You will notice that we are using an 'AADEmail' property of our User object. You will need to add this to the User class in the Model folder of our KylieBot project by adding this line:
public string AADEmail { get; set; }
You will also need to populate this property by adding the following in the two methods in the AuthResult class in our Models folder:
FromADALAuthenticationResult
public static AuthResult FromADALAuthenticationResult(Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult authResult, Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache tokenCache) { var result = new AuthResult { AccessToken = authResult.AccessToken, UserName = $"{authResult.UserInfo.GivenName} {authResult.UserInfo.FamilyName}", UserUniqueId = authResult.UserInfo.UniqueId, ExpiresOnUtcTicks = authResult.ExpiresOn.UtcTicks, TokenCache = tokenCache.Serialize(), Email = authResult.UserInfo.DisplayableId };
return result; }
FromMSALAuthenticationResult
public static AuthResult FromMSALAuthenticationResult(Microsoft.Identity.Client.AuthenticationResult authResult, Microsoft.Identity.Client.TokenCache tokenCache) { var result = new AuthResult { AccessToken = authResult.Token, UserName = $"{authResult.User.Name}", UserUniqueId = authResult.User.UniqueId, ExpiresOnUtcTicks = authResult.ExpiresOn.UtcTicks, TokenCache = tokenCache.Serialize(), Email = authResult.User.DisplayableId };
return result; }
Great, let's add the logging part to the MessagesController by adding the following lines to the class:
private static int messageCount; public static int MessageCount
{
get => messageCount; set => messageCount = value;
}
Finally, add these lines to the top of the Post method so it looks like this:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity) { MessageCount++; if (MessageCount > 1 ) { await BotLogger.Log(activity); }
}
The reason for the counter is that at the beginning of the Kylie Bot conversation, both the User and the Bot are added to the conversation and is essentially two calls of this message. As the calls are asynchronous, this typically results in two BotChat objects being created in Dynamics 365. To get around this, we will use the counter and only store the second message that we receive and thus logging all subsequent messages in Dynamics 365.
To test this, run the Kylie Bot project and the emulator. Follow the conversation prompts and login so that your Bot Emulator screen looks like this:
Next, log into Dynamics 365 and Navigate to the 'Activities' menu item under the 'Sales option like this:
After this, select Bot Chat from the drop down and you should see a screen like this:
Next, open up the record that you see from the screen by double clicking it. You should see the details and transcript of the chat like this:
If we ran the same process as above but sent another message to Kylie Bot once we had been authenticated, we would see the following:
*Note the additional message to Kylie Bot, the additional line in the Bot Chat record in Dynamics 365, and the Regarding Field being populated with the Dynamics 365 Contact record that the Kylie Bot application was able to match because the user had registered on the portal we created in a previous post.
Good job! We have now completed the logging of our chats into Dynamics 365. Lookout for the next post where we will complete the Knowledge Base search request of the User against the Dynamics 365 Knowledge base.
The complete code for the solution can obtained by registering here (it's free) and then downloading the solution.
*Don't forget to check in your code!