This cross-platform library allows you to call SAP NetWeaver RFC functions from .NET 5, .NET Core and the .NET Framework.
The library is fully tested and production ready. Supported operating systems are Windows, Linux and macOS.
Also supports connection pooling for more complex applications, see below.
dotnet add package SapNwRfc
or
PM> Install-Package SapNwRfc
This library requires the SAP NetWeaver RFC Library 7.50 SDK C++ binaries that should be installed locally. For download and installation instructions see SAP Note 2573790.
You can either place the DLL's in your project output folder or put them in a folder available in the systems PATH
(Windows), LD_LIBRARY_PATH
(Linux) or DYLD_LIBRARY_PATH
(macOS) environment variable.
On Windows, the 7.50 version of the SAP binaries also require you to install the 64-bit version of the Visual C++ 2013 Redistributable package which can be downloaded and installed from here.
When your application is started as a child-process (f.e. when running it using Visual Studio for Mac), it is possible that the DYLD_LIBRARY_PATH
is not forwarded to the child-process. This is because of this runtime protection Apple has added to OSX.
The simplest solution to this problem is to make sure the SAP binaries are placed in the working directory of your application.
or to use the NativeLibrary.SetDllImportResolver
method in your application.
A more flexible workaround is to set the import resolver for the SapLibrary
using the SetDllImportResolver-method like this:
NativeLibrary.SetDllImportResolver(
assembly: typeof(SapLibrary).Assembly,
resolver: (string libraryName, Assembly assembly, DllImportSearchPath? searchPath) =>
{
if (libraryName == "sapnwrfc")
{
NativeLibrary.TryLoad(
libraryPath: Path.Combine("<your_sap_rfc_binaries_path>", libraryName),
handle: out IntPtr handle);
return handle;
}
return IntPtr.Zero;
});
string connectionString = "AppServerHost=MY_SERVER_HOST; SystemNumber=00; User=MY_SAP_USER; Password=SECRET; Client=100; Language=EN; PoolSize=5; Trace=8";
using var connection = new SapConnection(connectionString);
connection.Connect();
using var someFunction = connection.CreateFunction("BAPI_SOME_FUNCTION_NAME");
someFunction.Invoke();
class SomeFunctionParameters
{
[SapName("SOME_FIELD")]
public string SomeField { get; set; }
}
using var someFunction = connection.CreateFunction("BAPI_SOME_FUNCTION_NAME");
someFunction.Invoke(new SomeFunctionParameters
{
SomeField = "Some value",
});
class SomeFunctionParameters
{
[SapName("SOME_FIELD")]
public string SomeField { get; set; }
}
class SomeFunctionResult
{
[SapName("RES_ABC")]
public string Abc { get; set; }
}
using var someFunction = connection.CreateFunction("BAPI_SOME_FUNCTION_NAME");
var result = someFunction.Invoke<SomeFunctionResult>(new SomeFunctionParameters
{
SomeField = "Some value",
});
// Do something with result.Abc
class SomeFunctionResult
{
[SapName("RES_ABC")]
public string Abc { get; set; }
[SapName("RES_ADDR")]
public SomeFunctionResultItem Address { get; set; }
}
class SomeFunctionResultAddress
{
[SapName("STREET")]
public string Street { get; set; }
[SapName("NR")]
public string Number { get; set; }
}
class SomeFunctionResult
{
[SapName("RES_ABC")]
public string Abc { get; set; }
[SapName("RES_ITEMS")]
public SomeFunctionResultItem[] Items { get; set; }
}
class SomeFunctionResultItem
{
[SapName("ITM_NAME")]
public string Name { get; set; }
}
class SomeFunctionResult
{
[SapName("RES_ITEMS")]
public IEnumerable<SomeFunctionResultItem> Items { get; set; }
}
class SomeFunctionResultItem
{
[SapName("ITM_NAME")]
public string Name { get; set; }
}
class SomeFunctionParameters
{
[SapIgnore]
public string IgnoredProperty { get; set; }
[SapName("SOME_FIELD")]
public string SomeField { get; set; }
}
class SomeFunctionResult
{
[SapIgnore]
public string IgnoredProperty { get; set; }
[SapName("SOME_FIELD")]
public string SomeField { get; set; }
}
string connectionString = "AppServerHost=MY_SERVER_HOST; SystemNumber=00; User=MY_SAP_USER; Password=SECRET; Client=100; Language=EN; PoolSize=5; Trace=8";
SapServer.InstallGenericServerFunctionHandler(connectionString, (connection, function) =>
{
var attributes = connection.GetAttributes();
switch (function.GetName())
{
case "BAPI_SOME_FUNCTION_NAME":
var parameters = function.GetParameters<SomeFunctionParameters>();
function.SetResult(new SomeFunctionResult { Abc = "Some Value" });
break;
}
});
string connectionString = "GWHOST=MY_GW_HOST; GWSERV=MY_GW_SERV; PROGRAM_ID=MY_PROGRAM_ID; REG_COUNT=1";
using var server = SapServer.Create(connectionString);
server.Error += (object sender, SapServerErrorEventArgs args) => Console.WriteLine(args.Error.Message);
server.StateChange += (object sender, SapServerStateChangeEventArgs args) => Console.WriteLine(args.OldState + " -> " + args.NewState);
server.Launch();
var typeMetadata = connection.GetTypeMetadata("MY_STRUCTURE");
var typeName = typeMetadata.GetTypeName();
var fieldCount = typeMetadata.Fields.Count;
foreach (var fieldMetadata in typeMetadata.Fields)
{
var fieldName = fieldMetadata.Name;
var fieldType = fieldMetadata.Type;
}
typeMetadata.Fields.TryGetValue("FIELD_NAME", out var fieldNameMetadata);
var functionMetadata = connection.GetFunctionMetadata("BAPI_SOME_FUNCTION_NAME");
var functionName = functionMetadata.GetName();
var parameterCount = functionMetadata.Parameters.Count;
foreach (var parameterMetadata in functionMetadata.Parameters)
{
var parameterName = parameterMetadata.Name;
var parameterType = parameterMetadata.Type;
}
functionMetadata.Parameters.TryGetValue("PARAMETER_NAME", out var parameterNameMetadata);
SapLibrary.EnsureLibraryPresent();
This will throw an SapLibraryNotFoundException with a meaningful message in case the SAP RFC SDK binaries were not found.
The SapConnection and SapConnectionPool class both take a SapConnectionParameters instance or a connection string in the form of:
"AppServerHost=MY_SERVER_HOST; SystemNumber=00; User=MY_SAP_USER; Password=SECRET; Client=100; Language=EN; PoolSize=5; Trace=3";
Additional connection parameters can be added by creating a class that inherits from SapConnectionParameters:
public class MySapConnectionParameters : SapConnectionParameters
{
[SapName("CST_PARAM")]
public string CustomParameter { get; set; }
}
Input and output models used in function calls are mapped to and from SAP RFC parameter types by convention. In case the property name of the model differs from the SAP RFC parameter name, the [SapName]
-attribute can be used.
For each input and output model type, the library builds and caches a mapping function using expression trees.
SAP RFC parameter types don't have to be specified as they're converted by convention. Here's an overview of supported type mappings:
C# type | SAP RFC type | Remarks |
---|---|---|
int |
RFCTYPE_INT | 4-byte integer |
long |
RFCTYPE_INT8 | 8-byte integer |
double |
RFCTYPE_FLOAT | Floating point, double precision |
decimal |
RFCTYPE_BCD | |
string |
RFCTYPE_STRING / RFCTYPE_CHAR / ... | Gets a data field as string |
byte[] |
RFCTYPE_BYTE | Raw binary data, fixed length. Has to be used in conjunction with the [SapBufferLength] -attribute |
char[] |
RFCTYPE_CHAR | Char data, fixed length. Has to be used in conjunction with the [SapBufferLength] -attribute |
DateTime? |
RFCTYPE_DATE | Only the day, month and year value is used |
TimeSpan? |
RFCTYPE_TIME | Only the hour, minute and second value is used |
T |
RFCTYPE_STRUCTURE | Structures are constructed from nested objects (T) in the input or output model (see example) |
Array<T> |
RFCTYPE_TABLE | Tables are constructed from arrays of nested objects (T) in the input or output model (see example) |
IEnumerable<T> |
RFCTYPE_TABLE | Tables returned as IEnumerable |
The usage examples above are for simple applications that execute functions one-by-one on a single connection.
For more complex applications that require concurrency, connection pooling and retry on disconnect, it is advised to use the SapPooledConnection
and SapConnectionPool
from the Pooling
namespace.
See the SapConnectionPool
class for configuration options.
In the Startup.cs ConfigureServices
method, add:
services.AddSingleton<ISapConnectionPool>(_ => new SapConnectionPool(connectionString));
services.AddScoped<ISapPooledConnection, SapPooledConnection>();
In an ApiController
or service, do:
[ApiController]
public class UserController : ControllerBase
{
public UserController(ISapPooledConnection connection)
{
_connection = connection;
}
[HttpPost]
public IActionResult DoSomething(string action)
{
_connection.InvokeFunction("BAPI_SOME_FUNCTION_NAME");
return Ok();
}
}