Calling form in a luis based bot - c#

I am trying to create a bot wherein based on luis intent i need to ask customer to feed in his details and feedback. For example, if customer is unhappy i want his details to allow a callback. My luis intent identifies the dialog but i am not able to fire up a form. My luis dialog code is
namespace Microsoft.Bot.Sample.Luisbot
{
[Serializable]
public class FeedbackForm
{
[Prompt(new string[] { "Name?" })]
public string Name { get; set; }
[Prompt("Contact Number")]
public string Contact { get; set; }
[Prompt("Query")]
public string Query { get; set; }
public static IForm<FeedbackForm> BuildForm()
{
return new FormBuilder<FeedbackForm>()
.Build();
}
}
[Serializable]
class BasicLuisDialog : LuisDialog<object>
{
public BasicLuisDialog() : base(new LuisService(new LuisModelAttribute(ConfigurationManager.AppSettings["LuisAppId"], ConfigurationManager.AppSettings["LuisAPIKey"])))
[LuisIntent("Greetings")]
public async Task GreetingsIntent(IDialogContext context, LuisResult result)
{
await context.PostAsync("Hi. Please share your query");
context.Wait(MessageReceived);
}
[LuisIntent("Critical")]
public async Task CriticalIntent(IDialogContext context, LuisResult result)
{
await context.PostAsync("Thank you for your response.To help you better I will arrange a call back from our customer care team. Please provide following details");
var feedbackForm = new FormDialog<FeedbackForm>(new FeedbackForm(), FeedbackForm.BuildForm, FormOptions.PromptInStart,result.Entities);
context.Call(feedbackForm, FeedbackFormComplete);
context.Wait(MessageReceived);
}
}
}
And my messagecontroller code is
namespace Microsoft.Bot.Sample.LuisBot
{
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Bot.Connector;
[BotAuthentication]
public class MessagesController : ApiController
{
private static IForm<FeedbackForm> BuildForm()
internal static IDialog<FeedbackForm> MakeRoot()
{
return Chain.From(() => new BasicLuisDialog(BuildForm));
}
[ResponseType(typeof(void))]
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new BasicLuisDialog());
}
else
{
await this.HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
if (message.MembersAdded.Any(o => o.Id == message.Recipient.Id))
{
ConnectorClient client = new ConnectorClient(new Uri(message.ServiceUrl));
var reply = message.CreateReply();
reply.Text = "Welcome to RB Customer Care";
await client.Conversations.ReplyToActivityAsync(reply);
}
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
}
}
}
Can anyone help me understand what is wrong here. I am not a pro at C#

Related

Returning Status Codes from a class outside my Controller

