Accueil Nos publications Blog [ASP.NET Core] Ecriture de commandes

[ASP.NET Core] Ecriture de commandes

ASP.NET logoDans ce billet, je vous propose de découvrir la classe CommandLineApplication. Celle-ci est apparue avec ASP.NET Core, mais ne permet pourtant pas directement de développer une application web. En fait, elle va être au centre de petits utilitaires qui eux-mêmes contribueront au développement de vos projets web.

Attention, cet article a été écrit à partir de la beta7 d’ASP.NET Core. Les APIs décrites ici sont encore sujettes à changement d’ici la release finale de la plate-forme.

A quoi correspondent les commandes DNX ?

L’un des éléments de base de DNX, que vous utilisez probablement déjà sans le savoir lorsque vous développez avec ASP.NET 5, est son mécanisme de commandes.

En effet, dès lors que vous exécutez une application ASP.NET Core avec son fichier project.json afin de rechercher les enfants du noeud commands.

En prenant pour exemple le fichier project.json qui est créé avec le modèle d’application vide ASP.NET 5 actuel, le noeud commands est celui ci-dessous.

  "commands": {
    "web": "Microsoft.AspNet.Hosting --config hosting.ini"
  }

Chaque enfant de ce noeud est composé de deux éléments, une clé, ici web, et la commande à exécuter Microsoft.AspNet.Hosting –config hosting.ini.

Microsoft.AspNet.Hosting représente ici le nom d’une application console à exécuter. Le reste de la commande, –config hosting.ini, représente les paramètres à passer à l’application console qui sera exécutée.

Pour résumer, lorsque vous tapez dnx web ou que vous sélectionnez web dans la liste déroulante des commandes de Visual Studio 2015, vous allez tout simplement exécuter une application appelée Microsost.AspNet.Hosting en lui passant la chaîne d’arguments –config hosting.ini.

Notez que je parle ici d’application console, mais il s’agit en fait d’une application console au sens ASP.NET Core du terme. C’est-à-dire que si vous recherchez le paquet Microsoft.AspNet.Hosting dans votre cache local de paquets (C:\Users\Leo.dnx\packages sur mon poste), vous ne trouverez pas un fichier Microsoft.AspNet.Hosting.exe mais un fichier Microsoft.AspNet.Hosting.dll. Cependant, si vous ouvrez cette dernière avec votre décompilateur préféré, vous y trouverez une classe Program et sa méthode Main, exactement avec la même signature que le point d’entrée d’une application console classique.

Faîtes le test ! Créez une application console ASP.NET Core (il y a un modèle de projet adapté) et regardez ce qui est produit en sortie d’une compilation …

Le cas d’une application console utilisée en commande DNX a une autre particularité puisque, l’application, se fait alors automatiquement injecter tout un lot d’informations sur le contexte d’exécution et sur les arguments affectés à la commande. En effet, nous sommes dans ASP.NET Core donc même une application console hérite du conteneur IoC global et de l’injection de dépendances.

Créer sa propre commande

Si dans votre contexte de projet, vous rencontrez le besoin de créer votre propre commande, par exemple pour automatiser une tâche de développement classique, la procédure à suivre est assez simple.

Commencez par créer une application console ASP.NET Core.

Dans le constructeur de la classe Program, vous pouvez faire injecter tous les services dont vous avez besoin. Ecrivez ensuite toute la logique de l’application dans la méthode Main.

Dans mon exemple, j’ai choisi de lire quelques éléments depuis un fichier de configuration. Rien ne m’empêche d’ajouter quelques paquets dans mon fichier project.json, comme je le ferais dans une application web ASP.NET Core, en l’occurrence Microsoft.Framework.Runtime.Abstractions et Microsoft.Framework.Configuration.Json.

  "dependencies": {
    "Microsoft.Framework.Runtime.Abstractions": "1.0.0-beta7",
    "Microsoft.Framework.Configuration.Json": "1.0.0-beta7"
  }

Ensuite, je complète ma classe Program ci-dessous. Remarquez bien que j’ai pu faire injecter une instance de IApplicationEnvironment et construire un ConfigurationBuilder, tout comme dans une application web classique.

public class Program
{
    private readonly IApplicationEnvironment _applicationEnvironment;

    public Program(IApplicationEnvironment applicationEnvironment)
    {
        _applicationEnvironment = applicationEnvironment;   
    }

    public void Main(string[] args)
    {
        var configurationBuilder = new ConfigurationBuilder(_applicationEnvironment.ApplicationBasePath);

        configurationBuilder.AddJsonFile("configuration.json");

        var configuration = configurationBuilder.Build();

        Console.WriteLine("Hello :)");

        Console.ReadLine();
    }
}

Maintenant je vais ajouter un autre projet à ma solution, un projet web cette fois-ci.

Puis, je vais éditer son fichier project.json, afin d’ajouter le support d’une nouvelle commande basée sur l’application console que j’ai pu écrire juste avant.

  "commands": {
    "web": "Microsoft.AspNet.Hosting --config hosting.ini",
    "test": "ConsoleApp2"
  }

A présent, la commande test est automatiquement visible dans la liste déroulante de Visual Studio. Un clic nous permet de démarrer son exécution, une fenêtre CLI apparaît alors, et le message Hello 🙂 devient visible.

Recevoir les arguments passés à la commande

Notez que je référence ici le paquet dans sa version beta6. En effet, à l’heure où j’écris ces lignes, il n’est pas encore disponible dans une version plus récente. Cela dit, la version beta6 de ce paquet se mélange sans soucis avec les autres paquets labellisés beta7.

  "dependencies": {
    "Microsoft.Framework.Runtime.Abstractions": "1.0.0-beta7",
    "Microsoft.Framework.Configuration.Json": "1.0.0-beta7",
    "Microsoft.Framework.CommandLineUtils.Sources": "1.0.0-beta6"
  }

Cela nous donne alors accès à la classe CommandLineApplication. Je crée ici une instance de cette classe en initialisant différentes propriétés pour décrire la commande que je suis en train d’écrire. Je passe une lambda à la méthode OnExecute. Cette lambda sera exécutée dès lors que ma commande est invoquée sans argument. Enfin, la méthode Execute de la classe CommandLineApplication est la méthode qui encapsule toute la logique pour créer facilement une application console. Pour que tout fonctionne bien de bout en bout, il est nécessaire de lui passer les arguments reçus en paramètres de la méthode Main.

public class Program
{
    private readonly IApplicationEnvironment _applicationEnvironment;

    public Program(IApplicationEnvironment applicationEnvironment)
    {
        _applicationEnvironment = applicationEnvironment;
    }

    public void Main(string[] args)
    {
        var configurationBuilder = new ConfigurationBuilder(_applicationEnvironment.ApplicationBasePath);

        configurationBuilder.AddJsonFile("configuration.json");

        var configuration = configurationBuilder.Build();

        var app = new CommandLineApplication
        {
            Name = "ma-commande",
            Description = "Une commande pour découvrir les commandes avec DNX",
            FullName = "Ma commande de test"
        };

        app.OnExecute(() =>
        {
            app.ShowHelp();

            return 2;
        });

        app.Execute(args);

        Console.ReadLine();
    }
}

Jusque-là, rien d’extraordinaire… Maintenant, nous allons voir comment rendre notre application réellement fonctionnelle. Cela passe par l’ajout de commandes, qui correspondent à des switch pour choisir la fonctionnalité de l’application à invoquer. Sur la classe CommandLineApplication, il suffit d’appeler la méthode Command en nommant cette dernière et en lui passant une lambda. Comme pour la classe CommandLineApplication, c’est via cette lambda que l’on peut configurer la commande et décrire son fonctionnement via la méthode OnExecute.

Notez que j’ai ajouté un argument à la commande simplement via la méthode Argument. La valeur reçue en entrée de l’application sera alors automatiquement disponible via la propriété Value de l’objet retourné par l’appel à Argument.

Tous les appels à ces méthodes Command, Argument, etc. permettent en fait de configurer le parseur des arguments reçus par l’application console.

public class Program
{
    private readonly IApplicationEnvironment _applicationEnvironment;

    public Program(IApplicationEnvironment applicationEnvironment)
    {
        _applicationEnvironment = applicationEnvironment;
    }

