Appeller une fonction SAP RFC avec Azure Function

Peter KARDA
Publié par Peter KARDA
Catégorie : .Net / Azure / Azure Functions Logic Apps
30/06/2023

Récemment, on nous a demandé de migrer quelques flux de données fonctionnant sur un logiciel propriétaire vers Azure. Les flux n’étaient pas compliqués ; la seule particularité était que nous devions appeler certaines fonctions SAP RFC. Comme nous avons déjà mis en œuvre plusieurs workflows Logic Apps en utilisant des connecteurs SAP capables de faire de tels appels, nous n’avons pas vu de problème. Cependant, lorsque nous avons essayé de nous connecter à l’instance SAP cible, nous avons reçu le message d’erreur suivant : « Required parameters ‘[rfcGroupFilter]’ not set or invalid« . Nous avons revérifié les paramètres de connexion, puis les logs d’erreur sur la data gateway on-premise, mais nous n’avons rien trouvé d’anormal. Mais lorsque nous avons comparé côte à côte la Logic App qui fonctionne correctement avec celle qui nous donne l’erreur, nous avons réalisé que dans le cas de l’erreur, nous ciblons une version différente (plus ancienne) de SAP et qu’il pourrait y avoir un problème de compatibilité.

 

À la recherche d’une solution

Comme nous n’avons pas réussi à trouver comment le faire dans une Logic App, nous avons commencé à chercher une autre solution. Ne serait-il pas possible de le faire avec Azure Function ? Voyons rapidement ce que nous savons, et ce dont nous avons besoin pour cela :

  • Nous voudrions utiliser Azure Functions implémenté en C# en .NET, la seule façon de se connecter à SAP est d’utiliser le connecteur SAP .NET appelé NCo. Ce connecteur nécessite toutefois .NET Framework 4.x
  • Lorsque l’on cible .NET Framework 4.x, il faut utiliser la version d’Azure Function qui prend en charge ce cadre. Il y a quelques années, il s’agissait d’Azure Function v1, qui est désormais déprécié. Heureusement, Azure Function v4 prend désormais en charge les fonctions .NET Framework 4.x exécutées dans des processus isolés, ce qui devrait nous convenir (https://learn.microsoft.com/en-us/azure/azure-functions/functions-versions).
  • Le système SAP étant sur site et (dans notre cas) sur un réseau privé, nous devons créer une connexion Azure Hybrid à SAP

 

Mise en place

Commençons par le provisionnement des ressources Azure. Tout d’abord, j’ai provisionné une Azure Function App hébergée sur un App Service Plan et j’ai créé la connexion hybride à SAP. Le port SAP par défaut est 3300, mais il peut varier en fonction de la configuration. Si vous avez besoin d’aide pour créer la connexion hybride, vous pouvez trouver des informations dans un article que j’ai écrit il y a quelque temps (https://www.middleway.eu/using-azure-hybrid-connections/).

Nous pouvons maintenant créer et déployer notre fonction. Dans mon cas, j’ai créé la fonction avec Visual Studio en utilisant le template pour Azure Functions et en choisissant Function worker : « .NET Framework Isolated v4 ».

VS Azure Function Template

Une fois le projet de fonction créé, nous devons ajouter les références NCo comme indiqué précédemment. Vous pouvez soit installer la dernière version officielle du connecteur NCo 3.0 depuis le site web de SAP (vous aurez besoin du compte client SAP pour le télécharger), soit l’installer en tant que paquet NuGet. Par exemple, le package sapnco_x64 fait le job :

SAP NCo NuGet

Ce package NuGet ne contient pas la dernière version du NCo mais ce n’est pas grave. Nous avons tout ce qu’il faut pour commencer à mettre en œuvre notre Azure Function qui peut effectuer des appels SAP.

Implementation

L’implémentation de la fonction Azure pour appeler la fonction SAP RFC ne diffère pas beaucoup de l’implémentation d’autres types d’applications .NET. Le code fonctionnel est simplement placé dans le trigger de la fonction. Dans notre démo, nous allons créer une fonction HTTP trigger où je vais :

  • lire les paramètres d’entrée (dans mon cas, les paramètres d’entrée se trouvent dans le corps JSON de la demande)
  • Créer une configuration de connexion RFC (qui doit être initialisée avec vos valeurs de configuration)
  • Création de la destination RFC (connexion)
  • Invocation de la fonction RFC par son nom
  • Obtention du résultat
[Function("SapRfcCallDemo")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
    // Reading input JSON with parameters
    dynamic inputParameters = await req.ReadFromJsonAsync<dynamic>();

    HttpResponseData response = req.CreateResponse();
    response.Headers.Add("Content-Type", "text/plain; charset=utf-8");

    try
    {
        // Setting up the connection parameters
        RfcConfigParameters rfcPar = new RfcConfigParameters();
        rfcPar.Add(RfcConfigParameters.Name, "ApplicationServer");
        rfcPar.Add(RfcConfigParameters.Client, "100");
        rfcPar.Add(RfcConfigParameters.User, "sap_user_name");
        rfcPar.Add(RfcConfigParameters.Password, "sap_user_password");
        // The server host is the same as the one in hybrid connection
        rfcPar.Add(RfcConfigParameters.AppServerHost, "demosap.domain.com");
        rfcPar.Add(RfcConfigParameters.AppServerService, "sapservice");
        rfcPar.Add(RfcConfigParameters.SystemNumber, "00");
        rfcPar.Add(RfcConfigParameters.Language, "en");
        rfcPar.Add(RfcConfigParameters.SncQOP, "Authentication");

        // Create the destination
        RfcDestination dest = RfcDestinationManager.GetDestination(rfcPar);
        RfcRepository rfcRepository = dest.Repository;

        IRfcFunction sapFunction = rfcRepository.CreateFunction("MY_RFC_FUNCTION");
        sapFunction.SetValue("I_PARAM1", inputParameters.param1);
        sapFunction.SetValue("I_PARAM2", inputParameters.param2);
        sapFunction.SetValue("I_PARAM3", inputParameters.param3);

        // Calling the RFC function
        sapFunction.Invoke(dest);
        // Obtaining the result
        var result = sapFunction.GetTable("MY_RESULT_TABLE");

        response.StatusCode = HttpStatusCode.OK;

        if (result != null)
        {
            response.WriteString(result.ToString());
        }
    }
    catch (Exception ex)
    {
        response.StatusCode = HttpStatusCode.InternalServerError;
        response.WriteString(ex.Message.ToString());

        _logger.LogError($"Error occured: {ex.Message}");
    }

    return response;
}

Les paramètres d’entrée et la configuration doivent bien sûr être adaptés à vos besoins. Il en va de même pour l’analyse des résultats. Notez que la valeur du paramètre AppServerHost doit être la même que celle spécifiée dans la connexion hybride.

Ce qui est important de mentionner est que la fonction doit être compilée pour une plateforme cible spécifique afin que le connecteur NCo fonctionne correctement. Dans mon cas, je destine la compilation à la plateforme « x64« . Le code compilé pour « Any CPU » ne fonctionnera pas.

Résumé

Appeler des fonctions SAP RFC en utilisant Azure Function est un peu plus laborieux que d’utiliser le connecteur Logic App. Cependant, notre solution avec Azure Function nous a aidées à surmonter les problèmes de compatibilité que nous avions. Nous avons également pu bénéficier de l’exécution d’Azure Function dans un process isolé. Il découple les fonctions .NET de l’hôte Azure Functions, ce qui nous a permis d’utiliser .NET Framework 4.x et NCo tout en étant en mesure d’utiliser la dernière interface de trigger d’Azure Function et de la déployer sur la dernière version d’Azure Function v4.