Validating a field in a FormFlow - c#

Code
[Serializable]
public class FAQConversation
{
[Prompt("What product is your concern? {||}")]
public VASetting.SupportedProducts Products { get; set; }
[Prompt("Okay, tell me what is your question. Enter \"back\" to go back to Products Selection.")]
public string Inquiry { get; set; }
public static IForm<FAQConversation> BuildForm()
{
return new FormBuilder<FAQConversation>()
.AddRemainingFields()
.Field(new FieldReflector<FAQConversation>(nameof(Inquiry)).SetValidate(AnswerInquiry))
.Message("Hi this is Test Virtual Assistant")
.Build();
}
private static async Task<ValidateResult> AnswerInquiry(FAQConversation state, object value)
{
var asString = value as String;
var vaConfig = new SmartCareSetting(state.Products);
var result = new ValidateResult() { IsValid = false, Value = value };
if (!string.IsNullOrEmpty(asString))
{
var luisService = new LuisService(new LuisModelAttribute(vaConfig.AppID, vaConfig.SubscriptionKey, domain: vaConfig.HostName));
var luisResult = await luisService.QueryAsync(asString, CancellationToken.None);
result.Feedback = luisResult.TopScoringIntent.Intent.ToString();
}
return result;
}
}
My bot code above shows the conversation below.
I am creating a simple inquiry bot using FormFlow and Bot Framework.
I am validating the Inquiry field through LUIS and returning the intent for processing. I am getting the correct intent, in this case is EXC01. Afterwards, Im wondering why am I still getting prompted the Inquiry prompt.
Questions:
1. How can I finish the FormFlow after validating the Intent of the Inquiry?
2. I want to handle the returned Intent but not show it to the user. I'll be using the Intent string for querying to a Database. Can I do this inside the BuildForm()?

How can I finish the FormFlow after validating the Intent of the Inquiry?
In validation function AnswerInquiry, we can find that IsValid property of ValidateResult would be always false, which cause the issue. You can set IsValid property to true after you assign the returned intent as feedback. The following code snippet works for me, you can refer to it.
private static async Task<ValidateResult> AnswerInquiry(FAQConversation state, object value)
{
var asString = value as String;
var vaConfig = new SmartCareSetting(state.Products);
var result = new ValidateResult() { IsValid = false, Value = value };
if (!string.IsNullOrEmpty(asString))
{
var luisService = new LuisService(new LuisModelAttribute(vaConfig.AppID, vaConfig.SubscriptionKey, domain: vaConfig.HostName));
var luisResult = await luisService.QueryAsync(asString, CancellationToken.None);
result.Feedback = luisResult.TopScoringIntent.Intent.ToString();
//set IsValid to true
result.IsValid = true;
}
return result;
}
I want to handle the returned Intent but not show it to the user. I'll be using the Intent string for querying to a Database. Can I do this inside the BuildForm()?
After you can get the returned intent, if you’d like to query records from your database based on the returned intent, you can do it in your validation function.
Test result:

Related

How can I parse the JSON response from Microsoft Graph Api List Chat into the textbox

