Tutorial: Add action support (csharp)
Add action handling code
Purpose
In addition to utterance based invocation of your Skill, we have now introduced action based invocation similar to a method call whereby a client can invoke a specific function of your Skill passing data (slots) and receive response data back. The Skill can still prompt as usual in a conversational manner for missing slots and other needs.
Follow this part of the tutorial to wire up the action you created as part of the previous step.
Steps
Create a new class called to represent the new Action within your Dialogs folder and paste in the example class definition shown below. This class is based on the existing
SampleAction
and shows some extensions to handle an incoming object containing data and how to return an object back to the caller as part the end dialog operation.Note that action processing can send activities just like any Dialog and prompt for missing data or confirmation. Update the input and output types as per the previous step along with validation steps.
Note that an output object can be returned to a caller by passing it on the call to
EndDialogAsync
. This is then automatically added to theEndOfConversation
event and accessible by the caller.publicclassSampleActionInput{[JsonProperty("name")]publicstringName{get;set;}}publicclassSampleActionOutput{[JsonProperty("customerId")]publicintCustomerId{get;set;}}publicclassSampleAction:SkillDialogBase{publicSampleAction(IServiceProviderserviceProvider):base(nameof(SampleAction),serviceProvider){varsample=newWaterfallStep[]{PromptForNameAsync,GreetUserAsync,EndAsync,};AddDialog(newWaterfallDialog(nameof(SampleAction),sample));AddDialog(newTextPrompt(DialogIds.NamePrompt));InitialDialogId=nameof(SampleAction);}privateasyncTask<DialogTurnResult>PromptForNameAsync(WaterfallStepContextstepContext,CancellationTokencancellationToken){// If we have been provided a input data structure we pull out provided data as appropriate// and make a decision on whether the dialog needs to prompt for anything.if(stepContext.OptionsisSampleActionInputactionInput&&!string.IsNullOrEmpty(actionInput.Name)){// We have Name provided by the caller so we skip the Name prompt.returnawaitstepContext.NextAsync(actionInput.Name,cancellationToken);}varprompt=TemplateEngine.GenerateActivityForLocale("NamePrompt");returnawaitstepContext.PromptAsync(DialogIds.NamePrompt,newPromptOptions{Prompt=prompt},cancellationToken);}privateasyncTask<DialogTurnResult>GreetUserAsync(WaterfallStepContextstepContext,CancellationTokencancellationToken){dynamicdata=new{Name=stepContext.Result.ToString()};varresponse=TemplateEngine.GenerateActivityForLocale("HaveNameMessage",data);awaitstepContext.Context.SendActivityAsync(response);// Pass the response which we'll return to the user onto the next stepreturnawaitstepContext.NextAsync(cancellationToken:cancellationToken);}privateasyncTask<DialogTurnResult>EndAsync(WaterfallStepContextstepContext,CancellationTokencancellationToken){// Simulate a response object payloadvaractionResponse=newSampleActionOutput{CustomerId=newRandom().Next()};// We end the dialog (generating an EndOfConversation event) which will serialize the result object in the Value field of the ActivityreturnawaitstepContext.EndDialogAsync(actionResponse,cancellationToken);}privatestaticclassDialogIds{publicconststringNamePrompt="namePrompt";}}
Add the following line to your
Startup.cs
class to make the new action available for use.services.AddTransient<SampleAction>();
Within your
MainDialog.cs
class you need to add the newly created dialog so it’s available for use. Add this line to yourMainDialog
constructor whilst also creating a local variable.// Register dialogs_sampleAction=serviceProvider.GetService<SampleAction>();AddDialog(_sampleAction);
Within your
MainDialog.cs
class you need to manage a handler for each incoming Action your Skill supports. The name of your action specified within thename
property of your Action definition is mapped to theName
property of the incoming activity.privateasyncTask<DialogTurnResult>RouteStepAsync(WaterfallStepContextstepContext,CancellationTokencancellationToken){varactivity=stepContext.Context.Activity;if(activity.Type==ActivityTypes.Message&&!string.IsNullOrEmpty(activity.Text)){...}elseif(activity.Type==ActivityTypes.Event){varev=activity.AsEventActivity();if(!string.IsNullOrEmpty(ev.Name)){switch(ev.Name){case"SampleAction":{SampleActionInputactionData=null;if(ev.ValueisJObjecteventValue){actionData=eventValue.ToObject<SampleActionInput>();}// Invoke the SampleAction dialog passing input data if availablereturnawaitstepContext.BeginDialogAsync(nameof(SampleAction),actionData,cancellationToken);}default:{awaitstepContext.Context.SendActivityAsync(newActivity(type:ActivityTypes.Trace,text:$"Unknown Event '{ev.Name??"undefined"}' was received but not processed."),cancellationToken);break;}}}else{awaitstepContext.Context.SendActivityAsync(newActivity(type:ActivityTypes.Trace,text:"An event with no name was received but not processed."),cancellationToken);}}}
Build and deploy your updated Skill. You have now added an additional Action to your Skill which should be visible within a client application such as Power Virtual Agent.