Commander Cortana en C#

Classiquement, lorsque l’on veut permettre le lancement de notre application directement par le biais de Cortana on ajoute un fichier de VoiceCommandSet à notre projet que l’on envoi au système. Suite à des discussions avec des amis développeurs et à la question d’Etienne Deneuve sur le Grenier de la Communauté Windows, je me suis rendu compte qu’utiliser un XML statique pouvais poser problème dans certains cas d’applications plus dynamique. Vous l’avez compris, aujourd’hui, nous allons nous pencher sur le problème.

Fonctionnement

En réalité, nous allons tricher pour arriver à nos fins *rire démoniaque*. L’idée est très simple, créer un peu de code représentant l’architecture du XML mais directement avec des objets C#, nous sérialiserons ces objets directement avec une petite fonction en utilisant une petite nouveauté de C# 6 (voir Interpolated String sur le msdn) et nous enregistrerons tout cela dans un fichier temporaire pour pousser le dit fichier à Cortana. Le seul avantage de cette technique et d’être modulaire et de créer le XML dynamiquement. Le code que je vous présente a été réalisé très rapidement afin de répondre à la question d’Etienne Deneuve et de tester la faisabilité de la chose, à vous de l’améliorer si l’envie vous en prend J.

Coding Time

Lorsque l’on regarde la forme du XML pour Cortana nous pouvons remarquer assez rapidement une architecture assez simple qui se dégage.

  • VoiceCommand : La racine du fichier
    • CommandSet : Un catalogue de Commande par langue
      • Command : Une commande vocale

Nous allons donc partir pour faire un modèle pour chaque élément de notre architecture et lui faire une petite fonction Serialize() qui aura pour rôle de nous fournir le xml correspondant à son contenu.

Commençons donc par le modèle d’une commande :

public class CommandModel
{
    public string Name { get; }
    public string Example { get; }
    public string ListenFor { get; }
    public string Feedback { get; }

    public CommandModel(string name, string example, string listenFor, string feedback)
    {
        this.Name = name;
        this.Example = example;
        this.ListenFor = listenFor;
        this.Feedback = feedback;
    }

    public string Serialize()
    {
        return $"<Command Name=\"{this.Name}\"><Example>{this.Example}</Example><ListenFor>{this.ListenFor}</ListenFor><Feedback>{this.Feedback}</Feedback><Navigate/></Command>";
    }
}

Continuons avec le modèle d’un CommandSet :

public class CommandSetModel
{
    public string Language { get; }
    public string Name { get; }
    public string CommandPrefix { get; }
    public string Example { get; }
    public List<CommandModel> Commands { get; } = new List<CommandModel>();

    public CommandSetModel(string language, string name, string commandPrefix, string example)
    {
        this.Language = language;
        this.Name = name;
        this.CommandPrefix = commandPrefix;
        this.Example = example;
    }

    public string Serialize()
    {
        StringBuilder sb = new StringBuilder($"<CommandSet xml:lang=\"{this.Language}\" Name=\"{this.Name}\"><CommandPrefix>{this.CommandPrefix}</CommandPrefix><Example>{this.Example}</Example>");
        Commands.ForEach(model => sb.Append(model.Serialize()));
        return sb.Append("</CommandSet>").ToString();
    }
}

Et pour finir :

public class VoiceCommandsModel
{
    public List<CommandSetModel> Sets { get; } = new List<CommandSetModel>();

    public string Serialize()
    {
        StringBuilder sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"utf-8\"?><VoiceCommands xmlns=\"http://schemas.microsoft.com/voicecommands/1.1\">");
        Sets.ForEach(model => sb.Append(model.Serialize()));
        return sb.Append("</VoiceCommands>").ToString();
    }
}

Voilà, ça y est, nous avons notre architecture, plus qu’à faire le morceau de code qui enregistre le tout et l’envoi directement à Cortana !

public static async void RegisterVoiceCommandsModel(VoiceCommandsModel model)
{
    byte[] fileBytes = System.Text.Encoding.UTF8.GetBytes(model.Serialize().ToCharArray());

    StorageFile file = await ApplicationData.Current.TemporaryFolder.CreateFileAsync("tempCortana.xml", CreationCollisionOption.ReplaceExisting);

    using (var stream = await file.OpenStreamForWriteAsync())
    {
        stream.Write(fileBytes, 0, fileBytes.Length);
    }

    await Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(file);
}

Nous avons tout en place, plus qu’a le tester non ? Allez c’est parti pour un petit test dans le app.xaml.cs

public void InstallCortana()
{
    VoiceCommandsModel vc = new VoiceCommandsModel();
    CommandSetModel cs = new CommandSetModel("fr-fr", "CafeParisienCommandSet_fr-fr", "Valentin, ", "Je veux un café");
    vc.Sets.Add(cs);
    cs.Commands.Add(new CommandModel("test1", "Je veux un café", "Je veux un café", "Une minute, nous allons trouver ça."));
    CortanaHelper.RegisterVoiceCommandsModel(vc);
}

Plus qu’à appeler cette fonction dans le OnLaunched et ça y est, c’est magique, votre Cortana appelera votre application si vous lui dites « Valentin, je veux un café » … Oui, je suis toujours stagiaire dans ma tête ! 🙂

PS : Vous pouvez télécharger le sample du code directement ici : https://dl.dropboxusercontent.com/u/13395728/CortanaSample.zip

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *