tutoriels

Prompter dynamiquement une commande CL IBM i/AS400 avec Blazor et NTi

ParQuentin DESTRADE

image d’illustration de l’article

Contenu détaillé de l’article:Prompter dynamiquement une commande CL IBM i/AS400 avec Blazor et NTi

Générer dynamiquement des interfaces de saisie pour n'importe quelle commande CL IBM i avec Blazor et C#, grâce aux méthodes RetrieveCommandDefinition() et RetrieveCommandDefinitionXml() intégrées à NTi Toolbox extension. Une approche moderne et générique, en complément des écrans 5250.

Lorsqu'il s'agit de lancer des commandes CL sur IBM i, la plupart des utilisateurs passent encore par les écrans verts traditionnels (5250). Ces interfaces, bien qu’efficaces pour des initiés, sont souvent complexes et peu intuitives pour des utilisateurs ayant peu ou pas de compétence IBM i. Des solutions existent aujourd’hui pour moderniser, simplifier et enrichir l'expérience utilisateur.

Avec NTi et son extension Toolbox, disponible sous forme de package NuGet, il est possible d'interroger directement l'API système QCDRCMDD, fournie par IBM, pour récupérer automatiquement la description complète d'une commande CL.

La méthode RetrieveCommandDefinition() encapsule complètement cette opération, en effectuant automatiquement:

  • L'appel de l'API
  • La récupération du flux XML (CDML) de la commande
  • La désérialisation de ce flux en un objet C# fortement typé, directement utilisable dans les applications .NET.

Selon les besoins, deux approches sont possibles :

  • Récupérer directement le XML brut avec RetrieveCommandDefinitionXml() pour une exploitation personnalisée, notamment via des API ou des outils d'analyse externes.

Flux XML du CL CRTLIB retourné par QCDRCMDD

  • Utiliser l'objet C# structuré renvoyé par RetrieveCommandDefinition() pour générer automatiquement et dynamiquement des interfaces utilisateur modernes, notamment avec Blazor comme illustré dans ce tutoriel.

Objet CmdDefinition désérialisé en C#

Ainsi, à partir d'une seule instruction:

CommandDefinition commandDef = conn.RetrieveCommandDefinition("CRTLIB", "QSYS", false);

On obtient une description complète et exploitable immédiatement, comprenant notamment :

  • La liste exhaustive des paramètres (obligatoires et optionnels)
  • Leurs types précis (texte, numérique, qualifié, liste, etc.)
  • Les valeurs par défaut, spéciales et uniques
  • Toutes les dépendances conditionnelles (Control Prompt) entre les paramètres
  • Les sous-éléments détaillés de chaque paramètre complexe

Cette capacité à générer dynamiquement la structure des commandes CL ouvre la voie à de nouveaux usages : création de formulaires interactifs avec pré-remplissage automatique, automatisation avancée des validations de paramètres, et génération automatique de commandes CL prêtes à l'emploi.

💡 Le code présenté dans cet article est fourni à titre indicatif uniquement. Il ne constitue pas une solution définitive ni optimisée pour tous les contextes d'utilisation. Il illustre simplement ce qu'il est possible de réaliser avec les méthodes de NTi Toolbox et Blazor.

Le scénario : créer une interface dynamique pour n'importe quelle commande CL

Pour illustrer concrètement ces possibilités, ce tutoriel s'appuie sur une Blazor Web App (.NET 8) avec NTi Data Provider et l'extension Toolbox. L'objectif est de construire une interface dynamique et générique permettant :

  • De charger la structure détaillée de n'importe quelle commande CL (standard ou personnalisée)
  • De générer automatiquement les champs de saisie adaptés (texte, liste déroulante, cases à cocher, etc.)
  • De construire dynamiquement la syntaxe CL complète prête à l'emploi
  • D'exécuter cette commande validée directement côté IBM i

Le tout présenté dans une interface moderne, avec le design Carbon d’IBM.

Formulaire Blazor généré pour le CL CRTLIB

Étape 1 - Charger dynamiquement la structure d'une commande

La première étape consiste à charger dynamiquement la structure complète d'une commande CL saisie par l'utilisateur. Cette structure est ensuite exploitée pour générer automatiquement les champs du formulaire de saisie.

