Botframework V4:Cosmos DB配置

问题描述 投票:0回答:2

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

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

    }
c# botframework azure-cosmosdb
2个回答
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期间出现的异常的能力(例如网络分区,数据并发等) )。


1
投票

这不适合你的原因很可能是因为这两个链接都提到你需要在Azure的CosmosDB资源中创建一个新的集合。 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和conversationState存储在单独的集合中。我认为这有效......但“常规”方法只是创建一个CosmosDbStorage实例。机器人将userState和conversationState存储在集合中的单独文档中。请注意,除了上面的代码之外,你可能还需要像var userState = new UserState(_myStorage)这样的东西,因为你的代码也使用了userState而上面的代码却没有。

另外,根据Drew的回答,我认为来自that tutorial you linked的代码可能会导致一些问题,因为它已经过时了。最好的办法是找到一个relevant sample from the GitHub Repo并用它作为指导。 Basic Bot是一个很好的具有conversationState和userState功能的。

© www.soinside.com 2019 - 2024. All rights reserved.