botframework-sdk: Deserialization doesn't work when invoked from Azure Function

Hi,

I setup a bot on Azure Bot Service, downloaded the template, made it work with VS2015 and I’ve moved all code to separate class library to run it as precompiled function.

You can find whole code here: https://github.com/tomaszkiewicz/AzureFunctionsPersistenceProblem/tree/test

So, my function.json file looks like this:

{
  "scriptFile": "bin\\Bot.dll",
  "entryPoint": "Bot.Runner.Run",
  "bindings": [
    {
      "type": "httpTrigger",
      "direction": "in",
      "webHookType": "genericJson",
      "name": "req"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "disabled": false
}

The Bot.Runner.Run function is the same as it was from the template and I do the following on new message:

await Conversation.SendAsync(activity, () => new BasicProactiveEchoDialog());

So it’s typical calling of dialogs stack. At this step I am able to chat with the bot (tested both on emulator and on Azure Bot Service) so everything is loaded correctly etc.

The problem is that this dialog is not deserialized properly when it’s in the separate class library. It worked when it was a .csx file in Azure Function, it also works when I move it to BotApplication template project (the one based on ASP.NET MVC), but as soon as it gets into separate class library everything breaks.

I’ve tried to gather more details, so I’ve added both parameterless constructor and a method with OnDeserialize attribute, put a breakpoint in each of them and the behaviour is that OnDeserialize method gets invoked, when I lookup in the count variable in this dialog it is correctly deserialized but… after I resume the execution the parameterless constructor gets called.

I’ve found in documentation or stackoverflow thread (I don’t remember excactly where) that the behaviour of bot framework is that if it is not possible to sucessfully deserialize the dialog stack it gets resetted. So it’s probably that case here, but I cannot find a reason of unsucessful deserialization…

Could you take a look at it? Or maybe suggest what to check to provide more details?

Best regards

Łukasz Tomaszkiewicz

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 16 (2 by maintainers)

Most upvoted comments

Thank you @NicolasHumann for your pull request, it pointed me in the right direction. I extended it a little bit to work also for assemblies that are not loaded to the AppDomain yet.

Technically you are not required to use BotBuilder-Azure but it has some features that you would have to implement by yourself. Like the authentication stuff. And I hope it will fix the assembly loading problem in the future.

Until it gets fixed I created my own assembly resolve handler:

public class AzureFunctionsResolveAssembly : IDisposable
{
    public AzureFunctionsResolveAssembly()
    {
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }

    void IDisposable.Dispose()
    {
        AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
    }

    private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs arguments)
    {
        var assembly = AppDomain.CurrentDomain.GetAssemblies()
            .FirstOrDefault(a => a.GetName().FullName == arguments.Name);

        if (assembly != null)
        {
            return assembly;
        }

        // try to load assembly from file
        var assemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        var assemblyName = new AssemblyName(arguments.Name);
        var assemblyFileName = assemblyName.Name + ".dll";
        string assemblyPath;

        if (assemblyName.Name.EndsWith(".resources"))
        {
            var resourceDirectory = Path.Combine(assemblyDirectory, assemblyName.CultureName);
            assemblyPath = Path.Combine(resourceDirectory, assemblyFileName);
        }
        else
        {
            assemblyPath = Path.Combine(assemblyDirectory, assemblyFileName);
        }

        if (File.Exists(assemblyPath))
        {
            return Assembly.LoadFrom(assemblyPath);
        }

        return null;
    }
}

And this is how I use it in my Azure Functions Bot:

[FunctionName("Messages")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "messages")]HttpRequestMessage req, TraceWriter log)
{
    // use custom assembly resolve handler
    using(new AzureFunctionsResolveAssembly())
    using (BotService.Initialize())
    {
        // Deserialize the incoming activity
        string jsonContent = await req.Content.ReadAsStringAsync();
        var activity = JsonConvert.DeserializeObject<Activity>(jsonContent);

        // authenticate incoming request and add activity.ServiceUrl to MicrosoftAppCredentials.TrustedHostNames
        // if request is authenticated
        if (!await BotService.Authenticator.TryAuthenticateAsync(req, new[] { activity }, CancellationToken.None))
        {
            return BotAuthenticator.GenerateUnauthorizedResponse(req);
        }

        if (activity != null)
        {
            switch (activity.GetActivityType())
            {
                case ActivityTypes.Message:
                    await Conversation.SendAsync(activity, () => new RootDialog());
                    break;
                [...]
            }
        }
        return req.CreateResponse(HttpStatusCode.Accepted);
    }
}

Hi, @AdamMarczak To use the new VS 17 15.3 tooling for Azure function, you have to use https://github.com/Microsoft/BotBuilder-Azure but I found the bug and submit a pull request https://github.com/Microsoft/BotBuilder-Azure/pull/15 to correct the serialization issue

I was having the same problem using binary serialization through a storage queue. When attempting to deserialize the message with the BinaryFormatter the assembly containing the class would get the “could not find assembly exception”. Even though an instance of the class had already been created previously. The fusion loader recorded no bind failures so I was stuck until I found this post. I used the assembly resolver code posted by berhir and now the serialization is working. Thanks!