Multiple MS bots calling Same API - c#

I have created a MS bot framework API which will be called by more then 1 BOT.
I have created a Class to override the BotAuthentication and add this to the controller.
[BotAuthentication(CredentialProviderType = typeof(BotCredential))]
Here is BotCredential Class file:
public class BotCredential : ICredentialProvider
{
public async Task<string> GetAppPasswordAsync(string appId)
{
//return the app password
}
public async Task<bool> IsAuthenticationDisabledAsync()
{
return false;
}
public async Task<bool> IsValidAppIdAsync(string appId)
{
//return if appid is valid
}
}
Now in the Controller Post method:
MicrosoftAppCredentials credential = new MicrosoftAppCredentials()
{
MicrosoftAppId ="Appid received from the claim",
MicrosoftAppPassword = "Appsecret available from the claim"
};
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl), credential);
Activity reply= activity.CreateReply("Some reply");
await connector.Conversations.ReplyToActivityAsync(reply);
So now everything working fine for the first time.
For Example I have two Bots Bot1 and Bot2:
First I am sending a message from Bot1 and getting the reply. But after that, if I am sending a message from Bot2, the first message is giving exception with message:
doesn't match a known conversation
and for the second message from Bot2, it is giving the reply, and again if I am sending a message from Bot1 same thing is happening, and for the first message I am getting the same error and from second message. Onward it is working fine.

Related

Calling WebApi from Console Application is Failing

I have a WebApi that is running on Azure and fully tested via Postman
This API takes 2 headers
Content-Type: application/json
AppToken: {{AppToken}}
I want to call this API from a console application and I thought it is a pretty straight forward process. Here is my Main program
static void Main(string[] args)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("BASE_ADDRESS/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("AppToken", "MY_APP_TOKEN_VALUE");
// Call API to create the registration
CreateRegistration(client).Wait();
}
public static async Task CreateRegistration(HttpClient client)
{
using (client)
{
try
{
Registration reg1 = new Registration { email = "test#ssdafsds.com", clientId = 2342342, registrationId = 23423, ProgramId = 13 };
HttpResponseMessage responseMessage = await client.PostAsJsonAsync("api/auth/register", reg1);
responseMessage.EnsureSuccessStatusCode();
// Handle success
}
catch (Exception ex)
{
// Handle failure
Console.WriteLine(ex.StackTrace);
}
}
}
I am getting an internal server error as the "Reason Phrase" with status code 500. I am not sure why I am getting this error. I should be getting 201 Created like postman
Any idea how to solve this issue?
I made a mistake by passing a program id that I don't actually have in my database which failed the FK_Reference and caused this issue. Thank you all for your help.

Connection issues with Translator Text API version 3.0 - Microsoft Azure QnA chat bot

