tutoriels

API REST minimale .NET 8 sur IBM i avec NTi : CRUD, CL et RPG

ParQuentin DESTRADE

image d’illustration de l’article

Contenu détaillé de l’article:API REST minimale .NET 8 sur IBM i avec NTi : CRUD, CL et RPG

Comment créer une API REST minimale avec .NET 8 et NTi Data Provider pour interagir avec un système IBM i : CRUD SQL sur DB2 for i, exécution de commandes CL et appels de programmes RPG existants.

La force d'une architecture microservices réside dans sa simplicité : des composants légers, autonomes, exposés via des API REST et consommables par n'importe quel service ou application.

NTi Data Provider s'inscrit naturellement dans cette logique. En quelques lignes de C#, il est possible d'exposer directement les ressources IBM i (tables DB2, commandes CL, programmes RPG) sous forme d'endpoints REST prêts à l'emploi.

Ce tutoriel montre comment construire cette API en .NET 8, sans surcouche ni framework complexe. Une base technique reproductible en quelques minutes, conçue pour mesurer concrètement la simplicité de NTi dans une approche microservices.

Le code complet de cet exemple est disponible en bas de cette page.

Étape 1 - Initialiser une API minimale en .NET 8

Créer le projet depuis la ligne de commande :

dotnet new webapi -n myApp --use-minimal-apis --framework net8.0
cd myApp 

Le projet est généré avec un fichier Program.cs prêt à l'emploi et Swagger configuré par défaut. Il ne reste plus qu'à ajouter les packages nécessaires :

dotnet add package Aumerial.Data.Nti
dotnet add package Aumerial.Toolbox
dotnet add package Dapper

Puis les référencer dans Program.cs :

using Aumerial.Data.Nti;
using Aumerial.Toolbox;
using Dapper;

Enfin, définir la chaîne de connexion dans Program.cs :

string connectionString = "server=Server;user=User;password=Pwd;";

Dans cet exemple minimaliste, l'injection de dépendances pour la connexion est volontairement écartée.

Étape 2 - Opérations CRUD sur DB2 for i avec NTi et Dapper

Une API REST expose des ressources via des requêtes HTTP classiques (GET, POST, PUT, DELETE). Les quatre opérations de base, appelées CRUD, correspondent aux actions courantes sur des données :

  • CREATE : créer une nouvelle ressource → POST
  • READ : lire une ou plusieurs ressources → GET
  • UPDATE : mettre à jour une ressource existante → PUT
  • DELETE : supprimer une ressource existante → DELETE

Pour cet exemple, un script ACS crée la base de test GARAGEQ et la table CARS avec un jeu de données initial :

-- Création du SCHEMA GARAGEQ
CREATE SCHEMA GARAGEQ;

-- Création de la table CARS
CREATE TABLE GARAGEQ.CARS(ID INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1),
    BRAND VARCHAR(50),
    MODEL VARCHAR(50),
    YEAR INT,
    COLOR VARCHAR(30),
    PRIMARY KEY (ID)
);

-- Insertion d'un jeu de données
INSERT INTO GARAGEQ.CARS (BRAND, MODEL, YEAR, COLOR) VALUES
    ('Peugeot', '308', 2021, 'Anthracite'),
    ('Volkswagen', 'Caddy', 2015, 'Bleu'),
    ('Skoda', 'Octavia Combi RS', 2017, 'Blanc'),
    ('Toyota', 'Yaris', 2022, 'Jaune');

Côté C#, une classe Car correspond exactement aux champs de la table DB2 :

namespace myApp 
{
    public class Car
    {
        public int Id { get; set; }
        public string? Brand { get; set; }
        public string? Model { get; set; }
        public int Year { get; set; }
        public string? Color { get; set; }
    }
}

Implémentation des endpoints CRUD

GET - récupérer toutes les voitures :

app.MapGet("/cars", () =>
{
    using var conn = new NTiConnection(connectionString);
    conn.Open();
    var cars = conn.Query<Car>("SELECT * FROM GARAGEQ.CARS").ToList();
    return Results.Ok(cars);
})
.WithName("GetAllCar")
.WithOpenApi();

POST - ajouter une nouvelle voiture :

app.MapPost("/cars", (Car car) =>
{
    using var conn = new NTiConnection(connectionString);
    conn.Open();

    string addCarSql = "INSERT INTO GARAGEQ.CARS (BRAND, MODEL, YEAR, COLOR) VALUES (?, ?, ?, ?)";
    conn.Execute(addCarSql, new { car.Brand, car.Model, car.Year, car.Color });
    return Results.Created("/cars", car);
})
.WithName("AddNewCar")
.WithOpenApi();

