Testing using ConfigurationManager with .net core

.net core has moved away from web.config and app.config but realistically it’s a pattern that many projects will still want to follow. Fortunately there is help in the form of the System.Configuration.ConfigurationManager NuGet package. However, there is a snag when it comes to using this package with unit testing, it doesn’t work 😉 The problem is that it goes looking for a .config file related to the test host rather than your test project. I’m currently working around this with a quick static hack but the correct answer is to inject a new config. You can correct the path like this;

var myConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;            
myConfig = myConfig.Replace("testhost.dll.config", "MyProjectTests.dll");
var config = ConfigurationManager.OpenExeConfiguration(myConfig);

Hope that helps.

Advertisements

Prevent PromptValidator from showing a prompt

The PromptValidator in the Bot Framework is a powerful mechanism for collecting form information. However, one slight snag with it is that it only allows a limited control of the flow, i.e. you can say true/false and you can alter the (re)prompt. In certain situations you may want the validator to restart your dialog. That’s relatively easy, you just call dialogContext.ReplaceDialogAsync(this.Id). The problem is that whilst the framework will honor the replace request it will also continue with the retry prompt, almost certainly not what you want. To workaround this problem you provide a non-textual prompt and everything appears good to the user. E.g.

promptContext.Options.Prompt = new Activity(type: ActivityTypes.Typing);

Call overhead with large number of dialogs in Bot Framework

When you look at the samples for the Bot Framework you’ll no doubt see a pattern for the root dialog that looks like this;

public MyBot(MyBotAccessors accessors, ILoggerFactory loggerFactory)
{
    Dialogs = new DialogSet(accessors.DialogState);
    Dialogs.Add(new DialogA());
    Dialogs.Add(new DialogB());
    ...
    Dialogs.Add(new DialogN());
}

The great thing about the above pattern is that it is nice and clean, you know that the dialog set will be correctly initialized and therefore when you call var dialogResult = await dialogContext.ContinueDialogAsync or await dialogContext.BeginDialogAsync everything will just work.

The problem with the above is that the MyBot constructor will be called for every message that the bot receives. Therefore, every new DialogX will also be called. If you have a reasonably feature rich bot then this overhead might become a problem, especially if the constructors are doing anything…I mean you never write your constructor to do anything slow…right? Regardless I wanted to see if the overhead could be avoided…it can.

public MyBot(MyBotAccessors accessors, ILoggerFactory loggerFactory)
{
    // Initialize like before
    Dialogs = new DialogSet(accessors.DialogState);
}

public async Task OnTurnAsync(ITurnContext turnContext)
{
    ...
    // (skipping code to focus)
    var dialogContext = await Dialogs.CreateContextAsync(turnContext);

    // the context is loaded from state, regardless of our instances
    // so we can see what the Active Dialog Id and...
    if (dialogContext.ActiveDialog?.Id == "DialogB")
    {
        // create in JIT 
        Dialogs.Add(new DialogB());
    }
    ...

    // remember you'll also have to JIT to invoke the dialog
    // don't add it twice
    Dialogs.Add(new DialogB());
    await dialogContext.BeginDialogAsync(nameof(DialogB));
}

Extracting an Entity from LUIS in Bot Framework

Recent changes to the supporting LUIS libraries in the Bot Framework have puzzled me. I find that whilst getting the Top Intent from the RecognizerResult recognizerResult?.GetTopScoringIntent(); is easy, getting hold of an Entity seems very strange. I’m not sure if it’s my SDK, LUIS models or what, but this is the code I’m currently having to use…and I confess I still find it all very odd;

public static class RecognizerResultExtensions
{
    public static T GetEntity<T>(this RecognizerResult luisResult, string entityKey)
    {
        if (luisResult != null)
        {
            var data = luisResult.Entities as IDictionary<string, JToken>;

            if (data.TryGetValue(entityKey, out JToken value))
            {
                // wuuuh?
                return value.First.First.Value<T>();
            }
        }

        return default(T);
    }
}

Hope it helps

How to write a unit test for Bot Framework v4

I’ve previously written a quick post showing a unit test for v4 preview release. I thought I’d update it.

Goal

Unit Test a dialog we’ve written that asks for a user first and second name.

Steps for creating a unit Test

  1. Create a unit test project in your solution, I chose an MS Test project
  2. Using Nuget install Microsoft.Bot.Builder.Dialogs into the MS Test project
  3. Reference your Bot project
  4. Create a unit test, remember to make it async Task
[TestMethod]
public async Task CreatingAGoodContact()
{
    var convoState = new ConversationState(new MemoryStorage());

    var adapter = new TestAdapter()
        .Use(new AutoSaveStateMiddleware(convoState));

    var dialogState = convoState.CreateProperty("dialogState");

    var dialogs = new DialogSet(dialogState);
    dialogs.Add(new CreateContactDialog(null));

    await new TestFlow(adapter, async (turnContext, cancellationToken) =>
    {
        var dc = await dialogs.CreateContextAsync(turnContext, cancellationToken);
        await dc.ContinueDialogAsync(cancellationToken);
        if (!turnContext.Responded)
        {
            await dc.BeginDialogAsync("CreateContactDialog", null, cancellationToken);
        }
    })
    .Send("Say something to start test")
    .AssertReply("What is their first name?")
    .Send("Jane")
    .AssertReply("What is their last name?")
    .Send("Tan")
    .AssertReply($"I have created a contact called Jane Tan")
    .StartTestAsync();
}

