top of page
  • Kyle Hill

Kylie Bot Part 8 - Knowledge is Power

Finally, we add the meaningful part to our Kylie Bot where we can return some knowledge to the user. Let’s get started straight away! The first thing we will need is to add a CRMKnowledgeBaseArticle class to the Models folder of the CRMApi project. Right click on the Models folder and select ‘Add->Class’ like this:

Next, name the class like this:

And finally, copy the following code into the class to represent our required properties:

namespace CRMApi.Models { public class CRMKnowledgeBaseArticle { public string title { get; set; } public string description { get; set; } public DateTime publishedDate { get; set; } public string articleNumber { get; set; }

} }

Next, we need to create a web method in the CRMController class to handle our Knowledge Base search. To do this, copy the following code into the CRMController class:

[Route("CRM/SearchKB/{keyword}/")] [HttpGet] public List<CRMKnowledgeBaseArticle> SearchKB(string keyword) { if (CRMConnection != null) { connectCRM(); }

string fetchXML = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false' returntotalrecordcount='true' > <entity name='knowledgearticle'> <attribute name='knowledgearticleid' /> <attribute name='modifiedon' /> <attribute name='title' /> <attribute name='description'/> <attribute name='articlepublicnumber'/> <filter type = 'and'> <condition attribute = 'statuscode' value = '7' operator = 'eq' /> <condition attribute = 'keywords' value = '%" + keyword + @"%' operator = 'like' /> </filter> </entity> </fetch>";


EntityCollection collection = Dynamics365Helper.RetrieveXML(fetchXML);

List<CRMKnowledgeBaseArticle> kbaList = new List<CRMKnowledgeBaseArticle>();

if (collection != null && collection.Entities != null) { if (collection.Entities.Count > 0) { foreach (Entity e in collection.Entities) { CRMKnowledgeBaseArticle kba = null; if (collection.Entities != null) { kba = new CRMKnowledgeBaseArticle(); kba.title = e.Attributes["title"].ToString(); kba.description = e.Attributes["description"].ToString(); kba.articleNumber = e.Attributes["articlepublicnumber"].ToString(); kba.publishedDate = DateTime.Parse(e.Attributes["modifiedon"].ToString()); } kbaList.Add(kba); } } } return kbaList; }

*make sure to update the code with the address of your published API address!

Notice that the web method is using fetchxml to retrieve the Knowledge Articles we desire. A refresher on how to generate this fetchxml easily follows. First, launch the ‘Advanced Find’ utility from the top right hand corner of the screen (the icon looks like a funnel):

You should be presented with the following:

Next, select the correct entity that we wish to make use of (Knowledge Article) like this:

We will also need to filter the Knowledge Articles so that only the correct articles are returned. As such, we are including a filter on the ‘Status Reason’ field to ensure that only ‘Published’ articles are returned. Also, we are including the ‘Keywords’ field so that we can match what the user requests to the Knowledge Article search. The completed filter criteria looks like this:

Finally, we can select ‘Download fetchxml’ from the menu bar. We will then be able to save the fetchxml and use it in our web method from earlier.

Next, we need to publish our changes to the CRMApi project. To do this, right click on the CRMApi project and select ‘Publish’ like this:

You should then be presented with the publishing profile for the project. We see this screen because when we previously published the project, the profile that was used to do so, was stored, and now we can easily use it again. The publishing profile looks like this:

Click on the ‘Publish’ button and once the project has been successfully published, you should see the following screen pop up:

Next, we also need to add a class to our Models folder in the KylieBot project. This class will also be called CRMKnowledgeBaseArticle and will be added in the same way as we added it to the CRMApi project. We can then copy the following code to the class:

namespace KylieBot.Models { public class CRMKnowledgeBaseArticle { public string title { get; set; } public string description { get; set; } public DateTime publishedDate { get; set; } public string articleNumber { get; set; } } }

