My bot is based on Azure Functions. I need to access state data from a Function which is triggered by a queue, in order to push a pro-active message to the user. So I implemented code from https://stackoverflow.com/questions/46085614/, but when I tried to access the implementing method, I received a System.MissingMethodException: Method not found: 'Autofac.IContainer Microsoft.Bot.Builder.Dialogs.Conversation.get_Container()'. exception.
(again, this is an Azure Function)
[FunctionName("PasswordResponseFunction")]
public static async Task Run([ServiceBusTrigger("password-response", AccessRights.Listen, Connection = "ServiceBusReceivePasswordResponseConnection")]BrokeredMessage message, TraceWriter log)
{
try
{
Stream body = message.GetBody<Stream>();
BinaryFormatter formatter = new BinaryFormatter();
String password = formatter.Deserialize(body) as String;
log.Info($"C# ServiceBus queue trigger function processed message: { password }");
IMessageActivity messageActivity = Activity.CreateMessageActivity();
// Set some messageActivity properties here
await SetPrivateConversationDataAsync(messageActivity, "password", password); // <-Exception thrown at this point
ConnectorClient client = new ConnectorClient(new Uri(ActivityDataEntity.ServiceUrl));
Activity activity = (Activity)messageActivity;
await client.Conversations.SendToConversationAsync(activity);
}
catch (Exception ex)
{
log.Error(ex.Message, ex);
}
}
private static async Task SetPrivateConversationDataAsync<T>(IMessageActivity messageActivity, String key, T value)
{
using (ILifetimeScope scope = DialogModule.BeginLifetimeScope(Conversation.Container, messageActivity)) //System.MissingMethodException
{
IBotDataStore<BotData> botDataStore = scope.Resolve<IBotDataStore<BotData>>();
Address address = Address.FromActivity(messageActivity);
BotData privateConversationData = await botDataStore.LoadAsync(address, BotStoreType.BotPrivateConversationData, CancellationToken.None);
privateConversationData.SetProperty(key, value);
await botDataStore.SaveAsync(address, BotStoreType.BotPrivateConversationData, privateConversationData,
CancellationToken.None);
await botDataStore.FlushAsync(address, CancellationToken.None);
}
}
Conversation.Container, such as IContainer c = Conversation.Container; to the functionGet an IContainer
Exception as described.
Varonis.Bot.Application.PasswordResponseFunction.
System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(TStateMachine& stateMachine)
Varonis.Bot.Application.PasswordResponseFunction.SetPrivateConversationDataAsync(IMessageActivity messageActivity, String key, T value)
Varonis.Bot.Application.PasswordResponseFunction.
I Actually opened the Microsoft.Bot.Builder.dll with a decompiler (JetBrains dotPeek), and couldn't find the Conversation class there. But when I pressed F12 on the property in the code, the VS decompiler did take me to the property.
Have you tried initializing the AutoFac container using using Microsoft.Bot.Builder.Azure ?
using (BotService.Initialize())
{
Stream body = message.GetBody<Stream>();
BinaryFormatter formatter = new BinaryFormatter();
String password = formatter.Deserialize(body) as String;
log.Info($"C# ServiceBus queue trigger function processed message: { password }");
IMessageActivity messageActivity = Activity.CreateMessageActivity();
// Set some messageActivity properties here
await SetPrivateConversationDataAsync(messageActivity, "password", password); // <-Exception thrown at this point
ConnectorClient client = new ConnectorClient(new Uri(ActivityDataEntity.ServiceUrl));
Activity activity = (Activity)messageActivity;
await client.Conversations.SendToConversationAsync(activity);
}
@EricDahlvang no, but how would that cause a MissingMethodException? Wouldn't that be caused by a missing implementation of the Conversation.Container property?
Also, I think I tried the IContainer c = Conversation.Container; line in the bot function itself, IIRC inside the using (BotService.Initialize()) { .. } block (I don't have access to the code now), and it failed too.
I'll have to check again on Sunday.
BotService.Initialize sets up the autofac container. The code is here: https://github.com/Microsoft/BotBuilder-Azure/blob/master/CSharp/Library/Microsoft.Bot.Builder.Azure/BotService.cs#L63
That didn't help, @EricDahlvang . Still getting the same exception.
I'm unsure why you are seeing MissingMethodException. What are the usings in your function?
Have you tried using the Proactive Bot template? https://docs.microsoft.com/en-us/azure/bot-service/bot-service-concept-templates#proactive-bot It sounds like it covers what you are trying to accomplish. (triggering a function from a queue)
Looking over the code you've shared again, I notice you are creating a brand new message and sending that to your function:
IMessageActivity messageActivity = Activity.CreateMessageActivity();
await SetPrivateConversationDataAsync(messageActivity, "password", password);
However, this will not have the correct userid, channelid, conversationid, etc. to initialize the conversation data.
my using list:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using AzureFunctions.Autofac;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Connector;
using Microsoft.ServiceBus.Messaging;
using Varonis.Bot.Models;
using Varonis.Bot.Services.StorageService;
I didn't use this template. I used the simpleSendMessage example, which I found first, and frankly, looks more reasonable than opening a new connection to the bot with DirectLine and a series of JSON serializations/deserializations.
I'm setting the right conversation values at the // Set some messageActivity properties here line:
ActivityDataEntity activityData = //get data from storage. at the time I opened this issue, it was from a (temporary) static class.
IMessageActivity messageActivity = Activity.CreateMessageActivity();
messageActivity.Conversation = new ConversationAccount(id: activityData.ConversationId);
messageActivity.ChannelId = activityData.ChannelId;
messageActivity.From = new ChannelAccount(activityData.FromId, activityData.FromName);
messageActivity.Recipient = new ChannelAccount(activityData.ToId, activityData.ToName);
messageActivity.Text = $"The WiFi password for your guest is: {password}. ";
messageActivity.Type = ActivityTypes.Message;
This reaches the client (bot emulator), but not the bot function. But I'm not very bothered by that, as long as the user receives the answer.
It may help if I paste my root packages list:
"AzureFunctions.Autofac" Version="2.0.0"
"JetBrains.Annotations" Version="11.1.0"
"Microsoft.Azure.ServiceBus" Version="2.0.0"
"Microsoft.Azure.WebJobs.ServiceBus" Version="2.1.0"
"Microsoft.Bot.Builder" Version="3.12.2.4"
"Microsoft.Bot.Builder.Azure" Version="3.2.5"
"Microsoft.NET.Sdk.Functions" Version="1.0.11"
"Stateless" Version="4.0.0"
"System.ValueTuple" Version="4.4.0"
It looks like the using list is missing Microsoft.Bot.Builder.Azure
@tsahi Have you tried adding the using for Microsoft.Bot.Builder.Azure ?
I was working on another part of the bot so I didn't have time for this yet. I also changed the flow of my conversation so I no longer need the above code. But for the sake of the experiment, I tried it now, with no change. As expected, the using line is grayed out, meaning no type from this namespace is used in the class and the line can be removed (otherwise it wouldn't have compiled).
Accessing state data is useful in many ways. Another way I incidentally needed just now, is to reset the conversation if the user abandons the chat. I used this technique, which is similar, and fails the same way. In this case, the code is at the main bot function, and the Microsoft.Bot.Builder.Azure namespace is needed for the BotService class. The code is generally what you get when you open a new functions bot in the portal, with some modifications.
Ah, when you create a functions bot, you get a C# Script bot. I then created a new HTTP Trigger function project in Visual Studio, and copied the code over from the bot template.
Well, functions bots are a strange animal. The Bot Builder sdk was not initially designed for functions, but was plugged in afterwards. I would recommend a web app bot instead. If you have some use for a function, then call the function from the web app bot code. Building an entire bot using azure functions has many limitations and difficulties, as you've seen.
Our considerations for choosing a functions bot were a) hopefully it will cost less, since (in Consumption plan) you only pay for actual CPU time. This is an internal organization bot, with a few dozens requests per day. And b) it's a chance to learn new technology, which is always fun :)
Could it be because I'm using AzureFunctions.Autofac? This little package facilitates dependency injection in Functions, and basically has it's own Autofac container it uses to register and resolve dependencies. Could the two containers interfere with each other?
Can I use the container BotBuilder already has to register my own types, and resolve them? I don't have much experience with Autofac, I've been using Simple Injector so far.
I've never used that library, and I did not even know it existed until now. BotBuilder v3 uses autofac extensively. I do not know if AzureFunctions.Autofac would interfere with Autofac in the Bot Service.
I was not using AzureFunctions.Autofac, but repro'd the exact same issue. Pretty weird. Played with versions of the Nuget packages and all.
I'm just gonna roll back some changes, and switch over to the Web App version. I was actually working on transitioning a pre-prod bot over to Functions, for the scalability. But this'll have to do for our purposes. Considering there's an Azure template for building a Function-based bot, you'd like to think it...worked. But so it goes.
Glad I found this thread, though! I've been banging my head for an hour.
How is this closed? was a fix checked in?
I found a workaround for this. This bug is a manifestation of a bug in the function host, which is tied to Autofac 4.2.1. see Azure/azure-functions-host#2979 and Azure/azure-functions-host#1665. So to work around, I downgraded Autofac in my function to 4.2.1, and the problem disappeared.