I want to connect my Azure QnA Chat Bot with the translation layer cognitive system. I am using this page as a reference: https://learn.microsoft.com/en-us/azure/cognitive-services/translator/quickstart-csharp-translate
I am doing it in C# and on the online code editor of Microsoft Azure.
Unfortunately, I can not connect to the translation layer (at least it looks like that).
When I tried to debug it, I can see that it stops at this specific part:
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
I checked the network timeout errors and there are many (20). All of them say "There was an error sending this message to your bot: HTTP status code GatewayTimeout".
I can "build.cmd" normally, without any errors, and when I try to do Debug.WriteLine or Console.WriteLine, nothing is printed out (I even tried in VS and Emulator)
The only thing that I am doing differently, compared to the above link, is that I defined the "host" and "key" outside of the private method:
private static async Task<string> TranslateQuestionToEnglish (...)
So, I take any word and want to translate it into English.
When I take out those two lines of the code, and test a method with static values, it obviously works (all together with QnA and everything else).
Later on, I am calling this method in "Task MessageReceivedAsync".
I created a translation cognitive service, and the only thing that I took from there is the first key from "Keys" and used it here in this method. Is that the only thing that I need from created cognitive service??
Another thing that I am not sure about, and if that thing is making a problems is that when I go to all resources, I can see that my qnatestbot(web app bot) and translator_test(cognitive services) are of type "global" location, while my qnatestbot(app service) is of type "west europe" location. Can the thing that they are in different regions make a problems? Should I put all of them in West Europe (since I am in Germany)?
Although, now that I look at the translator_test(cognitive services) endpoint, I can see that it is ...api.congitivemicrosft.com/.../v1.0.
But, when I was creating a resource it was automatically created like this, without specifying it from my side? How can I change that?
I hope that somebody successfully came across such an issue and can help me. Thank you in advance
I want to connect my Azure QnA Chat Bot with the translation layer cognitive system. I am using this page as a reference: https://learn.microsoft.com/en-us/azure/cognitive-services/translator/quickstart-csharp-translate
I try to create a sample to achieve your requirement: translate user inputs to English and pass translation text to QnAMaker dialog, the sample works fine both on local and Azure, you can refer to it.
In MessagesController:
[BotAuthentication]
public class MessagesController : ApiController
{
static string uri = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=en";
static string key = "{the_key}";
/// <summary>
/// POST: api/Messages
/// receive a message from a user and send replies
/// </summary>
/// <param name="activity"></param>
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
// check if activity is of type message
if (activity.GetActivityType() == ActivityTypes.Message)
{
if (activity.Text != null)
{
var textinEN = await TranslateQuestionToEnglish(activity.Text);
activity.Text = textinEN;
}
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
private static async Task<string> TranslateQuestionToEnglish(string text)
{
System.Object[] body = new System.Object[] { new { Text = text } };
var requestBody = JsonConvert.SerializeObject(body);
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(uri);
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
request.Headers.Add("Ocp-Apim-Subscription-Key", key);
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
dynamic jsonResponse = JsonConvert.DeserializeObject(responseBody);
var textinen = jsonResponse[0]["translations"][0]["text"].Value;
return textinen;
}
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
In dialog:
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
/* Wait until the first message is received from the conversation and call MessageReceviedAsync
* to process that message. */
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
/* When MessageReceivedAsync is called, it's passed an IAwaitable<IMessageActivity>. To get the message,
* await the result. */
var message = await result;
var qnaAuthKey = GetSetting("QnAAuthKey");
var qnaKBId = Utils.GetAppSetting("QnAKnowledgebaseId");
var endpointHostName = Utils.GetAppSetting("QnAEndpointHostName");
// QnA Subscription Key and KnowledgeBase Id null verification
if (!string.IsNullOrEmpty(qnaAuthKey) && !string.IsNullOrEmpty(qnaKBId))
{
// Forward to the appropriate Dialog based on whether the endpoint hostname is present
if (string.IsNullOrEmpty(endpointHostName))
await context.Forward(new BasicQnAMakerPreviewDialog(), AfterAnswerAsync, message, CancellationToken.None);
else
await context.Forward(new BasicQnAMakerDialog(), AfterAnswerAsync, message, CancellationToken.None);
}
else
{
await context.PostAsync("Please set QnAKnowledgebaseId, QnAAuthKey and QnAEndpointHostName (if applicable) in App Settings. Learn how to get them at https://aka.ms/qnaabssetup.");
}
}
private async Task AfterAnswerAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
// wait for the next user message
context.Wait(MessageReceivedAsync);
}
public static string GetSetting(string key)
{
var value = Utils.GetAppSetting(key);
if (String.IsNullOrEmpty(value) && key == "QnAAuthKey")
{
value = Utils.GetAppSetting("QnASubscriptionKey"); // QnASubscriptionKey for backward compatibility with QnAMaker (Preview)
}
return value;
}
}
// Dialog for QnAMaker Preview service
[Serializable]
public class BasicQnAMakerPreviewDialog : QnAMakerDialog
{
// Go to https://qnamaker.ai and feed data, train & publish your QnA Knowledgebase.
// Parameters to QnAMakerService are:
// Required: subscriptionKey, knowledgebaseId,
// Optional: defaultMessage, scoreThreshold[Range 0.0 – 1.0]
public BasicQnAMakerPreviewDialog() : base(new QnAMakerService(new QnAMakerAttribute(RootDialog.GetSetting("QnAAuthKey"), Utils.GetAppSetting("QnAKnowledgebaseId"), "No good match in FAQ.", 0.5)))
{ }
}
// Dialog for QnAMaker GA service
[Serializable]
public class BasicQnAMakerDialog : QnAMakerDialog
{
// Go to https://qnamaker.ai and feed data, train & publish your QnA Knowledgebase.
// Parameters to QnAMakerService are:
// Required: qnaAuthKey, knowledgebaseId, endpointHostName
// Optional: defaultMessage, scoreThreshold[Range 0.0 – 1.0]
public BasicQnAMakerDialog() : base(new QnAMakerService(new QnAMakerAttribute(RootDialog.GetSetting("QnAAuthKey"), Utils.GetAppSetting("QnAKnowledgebaseId"), "No good match in FAQ.", 0.5, 1, Utils.GetAppSetting("QnAEndpointHostName"))))
{ }
}
Test result:
Note: We can use ConfigurationManager.AppSettings["QnAKnowledgebaseId"]; to access QnAKnowledgebaseId etc settings from web.config if run bot application on local. For more information, please refer to this SO thread.