Configuration préalable :

  • Installer le package NuGet Aumerial.Data.Nti
  • Installer le package NuGet Aumerial.Toolbox
  • Instancier une connexion NTiConnection pointant vers l'IBM i

Une fois cette configuration faite, l'application peut interroger le système pour récupérer la description complète d'une commande. Dès que l'utilisateur saisit le nom d'une commande (par exemple CRTLIB), l'application Blazor interroge l'API IBM i via NTi et récupère toutes les informations nécessaires à la construction dynamique du formulaire.

Déclaration des structures de données internes

Dans le code du composant Blazor, plusieurs variables permettent de gérer l'état du formulaire et de stocker les valeurs saisies :

// Objet principal contenant la définition complète de la commande
private CommandDefinition? Command;

// La commande actuellement sélectionnée
private Command? SelectedCommand;

// Stockage des valeurs simples associées à chaque mot-clé (Kwd)
private Dictionary<string, string> CommandValues = new();

// Gestion des paramètres qualifiés (ex : Fichier(NOM/LIB))
private Dictionary<string, Dictionary<string, string>> QualifierValues = new();

// Permet de saisir une valeur personnalisée (override)
private Dictionary<string, Dictionary<string, string>> QualifierCustom = new();

// Gestion des sous-éléments d'un paramètre complexe de type ELEM
private Dictionary<string, Dictionary<string, string>> ElementValues = new();

// Gestion des sélections multiples via des cases à cocher
private Dictionary<string, Dictionary<string, bool>> CheckedValues = new();

Ces dictionnaires permettent de gérer de manière indépendante chaque type de donnée associée aux différents paramètres.

Récupération et initialisation dynamique des données

Lorsqu'un utilisateur saisit une commande et valide, la méthode RetrieveCommandDefinition() est appelée :

try
{
    // Appel de la méthode RetrieveCommandDefinition() depuis NTiConnection via Toolbox
    Command = await Task.Run(() => DBService.Conn.RetrieveCommandDefinition(CommandName.ToUpper(), "QSYS", true));
    // Vérification qu'une commande valide a été retournée
    if (Command != null && Command.CmdDefinition.Any())
    {
        // Sélection de la première commande retournée
        SelectedCommand = Command.CmdDefinition.FirstOrDefault();
        if (SelectedCommand != null)
        {
            // Réinitialisation des dictionnaires avant utilisation
            CommandValues.Clear();
            QualifierValues.Clear();
            QualifierCustom.Clear();
            CheckedValues.Clear();
            ElementValues.Clear();
            // Parcours des paramètres pour initialisation individuelle
            foreach (var param in SelectedCommand.Parameters)
            {
                // Stockage de la valeur par défaut ou vide si non définie
                CommandValues[param.Kwd] = param.Dft ?? string.Empty;
                // Initialisation des checkboxes pour les paramètres à sélections multiples
                if (param.SpecialValues.Any() && int.TryParse(param.Max, out int max) && max > 1)
                {
                    CheckedValues[param.Kwd] = new Dictionary();
                    foreach (var spcVal in param.SpecialValues.SelectMany(s => s.SpecialValues))
                    {
                        CheckedValues[param.Kwd][spcVal.Value] = false;  // aucune sélection cochée par défaut
                    }
                }
                // Initialisation des paramètres qualifiés (type QUAL, ex: FILE(NOM/LIB))
                QualifierValues[param.Kwd] = new Dictionary();
                QualifierCustom[param.Kwd] = new Dictionary();

                int qualIndex = 0;
                foreach (var qual in param.Qualifiers)
                {
                    var key = !string.IsNullOrEmpty(qual.Prompt) ? qual.Prompt : $"{qual.Type}_{qualIndex++}";
                    
                    // Si le paramètre est de type QUAL, initialiser les valeurs à vide par défaut
                    QualifierValues[param.Kwd][key] = param.Type == "QUAL" ? "" : (qual.Dft ?? "");
                    QualifierCustom[param.Kwd][key] = ""; // toujours vide initialement pour une éventuelle saisie utilisateur
                }

                // Initialisation des sous-éléments pour les paramètres complexes (type ELEM)
                ElementValues[param.Kwd] = new Dictionary();
                foreach (var elem in param.Elements)
                {
                    ElementValues[param.Kwd][elem.Prompt] = elem.Dft ?? string.Empty;
                }
            }
        }
    }
}
catch (Exception ex)
{
    errorMessage = ex.Message;
}

