Manmax75
Manmax75

Reputation: 47

Multi-tenant bot data store

I've been implementing the following for a multi-tenant azure bot in C# and this works great for authenticating the app credentials.

C# : Creating a Single Bot Service to Support Multiple Bot Applications

https://www.microsoft.com/developerblog/2017/01/10/creating-a-single-bot-service-to-support-multiple-bot-applications/

However, I am wondering if the same is possible for the bot data store as well? I currently have the standard Azure Table storage bot data store implemented like so which uses a single connection string defined in the web.config:

Conversation.UpdateContainer(
            builder =>
            {
                builder.RegisterModule(new ReflectionSurrogateModule());

                builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));

                //var store = new InMemoryDataStore();

                var store = new TableBotDataStore(ConfigurationManager.AppSettings["BotStorage"]);

                builder.Register(c => store)
                    .Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
                    .AsSelf()
                    .SingleInstance();
            });

In my ICredentialProvider implementation (as outlined in the articles examples). If I store the customers connection string along side their App ID and Password, is it possible to pull that reference in the dependency injection above based on the customer/bot serialising the data store and then save/read that data to the customers Azure table storage?

Upvotes: 1

Views: 283

Answers (1)

kjr1995
kjr1995

Reputation: 420

You would need to create a custom bot data store that implements IBotDataStore<BotData> and use that. I was able to get a simple one that created the same TableBotDataStore each time. Below is what I used and a break point in the constructor gets hit each time I send a message.

public class CustomBotStore : IBotDataStore<BotData>
{
    private IBotDataStore<BotData> _store;

    public CustomBotStore()
    {
        string connString = ConfigurationManager.ConnectionStrings["BotState"].ConnectionString;

        _store = new TableBotDataStore(connString, "testbotdata"); // requires Microsoft.BotBuilder.Azure Nuget package 
    }

    public Task<bool> FlushAsync(IAddress key, CancellationToken cancellationToken)
    {
        return _store.FlushAsync(key, cancellationToken);
    }

    public Task<BotData> LoadAsync(IAddress key, BotStoreType botStoreType, CancellationToken cancellationToken)
    {
        return _store.LoadAsync(key, botStoreType, cancellationToken);
    }

    public Task SaveAsync(IAddress key, BotStoreType botStoreType, BotData data, CancellationToken cancellationToken)
    {
        return _store.SaveAsync(key, botStoreType, data, cancellationToken);
    }
}


public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);

        Conversation.UpdateContainer(
        builder =>
        {
            builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));

            builder.Register(c => new CustomBotStore())
                .Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
                .AsSelf()
                .InstancePerLifetimeScope();
        });
    }
}

I'm not sure what the implications of having an instance per lifetime scope are because the sample uses a single instance. But you should be able to use this and then inspect the HttpContext in the constructor and use the correct connection string.

Upvotes: 1

Related Questions