Botframework-sdk: [.Net SDK, Azure Function] MissingMethodException thrown when accessing Microsoft.Bot.Builder.Dialogs.Conversation.Container

Created on 26 Apr 2018  路  19Comments  路  Source: microsoft/botframework-sdk

Bot Info

  • SDK Platform: .NET
  • SDK Version: 3.15 (happens on 3.12.2.4 too)
  • Active Channels: bot emulator, WebChat, Direct Line
  • Deployment Environment: local development with Emulator

Issue Description

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.

Code Example

(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);
            }
        }

Reproduction Steps

  1. Create a Class Library C# Function Bot

    1. Create a C# Function Bot

    2. Create an Azure Function with HTTP Trigger project in Visual Studio 2017

    3. Copy the code from the generated Function Bot to your Function

    4. Delete the C# Script bot from the Azure Portal

    5. Deploy the function you created

  2. Add a call to Conversation.Container, such as IContainer c = Conversation.Container; to the function
  3. make a call to the bot from Bot Emulator

Expected Behavior

Get an IContainer

Actual Results

Exception as described.

Stack Trace:

Varonis.Bot.Application.PasswordResponseFunction.d__1`1.MoveNext()
System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(TStateMachine& stateMachine)
Varonis.Bot.Application.PasswordResponseFunction.SetPrivateConversationDataAsync(IMessageActivity messageActivity, String key, T value)
Varonis.Bot.Application.PasswordResponseFunction.d__0.MoveNext() in PasswordResponseFunction.cs: line: 48

Additional Info

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.

All 19 comments

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.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

daveta picture daveta  路  3Comments

clearab picture clearab  路  3Comments

Arimov picture Arimov  路  3Comments

stijnherreman picture stijnherreman  路  3Comments

akakoychenko picture akakoychenko  路  3Comments