The code below was working - when a new user is added to the Teams channel the bot sends a welcome message to the user personally and not to the whole channel. For some reason it is no longer working - I believe it has to do with CreateConversationAsync() method. The V4 docs state: "This method is now obsolete because the ConversationReference argument is now redundant. Use the overload without this argument." but I haven't been able to figure out how to properly update the code below to work.
CreateConversationAsync: (This method passes the conversation reference (now obsolete) to ContinueConversationAsync())
ConversationReference conversationReference = null;
return await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
teamsChannelId,
serviceUrl,
credentials,
conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await Task.FromResult(false).ConfigureAwait(false);
}, cancellationToken).ContinueWith(_ => { return conversationReference; }).ConfigureAwait(false);
ContinueConversationAsync:
if (conversationReference != null)
{
await turnContext.Adapter.ContinueConversationAsync(
BotAppId,
conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(MessageFactory.Text(messages[0]), cancellationToken: c2).ConfigureAwait(false);
},cancellationToken).ConfigureAwait(false);
}
ConversationParameters for reference:
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = this.TeamsHelper.GetRecipient(turnContext),
Members = new List<ChannelAccount>() { member },
TenantId = this.TeamsHelper.GetChannelTennantId(channelData),
TopicName = "Testing",
};
Any help would be greatly appreciated!
------ UPDATED WITH SNIPPET ------
var teamsChannelId = turnContext.Activity.TeamsGetChannelId();
var serviceUrl = turnContext.Activity.ServiceUrl;
var credentials = new MicrosoftAppCredentials(BotAppId, BotAppPassword);
var channelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new List<ChannelAccount>() { member },
//TenantId = turnContext.Activity.Conversation.TenantId,
TenantId = channelData.Tenant.Id,
TopicName = "Testing Topic ",
};
var conversationReference = new ConversationReference()
{
ChannelId = teamsChannelId,
Bot = turnContext.Activity.Recipient,
ServiceUrl = serviceUrl,
Conversation = new ConversationAccount() { ConversationType = "channel", IsGroup = false, Id = teamsChannelId, TenantId = channelData.Tenant.Id },
};
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
teamsChannelId,
serviceUrl,
credentials,
conversationParameters,
async (t1, c1) =>
{
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(
BotAppId,
conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(MessageFactory.Text("This will be the first response to the new thread"), c2).ConfigureAwait(false);
},
cancellationToken).ConfigureAwait(false);
},
cancellationToken).ConfigureAwait(false);
Wanted to update everyone with a suggestion I was given:
If you want to send a message to user personally, you will need to pass conversation Id of that 1:1 chat not the channel, so to get that, conversation reference should be like this (please see variable conversationReference inside CreateConversationAsync) :
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
"msteams",
serviceUrl,
credentials,
conversationParameters,
async (t1, c1) =>
{
var userConversation = t1.Activity.Conversation.Id;
var conversationReference = new ConversationReference
{
ServiceUrl = serviceUrl,
Conversation = new ConversationAccount
{
Id = userConversation,
},
};
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(
BotAppId,
conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(MessageFactory.Text("This will be the first response to the new thread"), c2).ConfigureAwait(false);
},
cancellationToken).ConfigureAwait(false);
},
cancellationToken).ConfigureAwait(false);
I was able to test this approach locally, and it worked!
Related
Can't find good example of auth flow for link unfurling. I managed to run oauth flow using this example. But after user provided login and password and bot hits OnTeamsAppBasedLinkQueryAsync second time GetUserTokenAsync still returns null. So I don't follow where should I get then token from when auth flow is finished. Should I persist it somehow? Will Teams send me the token on every request or how it should work?
So in my case following code always returns null:
var tokenResponse = await (turnContext.Adapter as IUserTokenProvider)
.GetUserTokenAsync(turnContext, _connectionName, default(string),
cancellationToken: cancellationToken);
It seems the 'state' field is not present on AppBasedLinkQuery. When the auth flow completes, OnTeamsAppBasedLinkQueryAsync will be called again and the turnContext.Activity.Value will contain the url and the 'state' (or magic code). We will get this field added to AppBasedLinkQuery (created an issue here: microsoft/botbuilder-dotnet#3429 ).
A workaround is to retrieve the state/magiccode directly from the Activity.Value Something like:
protected async override Task<MessagingExtensionResponse> OnTeamsAppBasedLinkQueryAsync(ITurnContext<IInvokeActivity> turnContext, AppBasedLinkQuery query, CancellationToken cancellationToken)
{
var magicCode = string.Empty;
var state = (turnContext.Activity.Value as Newtonsoft.Json.Linq.JObject).Value<string>("state");
if (!string.IsNullOrEmpty(state))
{
int parsed = 0;
if (int.TryParse(state, out parsed))
{
magicCode = parsed.ToString();
}
}
var tokenResponse = await(turnContext.Adapter as IUserTokenProvider).GetUserTokenAsync(turnContext, _connectionName, magicCode, cancellationToken: cancellationToken);
if (tokenResponse == null || string.IsNullOrEmpty(tokenResponse.Token))
{
// There is no token, so the user has not signed in yet.
// Retrieve the OAuth Sign in Link to use in the MessagingExtensionResult Suggested Actions
var signInLink = await(turnContext.Adapter as IUserTokenProvider).GetOauthSignInLinkAsync(turnContext, _connectionName, cancellationToken);
return new MessagingExtensionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "auth",
SuggestedActions = new MessagingExtensionSuggestedAction
{
Actions = new List<CardAction>
{
new CardAction
{
Type = ActionTypes.OpenUrl,
Value = signInLink,
Title = "Bot Service OAuth",
},
},
},
},
};
}
var heroCard = new ThumbnailCard
{
Title = "Thumbnail Card",
Text = query.Url,
Images = new List<CardImage> { new CardImage("https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png") },
};
var attachments = new MessagingExtensionAttachment(HeroCard.ContentType, null, heroCard);
var result = new MessagingExtensionResult("list", "result", new[] { attachments });
return new MessagingExtensionResponse(result);
}
I have recently migrated my project from bot framework v3 to bot V4.
I have FeedbackDialog file in this i have implemented waterfallsteps, choiceprompts.
Everything and the flow is working correctly in Emulator.
But it is not working properly means the flow is not working fine in Webchannel
Please provide the solution to this problem.
Here is my code.
public FeedbackDialog() : base(nameof(FeedbackDialog))
{
AddDialog(new ChoicePrompt("ShowChoicePrompt") { Style = ListStyle.HeroCard });
AddDialog(new ChoicePrompt(nameof(ConfirmPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
StartAsync,
MessageReceivedAsync,
ResumeAfterPositiveFeedbackSelectionClarification
}));
InitialDialogId = nameof(WaterfallDialog);
}
public async Task<DialogTurnResult> StartAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var reply = ((Activity)stepContext.Context.Activity).CreateReply(BotConstants.feedbackRequestText);
reply.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "👍", Type=ActionTypes.PostBack, Value=BotConstants.positiveFeedbackValue },
new CardAction(){ Title = "👎", Type=ActionTypes.PostBack, Value=BotConstants.negativeFeedbackValue }
}
};
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
// return await stepContext.PromptAsync(nameof(ConfirmPrompt), promptOptions, cancellationToken);
}
public async Task<DialogTurnResult> MessageReceivedAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var data=stepContext.Result;
var res = stepContext.Result;
// string res= Convert.ToString(data.GetType().GetProperty("Synonym").GetValue(data, null));
var feedbackDetails = (FeedbackData)stepContext.Options;
var userFeedback =Convert.ToString(res);
// var userFeedback = feedbackDetails.userFeedback;
string userQuestion = feedbackDetails.userQuestion;
string intent = feedbackDetails.intent;
int Id = feedbackDetails.Id;
if (userFeedback.Contains(BotConstants.positiveFeedbackValue) || userFeedback.Contains(BotConstants.negativeFeedbackValue))
{
// create telemetry client to post to Application Insights
TelemetryClient telemetry = new TelemetryClient();
telemetry.InstrumentationKey = Configuration.CDPOSConfigurationManager.GetAppSetting("InstrumentationKey");
RequestRepository requestRepository = new RequestRepository();
if (userFeedback.Contains(BotConstants.positiveFeedbackValue))
{
// post feedback to App Insights
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"LuisIntent", intent },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("Yes-Vote", properties);
var chatFeedback = true;
//requestRepository.AddChatbotEntryToDB(userQuestion, intent, chatFeedback);
requestRepository.updateChatFeedbackStatus(Id, chatFeedback);
var options = BotConstants.positiveFeedbackOption;
var descriptions = BotConstants.positiveFeedbackOptionDesc;
Activity textPrompt = stepContext.Context.Activity.CreateReply(BotConstants.positiveFeedbackResponse);
List<Microsoft.Bot.Builder.Dialogs.Choices.Choice> choices = new List<Microsoft.Bot.Builder.Dialogs.Choices.Choice>()
{
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.YeshaveAnotherQuestionOption, Synonyms = new List<string> { "Yes" } },
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.NothankyouOption, Synonyms = new List<string> { "No" }}
};
try
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions {Prompt= MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices= choices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
catch (Exception ex)
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices = choices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
else if (userFeedback.Contains(BotConstants.negativeFeedbackValue))
{
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"LuisIntent", intent },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("No-Vote", properties);
var chatFeedback = false;
//requestRepository.AddChatbotEntryToDB(userQuestion, intent, chatFeedback);
requestRepository.updateChatFeedbackStatus(Id, chatFeedback);
await stepContext.Context.SendActivityAsync(BotConstants.negativeFeedbackResponse);
await stepContext.Context.SendActivityAsync(BotConstants.logTicketResponse);
var options = BotConstants.negativeFeedbackOption;
var descriptions = BotConstants.negativeFeedbackDesc;
List<Microsoft.Bot.Builder.Dialogs.Choices.Choice> NegativeChoices = new List<Microsoft.Bot.Builder.Dialogs.Choices.Choice>()
{
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.logTicket, Synonyms = new List<string> { BotConstants.logTicketOption } },
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.rephraseQuestionOption, Synonyms = new List<string> { BotConstants.rephraseQuestionOption }}
};
try
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices = NegativeChoices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
catch (Exception ex)
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.question), Choices = NegativeChoices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
}
else
{
return await stepContext.BeginDialogAsync(nameof(RootDialog), null, cancellationToken);
// no feedback, return to main dialog
// return await stepContext.EndDialogAsync(userFeedback, cancellationToken);
//return stepContext.BeginDialogAsync(userFeedback, cancellationToken);
}
return await stepContext.EndDialogAsync(userFeedback, cancellationToken);
}
private async Task<DialogTurnResult> ResumeAfterPositiveFeedbackSelectionClarification(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var data = stepContext.Result;
if (stepContext.Result != null)
{
string selection = Convert.ToString(data.GetType().GetProperty("Synonym").GetValue(data, null));
// var selection = stepContext.Result;
if (selection == BotConstants.YeshaveAnotherQuestionOption || selection == BotConstants.haveAnotherQuestionOption || selection == BotConstants.rephraseQuestionOption)
{
await stepContext.Context.SendActivityAsync(BotConstants.askAnotherQuestion);
}
else
{
await stepContext.Context.SendActivityAsync(BotConstants.logaTicketResponse);
}
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
public static void Register(HttpConfiguration config)
{
var builder = new ContainerBuilder();
// builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<MessagesController>().InstancePerRequest();
// The ConfigurationCredentialProvider will retrieve the MicrosoftAppId and
// MicrosoftAppPassword from Web.config
builder.RegisterType<ConfigurationCredentialProvider>().As<ICredentialProvider>().SingleInstance();
// Create the Bot Framework Adapter with error handling enabled.
builder.RegisterType<AdapterWithErrorHandler>().As<IBotFrameworkHttpAdapter>().SingleInstance();
// The Memory Storage used here is for local bot debugging only. When the bot
// is restarted, everything stored in memory will be gone.
IStorage dataStore = new MemoryStorage();
// Create Conversation State object.
// The Conversation State object is where we persist anything at the conversation-scope.
var conversationState = new ConversationState(dataStore);
builder.RegisterInstance(conversationState).As<ConversationState>().SingleInstance();
// Register the main dialog, which is injected into the DialogBot class
builder.RegisterType<RootDialog>().SingleInstance();
// Register the DialogBot with RootDialog as the IBot interface
builder.RegisterType<DialogBot<RootDialog>>().As<IBot>();
var container = builder.Build();
var resolver = new AutofacWebApiDependencyResolver(container);
config.DependencyResolver = resolver;
}
When a bot only works when it's running locally, memory storage is very often the issue. When a bot is deployed to an app service, there may be multiple instances of the service running simultaneously to provide high availability, and each instance will have its own memory. Like all REST API's, your bot needs to not rely on memory for the state it accesses across multiple turns. You need external storage so that all instances of the service have access to the same data, and so the data isn't lost when the service restarts. Please refer to the documentation for more information.
How to get the value inside if else in a waterfall step dialog and pass it to the next step?
Please refer to the code below thank you. Any help is appreciated thank you.
UPDATE: Choosing "Near me" is working fine but when choosing "Somewhere Else" it's getting an error.
AddStep(async (stepContext, cancellationToken) =>
{
var realEstateType = stepContext.Result as FoundChoice;
var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
state.RealEstateType = realEstateType.Value;
return await stepContext.PromptAsync("choicePrompt",
new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply("Which location are you considering?"),
Choices = new[] {new Choice {Value = "Near me"},
new Choice {Value = "Somewhere else"}
}.ToList()
});
});
AddStep(async (stepContext, cancellationToken) =>
{
var nearOrSomewhereElse = stepContext.Result as FoundChoice;
var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
state.NearOrSomewhereElse = nearOrSomewhereElse.Value;
var value = "";
if (state.NearOrSomewhereElse == "Somewhere else")
{
await stepContext.PromptAsync("textPrompt",
new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply("Which location are you considering?")
});
value = stepContext.Result as string; // i think this is the error. How can i get the result of the block of code inside this if block?
}
else if (state.NearOrSomewhereElse == "Near me")
{
value = "Near me";
}
return await stepContext.NextAsync(value, cancellationToken);
});
AddStep(async (stepContext, cancellationToken) =>
{
var nearOrSomewhereElse = stepContext.Result as string;
var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
state.NearOrSomewhereElse = nearOrSomewhereElse;
return await stepContext.PromptAsync("choicePrompt",
new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply($"Please indicate the size of {state.RealEstateType}"),
Choices = new[] {new Choice {Value = "Size 1"},
new Choice {Value = "Size 2"},
new Choice {Value = "Size 3"}
}.ToList()
});
});
enter code here
The easiest way to do this is using the WaterfallStepContext::NextAsync API passing the value you want to fall through to the next step which will then be accessible to that next step via the WaterfallStepContext::Result property.
That would look something like this:
// Your first step elided for brevity
AddStep(async (stepContext, cancellationToken) =>
{
var nearOrSomewhereElse = stepContext.Result as FoundChoice;
var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
state.NearOrSomewhereElse = nearOrSomewhereElse.Value;
if (state.NearOrSomewhereElse == "Near me")
{
value = "Near me";
}
else if (state.NearOrSomewhereElse == "Somewhere else")
{
//prompt user. user's answer will be stored to value.
value = "User input";
}
// Call NextAsync passing on the value
return await stepContext.NextAsync(value, cancellationToken);
});
AddStep(async (stepContext, cancellationToken) =>
{
// Retrieve the result of the previous step
var x = stepContext.Result as string;
// … use the value here …
});
Bot Info
SDK Platform: .NET
SDK Version: 3.14.0.7
Active Channels: Web
Deployment Environment: Local development with Emulator
Issue Description
We've trying to unit test every case that we have stored in a certain Dictionary, it seems to be working fine when the user sends and string and the test has to answer with a string. But we can't find any documentation on how to test the other kind of dialogs, like with attachments, buttons, etc.
We wish to make a dictionary of string,objects where the string is what we ask the bot and de object is either a string, Attachment, Dialog.
Code Example
This is how we store the answers:
public static Dictionary<string, object> data = new Dictionary<string, object>{
{"Nuevo", "Que quieres crear?"},
{"Ayuda", "Ya te ayudas!"},
{"Adios", "Nos vemos!"},
{
"Coche",
new Attachment() {
ContentUrl = "https://media.ed.edmunds-media.com/subaru/impreza/2006/oem/2006_subaru_impreza_sedan_sti_fq_oem_1_500.jpg",
ContentType = "image/png",
Name = "Subaru_Impreza.png"
}
},
{
"Moto",
new Attachment() {
ContentUrl = "http://motos.honda.com.co/sites/default/files/motos/cb-1000-r-cc-menu-honda.png",
ContentType = "image/png",
Name = "moto.png"
}
},
{
"Perro",
new Attachment() {
ContentUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Taka_Shiba.jpg/1200px-Taka_Shiba.jpg",
ContentType = "image/png",
Name = "ShibaInu.png"
}
}
};
This is how the bot works and returns everything, this is working as intended for at least text and attachments but we haven't done it for more type of messages.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var r = context.MakeMessage();
foreach (var item in data)
{
if (item.Key == activity.Text)
{
if (item.Value is Attachment)
{
r.Attachments = new List<Attachment>() { item.Value as Attachment };
}
if (item.Value is string)
{
r.Text = item.Value.ToString();
}
break;
}
}
// return our reply to the user
await context.PostAsync(r);
context.Wait(MessageReceivedAsync);
}
But when we want to make the test for it, it only works when what we send is a string not a IMessageActivity, which works in the emulator.
The code for the test:
[TestMethod]
public async Task Pregunta_respuesta_prueba()
{
foreach (var item in RootDialog.data)
{
var preg = item.Key;
var resp = item.Value;
if (item.Value is Attachment)
{
Attachment auxText = resp as Attachment;
resp = auxText.ContentUrl;
}
using (ShimsContext.Create())
{
// Arrange
var waitCalled = false;
object message = null;
var target = new RootDialog();
var activity = new Activity(ActivityTypes.Message)
{
Text = preg
};
var awaiter = new Microsoft.Bot.Builder.Internals.Fibers.Fakes.StubIAwaiter<IMessageActivity>()
{
IsCompletedGet = () => true,
GetResult = () => activity
};
var awaitable = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIAwaitable<IMessageActivity>()
{
GetAwaiter = () => awaiter
};
var context = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIDialogContext();
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.PostAsyncIBotToUserStringStringCancellationToken = (user, s1, s2, token) =>
{
message = s1;
Console.WriteLine(message);
return Task.CompletedTask;
};
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.WaitIDialogStackResumeAfterOfIMessageActivity = (stack, callback) =>
{
if (waitCalled) return;
waitCalled = true;
// The callback is what is being tested.
callback(context, awaitable);
};
// Act
await target.StartAsync(context);
// Assert
Assert.AreEqual(resp, message);
}
}
}
If you check this part of the code
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.PostAsyncIBotToUserStringStringCancellationToken = (user, s1, s2, token) =>
{
message = s1;
Console.WriteLine(message);
return Task.CompletedTask;
};
```
It does only works when the bot is returning an string, we can't even check if it is an activiy, this happens because the Fake Context that we create for the test is not working as expected.
That IDialogContext that we are faking doesnt seem to work at all when it is an object, but it does work when it is a string.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
/// Here when the test is running, this context.MakeMessage is null, but when the bot
/// is working, it wors perfectly.
var r = context.MakeMessage();
foreach (var item in data)
{
if (item.Key == activity.Text)
{
if (item.Value is Attachment)
{
r.Attachments = new List<Attachment>() { item.Value as Attachment };
}
if (item.Value is string)
{
r.Text = item.Value.ToString();
}
break;
}
}
// return our reply to the user
await context.PostAsync(r);
context.Wait(MessageReceivedAsync);
}
Reproduction Steps
To try this out you can try to test with an attachment, code is in this repository.
In stead of using PostAsyncIBotToUserStringStringCancellationToken, you can use context.PostAsyncIMessageActivityCancellationToken And, in the RootDialog's MessageReceivedWithTextAsync respond with an activity reply instead of just a string.
public async Task MessageReceivedWithTextAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
string r = "";
foreach (var item in dataText)
{
if (item.Key == activity.Text)
{
r = item.Value;
break;
}
}
var reply = activity.CreateReply(r);
foreach (var item in dataAtt)
{
if (item.Key == activity.Text)
{
reply.Attachments.Add(item.Value);
reply.Text = "attachment";
break;
}
}
if ((string.IsNullOrWhiteSpace(r) || r == null) && reply.Attachments.Count == 0)
{
reply.Text = "No tengo respuesta para eso.";
}
// return our reply to the user
await context.PostAsync(reply);
}
Here are the changes to the test method:
[TestMethod]
public async Task Bot_Test_Attachments()
{
foreach (var item in RootDialog.dataAtt)
{
var preg = item.Key;
var att = item.Value;
using (ShimsContext.Create())
{
var waitCalled = false;
IMessageActivity message = null;
var target = new RootDialog();
var activity = new Activity(ActivityTypes.Message)
{
Text = preg,
From = new ChannelAccount("id","name"),
Recipient = new ChannelAccount("recipid","recipname"),
Conversation = new ConversationAccount(false,"id","name")
};
var awaiter = new Microsoft.Bot.Builder.Internals.Fibers.Fakes.StubIAwaiter<IMessageActivity>()
{
IsCompletedGet = () => true,
GetResult = () => activity
};
var awaitable = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIAwaitable<IMessageActivity>()
{
GetAwaiter = () => awaiter
};
var context = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIDialogContext();
context.PostAsyncIMessageActivityCancellationToken = (messageActivity, token) => {
message = messageActivity;
return Task.CompletedTask;
};
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.WaitIDialogStackResumeAfterOfIMessageActivity = (stack, callback) =>
{
if (waitCalled) return;
waitCalled = true;
callback(context, awaitable);
};
await target.MessageReceivedWithTextAsync(context, awaitable);
Assert.AreEqual(att, message.Attachments[0]);
}
}
}
I am working on bot technology, in one of my projet i wrote below lines of code
private async void DeliveryProgressReport(IDialogContext context, Activity message)
{
MessagesController.progressdetails = SQLDatabaseService.getinprogressdetails();
var progress = MessagesController.progressdetails;
if (progress.Count > 0)
{
try
{
Activity replyToConversation = message.CreateReply("**In Progress Report Details**");
replyToConversation.Recipient = message.From;
replyToConversation.Type = "message";
replyToConversation.Attachments = new List<Attachment>();
Dictionary<string, string> progresslist = new Dictionary<string, string>();
foreach (var progressreport in progress)
{
//Invoke the machine learning model for predicting the delivery status of delivery person
//var deliveryStatus= await InvokeRequestResponseServiceOfDeliveryPersonPredictionExp1();
//await Task.Delay(TimeSpan.FromSeconds(5));
var deliveryStatus = await InvokeRequestResponseServiceOfDeliveryPersonPredictionExp(progress[0].Name, progress[0].Mobile_Number);
progresslist.Add(progressreport.Name, progressreport.Mobile_Number);
List<CardImage> cardImages = new List<CardImage>();
cardImages.Add(new CardImage(url: progressreport.Photo_Url));
ThumbnailCard tlcard = new ThumbnailCard()
{
Title = "Name:" + progressreport.Name,
Subtitle = "Call:" + progressreport.Mobile_Number,
Images = cardImages,
Text = "Staus by Using Machine Learning Prediction:" + deliveryStatus
};
Attachment plAttachment = tlcard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
}
replyToConversation.AttachmentLayout = AttachmentLayoutTypes.List;
await context.PostAsync(replyToConversation);
} catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
else
{
Activity replyToConversation = message.CreateReply("**There are no in progress deliveries are found**");
await context.PostAsync(replyToConversation);
}
}
private async Task<string> InvokeRequestResponseServiceOfDeliveryPersonPredictionExp(string name, string mobile_Number)
{
string status = "";
//Func<Stream, Task> copyStreamAsync = async stream =>
//{
//await Task.Factory.StartNew(async () =>
//{
//using (stream)
//using (var sourceStream = await sourceContent.Content.ReadAsStreamAsync())
//{
// await sourceStream.CopyToAsync(stream);
//}
//var client = new HttpClient();
using (var client = new HttpClient())
{
var scoreRequest = new
{
Inputs = new Dictionary<string, StringTable>() {
{
"input1",
new StringTable()
{
ColumnNames = new string[] {"Id", "Name", "Mobile_Number", "CourierCompany_Name", "Status", "EmailId"},
Values = new string[,] { { "", name, mobile_Number, "", "","" }, { "", name, mobile_Number, "", "", "" }, }
}
},
},
GlobalParameters = new Dictionary<string, string>()
{
}
};
const string apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx=="; // Replace this with the API key for the web service
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.BaseAddress = new Uri("My Request URL");
// WARNING: The 'await' statement below can result in a deadlock if you are calling this code from the UI thread of an ASP.Net application.
// One way to address this would be to call ConfigureAwait(false) so that the execution does not attempt to resume on the original context.
// For instance, replace code such as:
// result = await DoSomeTask()
// with the following:
// result = await DoSomeTask().ConfigureAwait(false)
//var status = await PostRequest(scoreRequest,client).ConfigureAwait(false);
HttpResponseMessage response = await client.PostAsJsonAsync("", scoreRequest);//.ConfigureAwait(false);
string correctLocation = "";
string wrongLocation = "";
string notReached = "";
string personMismatch = "";
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
var results = JsonConvert.DeserializeObject<RootObject>(result);
foreach (var value in results.Results.output1.value.Values)
{
status = value[8].ToString();
correctLocation = value[4].ToString();
notReached = value[5].ToString();
personMismatch = value[6].ToString();
wrongLocation = value[7].ToString();
}
Debug.WriteLine("Result: {0}", result);
return status;
}
else
{
Debug.WriteLine(string.Format("The request failed with status code: {0}", response.StatusCode));
// Print the headers - they include the requert ID and the timestamp, which are useful for debugging the failure
Debug.WriteLine(response.Headers.ToString());
string responseContent = await response.Content.ReadAsStringAsync();
Debug.WriteLine(responseContent);
return status;
}
};
// return status;
}
After executing this below line I got the exception like asynchronous module or handler completed while an asynchronous operation was still pending
await context.PostAsync(replyToConversation);
Before posting this question I had followed through this below links, but I didn't resolve it.
Async Void, ASP.Net, and Count of Outstanding Operations
Web Api + HttpClient: An asynchronous module or handler completed while an asynchronous operation was still pending
Please tell how to resolve this exception.
-Pradeep
Finally, I resolved the above exception when I am return Task instead of void in DeliveryProgressReport method. and also where ever I was called the await DeliveryProgressReport() method there also I return Task instead of void.
-Pradeep