為工作紀錄。於 .NET6 環境使用 C# 呼叫 SAP API 應用紀錄。on 2022-10-12。
SAP API 為 SAParrow-up-right 獨立的規格用於通訊,功能效果類似 Web API,當然規格完全不相容。不相容的好處當然就是"安全"囉。官方與 C# 通訊的模組,SAP Connector for Microsoft .NET 3.0arrow-up-right,仍活在 .NET Framework 3.0 很無言啊,現在都是 .NET6 的時代了。
SAP API
還好很幸運有先行者解決了與 .NET Core 通訊的問題,SapNwRfcarrow-up-right,在使用時期雖是為 .NET5 設計的然在 .NET6 仍然有效。
SapNwRfc 的實作其實是把 SAP 原生的 native DLL 包裏起來,外殼是 .NET Core 內層是 native DLL 函式庫,依不同平台各自下載。我們使用的版本是 the SAP NetWeaver RFC SDK Version 7.50 Librariesarrow-up-right,下載此資源需有 SAP 帳戶才行。它提供有 AIX 64-bit、Linux.64、Windows EM64T、Windows 32-bit (client only) 四個版本。我們用的是 Windows EM64T 版本。
Operating System
Unicode SAP NetWeaver RFC SDK libraries
Windows EM64T
libsapucum.dll
libicudecnumber.dll
sapnwrfc.dll
icuin50.dll
icuuc50.dll
icudt50.dll
IDE: Visual Studio 2022 執行平台: .NET6 系統骨架:Blazor Server App 開發OS: Win 11 部署目的地OS: Windows Server 2019
自 NuGet 安裝 SapNwRfc 套件:
下載 the SAP NetWeaver RFC SDK Version 7.50 Librariesarrow-up-right。 注意,下載此資源需透過有授權的 SAP 帳戶。
安裝適用於 Visual Studio 2013 的 Visual C++ 可轉散發套件arrow-up-right。 太概 SapNwRfc SDK Libraries 是用 VS2013 開發的 navitve DLL 故需有此模組才能運轉吧。
引用 SapNwRfc 原生函式庫有兩用方法,新的方法是透過 NativeLibrary.SetDllImportResolverarrow-up-right ,但這招不好用且失敗了。反而舊的方法,用 PATH 環境參數找 SapNwRfc 原生函式庫四個檔案:sapnwrfc.dll、icudt50.dll、icuin50.dll、icuuc50.dll,簡單且成功了。
NativeLibrary.SetDllImportResolver
1)把拿到的 SapNwRfc 原生函式庫全部存入 C:\MyLib\nwrfcsdk。
C:\MyLib\nwrfcsdk
2)設定 PATH
開啟【檢視進階系統設定】工具程式,選取[系統內容\環境變數\系統變數\PATH]編輯環境變數,把上面放原生函式庫的路徑加入。
EOF
Last updated 3 years ago
PM> Install-Package SapNwRfc
{ ... "ConnectionStrings": { "SLP_SAPAPI": "AppServerHost=192.168.xxx.xxx; SystemNumber=11; User=xxxxxx; Password=xxxxxx; Client=800; Language=ZH;" }, }
using SapNwRfc.Pooling; var builder = WebApplication.CreateBuilder(args); IConfiguration Configuration = builder.Configuration; ... /// 註冊:ISapConnectionPool /// In the Startup.cs ConfigureServices method, add: builder.Services.AddSingleton<ISapConnectionPool>(_ => new SapConnectionPool(Configuration["ConnectionStrings:SLP_SAPAPI"])); builder.Services.AddScoped<ISapPooledConnection, SapPooledConnection>(); builder.Services.AddScoped<Vista.Services.SapApiService>(); ... var app = builder.Build(); ...
using SapNwRfc; namespace YourProject.Models; ///※ 參數傳遞以物件型式交換 public class UrApiName_Input { [SapName("ArgName01")] public string ArgName01 { get; set; } [SapName("ArgName02")] public string ArgName02 { get; set; } [SapName("ArgName03")] public string ArgName03 { get; set; } } ///※ 參數傳遞以物件型式交換 public class UrApiName_Output { /// RFCTable:多筆傳遞型式 [SapName("O_ITEMS")] public UrApiName_Item[] O_ITEMS { get; set; } } /// 多筆傳遞之欄位明細 public class UrApiName_Item { [SapName("ResultField01")] public string ResultField01 { get; set; } [SapName("ResultField02")] public string ResultField02 { get; set; } [SapName("ResultField03")] public string ResultField03 { get; set; } }
using Microsoft.Extensions.Logging; using SapNwRfc.Pooling; using System; using YourProject.Models; namespace YourProject.Services; internal class SapApiService { readonly ILogger<SapApiService> _logger; readonly ISapPooledConnection _sapConn; // 將以注入方式取得 SAP Connection。 public SapApiService(ILogger<SapApiService> logger, ISapPooledConnection sapConn) { _logger = logger; _sapConn = sapConn; } public bool CallUrApiName(UrApiName_Input input, out UrApiName_Output output) { const string funcName = "UrApiName"; try { //※ 一切的準備都是為了這一行指令:叫用 SAP API。 ------ ------ var result = _sapConn.InvokeFunction<UrApiName_Output>(funcName, input); _logger.LogInformation($"呼叫 SLPAPI.{funcName} 成功。"); output = result; return true; } catch (Exception ex) { string msg = $"呼叫 SLPAPI.{funcName} 失敗! " + ex.Message; _logger.LogError(ex, msg); output = null; return false; } } }
@using SapNwRfc @using System.Text @page "/test002" @inject IConfiguration config <PageTitle>測試 SAPAPI 通訊</PageTitle> ... @code { // resource Severity alertSeverity = Severity.Info; string alertMessage = null; // State SapInfo sap = new(); bool f_loading = false; string funcDesc = null; protected override void OnInitialized() { base.OnInitialized(); sap.connString = config["ConnectionStrings:SLP_SAPAPI"]; sap.funcName = "UrApiName"; } /// 一、確認有載入nwrfcsdk函式庫。 async Task EnsureLibraryPresent() { try { f_loading = true; //※ 確認有載入nwrfcsdk函式庫。 await Task.Run(() => SapLibrary.EnsureLibraryPresent()); alertSeverity = Severity.Success; alertMessage = "EnsureLibraryPresent 成功!"; } catch (Exception ex) { alertSeverity = Severity.Error; alertMessage = "EnsureLibraryPresent 失敗!" + ex.ToString(); } finally { f_loading = false; } } /// 二、測試連線字串 async Task TestSapConnection() { try { f_loading = true; await Task.Run(() => { using var conn = new SapConnection(sap.connString); conn.Connect(); conn.Disconnect(); alertSeverity = Severity.Success; alertMessage = "測試SAP連線成功。"; }); } catch (Exception ex) { alertSeverity = Severity.Error; alertMessage = "測試SAP連線失敗!" + ex.ToString(); } finally { f_loading = false; } } /// 三、取得 SAP API 參數資訊 async Task GetFuncMeta() { try { f_loading = true; await Task.Run(() => { using var conn = new SapConnection(sap.connString); conn.Connect(); StringBuilder fnDesc = new(); funcDesc = null; var fnMeta = conn.GetFunctionMetadata(sap.funcName); fnDesc.AppendLine($"Func. name: {fnMeta.GetName()}"); fnDesc.AppendLine($"Parameters count: {fnMeta.Parameters.Count}"); foreach (var prm in fnMeta.Parameters) { fnDesc.AppendLine($" {prm.Name}:{prm.Type.ToString()}:{prm.Description}"); } funcDesc = fnDesc.ToString(); //fnMeta.Parameters.TryGetValue("PARAMETER_NAME", out var parameterNameMetadata); conn.Disconnect(); alertSeverity = Severity.Success; alertMessage = "取 SAP API 函式資訊成功。"; }); } catch (Exception ex) { alertSeverity = Severity.Error; alertMessage = "取 SAP API 函式資訊失敗!" + ex.ToString(); } finally { f_loading = false; } } //## resource ------------------------ record SapInfo { public string connString { get; set; } public string funcName { get; set; } } }