Botframework V4:Cosmos DB配置

时间:2019-02-15 04:44:58

标签: c# botframework azure-cosmosdb

您好,我很难将cosmos db配置为botframework。在使用内存存储之前,它工作正常。我正在阅读thisthis作为指南。我在代码中的注释中包含了错误。谁能帮我这个。我将非常感谢您的帮助。我已经研究了三天。谢谢!

 public class Startup
{
    private const string CosmosServiceEndpoint = "xxxxxxxxxxx";
    private const string CosmosDBKey = "xxxxxxxxxxx";
    private const string CosmosDBDatabaseName = "xxxxxxxxxxx";
    private const string CosmosDBCollectionNameConState = "conversationState";
    private const string CosmosDBCollectionNameUserState = "userState";

    private ILoggerFactory _loggerFactory;
    private bool _isProduction = false;

    public Startup(IHostingEnvironment env)
    {
        _isProduction = env.IsProduction();

        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();

        Configuration = builder.Build();
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddBot<BasicBot>(options =>
        {
            ILogger logger = _loggerFactory.CreateLogger<BasicBot>();

            var secretKey = Configuration.GetSection("botFileSecret")?.Value;
            var botFilePath = Configuration.GetSection("botFilePath")?.Value;
            if (!File.Exists(botFilePath))
            {
                throw new FileNotFoundException($"The .bot configuration file was not found. botFilePath: {botFilePath}");
            }

            BotConfiguration botConfig = null;
            try
            {
                botConfig = BotConfiguration.Load(botFilePath ?? @".\echo-with-counter.bot", secretKey);
            }
            catch
            {
                var msg = @"Error reading bot file. Please ensure you have valid botFilePath and botFileSecret set for your environment.
                - You can find the botFilePath and botFileSecret in the Azure App Service application settings.
                - If you are running this bot locally, consider adding a appsettings.json file with botFilePath and botFileSecret.
                - See https://aka.ms/about-bot-file to learn more about .bot file its use and bot configuration.
                ";
                logger.LogError(msg);
                throw new InvalidOperationException(msg);
            }

            services.AddSingleton(sp => botConfig);

            var environment = _isProduction ? "production" : "development";
            var service = botConfig.Services.FirstOrDefault(s => s.Type == "endpoint" && s.Name == environment);
            if (service == null && _isProduction)
            {
                service = botConfig.Services.Where(s => s.Type == "endpoint" && s.Name == "development").FirstOrDefault();
                logger.LogWarning("Attempting to load development endpoint in production environment.");
            }

            if (!(service is EndpointService endpointService))
            {
                throw new InvalidOperationException($"The .bot file does not contain an endpoint with name '{environment}'.");
            }

            options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);

            options.OnTurnError = async (context, exception) =>
            {
                logger.LogError($"Exception caught : {exception}");
                await context.SendActivityAsync("Sorry, it looks like something went wrong.");
            };

            // The Memory Storage used here is for local bot debugging only. When the bot
            // is restarted, everything stored in memory will be gone.
            // IStorage dataStore = new MemoryStorage();

           // error : COSMOSDBSTORAGE DOES NOT CONTAIN CONSTRUCTOR TAKES 4 ARGUMENTS
           //IStorage dataStoreConversationState =
            // new CosmosDbStorage(
            //     uri,
            //     "** auth key **",
            //     "helloworldbot",
            //     "conversationstate");

            var uri = new Uri(CosmosServiceEndpoint);

            IStorage dataStoreConversationState =
            new CosmosDbStorage(new CosmosDbStorageOptions
            {
                AuthKey = CosmosDBKey,
                CollectionId = CosmosDBCollectionNameConState,
                CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
                DatabaseId = CosmosDBDatabaseName,
            });

            IStorage dataStoreUserState =
            new CosmosDbStorage(new CosmosDbStorageOptions
            {
                AuthKey = CosmosDBKey,
                CollectionId = CosmosDBCollectionNameUserState,
                CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
                DatabaseId = CosmosDBDatabaseName,
            });

           //error : THE NON GENERIC TYPE "CONVERSATIONsTATE" CANNOT BE USED WITH TYPED ARGUMENTS
            options.Middleware.Add(new ConversationState<BasicState>(dataStoreConversationState));
            options.Middleware.Add(new UserState<BasicUserState>(dataStoreUserState));

    }

2 个答案:

答案 0 :(得分:1)

很有可能这对您不起作用,是因为这两个链接都提到您需要在Azure的CosmosDB资源中创建一个New Collection。 Microsoft最近更新了CosmosDB资源,以要求使用分区键创建新集合,而Bot Framework尚不支持。当前有一个Design Change Request可以添加此功能,但C#Cosmos SDK却将其拖延了。

同时,首先在Azure中创建Cosmos资源,并且不要创建数据库或集合。仅使Cosmos资源。如果您指定的机器人框架SDK不存在,则它会建立一个新的数据库和集合,并且可以创建一个没有分区的数据库和集合...因此,让机器人在这里进行工作。

我使用the second link you posted更改了Simple Prompt bot sample以与Cosmos一起使用。注意:endpointkeythe CosmosDB Emulator的默认值,您可以根据需要在本地进行测试。

这是我的startup.cs:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Azure;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Integration;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Configuration;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.BotBuilderSamples
{
    /// <summary>
    /// The Startup class configures services and the app's request pipeline.
    /// </summary>
    public class Startup
    {
        private const string CosmosServiceEndpoint = "https://localhost:8081";
        private const string CosmosDBKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
        private const string CosmosDBDatabaseName = "bot-cosmos-sql-db";
        private const string CosmosDBCollectionName = "bot-storage";

        private static readonly CosmosDbStorage _myStorage = new CosmosDbStorage(new CosmosDbStorageOptions
        {
            AuthKey = CosmosDBKey,
            CollectionId = CosmosDBCollectionName,
            CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
            DatabaseId = CosmosDBDatabaseName,
        });

        private ILoggerFactory _loggerFactory;
        private bool _isProduction = false;

        public Startup(IHostingEnvironment env)
        {
            _isProduction = env.IsProduction();

            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();

            Configuration = builder.Build();
        }

        /// <summary>
        /// Gets the configuration that represents a set of key/value application configuration properties.
        /// </summary>
        /// <value>
        /// The <see cref="IConfiguration"/> that represents a set of key/value application configuration properties.
        /// </value>
        public IConfiguration Configuration { get; }

        /// <summary>
        /// This method gets called by the runtime. Use this method to add services to the container.
        /// </summary>
        /// <param name="services">The <see cref="IServiceCollection"/> specifies the contract for a collection of service descriptors.</param>
        /// <seealso cref="IStatePropertyAccessor{T}"/>
        /// <seealso cref="https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection"/>
        /// <seealso cref="https://docs.microsoft.com/en-us/azure/bot-service/bot-service-manage-channels?view=azure-bot-service-4.0"/>
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddBot<SimplePromptBot>(options =>
            {
                var secretKey = Configuration.GetSection("botFileSecret")?.Value;
                var botFilePath = Configuration.GetSection("botFilePath")?.Value;
                if (!File.Exists(botFilePath))
                {
                    throw new FileNotFoundException($"The .bot configuration file was not found. botFilePath: {botFilePath}");
                }

                // Loads .bot configuration file and adds a singleton that your Bot can access through dependency injection.
                var botConfig = BotConfiguration.Load(botFilePath ?? @".\simple-prompt.bot", secretKey);
                services.AddSingleton(sp => botConfig ?? throw new InvalidOperationException($"The .bot configuration file could not be loaded. botFilePath: {botFilePath}"));

                // Retrieve current endpoint.
                var environment = _isProduction ? "production" : "development";
                var service = botConfig.Services.FirstOrDefault(s => s.Type == "endpoint" && s.Name == environment);
                if (!(service is EndpointService endpointService))
                {
                    throw new InvalidOperationException($"The .bot file does not contain an endpoint with name '{environment}'.");
                }

                options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);

                // Creates a logger for the application to use.
                ILogger logger = _loggerFactory.CreateLogger<SimplePromptBot>();

                // Catches any errors that occur during a conversation turn and logs them.
                options.OnTurnError = async (context, exception) =>
                {
                    logger.LogError($"Exception caught : {exception}");
                    await context.SendActivityAsync("Sorry, it looks like something went wrong.");
                };

                // Memory Storage is for local bot debugging only. When the bot
                // is restarted, everything stored in memory will be gone.
                //IStorage dataStore = new MemoryStorage();

                // For production bots use the Azure Blob or
                // Azure CosmosDB storage providers. For the Azure
                // based storage providers, add the Microsoft.Bot.Builder.Azure
                // Nuget package to your solution. That package is found at:
                // https://www.nuget.org/packages/Microsoft.Bot.Builder.Azure/
                // Uncomment the following lines to use Azure Blob Storage
                // //Storage configuration name or ID from the .bot file.
                // const string StorageConfigurationId = "<STORAGE-NAME-OR-ID-FROM-BOT-FILE>";
                // var blobConfig = botConfig.FindServiceByNameOrId(StorageConfigurationId);
                // if (!(blobConfig is BlobStorageService blobStorageConfig))
                // {
                //    throw new InvalidOperationException($"The .bot file does not contain an blob storage with name '{StorageConfigurationId}'.");
                // }
                // // Default container name.
                // const string DefaultBotContainer = "<DEFAULT-CONTAINER>";
                // var storageContainer = string.IsNullOrWhiteSpace(blobStorageConfig.Container) ? DefaultBotContainer : blobStorageConfig.Container;
                // IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureBlobStorage(blobStorageConfig.ConnectionString, storageContainer);

                // Create Conversation State object.
                // The Conversation State object is where we persist anything at the conversation-scope.
                var conversationState = new ConversationState(_myStorage);

                options.State.Add(conversationState);
            });

            services.AddSingleton(sp =>
            {
                // We need to grab the conversationState we added on the options in the previous step.
                var options = sp.GetRequiredService<IOptions<BotFrameworkOptions>>().Value;
                if (options == null)
                {
                    throw new InvalidOperationException("BotFrameworkOptions must be configured prior to setting up the State Accessors");
                }

                var conversationState = options.State.OfType<ConversationState>().FirstOrDefault();
                if (conversationState == null)
                {
                    throw new InvalidOperationException("ConversationState must be defined and added before adding conversation-scoped state accessors.");
                }

                // The dialogs will need a state store accessor. Creating it here once (on-demand) allows the dependency injection
                // to hand it to our IBot class that is create per-request.
                var accessors = new SimplePromptBotAccessors(conversationState)
                {
                    ConversationDialogState = conversationState.CreateProperty<DialogState>("DialogState"),
                };

                return accessors;
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            _loggerFactory = loggerFactory;

            app.UseDefaultFiles()
                .UseStaticFiles()
                .UseBotFramework();
        }
    }
}

Here's a diff,因此您可以轻松查看代码差异。

以下是它的屏幕截图:

enter image description here

看起来您的代码还将userState和sessionState存储在单独的集合中。我认为这可行...但是“常规”方法是仅创建CosmosDbStorage的一个实例。机器人会将用户状态和对话状态存储在集合中的单独文档中。请注意,除了上面的代码之外,您可能还需要var userState = new UserState(_myStorage)之类的东西,因为您的代码还使用了userState,而上面的代码并未使用。

此外,与Drew的回答一致,我认为that tutorial you linked中的代码可能会引起一些问题,仅是因为它已过时。最好的办法是找到一个relevant sample from the GitHub Repo并将其用作指导。 Basic Bot是一个很好的对话和用户状态功能。

答案 1 :(得分:0)

您的代码使您看起来好像是在一次使用预发布位编写某些代码,或者是从某人那里复制了代码。 ConversationState本身不再是中间件,也不再是通用类。

因此,您不再为要维护的每个状态创建一个ConversationState<T>。相反,您将创建一个ConversationState,如果可以的话,它可以作为一种范围界定的“存储桶”,然后使用CreateProperty<T> API在该“存储桶”中创建许多属性。

赞:

var conversationState = new ConversationState(myStorage);

var myBasicStateProperty conversationState.CreateProperty<BasicState>("MyBasicState");

现在,正如我也说过的那样,它不再是中间件。相反,从CreateProperty<T>返回的是一个IStatePropertyAccessor<T>,您可以将其传递给需要使用它的任何对象(例如您的机器人)。同样,您也可以将ConversationState本身传递给机器人,以便它最终可以在回合结束时在机器人上调用SaveChangesAsync。另外,您可以配置AutoSaveStateMiddleware,这样可以在每次回合结束时为您保存状态,但是您无法控制处理在调用SaveChangesAsync期间出现的异常的能力(例如网络分区,数据并发等。