Tutoriels

Créer une API .NET multi-base : DB2 for i et PostgreSQL avec NTi

ParQuentin Destrade

image d’illustration de l’article

Contenu détaillé de l’article:Créer une API .NET multi-base : DB2 for i et PostgreSQL avec NTi

Cet article présente le développement d'une API .NET connectée à DB2 for i via NTi Data Provider et à PostgreSQL via Npgsql, couplée à une application Angular qui consomme les données exposées. Configuration des connecteurs, modélisation, services, controllers et composants Angular

NTi Data Provider est conçu pour permettre une connexion directe avec DB2 for i, sans surcouche ni driver additionnel. Pour en illustrer la simplicité, ce projet s'appuie sur une API .NET connectée simultanément à DB2 for i sur IBM i et PostgreSQL, couplée à une application Angular qui affiche les données de manière dynamique.

Partie 1 - Création de l’API

Étape 1 - Configuration de l'environnement de travail

Le projet utilise ASP.NET Core API avec Visual Studio, qui intègre nativement Swagger pour tester les endpoints sans Postman.

Trois packages sont nécessaires:

dotnet add package Aumerial.Data.Nti
dotnet add package Npgsql
dotnet add package Dapper
  • NTi - connexion à DB2 for i
  • Npgsql - connecteur standard pour PostgreSQL
  • Dapper - ORM léger pour mapper automatiquement les entités sur les deux bases

Étape 2 - Modélisation des données et configuration des services

Les modèles de données structurent les échanges entre l'API et les bases de données :

namespace AccessDataAPI.Models.Northwind
{
    public class Categories
    {
        public int categoryId { get; set; }
        public string categoryName { get; set; }
        public string description { get; set; }
    }
}
namespace AccessDataAPI.Models.Hotel
{
    public class Services
    {
        public int service_id { get; set; }
        public int nom_service { get; set; }
        public int tarif_service { get; set; }

    }
}

Un service de connexion distinct est configuré pour chaque base.

DB2Service - connexion à DB2 for i via NTi :

using Aumerial.Data.Nti;

namespace AccessDataAPI.Services
{
    public class DB2Service
    {
        public NTiConnection conn;

        public DB2Service(IConfiguration config)
        {
            string connectionString = config.GetConnectionString("Db2Database");
            conn = new NTiConnection(connectionString);
        }
    }
}

PGSQLService - connexion à PostgreSQL via Npgsql :

using Npgsql;

namespace AccessDataAPI.Services
{
    public class PGSQLService
    {
        public NpgsqlConnection conn;

        public PGSQLService(IConfiguration config)
        {
            string connectionString = config.GetConnectionString("PGDatabase");
            conn = new NpgsqlConnection(connectionString);
        }
    }
}

Étape 3 - Controllers

Les controllers traitent les requêtes entrantes, exécutent la logique CRUD et renvoient les réponses aux clients.

NorthwindCategoriesController - GET et POST sur DB2 for i :

using AccessDataAPI.Models.Northwind;
using Dapper;
using AccessDataAPI.Services;
using Microsoft.AspNetCore.Mvc;
using Aumerial.Data.Nti;

namespace AccessDataAPI.Controllers.NorthwindControllers
{
    [ApiController]
    [Route("[controller]")]
    public class NorthwindCategoriesController : Controller
    {
        private readonly DB2Service dbconn;

        public NorthwindCategoriesController(DB2Service dB2Service)
        {
            dbconn = dB2Service;
        }

        // Récupérer les catégories
        [HttpGet("/categories")]
        public IActionResult GetCategories()
        {
            try
            {
                string sql = "SELECT * FROM NORTHWIND.CATEGORIES";
                var categories = dbconn.conn.Query(sql).AsList();

                if (categories != null)
                    return Ok(categories);
                else
                    return NotFound($"Aucune catégorie trouvée");
            }
            catch (Exception ex)
            {
                return BadRequest(new { Error = $"Erreur : {ex.Message}" });
            }
        }

