Search code examples
c#botframework

Resolve enum from a custom attribute property search


I have the following enum used in CallForm FormDialog:

public enum CallTimeOptions
{
    [Describe("tonight")]
    [Terms("tonight", "this evening", "pm", "this pm")]
    Tonight = 1,

    [Describe("tomorrow morning")]
    [Terms("tomorrow morning", "tomorrow am", "am")]
    TomorrowMorning,

    [Describe("tomorrow noon time")]
    [Terms("tomorrow noon time", "tomorrow noon", "tomorrow lunch", "lunch")]
    TomorrowNoonTime,

    [Describe("tomorrow evening")]
    [Terms("tomorrow evening", "tomorrow pm", "tomorrow night")]
    TomorrowEvening

};

I am calling my CallForm from a LUIS intent that can have a datetimeV2 so something like this:

[LuisIntent("Call")]
public async Task Call(IDialogContext context, LuisResult result)
{
    EntityRecommendation entityRecommendation;

    if (result.TryFindEntity("builtin.datetimeV2.datetimerange", out entityRecommendation))
    {
        context.UserData.SetValue<string>(ContextKeys.CallTimeOption, entityRecommendation.Entity);

    }   

    context.UserData.Call(new CallForm(), CallFormResumeAfter);          
}

Then I want to prefill it in StartAsync of CallForm but don't know how...

public async Task StartAsync(IDialogContext context)
{
    var state = new CallTimeForm();           
    string calltime;

    if(context.UserData.TryGetValue(ContextKeys.CallTimeOption, out calltime))
    {
        state.PreferredCallTime = -- is there a way to match calltime with CallTimeOptions ? --
    }

     var form = new FormDialog<CallTimeForm>(
                                  state,
                                  BuildForm,
                                  FormOptions.PromptInStart);

        context.Call(form, this.AfterBuildForm);
}

Solution

  • An example of how to do this is via reflection and Attribute.GetCustomAttribute

    Retrieves a custom attribute of a specified type applied to an assembly, module, type member, or method parameter.

    method

    public static class EnumEx
    {
       public static T GetValueFromTerms<T>(string value)
       {
          var type = typeof(T);
          if (!type.IsEnum)
          {
             throw new InvalidOperationException();
          }
    
          foreach (var field in type.GetFields())
          {
             if (Attribute.GetCustomAttribute(field, typeof(TermsAttribute)) is TermsAttribute attribute)
             {
                // Search your attribute array or properties here
                // Depending on the structure of your attribute 
                // you will need to change this if Conidition
                if (attribute.ContainsTerms(value))
                {
                   return (T)field.GetValue(null);
                }
             }
             else
             {
                if (field.Name == value)
                {
                   return (T)field.GetValue(null);
                }
             }
          }
          throw new ArgumentException("Not found.", nameof(value));
          // or return default(T);
       }
    }
    

    Usage

    var callTimeOptions = EnumEx.GetValueFromTerms<CallTimeOptions>("tomorrow noon time");
    

    However, I think you are not going about this the right way.

    A more future proofed system would be to use a Dictionary<string,CallTimeOptions> to look up search terms and map them to enums.

    I know attributes seems neat and might work well if you arnt going to expand this much, however a dictionary could be loaded form db or file, and it might just be easier to maintain (depending on your requirements)