Multiple messages to the bot in quick succession crash it

Setup
I have a a bot that runs on .NET + Bot Framework + Azure + Facebook Messenger.
Initial Problem
I was trying to solve a problem when sending several messages to the bot triggers an exception and HTTP error 412. Microsoft describes this problem here: https://learn.microsoft.com/en-us/bot-framework/troubleshoot-general-problems#what-causes-an-error-with-http-status-code-412-precondition-failed-or-http-status-code-409-conflict
First Solution
In the page above, Microsoft provides an outdated sample code to resolve this issue. In this github issue, there is a revised version of that code that is supposed to work. I put it inside the constructor of my MessageController:
static MessagesController()
{
// Prevent exception in the bot and HTTP error 412 when the user
// sends multiple messages in quick succession. This may cause
// potential problems with consistency of getting/setting user
// properties.
// See https://learn.microsoft.com/en-us/bot-framework/troubleshoot-general-problems#what-causes-an-error-with-http-status-code-412-precondition-failed-or-http-status-code-409-conflict
// for details. The above link contains wrong code sample, revised
// code is from here: https://github.com/Microsoft/BotBuilder/issues/2345
var builder = new ContainerBuilder();
builder
.Register(c => new CachingBotDataStore(c.ResolveKeyed<IBotDataStore<BotData>>(typeof(ConnectorStore)), CachingBotDataStoreConsistencyPolicy.LastWriteWins))
.As<IBotDataStore<BotData>>()
.AsSelf()
.InstancePerLifetimeScope();
builder.Update(Conversation.Container);
}
Second Problem
Now, the exception still occurs when I send several messages to the bot in a quick succession. However, it changed from HTTP error 412 to something else:
One or more errors occurred. at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task1.GetResultCore(Boolean waitCompletionNotification) at System.Threading.Tasks.Task1.get_Result() at MyBot.SetUserDataProperty(Activity activity, String PropertyName, String ValueToSet) in C:\Users\xxx.cs:line 230
Update: I've checked the InnerException of the above and it turns out to be the same old HTTP error 412:
The remote server returned an error: (412) Precondition Failed.
The offending code is a function that writes to the bot storage. The line 230 referenced above is the last line of this function:
public static void SetUserDataProperty(Activity activity, string PropertyName, string ValueToSet)
{
StateClient client = activity.GetStateClient();
BotData userData = client.BotState.GetUserData(activity.ChannelId, activity.From.Id);
userData.SetProperty<string>(PropertyName, ValueToSet);
//client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
// Await async call without making the function asynchronous:
var temp = Task.Run(() => client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData)).Result;
}
Question
What else can I do to make sure that the user is able to send multiple messages in quick succession without triggering an exception when writing to the BotState storage?
I think there are a few issues here
The way you are trying to do this activity.GetStateClient(); is a only intended to be used for prototyping. We do no reccomend this method for production level code. You can set user data like context.UserData.SetValue("food", "Nachos" ); in the dialog and the values will automagically get saved when the dialog is serialized.
Most likely you are calling this method SetUserDataProperty from a dialog so when you do this var temp = Task.Run(() => client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData)).Result; it is conflicting and causing the error.
please review this blog post to learn more
Here is how to implement your follow up question:
if (activity.Type == ActivityTypes.Message)
{
var message = activity as IMessageActivity;
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
var key = new AddressKey()
{
BotId = message.Recipient.Id,
ChannelId = message.ChannelId,
UserId = message.From.Id,
ConversationId = message.Conversation.Id,
ServiceUrl = message.ServiceUrl
};
ConversationReference r = new ConversationReference();
var userData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None);
userData.SetProperty("key 1", "value1");
userData.SetProperty("key 2", "value2");
await botDataStore.SaveAsync(key, BotStoreType.BotUserData, userData, CancellationToken.None);
await botDataStore.FlushAsync(key, CancellationToken.None);
}
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
you will need to implement this class or something similar:
public class AddressKey : IAddress
{
public string BotId { get; set; }
public string ChannelId { get; set; }
public string ConversationId { get; set; }
public string ServiceUrl { get; set; }
public string UserId { get; set; }
}