This is the code what I have tried.
Based on my limited knowledge it cannot display JSON response of Microsoft.Graph API, it just appears the message box with the content of Microsoft.Graph.User.
How can I parse the JsonResponse into the textbox.
Documentation of ListChat:
https://learn.microsoft.com/en-us/graph/api/chat-list?view=graph-rest-1.0
private async void button2_Click(object sender, EventArgs e)
{
var scopes = new[] { "Chat.ReadBasic", "Chat.ReadWrite", "Chat.Read" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "5xxxx3-3xxa-4xxx1-9xx0c-exxxb0";
// Value from app registration
var clientId = "35xxxx-5xxx2-4xx9-8500-63xxxxaf";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var userName = "xx.xxxx#xxxi.com";
var password = "Axxxxx#";
// https://learn.microsoft.com/dotnet/api/azure.identity.usernamepasswordcredential
var userNamePasswordCredential = new UsernamePasswordCredential(
userName, password, tenantId, clientId, options);
GraphServiceClient graphClient = new GraphServiceClient(userNamePasswordCredential, scopes);
var request = await graphClient.Me.Request().GetAsync();
String txt = request.ToString();
MessageBox.Show(txt);
}
You can create HttpRequestMessage, send this message using HttpProvider on GraphServiceClient to get a response and read the content. It should be json.
For current user
var httpMessage = graphClient.Me.Request().GetHttpRequestMessage();
var response = await graphClient.HttpProvider.SendAsync(httpMessage);
var jsonContent = await response.Content.ReadAsStringAsync();
MessageBox.Show(jsonContent);
For chats
var httpMessage = graphClient.Me.Chats.Request().GetHttpRequestMessage();
var response = await graphClient.HttpProvider.SendAsync(httpMessage);
var jsonContent = await response.Content.ReadAsStringAsync();
MessageBox.Show(jsonContent);
At
var request = await graphClient.Me.Request().GetAsync();
you do not have JSON which you have to parse, but an object of type Microsoft.Graph.User.
Since this object does not implement it's own .ToString() it gives you the result of the base implementation which is it's object type name.
If you really need the object as JSON, you can use a JSONserializer like Newstonsoft provides and use it to serialize your data to a JSON string.
If you want to display data of the user you have to access it's properties and display their values.
You can try to deserialize it with Newtonsoft.Json
https://www.nuget.org/packages/Newtonsoft.Json/
To do that you need a class with the same properties as the .json file. So something like:
public class Response
{
public List<Value> Values {get;set;}
}
public class Value
{
public string Id {get;set;}
public string Topic {get;set;
.....
public ChatViewPoint ChatViewPoint {get;set;}
}
public class ChatViewPoint
{
public bool IsHidden {get;set;}
....
}
After you created the classes you can try to deserialize the string to the given datamodel.
Hope it works

C# Update Azure Cosmos Db json item with Upsert

I'm (new to CosmosDb) and trying to update an item in an Azure Cosmos db, but it's inserting instead.
The object sent is
public class Bank
{
public string id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
I tried passing only the Code and the Name values back (i.e. I did not include the id in the object, which causes an insert (I wanted an update).
I am now including the id which results in an error.
Error returned is:
ResourceType Document is unexpected.
ActivityId: a0d50426-c556-4b17-9646-93052699026e, Windows/10.0.19044 documentdb-netcore-sdk/2.16.2
So, it's values after a front end update (only changing the Name value) are:
Code: "FNB"
Name: "aa First Update Test"
id: "d76ade3d-7d02-46e5-a458-e9f0781bf044"
The DAL code:
var documentUri = UriFactory.CreateDocumentUri(DBName, "Banks", bank.Code);
try
{
Document doc = await client.UpsertDocumentAsync(documentUri, bank);
}
How do I get it to update?
TIA
Your Code is not clear and dont have enough information.try these functions.
protected DataContext(string endpointUrl, string databaseId,
string masterKey)
{
_databaseId = databaseId;
_masterKey = masterKey;
_databaseUri = UriFactory.CreateDatabaseUri(_databaseId);
this._client = new DocumentClient(new Uri(endpointUrl), _masterKey);
this._client.CreateDatabaseIfNotExistsAsync(new Database
{ Id = _databaseId });
this._client.CreateDocumentCollectionIfNotExistsAsync(
UriFactory.CreateDatabaseUri(_databaseId),
new DocumentCollection { Id = CollectionId });
_databaseCollectionUri = UriFactory.CreateDocumentCollectionUri(
_databaseId, CollectionId);
}
insert and update using
public async Task<Document> UpsertDocumentAsync(T entity)
{
var result = await this._client.UpsertDocumentAsync(
_databaseCollectionUri, entity);
return result;
}
Or Try please using the nuget Microsoft.Azure.Cosmos;
string cosmosDbConnectionString = CosmosDbConnectionKey;
CosmosClient cosmosClient = new CosmosClient(cosmosDbConnectionString);
var db = CosmosDbNameKey;
var container = ContainerKey;
await container.UpsertItemAsync(Model, new PartitionKey(Model.PK));
What I needed was the DocumentCollection (DocumentCollection Link) in the Upsert, but I had the Document Link (documentUri)
So,
public async Task<ExBool> UpdateAsyncPOCO(Bank bank)
{
// NB: UpsertDocumentAsync should take the DocumentCollection link, instead of Document link.
// This is a DocumentLink
var documentUri = UriFactory.CreateDocumentUri(DBName, "Banks", bank.Code);
// This is a DocumentCollection
var CollectionUri = UriFactory.CreateDocumentCollectionUri("demo", "Banks");
try
{
Document doc = await client.UpsertDocumentAsync(CollectionUri, bank);
}
catch (Exception ex)
{
HandleException(ex);
}
return result;
}
Insert and update work perfectly now.
The model and values for the update:
Code: "updated FNB 2"
Name: "updated First National Bank 22"
id: "d76ade3d-7d02-46e5-a458-e9f0781bf044"
Similarly, the Insert
Code: "qwerty"
Name: "qwertyuiop"
id: ""

How do I intercept a message in FormFlow before it reaches recognizers? (enum usage)

I would like to intercept what the user writes if he doesn't like any option in the list. My code is the following, but the validate function works only if the user chooses an option.
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace BotApplication.App_Code
{
public enum MainOptions { AccessoAreaRiservata = 1, AcquistoNuovaPolizza, RinnovoPolizza, Documenti, StatoPratica, AltroArgomento }
[Serializable]
public class MainReq
{
[Prompt("Indicare la tipologia della richiesta? {||}")]
public MainOptions? MainOption;
public static IForm<MainReq> BuildForm()
{
var form = (new FormBuilder<MainReq>()
.Field(nameof(MainOption),validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true };
{
string risposta = (response.ToString());
if (risposta == "AltroArgomento")
{
result.Feedback = "it works only if user choose an option";
result.IsValid = true;
}
return result;
}
})
.Build());
return form;
}
}
}
There are a few possible workarounds for you to consider. Normally if you want to account for situations where a user wants to ask a question or say something unrelated to the form, you'd have them cancel the form using the Quit command. If you want your bot to be smart enough to interpret when users change the subject in the middle of a form, that's a bit more advanced.
If you want to keep using a validate method, you can change your MainOption field to a string instead of a MainOptions? so that all user input gets sent to the validate method, but then you'd need to generate the list of choices yourself.
My recommendation is to use a custom prompter instead of a validate method. I've written a blog post that details how to make such a prompter. First you would provide a NotUnderstood template to indicate to your prompter when a message isn't a valid option in FormFlow. Then in the prompter you would call your QnAMaker dialog or do whatever you want with the message.
// Define your NotUnderstood template
[Serializable, Template(TemplateUsage.NotUnderstood, NOT_UNDERSTOOD)]
public class MainReq
{
public const string NOT_UNDERSTOOD = "Not-understood message";
[Prompt("Indicare la tipologia della richiesta? {||}")]
public MainOptions? MainOption;
public static IForm<MainReq> BuildForm()
{
var form = (new FormBuilder<MainReq>()
.Prompter(PromptAsync) // Build your form with a custom prompter
.Build());
return form;
}
private static async Task<FormPrompt> PromptAsync(IDialogContext context, FormPrompt prompt, MainReq state, IField<MainReq> field)
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
// Here is where we've made a change to the default prompter.
if (promptMessage.Text == NOT_UNDERSTOOD)
{
// Access the message the user typed with context.Activity
await context.PostAsync($"Do what you want with the message: {context.Activity.AsMessageActivity()?.Text}");
}
else
{
await context.PostAsync(promptMessage);
}
return prompt;
}
}

Bot framework DialogContext.Continue does not work

I have implement a bot using Microsoft Bot builder SDK v-4 (pre-release). To manage the conversation flow I have used two simple dialogs-
GreetingDialog - DialogBegin: To greet the user first time
public Task DialogBegin(DialogContext dc, IDictionary<string, object> dialogArgs = null)
{
var state = dc.Context.GetConversationState<EchoState>();
string greetMessage = string.Format("Hi, I am {0}.", _botName);
dc.Context.SendActivity(greetMessage);
IList<CardAction> suggestedActions = new List<CardAction>
{
//some card action suggestions
};
var activity = MessageFactory.SuggestedActions(suggestedActions, text: "Please select the area of conversation.");
dc.Context.SendActivity(activity);
dc.End();
return Task.CompletedTask;
}
ConversationDialog - DialogBegin: To continue the subsequent conversation after the user has been greeted
public Task DialogBegin(DialogContext dc, IDictionary<string, object> dialogArgs = null)
{
string activity = "test";
dc.Context.SendActivity(activity);
dc.Continue();
return Task.CompletedTask;
}
I am calling the GreetingDialog in the ConversationUpdate event and the ConversationDialog in the subsequent message received event, within the OnTurn method in my Bot class.
OnTurn event in my Bot class:
public async Task OnTurn(ITurnContext context)
{
var state = context.GetConversationState<EchoState>();
var dialogCtx = _dialogs.CreateContext(context, state);
if (context.Activity.Type == ActivityTypes.ConversationUpdate)
{
//Greet user first time
if (context.Activity.MembersAdded[0].Id == "default-user")
{
return;
}
if (!context.Responded)
{
var args = new Dictionary<string, object>
{
["greetingArgs"] = context.Activity.Text
};
await dialogCtx.Begin("greetingDialog", args);
}
}
else if (context.Activity.Type == ActivityTypes.Message)
{
await dialogCtx.Continue(); //this line is supposed to execute Begin the active dialog again??
//if (!context.Responded)
if(dialogCtx.ActiveDialog == null || !dialogCtx.Context.Responded)
{
var args = new Dictionary<string, object>
{
["conversationArgs"] = context.Activity.Text
};
await dialogCtx.Begin("conversationDialog", args);
}
}
}
Using the above code, I get redirected to ConversationDialog but it only happens through await dialogCtx.Begin("conversationDialog", args);. Isn't it supposed to redirect to DialogBegin of the Active dialog when I do await dialogCtx.Continue();? I can see the Active dialog is 'conversationDialog' and the debugger steps over through await dialogCtx.Continue();. Any help with this please?
I think I figured it out. We can implement the IDialogContinue interface for our Dialog class like this-
public class QnADialog : IDialog, IDialogContinue
{
public Task DialogBegin(DialogContext dc, IDictionary<string, object> dialogArgs = null)
{
string activity = "test";
dc.Context.SendActivity(activity);
//dc.Continue();
return Task.CompletedTask;
}
public Task DialogContinue(DialogContext dc)
{
dc.Context.SendActivity("dc continue");
dc.Context.Responded = true;
return Task.CompletedTask;
}
}
Then we can use the DialogContinue method to handle the DialogContext.Continue() from the calling code.

How to use FormFlow result at Microsoft BotFramework?

I create simple bot with Microsoft BotFramework and i use FormFlow.
So:
[Serializable]
public class Test
{
public String Name {get;set;}
public uint Age {get;set; }
}
internal static IDialog<Test> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(Test.BuildForm));
}
And:
public async Task<Message> Post([FromBody]Message message)
{
if (message.Type == "Message")
{
return await Conversation.SendAsync(message, MakeRootDialog);
}
else
{
return HandleSystemMessage(message);
}
}
So, Micrisoft BotEmulator works well (and bot) and ask me Name and Age of Person.
But, how to get result of this choise to use it?
And how to know what user type it? Should i use ConversationId?
P.S. i mean how can i get result from user name and user age?
I try to use:
var name= result.GetBotPerUserInConversationData<Test>("Name");
But it return null;
P.P.S: i use Bot Emulator: and get json responce like this:
GetBotPerUserInConversationData:DialogState { some binary data }
So, i use
var name= result.GetBotPerUserInConversationData<Test>("DialogState");
But get an error:
"exceptionMessage": "Error converting value System.Byte[] to type 'Test'. Path ''.",
"exceptionType": "Newtonsoft.Json.JsonSerializationException"
Hi since you are building the form, you can get the result in FormFlowComplete callback method as below
private async Task yourFormFlowComplete(IDialogContext context, IAwaitable<yourclass> result)
{
var res = await result;//res will contain the result set, if you build the form with a class
}
You can just 'chain' a Do call to the Chain.From
internal static IDialog<Test> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(Test.BuildForm))
.Do(async (context, formResult) =>
{
var completed = await formResult;
//your logic
}
}
'completed' will have the result of the form with the entries from the user.
You can refer to the AnnotatedSandwichBot where they are doing exactly what you need here.

Categories