How to handle end of conversation to start another dialog in QnA multi turn - Microsoft.Bot.Builder.AI.QnA.Dialogs.QnAMakerDialog - botframework

I have implemented multi turn QnA in our bot and using this class Microsoft.Bot.Builder.AI.QnA.Dialogs.QnAMakerDialog.
Now, I want to extend its functionality so that after mutli turn conversation, bot can ask user if the conversation helped or not? if not then bot will ask to log a ticket with help desk.
I am able to catch the end of multi turn dialog by overriding the Dialog.EndDialogAsync method but not able to start another dialog from there. Please help.
public class QnAMultiTurnBase : QnAMakerDialog
{
// Dialog Options parameters
public readonly string DefaultNoAnswer = Configuration.Messages("Troubleshoot", "NoAnswer");//"No QnAMaker answers found.";
public readonly string DefaultCardTitle = Configuration.Messages("Troubleshoot", "DidYouMean");//"Did you mean:";
public readonly string DefaultCardNoMatchText = Configuration.Messages("Troubleshoot", "NoneOfTheAbove");//"None of the above.";
public readonly string DefaultCardNoMatchResponse = Configuration.Messages("Troubleshoot", "Feedback");//"Thanks for the feedback.";
private readonly BotServices _services;
private readonly IConfiguration _configuration;
//private readonly IStatePropertyAccessor<Dictionary<string, string>> troubleshootQuery;
private readonly Dictionary<string, string> qnaPair = new Dictionary<string, string>();
private readonly string qnAMakerServiceName;
/// <summary>
/// Initializes a new instance of the <see cref="QnAMakerBaseDialog"/> class.
/// Dialog helper to generate dialogs.
/// </summary>
/// <param name="services">Bot Services.</param>
public QnAMultiTurnBase(BotServices services, IConfiguration configuration, string qnAMakerServiceName) : base()
{
this._services = services;
this._configuration = configuration;
this.qnAMakerServiceName = qnAMakerServiceName;
}
protected async override Task<IQnAMakerClient> GetQnAMakerClientAsync(DialogContext dc)
{
return this._services?.QnAServices[qnAMakerServiceName];
}
protected override Task<QnAMakerOptions> GetQnAMakerOptionsAsync(DialogContext dc)
{
return Task.FromResult(new QnAMakerOptions
{
ScoreThreshold = DefaultThreshold,
Top = DefaultTopN,
QnAId = 0,
RankerType = "Default",
IsTest = false
});
}
protected async override Task<QnADialogResponseOptions> GetQnAResponseOptionsAsync(DialogContext dc)
{
var noAnswer = (Activity)Activity.CreateMessageActivity();
noAnswer.Text = this._configuration["DefaultAnswer"] ?? DefaultNoAnswer;
var cardNoMatchResponse = MessageFactory.Text(DefaultCardNoMatchResponse);
var responseOptions = new QnADialogResponseOptions
{
ActiveLearningCardTitle = DefaultCardTitle,
CardNoMatchText = DefaultCardNoMatchText,
NoAnswer = noAnswer,
CardNoMatchResponse = cardNoMatchResponse,
};
return responseOptions;
}
public override Task EndDialogAsync(ITurnContext turnContext, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken = default(CancellationToken))
{
try
{
// end of multi turn convversation
// ask if conversation helped the user or not
}
catch (Exception)
{
turnContext.SendActivityAsync(MessageFactory.Text(Configuration.Messages("UnknownError"))).Wait();
throw;
}
return base.EndDialogAsync(turnContext, instance, reason, cancellationToken);
}
}

Add a new dialog and initiate the dialog added using BeginDialogAsync:
AddDialog(new MoreHelp());
return await stepContext.BeginDialogAsync(nameof(MoreHelp), UserInfo, cancellationToken);

You can refer to this documentation where it specifies how to create your own prompts to gather user input. A conversation between a bot and a user often involves asking (prompting) the user for information, parsing the user's response, and then acting on that information.
Dialog actions – ability to control dialogs, BeginDialog, RepeatDialog, GotoDialog, EndDialog, etc.
Please follow the below for multi turn.
https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/csharp_dotnetcore/05.multi-turn-prompt

Related

Store Workflow Activity Data When Publishing

I Need to store a specific activity data in another collection in database whenever a user publish a workflow in elsa.
I dont find any documentation, Please suggest me some resource or suggestion to achieve this. I have try to implement this with middleware. The Middleware code is
namespace WorkFlowV3
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class CustomMiddleware
{
private readonly RequestDelegate _next;
static HttpClient client = new HttpClient();
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
//Write Custom Logic Here....
client.BaseAddress = new Uri("#");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
string path = "/api/test-middleware-call";
HttpResponseMessage response = await client.GetAsync(path);
await _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomMiddleware>();
}
}
}
But in this process, I cant fetch the specific activity data.
The easiest way to store information in your own DB in response to the "workflow published" event is by implementing a notification handler (from MediatR) that handles the WorkflowDefinitionPublished notification.
For example:
public class MyWorkflowPublishedhandler : INotificationhandler<WorkflowDefinitionPublished>
{
private readonly IMyDatabaseStore _someRepository;
public MyWorkflowPublishedhandler(IMyDatabaseStore someRepository)
{
_someRepository = someRepository;
}
public async Task Handle(WorkflowDefinitionPublished notification, CancellationToken cancellationToken)
{
var workflowDefinition = notification.WorkflowDefinition;
// Your logic to do a thing.
}
}
To register this handler, from your Startup or Program class, add the following code:
services.AddNotificationHandler<MyWorkflowPublishedhandler>();
Your handler will be invoked every time a workflow gets published.

How to change locale for a conversation in runtme

I'm using botframework composer with multi language and want each user to be able to select preferred language/locale. After resolving the local code for his selection with a choice dialog, how can I set it in conversation so that his locale setting in his device will be overruled for rest of conversation?
Changing locale in emulator works fine, want same behaviour after user selection.
Setting turn.locale works for one turn, but is reset on next turn.
supposing you don't have control over the client, which would be the best.
You can resort to an old overload on the ever-growing hierarchy of bot adapters that hasn't been marked as deprecated.
You'd have to use the PostAsync method (api/post-messages endpoint) in the following controller (showing the one created by the current set of bot framework templates just for comparison):
[Route("api")]
[ApiController]
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter StreamingAdapter;
private readonly BotFrameworkAdapter PostAdapter;
private readonly ConversationLocales ConversationLocales;
private readonly IBot Bot;
public BotController(
IBotFrameworkHttpAdapter streamingAdapter,
BotFrameworkAdapter postAdapter,
ConversationLocales conversationLocales,
IBot bot)
{
StreamingAdapter = streamingAdapter;
PostAdapter = postAdapter;
Bot = bot;
}
[HttpPost("messages"), HttpGet("messages")]
public async Task PostOrStreamingAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await StreamingAdapter.ProcessAsync(Request, Response, Bot);
}
[HttpPost("post-messages")]
public async Task<InvokeResponse> PostAsync([FromBody] Activity activity)
{
var savedLocale = ConversationLocales.GetLocaleForConversation(activity.Conversation.Id);
activity.Locale = savedLocale ?? activity.Locale;
return await PostAdapter.ProcessActivityAsync(string.Empty, activity, Bot.OnTurnAsync, default);
}
}
That's supposing you implement a ConversationLocales service that allows you to keep the selected locale for each conversation id.
In the code above we're using the BotFrameworkAdapter adapter instead of IBotFrameworkHttpAdapter, however the AdapterWithErrorHandler used in the templates inherits indirectly from BotFrameworkAdapter, so you could do something like this in ConfigureServices to register "both" adapters:
services.AddSingleton<AdapterWithErrorHandler>();
services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetRequiredService<AdapterWithErrorHandler>());
services.AddSingleton<BotFrameworkAdapter>(sp => sp.GetRequiredService<AdapterWithErrorHandler>());
To have a single adapter instance.
Using this method the adapter won't be able to use the bot channel streaming endpoints, but that shouldn't be much of a trouble, as long as you don't use the speech client.
You can also read some other details that might be relevan to you in my blog post How does a Bot Builder v4 bot work?, it's a bit dated but still valid.
UPDATE - Found a better solution 😊
This one works with the current wave of adapters and uses the messages pipeline, so it's "modern".
It also requires you to use a custom runtime, that you'll customize as follows.
1 - Create the following middleware
public class LocaleSelectionMiddleware : IMiddleware
{
private readonly IStatePropertyAccessor<string> _userLocale;
public LocaleSelectionMiddleware(UserState userState)
{
_userLocale = userState.CreateProperty<string>("locale");
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
{
if (turnContext is null)
{
throw new ArgumentNullException(nameof(turnContext));
}
var userLocale = await _userLocale.GetAsync(turnContext, () => turnContext.Activity.Locale);
turnContext.Activity.Locale = userLocale;
(turnContext as TurnContext).Locale = userLocale;
await next(cancellationToken).ConfigureAwait(false);
}
}
2 - Configure the middleware in the adapter in GetBotAdapter() in Startup.cs
public class Startup
{
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
this.HostingEnvironment = env;
this.Configuration = configuration;
}
//...
public BotFrameworkHttpAdapter GetBotAdapter(IStorage storage, BotSettings settings, UserState userState, ConversationState conversationState, IServiceProvider s)
{
var adapter = IsSkill(settings)
? new BotFrameworkHttpAdapter(new ConfigurationCredentialProvider(this.Configuration), s.GetService<AuthenticationConfiguration>())
: new BotFrameworkHttpAdapter(new ConfigurationCredentialProvider(this.Configuration));
adapter
.UseStorage(storage)
.UseBotState(userState, conversationState)
.Use(new RegisterClassMiddleware<IConfiguration>(Configuration))
.Use(new LocaleSelectionMiddleware(userState)) // <-- Add the middleware here
.Use(s.GetService<TelemetryInitializerMiddleware>());
//...
return adapter;
}
//...
}
3 - Set the user.locale property in any dialog
Set the user.locale property from any dialog, and the next turn will have the desired locale, and will be persisted in the user state, until they change it again.

Access IStatePropertyAccessor botstate in a controller in v4

If you need to access the state in botframework v4 outside of a bot context (that does not implement IBot), for instance a Controller, how could you easily get a hold of this state object? The problem is that you can't really inject it directly because it needs to be initialized with a ChannelId and ConversationId.
The following approach works but looks a bit strange as I am using the TestAdapter to initialize the TurnContext. Isn't there a better, more obvious method to get a hold of the state?
public class SampleController : ControllerBase
{
private readonly MyBotAccessors _botAccessors;
public SampleController(MyBotAccessors botAccessors)
{
_botAccessors = botAccessors;
}
}
public async Task Reset(string conversationKey, string channelId)
{
var turnContext = new TurnContext(new TestAdapter(), new Activity { ChannelId = channelId, Conversation = new ConversationAccount { Id = conversationKey } });
await _botAccessors.MyContextState.SetAsync(turnContext, new MyContextState().Reset());
await _botAccessors.ConversationState.SaveChangesAsync(turnContext);
}

Xamarin Android Share Link/Text via social media from custom renderer

I wan't to share a link via social media from custom renderer
public class CustomActions : ICustomActions
{
Context context = Android.App.Application.Context;
public void ShareThisLink()
{
Intent sharingInt = new Intent(Android.Content.Intent.ActionSend);
sharingInt.SetType("text/plain");
string shareBody = "https://www.google.com";
sharingInt.PutExtra(Android.Content.Intent.ExtraSubject, "Subject");
sharingInt.PutExtra(Android.Content.Intent.ExtraText, shareBody);
context.StartActivity(Intent.CreateChooser(sharingInt, "Share via"));
}
}
This error occur
Android.Util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
even when I added the below code I still get same error
sharingInt.AddFlags(ActivityFlags.NewTask);
The problem is that Intent.CreateChooser creates yet another Intent. What you want to do is to set the flag on this new intent:
public void ShareThisLink()
{
Intent sharingInt = new Intent(Android.Content.Intent.ActionSend);
sharingInt.SetType("text/plain");
string shareBody = "https://www.google.com";
sharingInt.PutExtra(Android.Content.Intent.ExtraSubject, "Subject");
sharingInt.PutExtra(Android.Content.Intent.ExtraText, shareBody);
var intent = Intent.CreateChooser(sharingInt, "Share via");
intent.AddFlags(ActivityFlags.NewTask);
context.StartActivity(intent);
}
Alternatively to avoid the need to do this, you could cache the MainActivity instance Xamarin.Forms uses:
public MainActivity
{
public static MainActivity Instance {get;private set;}
protected override void OnCreate(Bundle bundle)
{
Instance = this;
...
}
}
And then use the Instance as the Context in your code instead of the Application.Context

How can I unit test an AuthorizeWhereIn attribute with Ninject