How to get the Service URL of a deployed bot application in Microsoft Bot framework?

I am using DirectLine API to send message to the bot, I need the service URL of the Published Bot to perform a post request for the load test as mentioned in the steps here https://blog.botframework.com/2017/06/19/Load-Testing-A-Bot/
This is the code, can anyone point where I am going wrong
private static async Task<Chat> TalkToTheBot(string Message)
{
Chat objChat = null;
// Connect to the DirectLine service
try
{
DirectLineClient client = new DirectLineClient(directLineSecret);
Conversation conversation = await client.Conversations.StartConversationAsync();
string watermark = null;
Activity reply = new Activity
{
From = new ChannelAccount("User1", "User Name"),
Text = "Hello",
Type = ActivityTypes.Message,
};
//await client.Conversations.PostActivityAsync(conversation.ConversationId, reply.CreateReply(text: Message, locale: "en-US"), CancellationToken.None);
await client.Conversations.PostActivityAsync(conversation.ConversationId,reply , CancellationToken.None);
// Get the response as a Chat object
objChat = await ReadBotMessagesAsync(client, conversation.ConversationId, watermark);
}
catch (Exception e)
{
throw;
}
// Return the response as a Chat object
return objChat;
}
private static async Task<Chat> ReadBotMessagesAsync(DirectLineClient client, string conversationId, string watermark)
{
// Create an Instance of the Chat object
Chat objChat = new Chat();
// We want to keep waiting until a message is received
bool messageReceived = false;
while (!messageReceived)
{
// Get any messages related to the conversation since the last watermark
ActivitySet messages = await client.Conversations.GetActivitiesAsync(conversationId, watermark, CancellationToken.None);
// Set the watermark to the message received
watermark = messages?.Watermark;
// Get all the messages
var messagesFromBotText = from message in messages.Activities
where message.From.Id == botId
select message;
// Loop through each message
foreach (var message in messagesFromBotText)
{
// We have Text
if (message.Text != null)
{
// Set the text response
// to the message text
objChat.ChatResponse
+= " "
+ message.Text.Replace("\n\n", "<br />");
}
}
// Mark messageReceived so we can break
// out of the loop
messageReceived = true;
}
// Set watermark on the Chat object that will be
// returned
objChat.watermark = watermark;
// Return a response as a Chat object
return objChat;
}
Per the article,
The serviceUrl property here is critical to note, and needs to be set
to the endpoint of your message sink/client.
and:
In order to test your bot, you’ll need to create a custom UI/message
sink to send and receive messages to your bot. This message sink will
effectively act like a channel and accept HTTP POST messages with
JSON-serialized bot framework activities.
Which basically means that you will have to build a "message client" and the url of that client is the one that you will have to provide in the serviceUrl of your request.

Microsoft DirectLine API to bot does not work

