Requirement
FormStateModel already contains FIRST input that users types.
Code
Simply I want to put the string that is in activity.Text inside FormStateModel:
private IDialog<FormStateModel> MakeRootDialog(string input)
{
return Chain.From(() => new FormDialog<FormStateModel>(
new FormStateModel() { Question = input },
ContactDetailsForm.BuildForm,
FormOptions.None));
}
=
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(
toBot: activity,
MakeRoot: () => this.MakeRootDialog(activity.Text));
}
else
{
await HandleSystemMessageAsync(activity);
}
var response = this.Request.CreateResponse(HttpStatusCode.OK);
return response;
}
On ConversationUpdate I start conversation simply by asking "Please type your Question:"
private static async Task<Activity> HandleSystemMessageAsync(Activity message)
{
switch (message.Type)
{
case ActivityTypes.DeleteUserData:
break;
case ActivityTypes.ConversationUpdate:
await Welcome(message);
break;
(...)
In that way:
private static async Task Welcome(Activity activity)
{
(...)
reply.Text = string.Format("Hello, how can we help you today? Please type your Question:");
await client.Conversations.ReplyToActivityAsync(reply);
(...)
}
But I can not find a way how to pass it. In this case this exception occurs:
anonymous method closures that capture the environment are not serializable, consider removing environment capture or using a reflection serialization surrogate:
Is there any way around that to populate state model at this step?
Solved by calling RootDialog inside MessagesController, then Calling new FormDialog by context.Call(form, (...));
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
await Conversation.SendAsync(activity, () => new LayerDialog());
}
LayerDialog:
[Serializable]
public class LayerDialog: IDialog<IMessageActivity>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.OnMessageReceivedAsync);
}
private async Task OnMessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var awaited = await result;
FormStateModel model = new FormStateModel();
model.Value = awaited.Text;
var form = new FormDialog<FormStateModel >(model ,
BuildForm , FormOptions.PromptInStart);
context.Call(form , this.AfterResume);
}
Related
I have a method to make an order,
public async Task<bool> Order(Request request)
{
// Each step does different things.
await Step1(request);
await Step2(request);
await Step3(request);
await Step4(request);
...
await StepN(request);
}
public async Task<bool> Step1(Request request)
{
var amount1 = await changeSomething1(request);
await Pay1(amount1);
}
public async Task<bool> Step2(Request request)
{
var amount2 = await changeSomething2(request);
await Pay2(amount2);
}
public async Task<bool> StepX(Request request)
{
var amountX = await changeSomethingX(request);
await PayX(amountX);
}
Now I need to preview the Order without any PayX(amount) is called. I don't want to add a boolen parameter to skip it, like the following code, which looks pretty ugly.
public async Task<bool> Order(Request request, bool preview = false)
{
await Step1(request, preview);
await Step2(request, preview);
await Step3(request, preview);
await Step4(request, preview);
....
await StepN(request, preview);
}
public async Task<bool> StepX(Request request, bool preview = false)
{
var amountX = await changeSomethingX(request);
if(!preview) await PayX(amountX);
}
What pattern can be applied here? Thanks a lot.
It seems to me that you need some middleware-like pattern (Just like middlewares in Asp.Net for example). That's my proposal:
First create An OrderMiddleware class that encapsulates your 2 methods, Step and Pay. In this case we pass step and pay as delegates to the constructor to achieve maximum flexibility
public delegate Task<int> StepDelegate(Request request);
public delegate Task PayDelegate(int amount);
public class OrderMiddleware
{
// Private fields
private readonly StepDelegate _step;
private readonly PayDelegate _pay;
// Initialization
public OrderMiddleware(StepDelegate step, PayDelegate pay)
{
_step = step;
_pay = pay;
}
// Public
public async Task Order(Request request, bool preview)
{
var amount = await _step.Invoke(request);
if (!preview)
await _pay.Invoke(amount);
}
}
Then you need a class to handle a list of OrderMiddlewares that represent your complete pipeline.
public class OrderPipeline
{
// Private fields
private readonly List<OrderMiddleware> _orderMiddlewares;
// Initialization
public OrderPipeline()
{
_orderMiddlewares = new()
{
new(Step1, Pay1),
new(Step2, Pay2)
};
}
// Order Handling
public async Task Order(Request request, bool preview = false)
{
foreach(var middleware in _orderMiddlewares)
await middleware.Order(request, preview);
}
// Middlewares
public async Task Step1(Request request)
{
var amount1 = await changeSomething1(request);
await Pay1(amount1);
}
public async Task Step2(Request request)
{
var amount2 = await changeSomething2(request);
await Pay2(amount2);
}
}
In this way you order method can work with a list of middlwares.
Just few notes:
If you need to pass other parameters to every middleware, in addiction to the preview bool, consider to create a OrderConfiguration class that encapsulates all these data, and pass it instead. In that way the signature remains clean and you do not need to do any refactoring
Maybe you want to separate you middleware registration logic from your OrderPipeline class in order to not violate the open-closed principle:
public class OrderPipeline
{
// Private fields
private readonly List<OrderMiddleware> _orderMiddlewares = new();
// Order Handling
public async Task Order(Request request, bool preview = false)
{
foreach(var middleware in _orderMiddlewares)
await middleware.Order(request, preview);
}
public void AddMiddleware(OrderMiddleware orderMiddleware)
{
_orderMiddlewares.Add(orderMiddleware);
}
}
I have the following SignalR Hub with in-memory connection management.
[Authorize]
public class ChatHub : Hub
{
private static readonly ConnectionMapping<string> _connections =
new ConnectionMapping<string>();
private readonly IMessageRepository _messagesRepository;
public ChatHub(IMessageRepository messageRepository)
{
_messagesRepository = messagesRepository;
}
public override async Task OnConnectedAsync()
{
var name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
//await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
var name = Context.User.Identity.Name;
_connections.Remove(name, Context.ConnectionId);
//await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnDisconnectedAsync(exception);
}
public async void SendMessage(ChatMessage chatMessage)
{
var name = Context.User.Identity.Name;
var chatHistoryMessage = await _messagesRepository.SaveMessageAsync(chatMessage);
var availableConnections = _connections.GetConnections(name);
if (availableConnections.Any())
{
foreach (var connectionId in availableConnections)
{
Clients.Client(connectionId).SendAsync("ReceiveMessage", chatHistoryMessage);
}
}
else
{
}
}
}
However, when executing the code the following line
Clients.Client(connectionId).SendAsync("ReceiveMessage", 1);
raises an object disposed error on Clients.
This issue started happening when I added the repository line:
var chatHistoryMessage = await _messagesRepository.SaveMessageAsync(chatMessage);
SaveMessageAsync method:
public async Task<ChatHistory> SaveMessageAsync (ChatMessage chatMessage)
{
using (var conn = new SqlConnection(ConnProvider.ConnectionString))
{
await conn.OpenAsync();
return (await conn.QueryAsync<ChatHistory>("[mob].[spSaveChatMessage]",
new
{
..
},
commandType: CommandType.StoredProcedure)).FirstOrDefault();
}
}
Why would my Clients object be disposed? If I wait with the debugger that issue never happens.
It looks like the SendMessage method should be an async Task rather than an async void.
The issue could be caused by the way the SignalR framework runs async voids.
See this article for a good overview.
Async methods returning void don’t provide an easy way to notify the
calling code that they’ve completed.
Code flow is as follows:
I have a DialogA which calls a qnaMaker Dialog in its StartAsync method.
The qnaMaker Dialog callback calls promptDialog function in a helper class(which is not a Dialog by the way)
Once user selects appropriate choice on the prompt, a callback is called which post a message
Error appears after the message("Helpful"/ "Not helpful") has been posted
My first incination was to use context.Wait() after the message has been posted from OnOptionSelectedForPrompt callback, bit it seems that calling wait here somehow executes the ResumeAfter callback for DialogA.
Code Sample: DialogA.cs
[Serializable]
public class DialogA : IDialog<object>
{
private string tool = "sometool";
private string userInput = String.Empty;
private bool sendToQnaMaker = false;
public DialogA(string userQuery)
{
if (userQuery.Length > 0)
{
sendToQnaMaker = true;
userInput = userQuery;
}
}
public async Task StartAsync(IDialogContext context)
{
if(sendToQnaMaker)
{
await context.Forward(new QnaDialog(), QnaMakerDialogCallback, BotHelper.CreateMessageActivityForQnaMaker(userInput), CancellationToken.None);
} else
{
await BotHelper.ShowWelcomeMessageOnMenuSection(context, tool);
context.Wait(OnMessageReceived);
}
}
private async Task QnaMakerDialogCallback(IDialogContext context, IAwaitable<object> result)
{
var response = await result;
bool isInQnaMaker = Convert.ToBoolean(response);
BotHelper botHelper = new BotHelper(tool);
botHelper.showPrompt(context)
}
BotHelper.cs
public void showPrompt(IDialogContext context)
{
PromptDialog.Choice(context, OnOptionSelectedForPrompt, Constants.promptChoices, MiscellaneousResponses.answerFeedbackMessage, MiscellaneousResponses.invalidOptionMessage, 3);
}
public async Task OnOptionSelectedForPrompt(IDialogContext context, IAwaitable<string> result)
{
string optionSelected = await result;
switch (optionSelected)
{
case MiscellaneousResponses.helpfulMessage:
string botreply = "Helpful";
await context.PostAsync(botreply);
break;
case MiscellaneousResponses.notHelpfulMessage:
string botResponse = "Not Helpful";
await context.PostAsync(botResponse);
break;
}
}
}
Created a separate dialog instead of using the BotHelper class and properly handled dialog stack using Context.Done and Context.Wait.
I have these functions
public async Task<List<Machine>> GetMachines()
{
await Initialize();
await SyncMachines();
return await machineTable.ToListAsync();
}
public async Task InsertMachines(List<Machine> machines)
{
await Initialize();
await Task.WhenAll(machines.Select(m => machineTable.InsertAsync(m)));
await SyncMachines();
}
I am writing a parent class to put these functions in, such that the functions getMachines() and InsertMachines() become getObject and InsertObject where the List<Machine> can be a list of any objects, so they can return and accept any type of list
How do I declare functions like this?
According to your description, I created my azure sync table class as follows, you could refer to it:
public class AzureCloudSyncTable<TModel> where TModel : class
{
public static MobileServiceClient MobileService = new MobileServiceClient(
"https://{your-mobile-app-name}.azurewebsites.net"
);
private IMobileServiceSyncTable<TModel> syncTable = MobileService.GetSyncTable<TModel>();
#region Offline sync
private async Task InitLocalStoreAsync()
{
if (!MobileService.SyncContext.IsInitialized)
{
var store = new MobileServiceSQLiteStore("localstore.db");
store.DefineTable<TModel>();
await MobileService.SyncContext.InitializeAsync(store);
}
await SyncAsync();
}
private async Task SyncAsync()
{
await PushAsync();
await syncTable.PullAsync(typeof(TModel).Name, syncTable.CreateQuery());
}
private async Task PushAsync()
{
try
{
await MobileService.SyncContext.PushAsync();
}
catch (MobileServicePushFailedException ex)
{
if (ex.PushResult != null)
{
foreach (var error in ex.PushResult.Errors)
{
await ResolveConflictAsync(error);
}
}
}
}
private async Task ResolveConflictAsync(MobileServiceTableOperationError error)
{
//var serverItem = error.Result.ToObject<TModel>();
//var localItem = error.Item.ToObject<TModel>();
//// Note that you need to implement the public override Equals(TModel item)
//// method in the Model for this to work
//if (serverItem.Equals(localItem))
//{
// // Items are the same, so ignore the conflict
// await error.CancelAndDiscardItemAsync();
// return;
//}
//// Client Always Wins
//localItem.Version = serverItem.Version;
//await error.UpdateOperationAsync(JObject.FromObject(localItem));
// Server Always Wins
//await error.CancelAndDiscardItemAsync();
}
#endregion
#region public methods
public async Task<List<TModel>> GetAll()
{
await InitLocalStoreAsync();
await SyncAsync();
return await syncTable.ToListAsync();
}
public async Task Insert(List<TModel> items)
{
await InitLocalStoreAsync();
await Task.WhenAll(items.Select(item => syncTable.InsertAsync(item)));
await PushAsync();
}
#endregion
}
Additionally, for more details about Handling Conflict Resolution, you could refer to adrian hall's book here.
i have a basic form. Depending on the choices of the user, it will direct it to various formflows. But i am unable to achieve this. It repeats first formFlow again and again in Microsoft BOT framework ?
//These are two forms that i have initiated. If a count is 1 then it must open first formflow otherwise the second formflow.
internal static IDialog<General> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(General.BuildForm));
}
internal static IDialog<ProfileForm> MakeRootDialog1()
{
return Chain.From(() => FormDialog.FromForm(ProfileForm.BuildForm));
}
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message && General.count == 0)
{
await Conversation.SendAsync(activity, MakeRootDialog);
General.count = 1;
}
else if(activity.Type == ActivityTypes.Message && General.count >= 1)
{
await Conversation.SendAsync(activity, MakeRootDialog1);
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
I was able to repro the problem but I'm still thinking how that could be solved using Chains in the MessageController.
My suggestion to unblock you is to move the "IF" logic for the Forms into a separate dialog. Something like the following:
Controller
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
RootDialog
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
if (General.count == 0)
{
General.count = 1;
context.Call(FormDialog.FromForm<General>(General.BuildForm, FormOptions.PromptInStart), async (ctx, formResult) => ctx.Wait(this.MessageReceivedAsync));
}
else if (General.count >= 1)
{
context.Call(FormDialog.FromForm<ProfileForm>(ProfileForm.BuildForm, FormOptions.PromptInStart), async (ctx, formResult) => ctx.Wait(this.MessageReceivedAsync));
}
}
}
This is a personal opinion => I prefer to use Dialogs since once the bot start to grow it's easier to follow the logic and to separate the components.