    public void Main(string[] args)
    {
        var configurationBuilder = new ConfigurationBuilder(_applicationEnvironment.ApplicationBasePath);

        configurationBuilder.AddJsonFile("configuration.json");

        var configuration = configurationBuilder.Build();

        var app = new CommandLineApplication
        {
            Name = "ma-commande",
            Description = "Une commande pour découvrir les commandes avec DNX",
            FullName = "Ma commande de test"
        };

        app.Command("configuration", c =>
        {
            c.Description = "Lit une valeur depuis le fichier de configuration de l'application";

            var configurationKeyArgument = c.Argument("[clé]", "Clé de configuration à lire");

            c.OnExecute(() =>
            {
                Console.WriteLine(configuration[configurationKeyArgument.Value]);

                return 0;
            });
        });

        app.OnExecute(() =>
        {
            app.ShowHelp();

            return 0;
        });

        app.Execute(args);

        Console.ReadLine();
    }
}

Je peux maintenant invoquer mon application console de la manière suivante : ConsoleApp2 configuration test, où test est une clé dans le fichier de configuration de l’application.

Notez que si je lance l’application sans préciser un nom de commande, l’écran d’aide s’affiche toujours automatiquement, car c’est le comportement que nous avions spécifié dans la lambda passée à OnExecute.

Ma commande de test

Usage: ma-commande [command]

Commands:
  configuration  Lit une valeur depuis le fichier de configuration de l'applicat
ion

Dans l’exemple ci-dessous, j’ai ajouté une option facultative grâce à la méthode Option. J’ai branché cette option sur les commutateurs -u et –upperize** et ajouté le support des commutateurs *-?, -h et –help afin d’afficher l’aide de la commande configuration.

public class Program
{
    private readonly IApplicationEnvironment _applicationEnvironment;

    public Program(IApplicationEnvironment applicationEnvironment)
    {
        _applicationEnvironment = applicationEnvironment;
    }

    public void Main(string[] args)
    {
        var configurationBuilder = new ConfigurationBuilder(_applicationEnvironment.ApplicationBasePath);

        configurationBuilder.AddJsonFile("configuration.json");

        var configuration = configurationBuilder.Build();

        var app = new CommandLineApplication
        {
            Name = "ma-commande",
            Description = "Une commande pour découvrir les commandes avec DNX",
            FullName = "Ma commande de test"
        };

        app.Command("configuration", c =>
        {
            c.Description = "Lit une valeur depuis le fichier de configuration de l'application";

            var upperizeOption = c.Option("-u|--upperize", "Passe la valeur en majuscule", CommandOptionType.NoValue);

            var configurationKeyArgument = c.Argument("[clé]", "Clé de configuration à lire");

            c.HelpOption("-?|-h|--help");

            c.OnExecute(() =>
            {
                if(upperizeOption.HasValue())
                {
                    Console.WriteLine(configuration[configurationKeyArgument.Value].ToUpper());
                }
                else
                {
                    Console.WriteLine(configuration[configurationKeyArgument.Value]);
                }

                return 0;
            });
        });

        app.OnExecute(() =>
        {
            app.ShowHelp();

            return 2;
        });

        app.Execute(args);

        Console.ReadLine();
    }
}

Ainsi, si je lance mon application de la manière suivante : ConsoleApp2 configuration -?, la sortie suivante est affichée.

Usage: ma-commande configuration [arguments] [options]

Arguments:
  [clé]  Clé de configuration à lire

Options:
  -u|--upperize  Passe la valeur en majuscule
  -?|-h|--help   Show help information

Conclusion

Je pense avoir fait le tour des éléments les plus importants de la classe CommandLineApplication. Avec du recul, je trouve cela assez étonnant qu’il ait fallu attendre 2015 pour disposer enfin d’une classe du Framework .NET pour nous permettre de créer des applications console de manière aussi simple et avec un parseur d’arguments digne de ce nom.

Quoiqu’il en soit, le fait que l’écosystème d’ASP.NET Core ne soit pas uniquement centré autour de Visual Studio redonne toute son importance à des utilitaires écrits et distribués sous la forme d’applications console. Nul doute que vous rencontrerez le besoin de créer vos propres utilitaires lors du développement de vos projets.