So to explain my problem, I have to give you the context.
I got a Bot built with microsoft bot framework deployed on slack. Now it can happen these "events" on my backend that the bot communicates with. When a event occurs, I want to notify my bot of it and then let it send a message to all of it's conversations that something has happend. So basicly:
Backend>Microserivce>Bot>users
To do this I have to store all conversations in my backend, which I do in a database there. When a event happends, the backend will post an activity to the bot with all the conversations(basicly their id's) and the event it should show them.
So in essence my backend need to post a message to my bot.
For doing this I found the microsoft directline api which acts as a middleman here, a more abstract way to talk with the bot. The problem is that I don't know how to do it. I followed microsofts own tutorial but it doesn't seem to work for me:
This is the endpoint that my backend uses to notify the bot. "content" contains conversations and events as a json formated string.
[HttpPost]
[Route("conversationsEvents")]
public HttpResponseMessage PostConversationsEvents([FromBody]string content)
{
NotifyBot.Notify(content);
return Request.CreateResponse(HttpStatusCode.NoContent );
}
NotifyBot.Notify(content) looks like this:
private static async Task StartBotConversation( string contents)
{
string directLineSecret = "secret";
string fromUser = "microserviceNotifyEndpoint";
Activity activity = new Activity
{
From = new ChannelAccount(fromUser),
Text = contents,
Type = ActivityTypes.Event
};
DirectLineClient client = new DirectLineClient(directLineSecret);
var conversation = await client.Conversations.StartConversationAsync();
await client.Conversations.PostActivityAsync(conversation.ConversationId, activity);
}
Basicly the execution get's stuck at var conversation = await client.Conversations.StartConversationAsync(); , it just waits forever.
I tried changing it to var conversation = await client.Conversations.StartConversationAsync().ConfigureAwait(continueOnCapturedContext: false);´the execution goes on but the activity doesn't seem to get posted.
I'm not sure why the call to .StartConversationAsync() would freeze in your case. Maybe you haven't enabled the Direct Line channel on dev.botframework.com/bots? Nonetheless, as pointed out by Sergey, the Direct Line is a Channel and not a means for communicating with your bot on other channels.
Check out the Connector Client: bot-builder-dotnet-connector
Here is a static example of using it to proactively send a message to a user from a bot: MicrosoftDX/botFramework-proactiveMessages - sample: ConversationStarter.cs
pertinent code from sample:
public static async Task Resume(string conversationId,string channelId)
{
var userAccount = new ChannelAccount(toId,toName);
var botAccount = new ChannelAccount(fromId, fromName);
var connector = new ConnectorClient(new Uri(serviceUrl));
IMessageActivity message = Activity.CreateMessageActivity();
if (!string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(channelId))
{
message.ChannelId = channelId;
}
else
{
conversationId = (await connector.Conversations.CreateDirectConversationAsync( botAccount, userAccount)).Id;
}
message.From = botAccount;
message.Recipient = userAccount;
message.Conversation = new ConversationAccount(id: conversationId);
message.Text = "Hello, this is a notification";
message.Locale = "en-Us";
await connector.Conversations.SendToConversationAsync((Activity)message);
}
The serviceUrl, the channelId, conversationId, toId, fromId, etc are cached from previous communication by the user to the bot (these are statically stored in this example, so only work for one user). This example shows how it is possible to proactively send a message to a user from a bot. The Direct Line api is not required.
You don't need to use DirectLine, it is designed for creating alternative bot UIs.
To implementing what your want, you may try the following:
First, you need to store users addresses to whom you want to send the messages. It my be done by storing the ResumptionCookie of a user last message in your backend database.
var state = new ResumptionCookie(message).GZipSerialize();
When your PostConversationsEvents is called, you may resume the conversation at the latest point with each users.
var resumptionCookie = ResumptionCookie.GZipDeserialize(state);
var message = resumptionCookie.GetMessage();
message.Text = content;
await Conversation.ResumeAsync(resumptionCookie, message);
It is not the only solution. As I said, in this case you just resumed the conversation with the user at the latest point. Another solution is to save the user address (user the same ResumptionCookie class) but start the conversation when you need to:
var resumptionCookie = ResumptionCookie.GZipDeserialize(state);
var message = cookie.GetMessage();
ConnectorClient client = new ConnectorClient(new Uri(message.ServiceUrl));
var conversation = await
client.Conversations.CreateDirectConversationAsync(message.Recipient, message.From);
message.Conversation.Id = conversation.Id;
var newMessage = message.CreateReply();
newMessage.Text = content;
await client.Conversations.SendToConversationAsync(newMessage);
See more details on BotFramework documentation.

Categories