Une fois la structure complète récupérée, on parcourt chaque paramètre retourné par l'API pour déterminer :

  • Les valeurs par défaut à afficher dans les champs simples
  • Les options multiples (cases à cocher) et leur état initial (décochées par défaut)
  • Les éventuelles valeurs qualifiées (nom/bibliothèque) à présenter
  • Les sous-éléments des paramètres complexes à afficher distinctement

Interface Blazor dynamique avec champs dépendants

Cette démarche garantit d'avoir immédiatement à disposition toutes les informations nécessaires, structurées et isolées dans les dictionnaires, pour construire dynamiquement une interface adaptée à chaque commande CL, quelle que soit sa complexité.

👉 Pour plus de détails sur la structure retournée, vous pouvez consulter la documentation de la méthode RetrieveCommandDefinition.

Étape 2 - Générer dynamiquement les champs de saisie

Une fois la structure de la commande CL chargée et les dictionnaires d'état initialisés, on peut générer dynamiquement les champs de saisie adaptés à chaque paramètre dans l'interface Blazor.

On parcourt chaque paramètre issu de SelectedCommand.Parameters et on applique une logique conditionnelle pour afficher le bon composant en fonction du type et des contraintes définies côté IBM i : type simple, valeurs spéciales, paramètre qualifié, éléments imbriqués, sélection multiple, etc.

Une logique pilotée par les métadonnées

Chaque paramètre est traité dans une boucle @foreach, et selon ses propriétés (Type, SpecialValues, Elements, etc.), un rendu spécifique est généré :

@foreach (var param in SelectedCommand.Parameters)
{
    if (param.Type == "QUAL")
    {
        foreach (var key in QualifierValues[param.Kwd].Keys)
        {
            <input class="bx--text-input" @bind="QualifierValues[param.Kwd][key]" placeholder="@key" />
        }
    }
    else if (param.SpecialValues.Any())
    {
        <select class="bx--select-input" @bind="CommandValues[param.Kwd]">
            <option value="">Choisir</option>
            @foreach (var val in param.SpecialValues.SelectMany(s => s.SpecialValues))
            {
                <option value="@val.Value">@val.Value</option>
            }
        </select>
    }
    else if (param.Elements.Any())
    {
        <fieldset>
            @foreach (var elem in param.Elements)
            {
                <input class="bx--text-input" @bind="ElementValues[param.Kwd][elem.Prompt]" placeholder="@elem.Prompt" />
            }
        </fieldset>
    }
    else if (param.Type == "CHAR")
    {
        <input class="bx--text-input" @bind="CommandValues[param.Kwd]" placeholder="@param.Prompt" maxlength="@param.Len" />
    }
}

Génération dynamique des champs d’entrée CL

Le rendu s'adapte au comportement attendu côté IBM i grâce aux métadonnées renvoyées par l'API via NTi :

  • La longueur maximale autorisée - param.Len
  • Le texte de suggestion - param.Prompt
  • Les valeurs par défaut - param.Dft
  • Les valeurs autorisées - SpecialValues
  • Les groupes de saisie (qualifiés, éléments imbriqués)

Chaque champ est lié directement aux dictionnaires d'état initialisés (CommandValues, QualifierValues, ElementValues, etc.), ce qui permet de capturer immédiatement les valeurs saisies ou sélectionnées.

Gestion des dépendances

Certaines commandes intègrent des dépendances conditionnelles entre paramètres, connues sous le nom de Control Prompt et exposées via SelectedCommand.Dependencies.

Ces règles peuvent être exploitées pour désactiver, pré-remplir ou rendre obligatoires certains champs en fonction de valeurs saisies dans d'autres. Par exemple : si le paramètre TYPE vaut *USR, alors OWNER devient obligatoire.

💡Ce tutoriel n'aborde pas en détail leur gestion dans l'interface, mais tous les éléments nécessaires sont accessibles via RetrieveCommandDefinition() pour les intégrer facilement dans le composant Blazor.