I am trying to move logic that I do on all of my controllers into a class to follow the "Do Not Repeat Yourself" principle. What I am struggling with is how to elegantly return an error code.
Here are some examples of what I currently do within each controller:
public class SomethingRequest
{
public SomethingModel Something { get; set; }
public string Token { get; set; }
}
public ActionResult GetSomething(SomethingRequest request)
{
var something = request.Something;
var token = request.Token;
if (something == null)
{
return BadRequest("Something object is null. You may have sent data incorrectly");
}
if (token == null || token != "1234")
{
return Unauthorized("Token object is null");
}
}
Now what I want to do is move the last two parts of that into their own class:
public class RequestValidation
{
public void TokenCheck(string token)
{
if (token == null || token != "1234")
{
// doesn't work
return Unauthorized("Token object is null");
}
}
public void DataCheck(object someObject)
{
if (someObject == null)
{
// doesn't work
return BadRequest("Object is null. You may have sent data incorrectly");
}
}
}
And then I want to call them from SomethingController like so
RequestValidation.TokenCheck(token);
and
RequestValidation.DataCheck(something);
and then have them return the bad request or an exception.
How should I accomplish this?
A common way to do this is to have a helper class that returns the result of validations and/or operations to the Controller:
public class ValidationResult
{
public bool Succeeded { get; set; }
public string Message { get; set; }
public int StatusCode { get; set; }
}
Since the question is tagged with ASP.NET Core, the correct way to do this would be to first create the interface:
public interface IRequestValidationService
{
ValidationResult ValidateToken(string token);
ValidationResult ValidateData(object data);
}
Then, create the implementation:
public class RequestValidationService : IRequestValidationService
{
public ValidationResult ValidateToken(string token)
{
if (string.IsNullOrEmpty(token) || token != "1234")
{
return new ValidationResult
{
Succeeded = false,
Message = "invalid token",
StatusCode = 403
};
}
return new ValidationResult { Succeeded = true };
}
...
}
Add it to the DI container (in the Startup class):
services.AddScoped<IRequestValidationService, RequestValidationService>();
Inject it into the SomethingController:
public SomethingController(IRequestValidationService service)
{
_requestValidationService = service;
}
And finally use it:
public IActionResult GetSomething(SomethingRequest request)
{
var validationResult = _requestValidationService.ValidateToken(request?.Token);
if (!validationResult.Succeeded)
{
return new StatusCode(validationResult.StatusCode, validationResult.Message);
}
}
Notice that for something as trivial as validating that something isn't null, you should be using model validation:
public class SomethingRequest
{
[Required(ErrorMessage = "Something is required, check your data")]
public SomethingModel Something { get; set; }
[Required(ErrorMessage = "Token is required!")]
public string Token { get; set; }
}
#CamiloTerevinto 's idea got me on the right path. His method would work, but from what I read in the documentation, the proper way is with "Action Filters".
I used this article as additional inspiration.
Here is my filter I named ValidationFilterAttribute
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Name_Of_Project.ActionFilters
{
// This filter can be applied to classes to do the automatic token validation.
// This filter also handles the model validation.
// inspiration https://code-maze.com/action-filters-aspnetcore/
public class ValidationFilterAttribute: IActionFilter
{
// passing variables into an action filter https://stackoverflow.com/questions/18209735/how-do-i-pass-variables-to-a-custom-actionfilter-in-asp-net-mvc-app
private readonly ILogger<ValidationFilterAttribute> _logger;
public ValidationFilterAttribute(ILogger<ValidationFilterAttribute> logger)
{
_logger = logger;
}
public void OnActionExecuting(ActionExecutingContext context)
{
//executing before action is called
// this should only return one object since that is all an API allows. Also, it should send something else it will be a bad request
var param = context.ActionArguments.SingleOrDefault();
if (param.Value == null)
{
_logger.LogError("Object sent was null. Caught in ValidationFilterAttribute class.");
context.Result = new BadRequestObjectResult("Object sent is null");
return;
}
// the param should be named request (this is the input of the action in the controller)
if (param.Key == "request")
{
Newtonsoft.Json.Linq.JObject jsonObject = Newtonsoft.Json.Linq.JObject.FromObject(param.Value);
// case sensitive btw
string token = jsonObject["Token"].ToString();
// check that the token is valid
if (token == null || token != "1234")
{
_logger.LogError("Token object is null or incorrect.");
context.Result = new UnauthorizedObjectResult("");
return;
}
}
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// executed after action is called
}
}
}
Then my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Adding an action Filter
services.AddScoped<ValidationFilterAttribute>();
}
Then I can add it to my controller.
using Name_Of_Project.ActionFilters;
namespace Name_Of_Project.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SomethingController : ControllerBase
{
// POST api/something
[HttpGet]
[ServiceFilter(typeof(ValidationFilterAttribute))]
public ActionResult GetSomething(SomethingRequest request)
{
var something= request.Something;
var token = request.Token;
}
}
Because I want to reuse this action filter many times, I need to figure out a way to pass in a parameter for the null check (could have many different objects coming in under the name of "request" that need to check). This is the answer I will be looking to for that portion of the solution.

