In Microsoft BotFramework v4 you normally propagate the states (UserState, ConversationState, PrivateConversationState) to a dialog by passing them as parameters to its constructor.
This way:
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
IStorage storage = new MemoryStorage(); // For testing only !
services.AddSingleton(new UserState(storage));
services.AddSingleton(new ConversationState(storage));
// ...
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
services.AddSingleton<GoviiBaseDialog>(x => new RootDialog(
x.GetRequiredService<UserState>(),
x.GetRequiredService<ConversationState>()
);
services.AddTransient<IBot, Bot<RootDialog>>();
}
}
Bot.cs
public class Bot<T> : ActivityHandler where T : Dialog
{
T _dialog;
BotState _userState, _conversationState;
public Bot(T dialog, UserState userState, ConversationState conversationState,)
{
_userState = userState;
_conversationState = conversationState;
_dialog = dialog;
}
public override async Task OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default)
{
await base.OnTurnAsync(context, cancellationToken);
await _userState.SaveChangesAsync(context);
await _conversationState.SaveChangesAsync(context);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> context, CancellationToken cancellationToken)
{
await _dialog.RunAsync(context, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
RootDialog.cs
public class RootDialog : ComponentDialog
{
UserState _userState;
ConversationState _conversationState;
public RootDialog(UserState uState, ConversationState cState) : base("id")
{
_userState = uState;
_conversationState = cState;
// Add some dialogs and pass states as parameters
AddDialog(new CustomDialog_1(uState, cState));
AddDialog(new CustomDialog_2(uState, cState));
// ...
AddDialog(new CustomDialog_N(uState, cState));
}
}
Now let's assume that those CustomDialogs again uses some other CustomDialogs which needs to access the state. The states have to be passed again and again as parameters to the constructors.
The question is: Is there another way to access the states to avoid passing them again and again as parameters?
How you access state in a dialog will depend on the scope of the state.
If the state is scoped to the dialog then you should be using dialog state. More specifically, you should be using the state property of the dialog's associated dialog instance. There's some discussion of that in a recent answer here: Dialogs keep their variable values in new conversation in MS BotFramework v4
(Read about dialog instances here). Anything you want your dialog to keep track of, you should put in the associated dialog instance's state object, and the best place to see examples of how to do that is in the SDK source code itself. For example, you can see how a waterfall dialog keeps track of things like its custom values and what step it's on:
// Update persisted step index var state = dc.ActiveDialog.State; state[StepIndex] = index;
If the state has a greater scope than one instance of one dialog, you can pass the bot state objects to your dialogs like you've been doing. This can potentially be made easier if you put the dialogs in dependency injection so that your bot state can be automatically injected into their constructors. If your dialogs access state properties that are also used outside of the dialogs then it makes sense to give the dialogs a state property accessor instead of the state itself, reducing redundancy and separating concerns.
If you want to make sure your bot state is accessible anywhere you have a turn context, there's actually a built-in way to automatically add your bot state to turn state every turn. This is with the UseBotState
extension method:
adapter.UseBotState(userState, conversationState);
You can then retrieve the state like this:
var userState = turnContext.TurnState.Get<UserState>();
var conversationState = turnContext.TurnState.Get<ConversationState>();