问题
I made a new ComponentDialog by creating a class for it that extends ComponentDialog
like so:
public class GetPersonInfoDialog : ComponentDialog
{
protected readonly ILogger Logger;
public GetPersonInfoDialog(IConfiguration configuration, ILogger<GetPersonInfoDialog> logger)
: base("get-person-info", configuration["ConnectionName"])
{ }
// code ommitted
}
}
Then I added it in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSingleton<GreetingDialog>();
services.AddTransient<IBot, AuthBot<GreetingDialog>>();
// My new component dialog:
services.AddSingleton<GetPersonInfoDialog>();
services.AddTransient<IBot, AuthBot<GetPersonInfoDialog>>();
}
}
But what I noticed is that only the last Dialog gets used. GreetingDialog now no longer works, saying:
DialogContext.BeginDialogAsync(): A dialog with an id of 'greeting' wasn't found. The dialog must be included in the current or parent DialogSet. For example, if subclassing a ComponentDialog you can call AddDialog() within your constructor.
I did notice, however, that the GetPersonInfo dialog does indeed begin and the Greeting dialog doesn't anymore. It's like I can only use one or the other. I noticed only the last added "transient" gets used, as if it overrides the previous transients.
How do I add multiple component dialogs in my Startup.cs file? Or am I even going about this correctly? I did not find any documentation explaining how to have multiple ComponentDialogs.
回答1:
In Startup.cs
I will start with your Startup.cs
file since that is where the first issue is, then I will suggest an alternative design.
What you are effectively doing with the following block of code is:
services.AddSingleton<GreetingDialog>();
services.AddTransient<IBot, AuthBot<GreetingDialog>>();
services.AddSingleton<GetPersonInfoDialog>();
services.AddTransient<IBot, AuthBot<GetPersonInfoDialog>>();
- Registering a single instance of the
GreetingDialog
for your bot (essentially a static). - Registering the
IBot
interface to return a newAuthBot
of typeGreetingDialog
everytime anIBot
is requested. - Registering a single instance of the
GetPersonInfoDialog
for your bot (essentially a static). - Registering the
IBot
interface (again) to return a newAuthBot
of typeGetPersonInfoDialog
everytime anIBot
is requested (which will overwrite the registration in step 2).
You can read a lot more about Service lifetimes here.
So what you actually want is more like the below:
public void ConfigureServices(IServiceCollection services)
{
// Other code
// Register dialogs
services.AddTransient<GreetingDialog>();
services.AddTransient<GetPersonInfoDialog>();
// Some more code
// Configure bot
services.AddTransient<IBot, DialogBot<GreetingDialog>>();
}
Error message
DialogContext.BeginDialogAsync(): A dialog with an id of 'greeting' wasn't found. The dialog must be included in the current or parent DialogSet. For example, if subclassing a ComponentDialog you can call AddDialog() within your constructor.
This error message is caused because your GetPersonInfoDialog
doesn't know about your GreetingDialog
(and it shouldn't). I believe this is a runtime error because I remember running into a similar issue myself. Since you haven't provided the full implementation for your GetPersonInfoDialog
class I have to assume that somewhere inside there you are trying to do something like the following:
dialogContext.BeginDialogAsync("greeting");
or
dialogContext.BeginDialogAsync(nameof(GreetingDialog));
as per the documentation the first parameter is the ID of the dialog to start, this ID is also used to retrieve the dialog from the dialog stack. In order to call one dialog from inside another, you will need to add it to the parent dialog's DialogSet
. The accepted way to do this is to add a call inside the constructor for the parent dialog like so:
public ParentDialog(....)
: base(nameof(ParentDialog)
{
// Some code
// Important part
AddDialog(new ChildDialog(nameof(ChildDialog)));
}
This uses the AddDialog method provided by the Microsoft.Bot.Builder.Dialogs NuGet package and exposed through the ComponentDialog
class.
Then when you want to display the ChildDialog
you would call:
dialogContext.BeginDialogAsync(nameof(ChildDialog));
In your case you can replace ParentDialog
with GetPersonInfoDialog
and ChildDialog
with GreetingDialog
. Since your GreetingDialog
is only likely to be used once (it is not a utility dialog that could be called multiple times but with different arguments - in this case you would want to provide a specific ID rather than using nameof(GreetingDialog)
) it is fine to go with the string representation of the class name as the DialogId, you can using "greeting" inside the the AddDialog
call but you would also have to update the BeginDialogAsync
call to also use "greeting".
An alternative design
Since I don't believe you want either GreetingDialog
or GetPersonInfoDialog
to be your actual starting points, I would suggest adding another dialog called MainDialog
which inherits from the RouterDialog
class (Microsoft.Bot.Builder.Solutions.Dialogs NuGet package). Based on the architecture (Virtual Assistant Template) here you would have your MainDialog
spawn off your GreetingDialog
and GetPersonInfoDialog
.
Assuming that your GreetingDialog
is only a single stage where it sends a card or some text to the user to welcome them, it could be completely replaced by the OnStartAsync
method which sends your card/message. Getting your user to your GetPersonInfoDialog
would then be handled by the RouteAsync
method example here.
The changes you would need to make to your existing project to get this wired up are (assuming that you keep the GreetingDialog
):
- Add Transient registrations in
Startup.cs
forGreetingDialog
,GetPersonInfoDialog
andMainDialog
. - Add a Transient registration for mapping
IBot
toAuthBot<MainDialog>
- Add calls inside the constructor of
MainDialog
to add the child dialogsGreetingDialog
andGetPersonInfoDialog
. - In the
OnBeginDialog
orOnStartAsync
ofMainDialog
start yourGreetingDialog
. - In the
RouteAsync
ofMainDialog
handle any conditions around showing theGetPersonInfoDialog
before displaying it. - There may be some additional steps that I have missed.
Helpful links:
- Implementing sequential conversation flow.
- Reusing dialogs.
- Virtual Assistant template outline
- Virtual Assistant template architecture.
Edit
To achieve what you want within the OAuth sample you could do the following:
In LogoutDialog.cs change:
private async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
to
protected virtual async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
In MainDialog.cs add:
protected override async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message)
{
var text = innerDc.Context.Activity.Text.ToLowerInvariant();
if (text == "check email")
{
//
return innerDc.BeginDialogAsync(/*TODO*/);
}
else if (text == "check calender")
{
//
return innerDc.BeginDialogAsync(/*TODO*/);
}
// etc
return await base.InterruptAsync(innerDc, cancellationToken);
}
return null;
}
along with registering your Calendar, Email, etc dialogs in the constructor for MainDialog
using the AddDialog
method.
I would seriously advise you to look at using the Virtual Assistant Template. As it uses LUIS to determing the user's intent (check email, check calendar etc), then route them accordingly, the relevant code is in this method. Using LUIS to determine intents has the advantage of being able to tie multiple ways of asking the same thing to the same intent, so you're not relying on your users to explicitly type "check calendar", you can have "show me my calendar", "what is my availability for next Monday", "am I free this afternoon", "check if I have any appointments tomorrow" etc. In fact Microsoft has already built Skills for email, and calendar which work with the Virtual Assistant Template, it should be easy enough to port the login code over to this template.
来源:https://stackoverflow.com/questions/56711455/how-do-i-add-multiple-componentdialogs