How do I redirect the message to the corresponding LUIS app

I have a bot with a root LuisDialog and 4 more LuisDialogs each one with a different LUIS model. Following the conversation started here I've implemented a similar DialogFactory strategy.
When a user sends a question that matches "None" intent in my root dialog, I evaluate the rest of dialogs until I find a match and then forward the message to the "winner".
The problem I'm facing is that I'm getting the http error: 429 (Too Many Requests) when querying LUIS (BaseDialog class).
Any ideas about how to face this?
The "None" intent in my root dialog:
[LuisIntent("None")]
public async Task None(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var activity = await message;
var factory = new DialogFactory();
BaseDialog<object> dialog = await factory.Create(result.Query);
if (dialog != null)
{
await context.Forward(dialog, EndDialog, activity, CancellationToken.None);
}
else
{
await context.PostAsync("No results!");
}
}
public static async Task EndDialog(IDialogContext context, IAwaitable<object> result)
{
//...
}
The DialogFactory class:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading.Tasks;
namespace CodeBot.Dialogs
{
public class DialogFactory
{
private static object _lock = new object();
private static List<BaseDialog<object>> Dialogs { get; set; }
public async Task<BaseDialog<object>> Create(string query)
{
query = query.ToLowerInvariant();
EnsureDialogs();
foreach (var dialog in Dialogs)
{
if (await dialog.CanHandle(query))
{
return dialog;
}
}
return null;
}
private void EnsureDialogs()
{
if (Dialogs == null || (Dialogs.Count != 4))
{
lock (_lock)
{
if (Dialogs == null)
{
Dialogs = new List<BaseDialog<object>>();
}
else if (Dialogs.Count != 4)
{
Dialogs.Clear();
}
Dialogs.Add((BaseDialog<object>)Activator.CreateInstance(typeof(Dialog1));
Dialogs.Add((BaseDialog<object>)Activator.CreateInstance(typeof(Dialog2));
Dialogs.Add((BaseDialog<object>)Activator.CreateInstance(typeof(Dialog3));
Dialogs.Add((BaseDialog<object>)Activator.CreateInstance(typeof(Dialog4));
}
}
}
}
}
And finally, the BaseDialog class (where I'm getting the error):
using Microsoft.Bot.Builder.Dialogs;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Luis;
using System;
namespace CodeBot.Dialogs
{
[Serializable]
public class BaseDialog<R> : LuisDialog<R>
{
public LuisModelAttribute Luis_Model { get; private set; }
public BaseDialog(LuisModelAttribute luisModel) : base(new LuisService(luisModel))
{
Luis_Model = luisModel;
}
public async Task<bool> CanHandle(string query)
{
try
{
var tasks = services.Select(s => s.QueryAsync(query, CancellationToken.None)).ToArray();
var results = await Task.WhenAll(tasks); <-- Error!!!
var winners = from result in results.Select((value, index) => new { value, index })
let resultWinner = BestIntentFrom(result.value)
where resultWinner != null
select new LuisServiceResult(result.value, resultWinner, this.services[result.index]);
var winner = this.BestResultFrom(winners);
return winner != null && !winner.BestIntent.Intent.Equals(Constants.NONE, StringComparison.InvariantCultureIgnoreCase);
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine($"CanHandle error: {e.Message}");
return false;
}
}
}
}
The 429 error is caused by your application (key) hitting the LUIS API too heavily.
You need to either throttle your requests to ensure you stay below the threshold of the free tier, or upgrade to the Basic plan which allows 50 requests a second.

BOT Framework - Unable to Connect Luis- AI Dialog

Below are the source Code for communicating to LUIS- AI from my Bot application. When I try to communicate I am always getting Access Denied response. I don't know what I am missing here.
[LuisModel("8c9285fb-198a-4f49-8fe4-b08ac5541ac2", "5c47c63887e346c2aee24d1755e07d29")]
[Serializable]
public class LUISDialog:LuisDialog<RoomReservation>
{
private readonly BuildFormDelegate<RoomReservation> Reservation;
public LUISDialog(BuildFormDelegate<RoomReservation> reservceRoom)
{
this.Reservation = reservceRoom;
}
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext dialogContext, LuisResult luisResult)
{
await dialogContext.PostAsync("I am sorry I don't know what you mean ");
dialogContext.Wait(MessageReceived);
}
[LuisIntent("Greeting")]
public async Task Greeting(IDialogContext dialogContext, LuisResult luisResult)
{
dialogContext.Call(new GreetingDialog(), CallBack);
}
private async Task CallBack(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceived);
}
[LuisIntent("Reservation")]
public async Task RoomReservation(IDialogContext dialogContext, LuisResult luisResult)
{
FormDialog<RoomReservation> enrollmentForm =new FormDialog<RoomReservation>(new RoomReservation(),this.Reservation, FormOptions.PromptInStart);
dialogContext.Call(enrollmentForm, CallBack);
}
[LuisIntent("QueryAmenities")]
public async Task QueryAmenities(IDialogContext dialogContext, LuisResult luisResult)
{
foreach (var entity in luisResult.Entities.Where(entity=>entity.Type=="Amenity"))
{
var value = entity.Entity.ToLower();
if (value == "pool" || value == "gym" || value == "wifi" || value == "towels")
{
await dialogContext.PostAsync("Yes we have that");
dialogContext.Wait(MessageReceived);
return;
}
await dialogContext.PostAsync("I'am sorry we don't have that");
dialogContext.Wait(MessageReceived);
return;
}
await dialogContext.PostAsync("I'am sorry we don't have that");
dialogContext.Wait(MessageReceived);
}
}
Screen shot of error I am getting
Controller Code
[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
//ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
//// calculate something for us to return
//int length = (activity.Text ?? string.Empty).Length;
//// return our reply to the user
//Activity reply = activity.CreateReply($"You sent {activity.Text} which was {length} characters");
//await connector.Conversations.ReplyToActivityAsync(reply);
// await Conversation.SendAsync(activity, () => HotelBotDialog.dialog);
await Conversation.SendAsync(activity, MakeLuisDialog);
}
else
{
await HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
LUIS Intent
Please help me to resolve this
I just tested a connection to your LUIS and it seems to be working: https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/8c9285fb-198a-4f49-8fe4-b08ac5541ac2?subscription-key=5c47c63887e346c2aee24d1755e07d29&verbose=true&q=hello
Probrably you are using the endpoint key instead the Programmatic Key API. Check this:
enter image description here

How can you quit from dialog in C# bot-framework?

I'm starting a project for a ChatBot in C# with the bot framework.
I choose the ContosoFlowers example for learning about the use of bot framework. In the AddressDialog users cannot quit the dialog after they enter to it without providing an address.
How can I update the code so when users reply "Cancel" or "Abort" or "B" or "Back" they quit the dialog?
namespace ContosoFlowers.BotAssets.Dialogs
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Extensions;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Properties;
using Services;
[Serializable]
public class AddressDialog : IDialog<string>
{
private readonly string prompt;
private readonly ILocationService locationService;
private string currentAddress;
public AddressDialog(string prompt, ILocationService locationService)
{
this.prompt = prompt;
this.locationService = locationService;
}
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync(this.prompt);
context.Wait(this.MessageReceivedAsync);
}
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
var addresses = await this.locationService.ParseAddressAsync(message.Text);
if (addresses.Count() == 0)
{
await context.PostAsync(Resources.AddressDialog_EnterAddressAgain);
context.Wait(this.MessageReceivedAsync);
}
else if (addresses.Count() == 1)
{
this.currentAddress = addresses.First();
PromptDialog.Choice(context, this.AfterAddressChoice, new[] { Resources.AddressDialog_Confirm, Resources.AddressDialog_Edit }, this.currentAddress);
}
else
{
var reply = context.MakeMessage();
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
foreach (var address in addresses)
{
reply.AddHeroCard(Resources.AddressDialog_DidYouMean, address, new[] { new KeyValuePair<string, string>(Resources.AddressDialog_UseThisAddress, address) });
}
await context.PostAsync(reply);
context.Wait(this.MessageReceivedAsync);
}
}
private async Task AfterAddressChoice(IDialogContext context, IAwaitable<string> result)
{
try
{
var choice = await result;
if (choice == Resources.AddressDialog_Edit)
{
await this.StartAsync(context);
}
else
{
context.Done(this.currentAddress);
}
}
catch (TooManyAttemptsException)
{
throw;
}
}
}
}
You just need to handle the quit conditions at the top of the MessageReceivedAsync method.
At the top of the dialog you can add something like
private static IEnumerable<string> cancelTerms = new[] { "Cancel", "Back", "B", "Abort" };
And also add this method:
public static bool IsCancel(string text)
{
return cancelTerms.Any(t => string.Equals(t, text, StringComparison.CurrentCultureIgnoreCase));
}
Then is just a matter of understanding if the message sent by the user matches any of the cancelation terms. In the MessageReceivedAsync method do something like:
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
if (IsCancel(message.Text))
{
context.Done<string>(null);
}
// rest of the code of this method..
}
You can also go a bit more generic and create a CancelableIDialog similar to what was done in the CancelablePromptChoice.