We will also need to create a property for the searchTerm that the user entered. I have created this in the User object that we initially created and have populated it with the text of the first message that the user sent, once the user has been authenticated.

Now that we have our building blocks in place, we can call our web method from the KylieBot RootDialog. To do this, add the following code to the ResumeAfterAuth method:

List<Attachment> kbaList = await SearchKB(); if (kbaList.Count > 0) { var reply = context.MakeMessage();

reply.AttachmentLayout = AttachmentLayoutTypes.Carousel; reply.Attachments = kbaList;

await context.PostAsync("I found some Knowledge Articles: "); await context.PostAsync(reply);

context.Wait(this.MessageReceivedAsync); } else { await context.PostAsync("I couldn't find anything :("); context.Wait(this.MessageReceivedAsync); }

You will also need to add the following method to the RootDialog class:

public async Task<List<Attachment>> SearchKB() { List<Attachment> llist = new List<Attachment>();

if (MessagesController.LocalUser != null) { HttpClient cons = new HttpClient(); cons.BaseAddress = new Uri("YOUR CRMApi Address"); cons.DefaultRequestHeaders.Accept.Clear(); cons.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

List<Models.CRMKnowledgeBaseArticle> crmKBA = new List<Models.CRMKnowledgeBaseArticle>();


using (cons) { HttpResponseMessage res = await cons.GetAsync("CRM/SearchKB/" + MessagesController.LocalUser.searchTerm + "/"); if (res.IsSuccessStatusCode) { crmKBA = await res.Content.ReadAsAsync<List<Models.CRMKnowledgeBaseArticle>>(); } }


if (crmKBA.Count > 0) { foreach (Models.CRMKnowledgeBaseArticle kb in crmKBA) { Attachment a = BotHelper.GetHeroCard( kb.title + " (" + kb.articleNumber + ")", "Published: " + kb.publishedDate.ToShortDateString(), kb.description, new CardImage(url: "https://azurecomcdn.azureedge.net/cvt-5daae9212bb433ad0510fbfbff44121ac7c759adc284d7a43d60dbbf2358a07a/images/page/services/functions/01-develop.png"), new CardAction(ActionTypes.OpenUrl, "Learn more", value: "https://YOUR PORTAL ADDRESS.microsoftcrmportals.com/knowledgebase/article/" + kb.articleNumber)); llist.Add(a); } } } return llist; }

*Make sure you update the link text with the address of the portal that you have previously created!

I have also added a BotHelper class to the Helpers folder in our KylieBot project. You can do the same and add the following code to the class:

namespace KylieBot.Helpers { public class BotHelper { public static Attachment GetHeroCard(string title, string subtitle, string text, CardImage cardImage, CardAction cardAction) { var heroCard = new HeroCard { Title = title, Subtitle = subtitle, Text = text, Images = new List<CardImage>() { cardImage }, Buttons = new List<CardAction>() { cardAction }, };

return heroCard.ToAttachment(); }


public static Attachment GetThumbnailCard(string title, string subtitle, string text, CardImage cardImage, CardAction cardAction) { var heroCard = new ThumbnailCard { Title = title, Subtitle = subtitle, Text = text, Images = new List<CardImage>() { cardImage }, Buttons = new List<CardAction>() { cardAction }, };

return heroCard.ToAttachment(); } } }

Awesome, now we can test the updates to the functionality. To do this, run the KylieBot project from Visual Studio and then run and connect the emulator to KylieBot. This time, type in something meaningful when Kylie Bot prompts you. Authenticate as usual and you should see something similar to the following:

Notice that for each of the Knowledge Articles returned, an element has been added to a carousel and displayed to the user through Kylie Bot. Click on the ‘Learn More’ button will open the complete article on our Dynamics 365 Customer Engagement portal that we have setup earlier, like this:

Great job! Look out for the next post where we will publish our bot into the world.

*Don’t forget to check-in your code!

0 comments
bottom of page