I'm using a custom authorisation attribute (blatantly plagiarised from another SO answer) but have hit a hurdle where I can't find a way to unit test it. Unfortunately I do need to unit test is at the same time as I invoke my controller action so I'm trying to find a way to do the Ninject dependency injection in the unit test.
The AuthorizeWhereIn attribute is:
public class AuthorizeWhereIn : AuthorizeAttribute
{
/// <summary>
/// Add the allowed roles to this property.
/// </summary>
public new HCIRoles Roles;
/// <summary>
/// Checks to see if the user is authenticated and has the
/// correct role to access a particular view.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
// Make sure the user is authenticated.
if (!httpContext.User.Identity.IsAuthenticated)
return false;
// Get user's current roles
var roles = System.Web.Security.Roles.GetRolesForUser();
HCIRoles currentRoles = (HCIRoles)Enum.Parse(typeof(HCIRoles), string.Join(",", roles));
// Perform a bitwise operation to see if the user's role
// is in the passed in role values.
if (Roles != 0 && ((Roles & currentRoles) == 0))
return false;
return true;
}
}
The problem is the System.Web.Security.Roles.GetRolesForUser() call which isn't available in my unit test and which I want to mock any way. I can abstract that call into a separate interface and use Ninject to inject it for the web application but I can't find a way to do the same in a unit test.
So if I change the attribute to something like the below
public class AuthorizeWhereIn : AuthorizeAttribute
{
[Inject]
IRoleService RoleService { get; set; }
...
}
and my unit test code is along the lines of:
[TestMethod()]
public void IndexTest()
{
var builder = new TestControllerBuilder();
var controller = builder.CreateController<UserController>(dataLayer.Object);
var invoker = new ActionInvoker<UserController>();
var mockMembershipService = new Mock<IMembershipService>();
mockMembershipService.Setup(x => x.GetAllUsers(It.IsAny<int>(), It.IsAny<int>(), out total)).Returns(new MembershipUserCollection());
controller.MembershipService = mockMembershipService.Object;
builder.InitializeController(controller);
invoker.InvokeAction(controller.ControllerContext, x => x.Index());
}
And the controller being tested is:
[AuthorizeWhereIn(Roles = HCIRoles.Admin)]
public class UserController : BaseController
{
public ActionResult Index()
{
return View();
}
}
My question is how can I inject the RolseService depdency in the unit test given that I can't directly access the AuthroizeWhereIn attribute?
I've read and re-read the Ninject Filter extension for MVC3 http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/ but can't seem to apply it to this case.
given that I can't directly access the AuthroizeWhereIn attribute
Why not accessing it directly? That's what you are trying to test after all.
private class TestController : Controller { }
[TestMethod]
public void Test()
{
// arrange
var builder = new TestControllerBuilder();
var controller = new TestController();
builder.InitializeController(controller);
controller.ControllerContext = new ControllerContext(builder.HttpContext, new RouteData(), controller);
var httpContext = builder.HttpContext;
httpContext.Stub(x => x.Items).Return(new Hashtable());
var identity = new GenericIdentity("foo");
var roles = new string[0];
httpContext.User = new GenericPrincipal(identity, roles);
var ad = MockRepository.GeneratePartialMock<ActionDescriptor>();
var context = new AuthorizationContext(controller.ControllerContext, ad);
var sut = new AuthorizeWhereIn();
var service = MockRepository.GenerayeStub<IRoleService>();
sut.RoleService = service;
// TODO: set expectations on the service
// act
sut.OnAuthorization(context);
// assert
// TODO: assert on the type of context.Result
// If it is HttpUnauthorizedResult the authorization has failed
// (i.e. your custom AuthorizeCore method returned false)
}
A colleague ended up finding a clever solution using an extension method on IPrincipal. That way there's no need for dependency injection in the attribute since the HttpContext can be mocked with the Mvccontrib library.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
// Make sure the user is authenticated.
if (!httpContext.User.Identity.IsAuthenticated)
return false;
// Get user's current roles
//var roles = System.Web.Security.Roles.GetRolesForUser();
HCIRoles currentRoles = httpContext.User.GetRoles();
// Perform a bitwise operation to see if the user's role
// is in the passed in role values.
if (Roles != 0 && ((Roles & currentRoles) == 0))
return false;
return true;
}
public static HCIRoles GetRoles(this IPrincipal user)
{
HCIRoles roles = 0;
foreach (HCIRoles r in Enum.GetValues(typeof(HCIRoles)))
{
if (user.IsInRole(r.ToString()))
roles |= r;
}
return roles;
}

Resources