PUT - mettre à jour une voiture :

app.MapPut("/cars/{id}", (int id, Car car) =>
{
    using var conn = new NTiConnection(connectionString);
    conn.Open();

    string updateCar = "UPDATE GARAGEQ.CARS SET BRAND = ?, MODEL = ?, YEAR = ?, COLOR = ? WHERE ID = ?";
    conn.Execute(updateCar, new { car.Brand, car.Model, car.Year, car.Color, id });
})
.WithName("UpdateCarById")
.WithOpenApi();

DELETE - supprimer une voiture :

app.MapDelete("/cars/{id}", (int id) =>
{
    using var conn = new NTiConnection(connectionString);
    conn.Open();

    int carDeleted = conn.Execute("DELETE FROM GARAGEQ.CARS WHERE ID = ?", new { id });
    return carDeleted;
})
.WithName("DeleteCarById")
.WithOpenApi();

Chaque endpoint ouvre une connexion dédiée avec NTi. La requête SQL est exécutée avec Dapper via conn.Query<T>() pour les sélections, ou conn.Execute() pour les insertions, modifications et suppressions. Dapper gère automatiquement le mapping entre les résultats SQL et l'objet C# Car.
Aucune surcouche, aucun controller.

Étape 3 - Exécuter une commande CL avec vérification système

NTi ne se limite pas à l'accès base de données : il permet aussi d'exécuter des commandes CL directement depuis une API .NET. La méthode ExecuteClCommand() permet de piloter l'IBM i en lançant n'importe quelle commande, comme depuis un terminal 5250.

Avant toute exécution, CheckCLCommand() (disponible via l'extension Toolbox) permet de valider la commande côté IBM i pour détecter d'éventuelles erreurs de syntaxe ou paramètres manquants, sans l'exécuter :

app.MapPost("/execute/ClCommand", (string command) =>
{
    using var conn = new NTiConnection(connectionString);
    conn.Open();
    conn.CheckCLCommand(command);
    conn.ExecuteClCommand(command);
    return "Commande exécutée avec succès.";
})
.WithName("ExecuteClCommand")
.WithOpenApi();

Étape 4 - Changer dynamiquement la bibliothèque courante

Sur IBM i, la bibliothèque courante définit le contexte dans lequel les commandes et programmes sont exécutés. La méthode ChangeDatabase() de NTi permet de la modifier dynamiquement dans la session active :

app.MapPost("/ChangeLibraryOnIbmi", (string libraryName) =>
{
    using var conn = new NTiConnection(connectionString);
    conn.Open();

    conn.ChangeDatabase(libraryName);
    return conn.Database;
})
.WithName("ChangeCurrentLibrary")
.WithOpenApi();

ChangeDatabase() agit uniquement sur la session en cours, celle du job NTi.

Étape 5 - Appeler un programme RPG pour mettre à jour un nom client

NTi permet d'appeler directement un programme RPG depuis une API .NET, avec passage de paramètres typés, comme depuis un écran vert ou un batch. Les traitements métiers existants peuvent ainsi être encapsulés dans une API REST, sans réécrire toute la logique en C#.

Le programme RPG PGMCUST01, situé dans la bibliothèque NORTHWIND, attend deux paramètres :

  • Un identifiant client id : alphanumérique de 5 caractères
  • Un nouveau nom Name : alphanumérique de 30 caractères

Voici comment exposer cet appel de programme via un endpoint REST :

app.MapPost("/customer/{id}/name", (string id, Customer customer) =>
{
    using var conn = new NTiConnection(connectionString);
    conn.Open();

    var parameters = new List<NTiProgramParameter>
    {
        new NTiProgramParameter(id, 5),
        new NTiProgramParameter(customer.Name, 30)
    };
    conn.ExecuteClCommand("CHGCURLIB NORTHWIND");
    conn.CallProgram("NORTHWIND", "PGMCUST01", parameters);

    return Results.Ok($"Nom du client ID {id} mis à jour avec succès : {customer.Name}.");
})
.WithName("CallRPGProgram")
.WithOpenApi();

Étape 6 - Swagger et documentation automatique

Swagger est activé par défaut dans le template webapi généré par dotnet new webapi. Il génère automatiquement une documentation interactive de l'API REST, accessible directement depuis un navigateur. Cette interface permet de tester les endpoints en temps réel, vérifier les paramètres attendus, observer les réponses retournées, et visualiser la structure complète des objets utilisés comme Car ou Customer.

Swagger joue également un rôle clé pour la consommation de l'API côté client : la documentation générée permet à des outils externes ou à des développeurs front-end de générer automatiquement du code pour consommer les endpoints.

Conclusion

Avec .NET 8, Dapper et NTi Data Provider, il est possible de construire en quelques minutes une API REST complète, légère et parfaitement fonctionnelle, capable d'interagir nativement avec un environnement IBM i.

Chaque fonctionnalité, de l'exécution de requêtes SQL à l'appel d'un programme RPG, en passant par l'exécution sécurisée de commandes CL, peut être exposée de manière simple, stable et interopérable.

Que ce soit pour réaliser un POC, créer des services techniques internes ou initier une stratégie de modernisation progressive, cette base d'API minimaliste est un excellent point de départ, clair, modulaire, et aligné avec les principes du microservice.

using Aumerial.Data.Nti;
using Dapper;
using Aumerial.Toolbox;

namespace myApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddAuthorization();
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

           string connectionString = "server=Server;user=User;password=Pwd;";

            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();
            app.UseAuthorization();
        
            /// GET ALL
            app.MapGet("/cars", () =>
            {
                using var conn = new NTiConnection(connectionString);
                conn.Open();
                var cars = conn.Query<Car>("SELECT * FROM GARAGEQ.CARS").ToList();
                return cars;
            })
            .WithName("GetAllCar")
            .WithOpenApi();

            // GET BY ID
            app.MapGet("/cars/{id}", (int id) =>
            {
                using var conn = new NTiConnection(connectionString);
                conn.Open();
                var carById = conn.QuerySingleOrDefault<Car>("SELECT * FROM GARAGEQ.CARS WHERE ID = ?", new { id });
                return carById;
            })
            .WithName("GetCarById")
            .WithOpenApi();

            // POST
            app.MapPost("/cars", (Car car) =>
            {
                using var conn = new NTiConnection(connectionString);
                conn.Open();
                string addCarSql = "INSERT INTO GARAGEQ.CARS (BRAND, MODEL, YEAR, COLOR) VALUES (?, ?, ?, ?)";
                conn.Execute(addCarSql, new { car.Brand, car.Model, car.Year, car.Color });
                return Results.Created("/cars", car);
            })
            .WithName("AddNewCar")
            .WithOpenApi();

            // PUT
            app.MapPut("/cars/{id}", (int id, Car car) =>
            {
                using var conn = new NTiConnection(connectionString);
                conn.Open();
                string updateCar = "UPDATE GARAGEQ.CARS SET BRAND = ?, MODEL = ?, YEAR = ?, COLOR = ? WHERE ID = ?";
                conn.Execute(updateCar, new { car.Brand, car.Model, car.Year, car.Color, id });
            })
            .WithName("UpdateCarById")
            .WithOpenApi();

            // DELETE
            app.MapDelete("/cars/{id}", (int id) =>
            {
                using var conn = new NTiConnection(connectionString);
                conn.Open();
                int carDeleted = conn.Execute("DELETE FROM GARAGEQ.CARS WHERE ID = ?", new { id });
                return carDeleted;
            })
            .WithName("DeleteCarById")
            .WithOpenApi();

            // EXECUTE CL COMMAND
            app.MapPost("/execute/ClCommand", (string command) =>
            {
                using var conn = new NTiConnection(connectionString);
                conn.Open();
                conn.CheckCLCommand(command);
                conn.ExecuteClCommand(command);
                return "Commande exécutée avec succès.";
            })
            .WithName("ExecuteClCommand")
            .WithOpenApi();

            // CHANGE CURRENT LIBRARY
            app.MapPost("/ChangeLibraryOnIbmi", (string libraryName) =>
            {
                using var conn = new NTiConnection(connectionString);
                conn.Open();
                conn.ChangeDatabase(libraryName);
                return conn.Database;
            })
            .WithName("ChangeCurrentLibrary")
            .WithOpenApi();

            // CALL AN EXISTING RPG PGM
           app.MapPost("/customer/{id}/name", (string id, Customer customer) =>
            {
                using var conn = new NTiConnection(connectionString);
                conn.Open();

                var parameters = new List<NTiProgramParameter>
                {
                    new NTiProgramParameter(id, 5),
                    new NTiProgramParameter(customer.Name, 30)
                };
                conn.ExecuteClCommand("CHGCURLIB NORTHWIND");
                conn.CallProgram("NORTHWIND", "PGMCUST01", parameters);
                return Results.Ok($"Nom du client ID {id} mis à jour avec succès : {customer.Name}.");
            })
            .WithName("CallRPGProgram")
            .WithOpenApi();

            app.Run();
        }
    }
}

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