.NET Connector for SAP Netweaver RFC
Stable | Latest |
---|---|
The latest stable version can be found here: https://github.com/dbosoft/YaNco/tree/support/4.3
This library provides an alternative SAP .NET Connector based on the SAP NetWeaver RFC Library.
Features:
.NET
The library requires .NET Framework >= 4.7.1 or .NET Core 2.0 or higher.
Supported platforms: Windows, Linux and MacOS.
Windows: C++ Runtimes
On Windows the Visual Studio 2013 (VC++ 12.0) runtime library has to be installed. Library can be downloaded here: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads
SAP Netweaver RFC SDK
To use YaNco you need to obtain SAP NW RFC SDK 750 from _SAP Support Portal_.
A prerequisite to download is having a customer or partner account on SAP Support Portal and if you are SAP employee please check SAP Note 1037575 - Software download authorizations for SAP employees.
SAP NW RFC SDK 750 is fully backwards compatible, supporting all NetWeaver systems, from today, down to release R/3 4.6C. You can therefore always use the newest version released on SAP Support Portal and connect to older systems as well.
The easiest way to get started is by installing the available NuGet package. Take a look at the Using section learning how to configure and use YaNco. Go to the Build section to find out how to build YaNco.
Samples and articles
Please note that most samples have not yet been updated to version 5!
In order to call remote enabled ABAP function module (ABAP RFM), first a connection must be opened. The connection settings have to be build from a string/string dictionary, for example from a ConfigurationBuilder.
var configurationBuilder =
new ConfigurationBuilder();
configurationBuilder.AddUserSecrets<Program>();
var config = configurationBuilder.Build();
var settings = new Dictionary<string, string>
{
{"ashost", config["saprfc:ashost"]},
{"sysnr", config["saprfc:sysnr"]},
{"client", config["saprfc:client"]},
{"user", config["saprfc:username"]},
{"passwd", config["saprfc:password"]},
{"lang", "EN"}
};
With these settings you can now create a ConnectionBuilder instance and use it to build a connection builder function.
var connectionBuilder = new ConnectionBuilder(settings);
var connFunc = connectionBuilder.Build();
The connection builders Build() method returns a function that can be reused to open connections.
Under the hood the ConnectionBuilder also creates also a SAPRfcRuntime instance. The SAPRfcRuntime abstracts between the SAP Netweaver RFC SDK and YaNco and encapsulates all I/O between YaNco and the RFC SDK.
You can customize the runtime on the ConnectionBuilder with the ConfigureRuntime() method. For example to add a logger:
var connectionBuilder = new ConnectionBuilder(connFunc)
.ConfigureRuntime(c =>
c.WithLogger(new MyLogger()));
Please note: In versions below 5.0 we used the IRfcRuntime interface implemented by type RfcRuntime. IRfcRuntime and RfcRuntime are now deprecated.
The new SAPRfcRuntime has a different concept (see below for functional IO patterns). Therefore ConfigureRuntime now configures the runtime settings used to create a new SAPRfcRuntime.
For classic OO usage the RfcContext is the easiest method call functions from .NET to SAP. You can open a RfcContext directly from the connection function that you build with ConnectionBuilder.Build()
var connectionBuilder = new ConnectionBuilder(settings);
var connFunc = connectionBuilder.Build();
using (var context = new RfcContext(connFunc))
{
...
}
The RfcContext will automatically open and close the connection and is disposeable.
We provide a extension method on the RFCContext that supports a syntax similar to the ABAP call function command, except that it is using function callbacks to pass or retrieve data:
using (var context = new RfcContext(connFunc))
{
await context.CallFunction("DDIF_FIELDLABEL_GET",
Input: f => f
.SetField("TABNAME", "USR01")
.SetField("FIELDNAME", "BNAME"),
Output: f => f
.GetField<string>("LABEL"))
// this is from language.ext to extract the value from a either
.Match(r => Console.WriteLine($"Result: {r}"), // should return: User Name
l => Console.WriteLine($"Error: {l.Message}"));
}
The Result of the function is a Either<L,R> type (see language.ext Either left right monad). The Match call at the end either writes the result (right value) or a rfc error (left value).
Structures
Structures can be set or retrieved the same way. Another example extracting company code details (you may have to change the company code if you try this example):
using (var context = new RfcContext(connFunc))
{
await context.CallFunction("BAPI_COMPANYCODE_GETDETAIL",
Input: f => f
.SetField("COMPANYCODEID", "1000"),
Output: f => f
.MapStructure("COMPANYCODE_DETAIL", s=> s
.GetField<string>("COMP_NAME"))
)
.Match(r => Console.WriteLine($"Result: {r}"),
l => Console.WriteLine($"Error: {l.Message}"));
}
Alternatively, you can also use a LINQ syntax:
using (var context = new RfcContext(connFunc))
{
await context.CallFunction("BAPI_COMPANYCODE_GETDETAIL",
Input: f => f
.SetField("COMPANYCODEID", "1000"),
Output: f => f
.MapStructure("COMPANYCODE_DETAIL", s =>
from name in s.GetField<string>("COMP_NAME")
select name
))
.Match(r => Console.WriteLine($"Result: {r}"),
l => Console.WriteLine($"Error: {l.Message}"));
}
Especially for complex structures, the LINQ syntax is often easier to read.
Tables
Getting table results is possible by iterating over the table rows to retrieve the table structures. Here an example to extract all company code name and descriptions:
using (var context = new RfcContext(connFunc))
{
await context.CallFunction("BAPI_COMPANYCODE_GETLIST",
Output: f => f
.MapTable("COMPANYCODE_LIST", s =>
from code in s.GetField<string>("COMP_CODE")
from name in s.GetField<string>("COMP_NAME")
select (code, name)))
.Match(
r =>
{
foreach (var (code, name) in r)
{
Console.WriteLine($"{code}\t{name}");
}
},
l => Console.WriteLine($"Error: {l.Message}"));
}
Input mapping
For Input (importing / changing ) arguments you can pass the value with the methods SetField, SetStructure and SetTable or a combination of all. For example to set values for a table you pass a IEnumerable to be processed to the SetTable method and provide a mapping function for each record in the IEnumerable:
var userNamesSearch = new string[] {"A*", "B*", "C*"};
var userList = await context.CallFunction("BAPI_USER_GETLIST",
Input:f => f.SetTable("SELECTION_RANGE", userNamesSearch ,
(structure,userName) => structure
.SetField("PARAMETER", "USERNAME")
.SetField("SIGN", "I")
.SetField("OPTION", "CP")
.SetField("LOW", userName)
),
Output: f=> f.MapTable("USERLIST", s=>s.GetField<string>("USERNAME"))
).IfLeftAsync(l=>throw new Exception(l.Message));
foreach (var userName in userList)
{
Console.WriteLine(userName);
}
YaNco is build to be used in functional programming. Functional programming allows you to make your code more reliable and move your code toward declarative and functional code rather than imperative.
In functional code you typical start with your own Runtime instance:
var runtime = SAPRfcRuntime.New();
var connectionEffect = new ConnectionBuilder<SAPRfcRuntime>(settings)
.Build();
Please note the type argument on the ConnectionBuilder
.
The Build method now returns an IO effect (Aff<RT, IConnection>
) that is not executed immediately, but only when the IO effect is called with runtime.
var fin = await connectionEffect.Run(runtime);
fin.IfFail(error => error.Throw());
Using this concept, you can chain multiple effects to build the call to the SAP system:
using static Dbosoft.YaNco.SAPRfc<Dbosoft.YaNco.Live.SAPRfcRuntime>;
var call = useConnection(connectionEffect, connection=>
from userName in callFunction(connection, "BAPI_USER_GET_DETAIL", f=>
f.SetField("USERNAME", "SAP*"),
f=> f.GetField<string>("USERNAME"))
select userName);
var fin = await call.Run(runtime);
fin.IfFail(error => error.Throw());
The call from above is without side effects, that means it will not cause any I/O without the runtime.
The static using imports methods of SAPRfc<RT>
so you can call useConnection and callFunction without any type.
You can also declare your own static classes where runtime is a type parameter, so you can replace SAPRfcRuntime with another runtime, e. g. for testing.
You can find a more general description of this concept in the language.ext wiki: https://github.com/louthy/language-ext/wiki/How-to-deal-with-side-effects
ABAP Callbacks
ABAP callbacks allows the backend system to call functions on the client.
There is build in support for the RFC_START_PROGRAM callback, that is used by SAP to request start of additional programs like saprfc and saphttp.
To register a start program callback you use the method WithStartProgramCallback
of the ConnectionBuilder
:
var connectionBuilder = new ConnectionBuilder(settings)
.WithStartProgramCallback(callback)
StartProgramDelegate callback = command =>
{
// validate and check the start request and start processes if necessary
// return ok if everything works or a error
return RfcErrorInfo.Ok();
};
You can register also other functions using following syntax:
var connectionBuilder = new ConnectionBuilder(settings)
.WithFunctionHandler("ZYANCO_SERVER_FUNCTION_1",
cf => cf
.Input(i =>
i.GetField<string>("SEND"))
.Process(Console.WriteLine)
.Reply((_, f) => f
.SetField("RECEIVE", "Hello from YaNco")))
In this example a function with the name ZYANCO_SERVER_FUNCTION_1 has to exist on the backend server, with two parameters (and CHAR field SEND and a CHAR field RECEIVE).
The registered function handler consists of 3 chained steps:
CallFunction
output mapping.CallFunction
). If you have no reply you can also call NoReply
to end the chain. RFC Servers
RFC servers can process RFC calls that have their origin in the SAP backend.
Instead of a opening a client connection a RFC server registers itself on the SAP system gateway. A pure RFC Server therefore needs no client connection at all.
However in practice also a client connection is used in most RFC Servers to obtain function and type metadata. YaNco supports both server only RFC Servers and RFC Servers with client connections:
var serverSettings = new Dictionary<string, string>
{
{ "SYSID", _configuration["saprfc:sysid"] }, // required for servers
{ "PROGRAM_ID", _configuration["saprfc:program_id"] },
{ "GWHOST", _configuration["saprfc:ashost"] },
{ "GWSERV", _configuration["saprfc:gateway"] },
{ "REG_COUNT", "1" }, // number of servers
};
var serverBuilder = new ServerBuilder(serverSettings)
.WithFunctionHandler(
"ZYANCO_SERVER_FUNCTION_1",
//build function definition
b => b
.AddChar("SEND", RfcDirection.Import, 30)
.AddChar("RECEIVE", RfcDirection.Export, 30),
cf => cf
.Input(i =>
i.GetField<string>("SEND"))
.Process(s =>
{
Console.WriteLine($"Received message from backend: {s}");
cancellationTokenSource.Cancel();
})
.Reply((_, f) => f
.SetField("RECEIVE", "Hello from YaNco")));
or by lookup of function metadata from client connection:
var serverBuilder = new ServerBuilder(serverSettings)
.WithClientConnection(clientSettings, c => c
.WithFunctionHandler("ZYANCO_SERVER_FUNCTION_1",
cf => cf
.Input(i =>
i.GetField<string>("SEND"))
.Process(Console.WriteLine)
.Reply((_, f) => f
.SetField("RECEIVE", "Hello from YaNco"))))
After configuring the RFC Server it can be started like this:
using var rfcServer = serverBuilder
.Build()
.StartOrException();
Transactional RFC
Transactional RFC (tRFC) is used in SAP Systems to synchronize transactions cross system boundaries. A tRFC call is identified by a unique transaction id that has is announced to the receiving side before the actual function call is send.
Assuming SAP is sending a tRFC call to your application following steps will happen during a tRFC call:
Check tRFC
In that step you save the incoming tRFC and verify if it was not allready saved before
Send tRFC call
The actual call will now be send. The recipient should process the data from the call but should not process it further.
Commit or Rollback
In case of a commit data can now be processed further or it has to be rolled back.
Confirm
Transaction is completed and can be removed or other cleanup operations can be executed.
To handle these steps in a RFC Server you can register a transactional RFC handler that will be called for each of these steps:
var serverBuilder = new ServerBuilder(serverSettings)
.WithTransactionalRfc(new MyTransactionRfcHandler())
// MyTransactionRfcHandler has to implement interface
// ITransactionalRfcHandler<RT>
public interface ITransactionalRfcHandler<RT>
{
Eff<RT,RfcRc> OnCheck(
IRfcHandle rfcHandle, string transactionId);
...
}
A sample implementation can be found in samples/net6.0/ExportMATMAS. This sample demonstrates how to receive IDocs with YaNco.
We use Visual Studio 2022 for building.
As explained above you have to obtain SAP NW RFC Library 750 from SAP Support Portal. But the SDK is only required to run test applications, so just building works without the RFC SDK.
If you download the SDK use the x64 version and copy to DLLs from lib folder to a directory in your PATH environment variable.
We use SemVer for versioning. For the versions available, see the tags on this repository.
See also the list of contributors who participated in this project.
The creators of YaNco, dbosoft, offer professional support plans which we strongly recommend for any organization using YaNco on a commercial basis.
They includes:
This project is licensed under the MIT License - see the LICENSE file for details
SAP, Netweaver are trademarks of SAP SE