        // Ajouter une nouvelle catégories
        [HttpPost("AddCategories")]
        public IActionResult CreateCategories([FromBody] Categories newCategorie)
        {
            try
            {
                string sql = "INSERT INTO NORTHWIND.CATEGORIES (CATEGORYNAME, DESCRIPTION) VALUES (?, ?)";
                var results = dbconn.conn.Execute(sql, new
                {
                    newCategorie.categoryName,
                    newCategorie.description
                });

                if (results > 0)
                    return Ok(newCategorie);
                else
                    return BadRequest(new { Message = "Impossible d'ajouter une nouvelle catégorie" });
            }
            catch (Exception ex)
            {
                return BadRequest(new { Error = $"Erreur : {ex.Message}" });
            }
        }
    }
}

HotelServicesController - GET sur PostgreSQL :

using Dapper;
using AccessDataAPI.Services;
using Microsoft.AspNetCore.Mvc;

namespace AccessDataAPI.Controllers.HotelServicesController
{
    [ApiController]
    [Route("[controller]")]
    public class HotelServicesController : Controller
    {
        private readonly PGSQLService pgconn;

        public HotelServicesController(PGSQLService pgsqlservice)
        {
            pgconn = pgsqlservice;
        }

        [HttpGet("ServicesWithinfo")]
        public IActionResult GetServiceWithInfo()
        {
            try
            {
                string sql = @"
                    SELECT s.service_id, s.nom_service, s.tarif_service,
                           r.reservation_id, r.client_id, r.chambre_id,
                           r.date_arrivee, r.date_depart, r.nombre_adultes,
                           r.nombre_enfants, r.statut
                    FROM hotel.services s
                    LEFT JOIN hotel.reservations_services rs ON s.service_id = rs.service_id
                    LEFT JOIN hotel.reservations r ON rs.reservation_id = r.reservation_id
                    ORDER BY s.service_id, r.reservation_id;";

                var results = pgconn.conn.Query(sql).ToList();

                if (results != null)
                    return Ok(results);
                else
                    return NotFound(new { message = "Aucun service trouvé." });
            }
            catch (Exception ex)
            {
                return BadRequest(new { Error = $"Erreur : {ex.Message}" });
            }
        }

        [HttpGet("ServicesReservationsCount")]
        public IActionResult GetReservationsByService()
        {
            try
            {
                string sql = @"
                    SELECT
                        s.nom_service,
                        s.tarif_service,
                        COUNT(r.reservation_id) AS reservationCount
                    FROM hotel.services s
                    LEFT JOIN hotel.reservations_services rs ON s.service_id = rs.service_id
                    LEFT JOIN hotel.reservations r ON rs.reservation_id = r.reservation_id
                    GROUP BY s.nom_service, s.tarif_service
                    ORDER BY s.nom_service;";

                var results = pgconn.conn.Query(sql).ToList();

                if (results != null)
                    return Ok(results);
                else
                    return NotFound($"Aucune réservation de service trouvée");
            }
            catch (Exception ex)
            {
                return BadRequest(new { Error = $"Erreur : {ex.Message}" });
            }
        }
    }
}

Les routes associent des URL spécifiques aux actions des controllers, garantissant que chaque requête atteint sa destination dans l'API.

Étape 4 - Swagger et CORS

Résultat de la requête GET dans Swagger :

Exemple d’exécution de la requête GET des catégories dans Swagger

Endpoints NorthwindCategories dans Swagger (GET et POST) :

Documentation Swagger des endpoints NorthwindCategories (GET et POST)

Les CORS (Cross-Origin Resource Sharing) sont configurés dans Program.cs pour autoriser les requêtes de l'application Angular hébergée sur un domaine différent.

Partie 2 - Création de l’application Angular

Étape 1 - Configuration de l'environnement Angular

L'environnement Angular est configuré avec :

  • Node.js
  • NPM
  • Angular CLI

Le modèle de conception MVVM sépare la logique d'affichage de la logique métier. Visual Studio Code est utilisé pour le développement.

Étape 2 - Modèle de données et services

Un modèle TypeScript est défini pour chaque entité, assurant une manipulation structurée des données dans l'application :

export class Categories {
    categoryId: number;
    categoryName: string;
    description: string;

    constructor(
        categoryId: number,
        categoryName: string,
        description: string,
    ) {
        this.categoryId = categoryId;
        this.categoryName = categoryName;
        this.description = description;
    }
}