Étape 3 - Construire dynamiquement la commande CL

À ce stade, tous les champs ont été renseignés et leurs valeurs sont stockées dans les dictionnaires internes. L'objectif est de reconstruire dynamiquement la syntaxe complète de la commande CL sous forme de chaîne prête à être exécutée côté IBM i.

Cette logique est centralisée dans la méthode BuildCommandCL() :

string BuildCommandCL()
{
    var commandCl = SelectedCommand.CommandName.ToUpperInvariant().Trim() + " ";
    foreach (var param in SelectedCommand.Parameters)
    {
        if (!string.IsNullOrWhiteSpace(CommandValues[param.Kwd]))
        {
            string value = CommandValues[param.Kwd].ToUpperInvariant().Trim();
            if (param.Kwd.Equals("TEXT", StringComparison.OrdinalIgnoreCase))
            {
                value = $"'{value}'"; // Le champ TEXT est entre quotes
            }
            commandCl += $"{param.Kwd.ToUpperInvariant()}({value}) ";
        }
        else
        {
            var segment = new List();

            // Éléments imbriqués (ELEM)
            if (ElementValues.TryGetValue(param.Kwd, out var elements))
            {
                var values = elements.Values.Where(v => !string.IsNullOrWhiteSpace(v))
                                            .Select(v => v.ToUpperInvariant().Trim()).ToList();
                if (values.Any())
                {
                    segment.Add($"{param.Kwd.ToUpperInvariant()}({string.Join(" ", values)})");
                }
            }

            // Paramètres qualifiés (QUAL)
            if (QualifierValues.TryGetValue(param.Kwd, out var qualifiers) &&
                QualifierCustom.TryGetValue(param.Kwd, out var customQualifiers))
            {
                var values = qualifiers.Keys.Select(key => !string.IsNullOrWhiteSpace(customQualifiers[key])
                                                           ? customQualifiers[key]
                                                           : qualifiers[key])
                                             .Where(v => !string.IsNullOrWhiteSpace(v)).ToList();

                if ((param.Kwd == "FILE" || param.Kwd == "SRCFILE") && values.Count < 2)
                {
                    // Ne rien générer si l’un des éléments est manquant
                }
                else if (values.Any())
                {
                    var combined = string.Join("/", values.AsEnumerable().Reverse());
                    segment.Add($"{param.Kwd.ToUpperInvariant()}({combined.ToUpperInvariant().Trim()})");
                }
            }

            // Sélections multiples (checkbox)
            if (CheckedValues.TryGetValue(param.Kwd, out var checkDict))
            {
                var checkedValues = checkDict.Where(x => x.Value).Select(x => x.Key.ToUpperInvariant().Trim());
                if (checkedValues.Any())
                {
                    segment.Add($"{param.Kwd.ToUpperInvariant()}({string.Join(",", checkedValues)})");
                }
            }

            if (segment.Any())
            {
                commandCl += string.Join(" ", segment) + " ";
            }
        }
    }
    return commandCl.Trim();
}

La commande finale est construite à partir du nom principal (DLTLIB, CRTLIB, etc.), suivi des paramètres formatés dynamiquement selon leur type :

  • Si une valeur simple est saisie via CommandValues, elle est ajoutée directement, avec un traitement particulier pour le paramètre TEXT (entouré de quotes simples)
  • Pour les paramètres imbriqués de type ELEM, les sous-valeurs sont assemblées séparées par un espace
  • Pour les paramètres qualifiés QUAL, les sous-valeurs sont combinées (ex. fichier/bibliothèque), en tenant compte des éventuelles saisies manuelles
  • Pour les valeurs multiples (cases à cocher), toutes les options sélectionnées sont concaténées avec une virgule, comme dans OPTION(*SRC,*MBR)

Le résultat est une commande CL complète, propre et exécutable côté IBM i, exactement comme si elle avait été saisie dans un terminal 5250, mais générée dynamiquement depuis une interface moderne.

Commande CL construite dynamiquement dans Blazor

Étape 4 - Exécuter la commande côté IBM i

Une fois la commande construite, elle est validée puis exécutée en deux temps dans la méthode SubmitCommand() :

