2

I'm doing a bot with de Microsoft Bot Framework version V4. The documentation is really awful, and I'm having problems with Cosmos DB (Azure) when I try to store de UserSate and the ConversationState.

I tried every result in Google but nothing has worked yet. Plus, there no much information about the framework really.

Bellow is the code of the file Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    services.AddBot<SeguritoBot>(options =>
   {
       var secretKey = Configuration.GetSection("botFileSecret")?.Value;
       var botFilePath = Configuration.GetSection("botFilePath")?.Value;

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

    // 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<SeguritoBot>();

    // 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.");
   };

   var optionsConversation = new CosmosDbStorageOptions()
   {
       CosmosDBEndpoint = new Uri("--secret--"),
       AuthKey = "--secret--",
       DatabaseId = "--secret--",
       CollectionId = "--secret--"
   };

   var optionsUser = new CosmosDbStorageOptions()
   {
       CosmosDBEndpoint = new Uri("--secret--"),
       AuthKey = "--secret--",
       DatabaseId = "--secret--",
       CollectionId = "--secret--"
   };

   IStorage dataStoreConversationState = new CosmosDbStorage(optionsConversation);
   IStorage dataStoreUserState = new CosmosDbStorage(optionsUser);

   options.Middleware.Add(new ConversationState<ConversationState>(dataStoreConversationState));
   options.Middleware.Add(new UserState<UserState>(dataStoreUserState));
   });
}

The last to lines are giving the error:

The non-generic type 'ConversationState' cannot be used with type arguments
The non-generic type 'ConversationState' cannot be used with type arguments 
Drew Marsh
  • 33,111
  • 3
  • 82
  • 100
  • @TomasPicierri Did the answer below help you? Do you need any more info? If it helped please make sure to upvote and/or mark it as the answer to help other people in the future. – Drew Marsh Jan 14 '19 at 23:01
  • 1
    Hi @DrewMarsh, my code is a mess right now, so I can't test yours right now. But the links did helped me to understand more the framework. Thanks! – Tomas Pichierri Jan 15 '19 at 18:26

1 Answers1

2

Ok, I'm not sure where you got this code from, but it looks like it's from a pre-release build. ConversationState and UserState are no longer middleware and no longer generic (e.g. don't have type arguments).

This is what a Startup::ConfigureServices should look like when using CosmosDB for state storage on a 4.x release build:

public class Startup
{
     public void ConfigureServices(IServiceCollection services)
     {
        // Only need a single storage instance unless you really are storing your conversation state and user state in two completely DB instances
        var storage = new CosmosDbStorage(new CosmosDbStorageOptions
        {
            // … set options here …
        });

        var conversationState = new ConversationState(storage);
        var userState = new UserState(storage);

        // Add the states as singletons
        services.AddSingleton(conversationState);
        services.AddSingleton(userState);

        // Create state properties accessors and register them as singletons
        services.AddSingleton(conversationState.CreateProperty<YourBotConversationState>("MyBotConversationState"));
        services.AddSingleton(userState.CreateProperty<YourBotUserState>("MyBotUserState"));

        services.AddBot<SeguritoBot>(options =>
        {
           // … set options here …
        });
     }
}

Now, in your bot, if you want to access those properties, you take them as dependencies via the constructor:

public class SeguritoBot : IBot
{
    private readonly ConversationState _conversationState;
    private readonly UserState _userState;
    private readonly IStatePropertyAccessor<YourBotConversationState> _conversationStatePropertyAccessor;
    private readonly IStatePropertyAccessor<YourBotUserState> _userStatePropertyAccessor;

    public SeguritoBot(
        ConversationState conversationState, 
        UserState userState,
        IStatePropertyAccessor<YourBotConversationState> conversationStatePropertyAccessor,
        IStatePropertyAccessor<YourBotUserState> userStatePropertyAccesssor)
    {
        _conversationState = conversationState;
        _userState = userState;
        _conversationStatePropertyAcessor = conversationStatePropertyAcessor;
        _userStatePropertyAcessor = userStatePropertyAcessor;
    }

    public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        var currentConversationState = await _conversationStatePropertyAccessor.GetAsync(
            turnContext,
            () => new YourBotConversationState(), 
            cancellationToken);

        // Access properties for this conversation
        // currentConversationState.SomeProperty

        // Update your conversation state property
        await _conversationStatePropertyAccessor.SetAsync(turnContext, currentConversationState, cancellationToken);

        // Commit any/all changes to conversation state properties
        await _conversationState.SaveChangesAsync(turnContext, cancellationToken);
    }
}

Obviously you can do the same with the user state property and you can support multiple properties per state scope with more calls to CreateProperty and injecting those IStatePropertyAccessor<T> as well, etc.

Drew Marsh
  • 33,111
  • 3
  • 82
  • 100
  • Hi Drew! I'm really lost with these because the Microsoft's documentation is really bad and it's changing all the time. Your code helped me a lot to understand some concepts. Sadly other things broke when I made the changes. Do you have some link with info about the concepts of ConversationSate and UserSate so I can read upon? – Tomas Pichierri Jan 10 '19 at 12:53
  • 1
    You can look at these two articles for some help on concepts, etc.. https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-concept-state?view=azure-bot-service-4.0 https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-state?view=azure-bot-service-4.0&tabs=csharp – Dana V Jan 10 '19 at 16:32
  • Let us know what broke and how. – Dana V Jan 10 '19 at 16:34
  • Yes, @DanaV's links are the ones you want to check out and if you are still having a problem please post another question and we'll do our best to help you out. And, if you have a second and you this answer useful, please give it an upvote and/or mark as the answer so it can help other people in the future! – Drew Marsh Jan 10 '19 at 16:48