MahApps TDD ShowProgressDialog

Sorry for two posts.
I have the following code which I stole from the MahApps example. This has the updated code example
using System.Threading.Tasks;
using MahApps.Metro.Controls.Dialogs;
namespace MyNameSpace {
public class MyClass {
public MyClass(IDialogCoordinator dialogCoordinator,IMyService myservice){
_dialogCoordinator =dialogCoordinator;
_mySerice =myservice;
}
public async Task<bool> MethodUnderTest() {
var controller = await _dialogCoordinator.ShowProgressAsync(this,"Please wait...", "We are baking some cupcakes!");
controller.SetIndeterminate();
await Task.Delay(5000);
controller.SetCancelable(true);
if (!controller.IsCanceled) {
controller.SetProgress(0.50);
controller.SetMessage("Half way there");
await Task.Factory.StartNew(()=> {
_myservice.MyMethodIWannaTest();
});
}
await controller.CloseAsync();
if (controller.IsCanceled) {
await dialogCoordinator.ShowMessageAsync(this,"No cupcakes!", "You stopped baking!");
return false;
} else {
await dialogCoordinator.ShowMessageAsync(this,"Cupcakes!", "Your cupcakes are finished! Enjoy!");
return true;
}
}
private IDialogCoordinator _dialogCoordinator;
private IMyService _myService;
}
}
How do I test to verify that MyMethodIWannaTest is called (Since I can't Moq ShowProgressAync???
using Moq;
using MahApps.Metro.Controls.Dialogs;
[TestMethod]
public void MethodUnderTestCallsMyServiceMyMethodIWannaTest() {
var _mockDialogCoordinator = new Mock<IDialogCoordinator>();
var _mockMyService = new Mock<IMyService>();
// Arrange
var viewModel = new MyClass(_mockDialogCoordinator.Object,_mockMyService.Object);
//NOT SURE WHAT TO DO TO MAKE IT WORK?
_mockDialogCoordinator.Setup(
d => d.ShowProgressAsync(viewModel, "Please wait...", "We are creating your package!", true, null))
.Returns(Task.Factory.StartNew(Activator.CreateInstance<ProgressDialogController>)); //Still does not work always returns null :(
// Act
viewModel.MethodUnderTest();
// Assert
_mockMyService.Verify(ms => ms.MyMethodIWannaTest());
}
Not sure what I am doing wrong, any help appreciated!!

Categories

Resources