private async Task SubmitCommand()
{
    messageSubmit = "";
    string commandCL = BuildCommandCL();
    try
    {
        DBService.Conn.CheckCLCommand(commandCL); 
        DBService.Conn.ExecuteClCommand(commandCL);
        messageSubmit = "Commande envoyée avec succès.";

        await Task.Delay(4000); 
        ResetForm();          
        ResetGenerateCommand(); 
    }
    catch (Exception ex)
    {
        messageSubmit = $"{ex.Message}"; 
    }
}

Vérification préalable avec CheckCLCommand()

Avant tout envoi, la commande est validée via CheckCLCommand(), disponible via l'extension Toolbox de NTi. Elle interroge l'IBM i pour analyser la validité syntaxique complète de la commande sans l'exécuter : mots-clés inconnus, valeur mal formatée, paramètre obligatoire manquant. Ce contrôle anticipé reproduit exactement le comportement du système en environnement 5250.

Vérification de syntaxe avec CheckCLCommand

Exécution avec ExecuteClCommand()

Une fois validée, la commande est transmise via ExecuteClCommand(), méthode exposée par la classe NTiConnection. Elle est exécutée dans la session déjà ouverte, avec son environnement complet : utilisateur, job actif, bibliothèque courante, autorisations. Tous les messages de retour ou erreurs système sont restitués comme si la commande avait été lancée directement depuis un terminal IBM i.

Exécution du CL avec ExecuteClCommand

Ce qui est controlé côté Blazor/.NET

L'intégralité du processus est maîtrisé dans l'application .NET, qui orchestre la génération de la commande, la validation syntaxique, l'exécution et la gestion des erreurs via un bloc try/catch.

Message de succès après envoi du CL

Bibliothèque MYLIB créée sur IBM i

Conclusion

Avec RetrieveCommandDefinition() et RetrieveCommandDefinitionXml(), proposées par NTi Toolbox, il est possible de générer dynamiquement des interfaces de saisie pour n'importe quelle commande CL, en s'appuyant uniquement sur les métadonnées renvoyées par l'API QCDRCMDD.

RetrieveCommandDefinition() encapsule entièrement ce processus côté .NET : elle interroge l'API IBM i, récupère le flux XML au format CDML, le décode et le désérialise en un objet fortement typé C#. Aucun parsing manuel ou manipulation de XML n'est nécessaire. Toute la structure de la commande (paramètres, types, valeurs, dépendances) est immédiatement disponible et exploitable dans le code.

Cette abstraction réduit considérablement la charge de travail côté développement, permettant ainsi de se concentrer uniquement sur l’UX et la logique métier, sans jamais coder manuellement la structure de chaque commande.

Mais surtout, on sort enfin du cadre figé d'un terminal 5250. L'interface devient portable, accessible depuis un navigateur, une application métier, un poste client ou une plateforme conteneurisée. En d'autres termes, ce changement d'approche ouvre la voie à de véritables nouveaux usages :

  • Formulaires simplifiés pour les équipes non spécialisées IBM i
  • Centralisation de commandes dans un portail d'administration
  • Intégration dans des workflows DevOps ou des outils de support
  • Automatisation de tâches systèmes ou techniques, sans session verte

On ne quitte pas l'IBM i, on l'étend. Vers le web, vers .NET, vers demain.

Les méthodes Toolbox n'ont pas vocation à remplacer les compétences IBM i, elles libèrent de la complexité technique et les prolongent dans un nouvel écosystème, plus agile et accessible.


Quentin Destrade

Démarrez dès maintenant

Récupérez votre licence d’essai gratuite en ligne
et connectez vos applications .NET à votre IBM i en quelques minutes.

Créez votre compte

Connectez-vous au portail Aumerial, générez votre licence d’essai et activez NTi sur votre IBM i en quelques instants.

Démarrer l’essai

Ajouter NTi à votre projet

Installez NTi Data Provider depuis NuGet dans Visual Studio et référencez-le dans votre projet .NET.

Voir la documentation

Besoin d’aide ?

Si vous avez des questions sur nos outils ou sur les options de licence, notre équipe est disponible pour vous aider.

Nous contacter
30 jours d’essai gratuit activation immédiate sans engagement aucun composant à installer côté IBM i