Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bot state so that the context service key is used consistently to access the cached state #3727

Merged
merged 8 commits into from
Apr 24, 2020

Conversation

v-kydela
Copy link
Contributor

@v-kydela v-kydela commented Apr 11, 2020

Fixes #3643

A bot state class's "context service key" is used to access the cached bot state in the turn state collection. Because the context service key is usually the name of the type (e.g. "UserState") there were some cases where the SDK was just using the type name instead of the actual context service key. This fails when a bot state class has a context service key that's different from the name of the type, as my new tests show.

In the case of a bot state memory scope, I also fixed the code to not rely on reflection to access the cached bot state. BotStateMemoryScope<T>.GetMemory now accesses the bot state instance in the same way that BotStateMemoryScope<T>.LoadAsync and BotStateMemoryScope<T>.SaveChangesAsync do, and once it has the bot state instance it can simply call BotState.Get. Because the classes that call GetMemory are expecting a dictionary and not a JObject, I modified BotState.Get to return an IDictionary<string, object>. I believe this is an improvement because that's the form that the cached bot state is already in and there's no need to convert it to a JObject. While it may seem like a dangerous change, the only place that BotState.Get is used is in one InspectionMiddleware method, and if any customers are using it and expecting a JObject then the compiler will just tell them to convert it to a JObject on their end, so there won't be any surprising behavior changes. I'm confident that the code is better this way and I can't imagine this breaking anything because the tests are all passing.

I have discovered that the problems I was seeing in BotStateMemoryScope<T> were not about GetMemory returning a JObject instead of a dictionary, they were about BotState.Get returning a copy of the of the state, which means any modifications wouldn't persist. I have also discovered that my "best option" to fix BotStateMemoryScope<T> by having Get return the original dictionary instead of a JObject will not work because it violates the contract. I see four other options:

  1. Create a new public Get method (with a different name) to return the actual dictionary instead of a copy
  2. Create an internal Get method and have the Bot Builder assembly share its members with the dialogs library
  3. Create a private Get method and have BotStateMemoryScope<T>.GetMemory access it using reflection
  4. Don't create any new methods and have BotStateMemoryScope<T>.GetMemory use reflection twice

I went with option 4 because it's the most similar to the way things are currently set up. The reason we need to use reflection twice is because the CachedBotState class is private and the context service key we need to access it is also private.

EDIT: I've thought of a fifth option. Since bot state is meant to be accessed using bot state property accessors, we could try to rework the adaptive dialogs library to access bot state using bot state property accessors. This would probably represent significant work/changes so I would not attempt it without some kind of go-ahead from the SDK team.

Copy link
Contributor

@tomlm tomlm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕐

@tomlm
Copy link
Contributor

tomlm commented Apr 22, 2020

OK, here's what I think we should do:

  1. Make existing class CachedBotState public
  2. Make existing method BotState.GetCachedState() method
  3. Change BotStateMemoryScope to use it
           var botState = GetBotState(dialogContext);
           var cachedState = botState.GetCachedState();
           return cachedState.State;

This gets rid of all reflection, and should be correct.

- Make BotState.CachedBotState public while changing its members from public to internal
- Make BotState.GetCachedState public
- Add a null check for newly public method and use BotAssert in other methods for consistency
- Document newly public method
- Write test for newly public method
@v-kydela
Copy link
Contributor Author

@tomlm - I've implemented your solution, and all checks are passing

Copy link
Contributor

@tomlm tomlm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@tomlm
Copy link
Contributor

tomlm commented Apr 24, 2020

looks good, thanks for the changess

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bot State failures when extending with a sub class
4 participants