Search code examples
c#botframeworkconstantsazure-language-understanding

Bot Framework - Luis Dialog - Set the LuisModel attribute from Web.config


I am writing a chat bot in Microsoft Bot Framework based on LUIS. (This is my first time using LUIS). I have a basic LuisDialog class where I would like to set the LuisModel attribute. The problem is, defining the LuisModel requires constant values. I would like to retrieve these values from the Web.config file.

This fails, and I get an error message saying "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type". (It is because of retrieving the values from the Web.config file.)

Is there a way to retrieve these values from Web.config file and set them in the attribute?

I tried the following solution but it was not successful:

[Serializable]
[LuisModel(Constants.LuisModelId, Constants.LuisSubscriptionKey)]
public class LuisDialog : LuisDialog<object>
{
    //...
}

internal static class Constants
{
    internal const string LuisModelId = WebConfigurationManager.AppSettings.Get("LuisModelId");
    internal const string LuisSubscriptionKey = WebConfigurationManager.AppSettings.Get("LuisSubscriptionKey");
}

Solution

  • You can't set them directly on the attributes from the web.config since attributes are evaluated at compile time.

    However, you CAN do it. The LuisDialog class constructor takes a parameter of ILuisService.

    public LuisDialog(params ILuisService[] services)
    

    Now, ILuisService, has a default implementation named appropriately, LuisService which has a constructor that takes an ILuisModel paramter

    public LuisService(ILuisModel model)
    

    Bear with me, I'm getting to the good stuff. ILuisModel's is implemented in the class LuisModelAttribute.

    public class LuisModelAttribute : Attribute, ILuisModel, ILuisOptions, IEquatable<ILuisModel>
    

    It's in LuisModelAttributes constructor that you should start to see where this is going

    public LuisModelAttribute(string modelID, string subscriptionKey,
            LuisApiVersion apiVersion = LuisApiVersion.V2, string domain = null, double threshold = 0.0d)
    

    This constructor takes a modelId and subscriptionKey! This is just what you're looking for. Now, the next question is how do you put all of this together? The answer is dependency injection using autofac.

    I typically declare a new Autofac model class for all my DI registrations.

    internal sealed class MainModule : Module
    

    The first thing we need to register is an instance of the LuisModelAttribute

    var luisModelAttr = new LuisModelAttribute(ConfigurationManager.AppSettings["luis:AppId"],
        ConfigurationManager.AppSettings["luis:ServiceKey"],
        LuisApiVersion.V2,
        ConfigurationManager.AppSettings["luis:APIHostName"])
    {
        BingSpellCheckSubscriptionKey = ConfigurationManager.AppSettings["luis:BingSpellCheckKey"],
        SpellCheck = true
    }
    

    This didn't register anything, it just created an instance of the LuisModelAttribute class. NOW, register it.

    builder.Register(c => luisModelAttr).AsSelf().AsImplmentedInterface().SingleInstance();
    

    Next, register your Luis dialog, you will need to include ILuisService in the constructor paramters

    builder.Register(c => new MyLuisDialog(c.Resolve<ILuisService>())
        .As<IDialog<IMessageActivity>>()
        .InstancePerDependency();
    

    One last registration for the LuisService, or in our case ILuisService

    builder.RegisterType<LuisService>()
        .Keyed<ILuisService>(FiberModule.Key_DoNotSerilialize)
        .AsImplementedInterface()
        .SingleInstance();
    

    So, what happened here? Because LuisModelAttribute implements ILuisModel, Autofac will know to include our LuisModelAttribute as the parameter for LuisService, and will know to include the LuisService, which implements ILuisService, into our luis dialog, MyLuisDialog.

    You specific implementation may vary but this example will get you what you need. Good luck!