C# NET6 call SAP API 紀錄 - 使用 SapNwRfc
為工作紀錄。於 .NET6 環境使用 C# 呼叫 SAP API 應用紀錄。on 2022-10-12。
關鍵知識/注意事項
SAP API
為 SAP 獨立的規格用於通訊,功能效果類似 Web API,當然規格完全不相容。不相容的好處當然就是"安全"囉。官方與 C# 通訊的模組,SAP Connector for Microsoft .NET 3.0,仍活在 .NET Framework 3.0 很無言啊,現在都是 .NET6 的時代了。
還好很幸運有先行者解決了與 .NET Core 通訊的問題,SapNwRfc,在使用時期雖是為 .NET5 設計的然在 .NET6 仍然有效。
SapNwRfc 的實作其實是把 SAP 原生的 native DLL 包裏起來,外殼是 .NET Core 內層是 native DLL 函式庫,依不同平台各自下載。我們使用的版本是 the SAP NetWeaver RFC SDK Version 7.50 Libraries,下載此資源需有 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 套件:
PM> Install-Package SapNwRfc
下載 the SAP NetWeaver RFC SDK Version 7.50 Libraries。 注意,下載此資源需透過有授權的 SAP 帳戶。
安裝適用於 Visual Studio 2013 的 Visual C++ 可轉散發套件。 太概 SapNwRfc SDK Libraries 是用 VS2013 開發的 navitve DLL 故需有此模組才能運轉吧。

二、設定環境參數
引用 SapNwRfc 原生函式庫有兩用方法,新的方法是透過 NativeLibrary.SetDllImportResolver
,但這招不好用且失敗了。反而舊的方法,用 PATH 環境參數找 SapNwRfc 原生函式庫四個檔案:sapnwrfc.dll
、icudt50.dll
、icuin50.dll
、icuuc50.dll
,簡單且成功了。
紀錄
1)把拿到的 SapNwRfc 原生函式庫全部存入 C:\MyLib\nwrfcsdk
。

2)設定 PATH
開啟【檢視進階系統設定】工具程式,選取[系統內容\環境變數\系統變數\PATH]編輯環境變數,把上面放原生函式庫的路徑加入。

三、開始寫程式
在 appsettings.json 設定 SAP API 連線字串
{
...
"ConnectionStrings": {
"SLP_SAPAPI": "AppServerHost=192.168.xxx.xxx; SystemNumber=11; User=xxxxxx; Password=xxxxxx; Client=800; Language=ZH;"
},
}
在 Program.cs 註冊注入服務
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();
...
定義通訊 Model
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; }
}
終於可以應用了 - 呼叫 SAP API
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;
}
}
}
其他:測試 SAP API 通訊
@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; }
}
}
EOF
Last updated