This site is obsolete and should be used for reference only. The information in this documentation is not guaranteed to work for Bot Framework SDK versions past 4.9.1.

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

  1. 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 the EndOfConversation 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";}}
  2. Add the following line to your Startup.cs class to make the new action available for use.

    services.AddTransient<SampleAction>();
  3. Within your MainDialog.cs class you need to add the newly created dialog so it’s available for use. Add this line to your MainDialog constructor whilst also creating a local variable.

    // Register dialogs_sampleAction=serviceProvider.GetService<SampleAction>();AddDialog(_sampleAction);
  4. 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 the name property of your Action definition is mapped to the Name 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);}}}
  5. 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.

close