Practical Dialog State in large Bot Framework v4 projects

State seems to be large topic in v4. I’ve written posts about it before, including; Waterfall Step Patterns and Changes in State Handling, but after more exposure to v4 I coming around to the decision that for more complex projects I’m going to recommend reverting back to how v3 manages state…mostly.

The v4 way, or not?

Using the Accessor Pattern for state is great, it provides a sense of concrete design. You create a dialog, you create one or more state accessors that will be passed to it. The use of the state is very explicit. This is all good. So what’s the problem? The problem is that for complex bot projects your state is going to be filled with little islands of seemingly unrelated data. Consider a simple scenario of a New Contact dialog. You invoke the dialog, which in-turn (pun not intended) invokes a New Address dialog which can also be used from other dialogs. You would write something like;

IStatePropertyAccessor contactStatePropertyAccessor;
IStatePropertyAccessor addressStatePropertyAccessor;

But in a complex Bot Project you may have tens or hundreds of dialogs;

IStatePropertyAccessor s1StatePropertyAccessor;
IStatePropertyAccessor s2StatePropertyAccessor;
...
IStatePropertyAccessor sNStatePropertyAccessor;

That’s not cool.

What about a Generic State Accessor?

So given we want to mitigate all these state islands, could we have a generic accessor? Short answer is, “yes”, actual answer is, “too dirty”.

IStatePropertyAccessor activeDialogStatePropertyAccessor;

Every dialog gets passed the same single accessor and they store their data in it. Ah but what our chain of dialogs, when we invoke our New Address won’t that overwrite the current New Contact state? Yes. So we’ll have to look at creating some form of dictionary/hash to avoid…hang on, this seems a lot like the Active Dialog state from v3. Is that still available?

Active Dialog State

Active Dialog State is the framework supplied state that follows the dialog stack. Or to put it another way, we don’t have to worry about the dialog stack. So how do we gain access to this in v4? Overrides;

public override Task BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
    this.dialogContext = outerDc;
    this.SaveState();
    return base.BeginDialogAsync(outerDc, options, cancellationToken);
}

public override Task ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default(CancellationToken))
{
    this.dialogContext = outerDc;
    this.InitializeFromState();
    return base.ContinueDialogAsync(outerDc, cancellationToken);
}

In this example I’ve duplicated the handling of the state per class, but making this generic with clones or actions would be the way forward;

private void SaveState()
{
    // EDIT: DO NOT use 'this', its like creating a mini sub stack
    // and confuses the dialog rehydration. Use smaller struct/class
    this.dialogContext.ActiveDialog.State["ObjectState"] = this;
}

private void InitializeFromState()
{
    if (this.dialogContext.ActiveDialog.State.TryGetValue("ObjectState", out object rawObject))
    {
        var dialog = contactDialogObject as NewContactDialog;
        if (dialog != null)
        {
            if (dialog != null)
            {
                this.FirstName = dialog.FirstName;
                this.Telephone = dialog.Telephone;
            }
        }
    }
}

So now when the dialog starts it fetches its own state from the Active Dialog (itself), and updates its properties. So we are now at roughly the same point we would be in v3, i.e. we’ve been re-hydrated. We now need to update the state when a values changes;

private async Task PromptForTelephoneStepAsync(
                                        WaterfallStepContext stepContext,
                                        CancellationToken cancellationToken)
{
    // we've passed the validation so save the result
    this.Name = stepContext.Result.ToString();
    this.SaveState();
    ...

That’s it. We now have a fully working state engine for dialogs that doesn’t leave us with lots of data islands. Note, the above can be tidied up through the use of inheritance, generics, etc. Also note that you would need to implement other overrides to support restarting the flow, etc.

I should caution that I don’t know about the limits of state, perhaps I will discover a size restriction, we’ll see.

“Prompt options are required for Prompt dialogs” in Bot Framework

A quick reminder style post. If you get this strange sounding exception; Prompt options are required for Prompt dialogs

Then you’ve probably made the same mistake I did and forgot to add the waterfall steps to the dialogset;

 var waterfallSteps = new WaterfallStep[]
            {
                    InitializeStateStepAsync,
                    PromptForDescriptionStepAsync,
                    PromptForTopicStepAsync,
                    DisplaySummaryStepAsync,
            };
// * forgot to add this line 
// AddDialog(new WaterfallDialog("DescAndTopicDialog", waterfallSteps));
AddDialog(new TextPrompt(DescriptionPrompt, ValidateDescriptionAsync));
AddDialog(new TextPrompt(TopicPrompt, ValidateTopicAsync));