How to use FormFlow result at Microsoft BotFramework? - c#

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.

Related

Return two models in a function

I'm trying to return two different models at the same function in C#, I don't know if it's possible.
public async Task<<Model1>, <Model2>> GetLocation()
{
string url = Utils.LimbleConnection.SetUrl("/locations");
try
{
return Model1;
} catch (HttpRequestException httpEx)
{
return Model2
}
}
Perhaps setting up to return both models. On success, the first item is true, second has the first model, third as null and vis versa for failure. On failure the code below returns a new instance of the model, you need to decide what the data comes back as e.g. populated with whatever you want.
Here I'm simply doing a proof of concept with Entity Framework, you can adapt to your web code.
public static async Task<(bool success, Customer customers, ContactTypes)> GetDataAsync()
{
try
{
await using var context = new CustomerContext();
return (true, context.Customer.FirstOrDefault(), null);
}
catch (Exception exception)
{
// log exception then return results
return (false, null, new ContactTypes());
}
}
Caller code deconstructs the method.
public async Task Demo()
{
var (success, customers, contactTypes) = await DataOperations.GetDataAsync();
if (success)
{
// use customers
}
else
{
// use contact type
}
}
You can try using a Tuple:
private Tuple<string, int> Method()
{
var tuple = new Tuple<string, int>("String Value", 0);
return tuple;
}

Is there a better way to generalize dbSet with the same attribute - Entity Framework

I have a common pattern
bool doesLinkExist = await [DBSet]
.AnyAsync(model => model.PartId == parameters.PartId).ConfigureAwait(false);
if(doesLinkExist)
throw exception (which has different messages)
[DBSet]=>Table in the database.
If I create a method it is really easy to pass the exception message but the DB set seems to be the problem as the code doesn't know that there are PartId columns in different [DBset]s/tables
What could be a way to bypass this problem and to create a common method?
Edit: In 2 words, I want to pass the [DBSet] as a parameter
This is the way that I would like this method to look like
private Task CheckLinkExistAsync(int idForLookUp, string errorMsg, DBSet table, CancellationToken cancellationToken)
{
bool LinkExist = await table.AnyAsync(model => model.Id == idForLookUp, cancellationToken).ConfigureAwait(false);
if(LinkExist)
throw exception (which has different messages)
}
Creating a method that accepts a Func as a parameter is probably what you're looking for.
private async Task<IActionResult> CheckLinkExistsAsync(int id, string errorMessage, Func<Task<T>> func)
{
bool exists = await _modelRepository.Any(x => x.Id == id);
if (exists)
{
return await func();
}
throw new Exception(errorMessage);
}
Afterwards, you can consume it like this
await CheckLinkExistsAsync(1, "Custom Error Message", async () => {
// DO STUFF AND RETURN
var t = new T();
return t;
});
I tried many different ways(with dynamic or DbSet< T>) but didn't think that I am filtering only on the ID so that's why I can just pass a List<int> and filter on it
private void CheckLinkExistsAsync(int partId, string errorMessage, List<int> idsToLookup)
{
bool exists = idsToLookup.Any(id => id == partId);
if(exists)
throw new Exception(errorMessage);
}
And I call it like this
CheckLinkExistsAsync(parameters.PartId, "ExceptionMessage",
await Db.Table.Select(model => model.PartId).ToListAsync(cancellationToken).ConfigureAwait(false));

Azure Functions Runtime v3 Middleware

Is there a way to access the request and response object in an azure middle ware.
Using a tutorial for a logging middleware I already got this far:
public class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
// Code before function execution here
await next(context);
// Code after function execution here
}
catch (Exception ex)
{
var log = context.GetLogger<ExceptionLoggingMiddleware>();
log.LogWarning(ex, string.Empty);
}
}
}
but I want to access the response and request object too. Like status code, body parameters, query parameters etc. Is this possible?
While there is no direct way to do this, but there is a workaround for accessing HttpRequestData (Not the best solution but it should work until there is a fix.):
public static class FunctionContextExtensions
{
public static HttpRequestData GetHttpRequestData(this FunctionContext functionContext)
{
try
{
KeyValuePair<Type, object> keyValuePair = functionContext.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
object functionBindingsFeature = keyValuePair.Value;
Type type = functionBindingsFeature.GetType();
var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
}
catch
{
return null;
}
}
}
And you can use it like this:
public class CustomMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
HttpRequestData httpRequestData = context.GetHttpRequestData();
// do something with httpRequestData
await next(context);
}
}
Check out this for more details.
For Http Response, there is no workaround AFAIK. Further, check out GH Issue#530, that says that documentation for this will be added soon. This capability looks like a popular demand and expected to be fixed soon (at the time of writing this).

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;
}
}

WebAPI Type of method that returns either Class or Error

I'd want to ask about way to return errors when it comes to WebAPI
I'm not sure whether I should use this approach, because this is not giving really good details except 500 internal server error code without UseDeveloperExceptionPage();
public async Task<Token> login(User creds)
{
var user = await Task.FromResult(_context.GetUser.....);
if (user == null)
{
throw new Exception("Invalid credentials");
}
return BuildToken(user); // Task<Token>
}
public class Token
{
public string token { get; set; }
public long Expires { get; set; }
}
So, should I create dynamic json and cast Error model / my JWT model to that dynamic json or something like that?
Basically I'd want to return either:
{
"Token": "aaaaa.aaaaaaaaaa.aaaaaaaaaaa",
"Expires": "111111111"
}
or
{
"message": "Invalid credentials"
}
More details:
Use method type:
IActionResult
and return errors as e.g
return new BadRequestObjectResult
(
new { message = "error message"}
);

Categories