Solution - Adaptive Card Event implementation Using WaterfallDialog Bot Framework V4

In this article, we are going fix one of the problems in Adaptive Card using in Bot Prompt Dialogs.

This article focuses on how to fix the AdaptiveCard Prompt dialog issue. It has divided into three sections,
  1. Problem statement
  2. Solution
  3. Test the application
First, we understand the problem.
 

Problem statement

 
Natively Adaptive card events do not work in the Prompt Dialog. If you use it in the Text Prompt, it will display adaptive card information and not handle the Adaptive card event. Even if you click the Submit or Cancel event, there is no response. Since we are using TextPrompt, bot accepts string input but Adaptive card return the JSON format both format are mismatching.
 
The example is given below- no event triggers from the Adaptive Card.
 
Solution -Adaptive Card Event implementation Using WaterfallDialog Bot Framework V4
 
How to make a response for JSON format? In the next section, we find a solution to this problem. 
 

Solution

 
Overwrite the Prompt Class & handle the AdatpiveCard (JSON format) event.
 
Let write the code to fix this issue.
 
What is the Prompt class?

This is the generic class derived from the Dialog class, all prompts class derived from this class. To implement AdaptiveCard event feature, first, we have to create a new class and should be derived from the Prompt class. I said earlier, Prompt class is a generic class so we have to define the data type.
 
What datatype has to defined?

AdatpiveCard is based on the JSON only, so while handling events like submit or cancel button, bot receives the JSON object as an input. so our datatype is a JSON object.
 
Creating the AdaptiveCardPrompt with JsonObject, 
  1. public class AdaptiveCardPrompt : Prompt<JObject>  
  2. {  

After creating the class, two functions must be overridden,
  • OnRecognizeAsync
  • OnPromptAsync
OnRecognizeAsync

OnRecognizeAsync functions attempt to recognize the user’s input, All type of prompt dialog logic has to be implemented in this function to validate the input, like TextPrompt checks for inut as string or not and NumberPrompt checks (entered number or not), the same way we are going to write our logic in this function, 
  1. protected override Task < PromptRecognizerResult < JObject >> OnRecognizeAsync(ITurnContext turnContext, IDictionary < string, object > state, PromptOptions options, CancellationToken cancellationToken =  
  2.  default) {  
  3.  if (turnContext == null) {  
  4.   throw new ArgumentException(nameof(turnContext));  
  5.  }  
  6.   
  7.  if (turnContext.Activity == null) {  
  8.   throw new ArgumentException(nameof(turnContext));  
  9.  }  
  10.   
  11.  var result = new PromptRecognizerResult < JObject > ();  
  12.   
  13.  if (turnContext.Activity.Type == ActivityTypes.Message) {  
  14.   if (turnContext.Activity.Value != null) {  
  15.    if (turnContext.Activity.Value is JObject) {  
  16.     result.Value = turnContext.Activity.Value as JObject;  
  17.     result.Succeeded = true;  
  18.    }  
  19.   }  
  20.  }  
  21.  return Task.FromResult(result);  
  22. }  
the main logic,
  1. if(turnContext.Activity.Value is JObject)  
  2. {  
  3.       result.Value = turnContext.Activity.Value as JObject;  
  4.       result.Succeeded = true;  
  5. }    
Here we are checking whether the user has submitted JSON or not. If validation is successful, set the result.Succeeded = true, return to the main function, otherwise set the value is false automatically and OnPromptAsync function gets invoked.
 
OnPromptAsync function

This function is used for RePrompt the user input. Once OnRecognizeAsync function set the result.Succeeded = false, this function gets invoked and RePrompt again the same dialog to the user to get the correct answers.

Ex
 
In NumberPrompt dialog, if the user enters the string value, Bot wont allow the user to go further, this logic is handled by the result.Succeeded = false and OnPromptAsync function,  
  1. protected override async Task OnPromptAsync(ITurnContext turnContext, IDictionary < stringobject > state, PromptOptions options, bool isRetry, CancellationToken cancellationToken =  
  2.  default) {  
  3.  if (turnContext == null) {  
  4.   throw new ArgumentException(nameof(turnContext));  
  5.  }  
  6.   
  7.  if (options == null) {  
  8.   throw new ArgumentException(nameof(options));  
  9.  }  
  10.   
  11.  if (isRetry && options.Prompt != null) {  
  12.   await turnContext.SendActivityAsync(options.RetryPrompt, cancellationToken).ConfigureAwait(false);  
  13.  } else if (options.Prompt != null) {  
  14.   await turnContext.SendActivityAsync(options.Prompt, cancellationToken).ConfigureAwait(false);  
  15.  }  
  16. } 
Our AdaptiveCardPrompt class is ready.
 

Test the AdaptiveCardPrompt class


Let us write a sample application using the Dialog concept to test the AdaptiveCardPrompt class
 
This sample gets the first name & last name from the user using the Adaptive card.

Adaptive Card user form information,
  1. {  
  2.   "$schema""http://adaptivecards.io/schemas/adaptive-card.json",  
  3.   "type""AdaptiveCard",  
  4.   "version""1.0",  
  5.   "body": [  
  6.     {  
  7.       "type""TextBlock",  
  8.       "text""User Form"  
  9.     },  
  10.     {  
  11.       "type""Input.Text",  
  12.       "id""firstName",  
  13.       "placeholder""What is your first name?"  
  14.     },  
  15.     {  
  16.       "type""Input.Text",  
  17.       "id""lastName",  
  18.       "placeholder""What is your last name?"  
  19.     }  
  20.   ],  
  21.   "actions": [  
  22.     {  
  23.       "type""Action.Submit",  
  24.       "title""Submit"  
  25.        
  26.     }  
  27.   ]  

Define AdaptiveCardPrompt class in the AddDialog,
  1. AddDialog(new AdaptiveCardPrompt(AdaptivePromptId)); 

In the UserFormAsync function we are reading the JSON file and use the AdaptiveCardPrompt dialog to show the information.

Note
Prompt Dialog should match with the AddDialog. 
  1. private static async Task < DialogTurnResult > UserFormAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) {  
  2.  var cardJson = PrepareCard.ReadCard("userform.json");  
  3.   
  4.  var cardAttachment = new Attachment() {  
  5.   ContentType = "application/vnd.microsoft.card.adaptive",  
  6.    Content = JsonConvert.DeserializeObject(cardJson),  
  7.  };  
  8.   
  9.  var opts = new PromptOptions {  
  10.   Prompt = new Activity {  
  11.    Attachments = new List < Attachment > () {  
  12.      cardAttachment  
  13.     },  
  14.     Type = ActivityTypes.Message,  
  15.     Text = "Please fill this form",  
  16.   }  
  17.  };  
  18.   
  19.  return await stepContext.PromptAsync(AdaptivePromptId, opts, cancellationToken);  
  20. }  
Once user has submitted the information, we are reading the output in the ResultUserFormAsync function. (In this function gets invoked OnRecognizeAsync function when we set the result.Succeeded = true)
  1. private static async Task < DialogTurnResult > ResultUserFormAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) {  
  2.  var value = stepContext.Result.ToString();  
  3.   
  4.  var promptOptions = new PromptOptions {  
  5.   Prompt = MessageFactory.Text(value)  
  6.  };  
  7.  return await stepContext.PromptAsync(nameof(TextPrompt), promptOptions, cancellationToken);  
  8. } 
Output
 
Solution -Adaptive Card Event implementation Using WaterfallDialog Bot Framework V4
 
Please find the code sample here
 

Conclusion

 
I hope you understand how to implement the AdaptiveCard Event in waterfall dialogs
 
Happy Reading. Happy coding.