everyone.
I have some issues with bot framework SDK. When I pass bot's link to use in MS Teams to another users, they said me there is duplication of messages.
I don't know if is my code. For example. I have a main class:
Bot<T> : ActivityHandler where T : Dialog
Inside this class I use this methods:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
var activity = turnContext.Activity;
//Using for Adaptive cards.
if (string.IsNullOrWhiteSpace(activity.Text) && activity.Value != null)
{
activity.Text = JsonConvert.SerializeObject(activity.Value);
}
await base.OnTurnAsync(turnContext, cancellationToken);
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
await _dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
protected override async Task OnEndOfConversationActivityAsync(ITurnContext<IEndOfConversationActivity> turnContext, CancellationToken cancellationToken)
{
await base.OnEndOfConversationActivityAsync(turnContext, cancellationToken);
await _conversationState.DeleteAsync(turnContext, cancellationToken);
}
I never use OnMembersAddedAsync, because my bot is 1 to 1.
Does anyone know how to solve this problem? I test my bot with different users, I tested by myself, but I don't know why I have that unexpected behavior.
Maybe, solve this problem or get a temporary solution.
Related
I have a requirement that a Bot message posted to MS Teams should expire after a timeout period. To achieve this I am calling UpdateAsyncActivity() on a separate thread after a timeout, however this fails with a NullReferenceException:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.Bot.Builder.BotFrameworkAdapter.UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken)
at Microsoft.Bot.Builder.TurnContext.<>c__DisplayClass31_0.<<UpdateActivityAsync>g__ActuallyUpdateStuff|0>d.MoveNext()--- End of stack trace from previous location where exception was thrown ---
at Microsoft.Bot.Builder.TurnContext.UpdateActivityInternalAsync(Activity activity, IEnumerable`1 updateHandlers, Func`1 callAtBottom, CancellationToken cancellationToken)
at Microsoft.Bot.Builder.TurnContext.UpdateActivityAsync(IActivity activity, CancellationToken cancellationToken)
at NotifyController.ClearCard(ITurnContext turnContext, Activity timeoutActivity, Int32 timeoutInMinutes) in NotifyController.cs:line 48
The code looks something like this:
[Route("api/notify")]
[ApiController]
public class NotifyController : ControllerBase
{
private IBotFrameworkHttpAdapter Adapter;
private readonly string _appId;
private readonly IConversationStorage _conversationStorage;
public NotifyController(IBotFrameworkHttpAdapter adapter, IConfiguration configuration, IConversationStorage conversationStorage)
{
Adapter = adapter;
_appId = configuration["MicrosoftAppId"] ?? string.Empty;
_conversationStorage = conversationStorage;
}
[HttpPost]
public async Task<IActionResult> PostForm([FromBody] RestData restData)
{
ConversationReference conversationReference =
_conversationStorage.GetConversationFromStorage(restData.ConversationId);
await ((BotAdapter)Adapter).ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
await BotCallback(restData.AdaptiveCard, context, token), default(CancellationToken));
return new ContentResult();
}
private async Task BotCallback(string adaptiveCard, ITurnContext turnContext, CancellationToken cancellationToken)
{
var activity = MessageFactory.Attachment(adaptiveCard.ToAttachment());
ResourceResponse response = await turnContext.SendActivityAsync(activity);
var timeoutActivity = turnContext.Activity.CreateReply();
timeoutActivity.Attachments.Add(AdaptiveCardExamples.TimeoutTryLater.ToAttachment());
timeoutActivity.Id = response.Id;
// SUCCESS
//Thread.Sleep(10000);
//await turnContext.UpdateActivityAsync(timeoutActivity);
// FAIL
Thread CardClearThread = new Thread(() => ClearCard(turnContext, timeoutActivity));
CardClearThread.Start();
}
private async void ClearCard(ITurnContext turnContext, Activity timeoutActivity)
{
Thread.Sleep(10000);
await turnContext.UpdateActivityAsync(timeoutActivity);
}
}
I want the timeout to happen on a separate thread so that PostForm() returns a response as soon as the original message is sent.
I presume what is happening is that some aspect of turnContext is being disposed when the main thread returns, causing the sleeping thread to fail when it wakes up.
Is there a solution/alternative approach to this?
With in some mins , Bot has to response to the team otherwise this issue get popup best solution for this to implement proactive message concept
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp
In the bot classes we create ConversationState and UserState objects that we can access using its accessors and create properties on them, and then save the data we store.
But how could I do the same if I want to access the data from a Dialog that's called from the Bot class? I know I can pass an object through the Context using BeginDialogAsync options parameter. How could I pass two of them instead of just one and how to get them in the dialog class?
Is there a way to access ConversationState and UserState without having to pass them from dialog to dialog?
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occured during the turn.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
// Run the Dialog with the new message Activity.
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
In this function of the CoreBot sample we can see the ConversationState and UserState are saved but they are not modified anywhere else, and in the second function a DialogState property is created in the child dialog but it isn't used either that I can see? Can somebody explain why are they created and how to access them from inside the Dialog that is just called?
You can use dependency injection.
public class UserStateClass
{
public string name { get; set; }
}
and
public class YourDialog : ComponentDialog
{
private readonly IStatePropertyAccessor<UserStateClass> _userStateclassAccessor;
public YourDialog(UserState userState)
: base(nameof(YourDialog))
{
_userProfileAccessor = userState.CreateProperty<UserStateClass>("UserProfile");
WaterfallStep[] waterfallSteps = new WaterfallStep[]
{
FirstStepAsync,
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
}
private async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userstate = await _userStateclassAccessor.GetAsync(stepContext.Context, () => new UserStateClass(), cancellationToken);
userstate.name = "pepe";
return await stepContext.EndDialogAsync();
}
}
I am using NLP Dispatch , through which i have a merged luis and QnA model, I am able to normally call and add my own logic inside top scoring intents block. Now i have a dialog class which i want to be called whenever a top scoring intent is being detected
namespace Microsoft.BotBuilderSamples
{
public class DispatchBot : ActivityHandler
{
private ILogger<DispatchBot> _logger;
private IBotServices _botServices;
public DispatchBot(IBotServices botServices, ILogger<DispatchBot> logger)
{
_logger = logger;
_botServices = botServices;
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
// First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(turnContext, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult.GetTopScoringIntent();
// Next, we call the dispatcher with the top intent.
await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
const string WelcomeText = "I am here to make your bot experience much more easier";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Hi {member.Name}, I am your IT assistant at your service . {WelcomeText}"), cancellationToken);
}
}
}
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case "l_mts-bot-809f":
await ProcessHomeAutomationAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
case "q_mts-bot":
await ProcessSampleQnAAsync(turnContext, cancellationToken);
break;
default:
_logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
await turnContext.SendActivityAsync(MessageFactory.Text($"Dispatch unrecognized intent: {intent}."), cancellationToken);
break;
}
}
private Activity CreateResponse(IActivity activity, Attachment attachment)
{
var response = ((Activity)activity).CreateReply();
response.Attachments = new List<Attachment>() { attachment };
return response;
}
private async Task ProcessHomeAutomationAsync(ITurnContext<IMessageActivity> turnContext, LuisResult luisResult, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessHomeAutomationAsync");
// Retrieve LUIS result for Process Automation.
var result = luisResult.ConnectedServiceResult;
var topIntent = result.TopScoringIntent.Intent;
var entity = result.Entities;
if (topIntent == "welcome")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Hi,This is your IT assistant"), cancellationToken);
}
if (topIntent == "None")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry I didnt get you!"), cancellationToken);
}
if (topIntent == "DateTenure")
{
// Here i want to call my dialog class
}
}
}
private async Task ProcessSampleQnAAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessSampleQnAAsync");
var results = await _botServices.SampleQnA.GetAnswersAsync(turnContext);
if (results.Any())
{
await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
}
}
I want as and when my top intent is detected , my custom dialog comes into action and the handling of conversation is then should be handled by my dialog class.
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case "l_mts-bot-809f":
//I MADE CHANGES HERE
await dc.BeginDialogAsync(nameof(DIALOG_CLASS_I_WANT_TO_START));
break;
case "q_mts-bot":
await ProcessSampleQnAAsync(turnContext, cancellationToken);
break;
default:
_logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
await turnContext.SendActivityAsync(MessageFactory.Text($"Dispatch unrecognized intent: {intent}."), cancellationToken);
break;
}
}
Just remember to add the dialog to either your main dialog class like this:
public MainDialog(UserState userState)
: base(nameof(MainDialog))
{
_userState = userState;
AddDialog(new DIALOG_CLASS_I_WANT_TO_START());
InitialDialogId = nameof(aDifferentDialogNotShownHere);
}
or to your startup.cs as a transient:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//all the other configure stuff...
// Register dialogs
services.AddTransient<MainDialog>();
services.AddTransient<DIALOG_CLASS_I_WANT_TO_START>();
Please help me to understand why this code cause a deadlock?
I have an asp.net web api application and I tried to make some controller method asynchronous.
[HttpPost]
[Authentication]
public async Task<SomeDTO> PostSomething([FromBody] SomeDTO someDTO)
{
return await _service.DoSomething(someDTO);
}
this is how looks the called service method:
public async Task<SomeDTO> DoSomething(SomeDTO someDTO)
{
...
var someTask = Task.Run(() =>
{
var entity = new SomeEntity(someDTO);
return _repository.Create(entity);
});
...
var result = await someTask;
...
}
And there is some globalhandler, that prints a response to a console.
public class AppGlobalHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var resp = base.SendAsync(request, cancellationToken);
Debug.WriteLine($"Response:{request.RequestUri}{Environment.NewLine}{resp?.ConfigureAwait(false).GetAwaiter().GetResult()?.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult()}");
return resp;
}
}
Looks like ConfigureAwait(false).GetAwaiter().GetResult()
blocks the caller thread, but I supposed that ConfigureAwait(false) should avoid this, isn't it?
ConfigureAwait(false) would not help you here because it must be all the way down in the call stack (see more here) not at place where you wait synchronously, i.e. it depends rather on the implementation of base.SendAsync. If it acquired a lock on current thread it's too late to do something about it. It is also not recommended in ASP.net pipeline to continue responding on other thread after all (see discussion here and post here).
Finally it is always a highly risky idea to wait synchronously in async context.
If you need to read content, why not doing it like that:
public class AppGlobalHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var resp = await base.SendAsync(request, cancellationToken);
var content = resp?.Content != null
? (await resp.Content.ReadAsStringAsync())
: string.Empty;
Debug.WriteLine($"Response:{request.RequestUri}{Environment.NewLine}{content}");
return resp;
}
}
I think you overlook async keyword in Task.Run() method.
public async Task<SomeDTO> DoSomething(SomeDTO someDTO)
{
var someTask = Task.Run( async () => //simply add this for async run
{
var entity = new SomeEntity(someDTO);
return _repository.Create(entity);
});
var result = await someTask;
}
OK so this is my first attempt at using a queue really and I'm confident there are some obvious issues here.
This code however has been working locally for me but am having issues having deployed to my test azure environment. At best it runs once but often not at all. The code is being hooked up using:
services.AddHostedService<ServiceBusListener>();
and then this is my main code:
public class ServiceBusListener : BackgroundService
{
private readonly QueueSettings _scoreUpdatedQueueSettings;
private readonly IEventMessageHandler _eventMessageHandler;
private QueueClient _queueClient;
public ServiceBusListener(IOptions<QueueSettings> scoreUpdatedQueueSettings,
IEventMessageHandler eventMessageHandler)
{
_eventMessageHandler = eventMessageHandler;
_scoreUpdatedQueueSettings = scoreUpdatedQueueSettings.Value;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
_queueClient = new QueueClient(_scoreUpdatedQueueSettings.ServiceBusConnectionString,
_scoreUpdatedQueueSettings.QueueName);
var messageHandlerOptions = new MessageHandlerOptions(_eventMessageHandler.ExceptionReceivedHandler)
{
MaxConcurrentCalls = 1,
AutoComplete = false
};
_queueClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
return Task.CompletedTask;
}
public override Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public async Task ProcessMessagesAsync(Message message, CancellationToken token)
{
await _eventMessageHandler.ProcessMessagesAsync(message, token);
await _queueClient.CompleteAsync(message.SystemProperties.LockToken);
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
}
return Task.CompletedTask;
}
}
As mentioned locally this works fine, every time a message hits the queue my relevant functions run.
It feels as though some code should be living in ExecuteAsync but I don't want to create a handler every x do I?
For context this is running on a wep app in azure, I've done it like that as we have an api that can be hit to manage some of the related data.
There seems to be little around this on the net so any help would be appreciated.
TIA