La communication avec l'API .NET s'appuie sur le module HttpClient d'Angular pour échanger des données au format JSON :

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Categories } from '../../models/northwind/categories/categories.model';
import { CategorieProductCount } from '../../models/northwind/categories/CategorieProductCount.model';
import { environment } from '../../../environments/environment';

const httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable({
    providedIn: 'root'
})
export class CategoriesService {

    private apiBaseUrl = environment.apiBaseUrl;

    private apiGetCategoriesUrl = `${this.apiBaseUrl}categories`;
    private apiGetCategoriesProductsCountUrl = `${this.apiBaseUrl}categoriesProductCount`;
    private apiAddCategoriesUrl = `${this.apiBaseUrl}NorthwindCategories/AddCategories`;
    private apiGetCategoriesWithMetaDataUrl = `${this.apiBaseUrl}/categoriesMetaData`;

    constructor(private http: HttpClient) { }

    getCategories(): Observable {
        return this.http.get(this.apiGetCategoriesUrl);
    }

    getCategoriesProductCount(): Observable {
        return this.http.get(this.apiGetCategoriesProductsCountUrl);
    }

    getCategoriesWithMetaData(): Observable {
        return this.http.get(this.apiGetCategoriesWithMetaDataUrl);
    }

    addCategories(categories: any): Observable {
        return this.http.post(this.apiAddCategoriesUrl, categories);
    }
}

Étape 3 - Développement des composants

L'intérêt d'Angular réside dans ses composants : chacun est composé d'un fichier TypeScript pour la logique métier, d'un template HTML et d'un fichier CSS ou SCSS pour le style. Ils peuvent être imbriqués pour favoriser la modularité.

La page principale orchestre les différents composants :

<div class="container">
    <div class="row">
        <div class="col-md-12 mt-4 text-center">
            <h1 class="mt-5">Base de données NORTHWIND DB2 for i</h1>
        </div>
        <div class="col-md-12 northwind-box" id="dashboardSection">
            <app-dashboard></app-dashboard>
        </div>
        <div class="col-md-12 northwind-box" id="categoriesSection">
            <app-categories></app-categories>
        </div>
        <div class="col-md-12 northwind-box" id="clientsSection">
            <app-clients-northwind></app-clients-northwind>
        </div>
        <div class="col-md-12 northwind-box" id="productsSection">
            <app-productscomponent></app-productscomponent>
        </div>
    </div>
</div>

Chaque composant appelle les méthodes de son service pour charger ou modifier les données :

// Load categories
loadCategories() {
    this.categoriesService.getCategories().subscribe(
        (categories: Categories[]) => {
            this.categorie = categories;
        }
    );
}

// Add a category
addCategorie() {
    if (this.addCategoryForm.valid) {
        this.categoriesService.addCategories(this.addCategoryForm.value)
            .subscribe(
                (newCategorie: Categories) => {
                    this.categorie.push(newCategorie);
                    this.showCategorieForm = false;
                    this.showMessageSuccess();
                }
            );
    } else {
        window.alert('le champs nom est requis');
    }
}

Dynamic charts can thus be created in just a few lines. Here, a bar chart displaying the number of products per category:

createChart() {
    const categories = this.chartData.map(data => data.categoryName);
    const productCounts = this.chartData.map(data => data.productCount);
    const colors = [
        'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)',
        'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)',
        'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)',
        'rgba(199, 199, 199, 0.2)', 'rgba(83, 102, 255, 0.2)',
        'rgba(40, 159, 64, 0.2)', 'rgba(255, 99, 71, 0.2)'
    ];

    const categorieChart = document.getElementById('CategoriesChart') as HTMLCanvasElement;
    const myChart = new Chart(categorieChart, {
        type: 'bar',
        data: {
            labels: categories,
            datasets: [{
                label: 'Number of products',
                data: productCounts,
                backgroundColor: colors,
                borderWidth: 0
            }]
        }
    });
}

Angular interface displaying a bar chart of products per category

Conclusion

This project demonstrates that with a solid grasp of object-oriented programming and SQL, working simultaneously with DB2 for i and PostgreSQL becomes as straightforward as with any other database.

NTi Data Provider plugs into the .NET ecosystem exactly like SQL Server, Oracle, or any other ADO.NET provider: same syntax, same logic.

IBM i resources become just another data source, fully available for building modern business applications, with no added complexity.


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