We tried to design SAPCO2 as a library for data processing over SAP in the simplest and fastest way. We focused on quick use, simplifying it as much as possible.
Referenced by SapNwRfc and NwRfcNet projects that were previously developed during the development phase. I recommend that you take a look at these two projects before examining the project. Especially the Wrapper parts have emerged by combining the common aspects of the two projects.
supportted .Net Core, Net 5 ve .Net Frameworks.
I would like to thank Mahmut KOLTUK for his contributions at many points in the new architectural structure.
Our library needs .dll files developed with C ++ in SAP NetWeaver RFC Library 7.50 SDK. For the installation of the relevant SDK package and more: You can refer to the official page of: sparkles: SAP.
After obtaining the relevant SKD (requires an SAP licensed account from you);
- Extract your zip file to a directory of your own and show the lib directory in the files you extract to the environment variable
PATH
(Windows),LD_LIBRARY_PATH
(Linux),DYLD_LIBRARY_PATH
(MacOS) according to your operating system. - or directly copy the contents of the lib folder to your output directory.
To use the SDK package in the Windows operating system, you must install the 64-bit version of Visual C ++ 2013 redistributable package.
PackageManager
Install-Package SapCo2
Or DotNet
dotnet add package SapCo2
or Package Referance
<PackageReference Include="SapCo2" Version="1.2.0.1" />
Note: Has dependencies Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Options.
You can access all the examples described in the document with the projects under the Samples directory.
To use it, we add it with .AddSapCo2
on Dependency injection
. It needs connection information in the add process.
To assign the connection information as a string
var connectionString = "Name=ALIAS1;AppServerHost=HOST_NAME; SystemNumber=00; User=USER; Password=PASSWORD; Client=CLIENT_CODE; Language=EN; PoolSize=100; Trace=0;";
or multiple in Appsettings.json
{
"SapCo2": {
"DefaultServer": "ALIAS1",
"Connections": [
{
"Alias": "ALIAS1",
"ConnectionPooling": {
"Enabled": false,
"PoolSize": 8,
"IdleTimeout": "00:00:30",
"IdleDetectionInterval": "00:00:01"
},
"ConnectionString": "Name=ALIAS1;User=USER;Password=PASSWORD;Client=CLIENT_CODE;SystemId:xxx;Language=EN;AppServerHost=HOST_NAME;SystemNumber=00;MaxPoolSize:100;PoolSize=50;IdleTimeout:600;Trace=0;",
"ConnectionOptions": {}
},
{
"Alias": "ALIAS2",
"ConnectionPooling": {
"Enabled": false,
"PoolSize": 8,
"IdleTimeout": "00:00:30",
"IdleDetectionInterval": "00:00:01"
},
"ConnectionString": "",
"ConnectionOptions": {
"Name": "ALIAS2",
"User": "USER",
"Password": "PASSWORD",
"Client": "CLIENT_CODE",
"SystemId": "xxx",
"Language": "TR",
"AppServerHost": "HOST_NAME",
"SystemNumber": "00",
"MaxPoolSize": "100",
"PoolSize": "50",
"IdleTimeout": "600",
"Trace": "0"
}
}
]
}
}
in appsetting.json;
- You can assign connection information with
ConnectionString
orConnectionOptions
. - You can define multiple connections as ALIAS1 or ALIAS2.
- Set a Default connection, it is sufficient to specify the
Alias
in the connection definitions in theDefaultServer
part.
Startup.cs veya Program.cs veya vb.
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json", optional: true)
.AddEnvironmentVariables()
.Build();
Dependencyinjection ile IServceProvider içerisinde .AddSapCo2
fonksiyonuna IConfiguration bilgisi atayarak kullanabiliriz.
var serviceCollection = new ServiceCollection();
serviceCollection.AddOptions<IConfiguration>();
serviceCollection.Configure<IOptions<IConfiguration>>(configuration);
serviceCollection.AddSapCo2(s=>s.ReadFromConfiguration(configuration));
var serviceProvider = serviceCollection.BuildServiceProvider();
var serviceCollection = new ServiceCollection()
.AddSapCo2(s =>
{
s.DefaultServer = "LIVE";
s.RfcServers = new List<RfcServer>()
{
new RfcServer()
{
Alias = "LIVE",
ConnectionString =
"Name=LIVE;User=USER;Password=PASSWORD;Client=CLIENT_CODE;SystemId:xxx;Language=EN;AppServerHost=HOST_NAME;SystemNumber=00;MaxPoolSize:100;PoolSize=50;IdleTimeout:600;Trace=0;",
ConnectionPooling = new RfcConnectionPoolingOption() {Enabled = true, PoolSize = 10}
}
};
});
var serviceProvider = serviceCollection.BuildServiceProvider();
SAPCO2 can be used in 3 ways. Table, Bapi and RFC. In addition to these, a feature has been added where we will receive Meta Data for RFC and BAPI.
The most basic of these is RFC (Remote Functon Call). In other stages, it is actually a customized and simplified form of it. You can perform all operations using only RFC.
It will be sufficient if you create your input and output objects before the function call and pass them as parameters.
As an example, I used the recipe function CS_BOM_EXPL_MAT_V2_RFC
for RFC. associated with it
I create the BomInputParameter class for the parameters I want to give as a condition, and the BomOutputParameter class for the values ​​I want to get back.
I create these classes by selecting the fields I want to use from import and export, Table objects related to SAP SE37
.
By marking my classes that I created with the RfcEntityPropertAttribute
attribute, we specify the corresponding name on the SAP side. If we want, we can also provide an explanation of what happened.
I exclude properties that are not on the SAP side but are in my class with the RfcEntityIgnorePropertyAttribute
attribute.
BomInputParameter Class
public sealed class BomInputParameter: IRfcInput
{
[RfcEntityProperty("AUMGB")]
public string Aumgb { get; set; }
[RfcEntityProperty("CAPID")]
public string Capid { get; set; }
[RfcEntityProperty("DATUV")]
public DateTime Datuv { get; set; }
[RfcEntityProperty("EMENG")]
public string Emeng { get; set; }
[RfcEntityProperty("MKTLS")]
public string Mktls { get; set; }
[RfcEntityProperty("MEHRS")]
public string Mehrs { get; set; }
[RfcEntityProperty("STPST")]
public string Stpst { get; set; }
[RfcEntityProperty("SVWVO")]
public string Svwvo { get; set; }
[RfcEntityProperty("WERKS")]
public string Werks { get; set; }
[RfcEntityProperty("VRSVO")]
public string Vrsvo { get; set; }
[RfcEntityProperty("STLAN")]
public string Stlan { get; set; }
[RfcEntityProperty("STLAL")]
public string Stlal { get; set; }
[RfcEntityProperty("MTNRV")]
public string Mtnrv { get; set; }
}
As a condition, I define the production site code with plantcode
and the material for which I want to get a recipe as a variable with materialCode
.
var inputParameter = new BomInputParameter {
Aumgb = "0",
Capid = "PP01",
Datuv = DateTime.Now,
Emeng = "1",
Mktls = "x",
Mehrs = "x",
Stpst = "0",
Svwvo = "x",
Werks = plantCode,
Vrsvo = "x",
Stlal = "1",
Stlan = "1",
Mtnrv = materialCode
};
For the return values, we reference the structor and the table in our BomOutputParameter class. We show the tables as []
array.
public sealed class BomOutputParameter: IRfcOutput
{
[RfcEntityProperty("STB")]
public Stb[] StbData { get; set; }
[RfcEntityProperty("TOPMAT")]
public Topmat Topmat { get; set; }
}
Table Stb Class
public sealed class Stb
{
[RfcEntityProperty("STUFE", Description = "Seviye")]
public int Level { get; set; }
[RfcEntityProperty("OJTXB", Description = "Nesne kısa metni (bileşen grubu)")]
public string Name { get; set; }
[RfcEntityProperty("OJTXP", Description = "Nesne kısa metni (kalem)")]
public string SubItemName { get; set; }
[RfcEntityProperty("MMEIN", Description = "Temel ölçü birimi")]
public string Unit { get; set; }
[RfcEntityProperty("MNGLG", Description = "Temel ölçü biriminde hesaplanan bileşen miktarı")]
public decimal UnitAmount { get; set; }
[RfcEntityProperty("MNGKO", Description = "Bileşen ölçü biriminde hesaplanan bileşen miktarı")]
public decimal Amount { get; set; }
[RfcEntityProperty("IDNRK", Description = "Ürün ağacı bileşenleri")]
public string SubItemCode { get; set; }
[RfcEntityProperty("MENGE", Description = "Bileşen miktarı")]
public decimal ComponentAmount { get; set; }
[RfcEntityProperty("STLTY", Description = "Ürün ağacı tipi")]
public string TreeType { get; set; }
[RfcEntityProperty("STLKN", Description = "Ürün ağacı kalemi düğüm numarası")]
public decimal TreeNodeNumber { get; set; }
[RfcEntityProperty("STPOZ", Description = "Dahili Sayaç")]
public decimal InternalCounter { get; set; }
[RfcEntityProperty("STLNR", Description = "Ăśst Seviye")]
public int UpperTreeId { get; set; }
[RfcEntityProperty("XTLNR", Description = "Alt Seviye")]
public int SubTreeId { get; set; }
[RfcEntityProperty("MATMK", Description = "Mal Grubu")]
public string MaterialGroup { get; set; }
}
Struct Topmat Class
public sealed class Topmat
{
[RfcEntityProperty("MATNR", Description = "Malzeme Tanımı")]
public string Code { get; set; }
[RfcEntityProperty("MAKTX", Description = "Tanım")]
public string Definition { get; set; }
}
We take one IClient
object from dependency injection and call the ExecuteRfcAsync
function.
using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
BomOutputParameter bomResult = await client.ExecuteRfcAsync<BomInputParameter, BomOutputParameter>("CS_BOM_EXPL_MAT_V2_RFC", inputParameter);
As a result, we can see the desired output from the BomOutputParameter
type in the bomResult
.
Here, the connection is made via the DefaultAlias we created in AppSettings
. To do this via a different alias, we need to call the UseServer
function before the Execute
function.
using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
client.UseServer("ALIAS_NAME");
BomOutputParameter bomResult = await client.ExecuteRfcAsync<BomInputParameter, BomOutputParameter>("CS_BOM_EXPL_MAT_V2_RFC", inputParameter);
The entire the code
public BomOutputParameter GetBillOfMaterial(string materialCode, string plantCode)
{
var inputParameter = new BomInputParameter
{
Aumgb = "0",
Capid = "PP01",
Datuv = DateTime.Now,
Emeng = "1",
Mktls = "x",
Mehrs = "x",
Stpst = "0",
Svwvo = "x",
Werks = plantCode,
Vrsvo = "x",
Stlal = "1",
Stlan = "1",
Mtnrv = materialCode
};
using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
BomOutputParameter bomResult = await client.ExecuteRfcAsync<BomInputParameter, BomOutputParameter>("CS_BOM_EXPL_MAT_V2_RFC", inputParameter);
return bomResult;
}
BAPI is a special type of RFC that is created by the system and is used for operations such as updating, deleting, creating, and contains a return value named RETURN
for each operation result.
To make your bapi calls easier, you should derive your output objects from the IBapiOutput
interface or from the BapiOutputBase
abstrak class. This will automatically add the RETURN
object in your output. After the operation, the MessageType value of this object is Abort or Error
error is thrown. The rest of the operations are the same as for the RFC call.
public sealed class RfcBapiOutputParameter
{
[RfcEntityProperty("CODE")]
public string Code { get; set; }
[RfcEntityProperty("TYPE")]
public string MessageType { get; set; }
[RfcEntityProperty("MESSAGE")]
public string Message { get; set; }
[RfcEntityProperty("LOG_NO")]
public string LogNo { get; set; }
[RfcEntityProperty("LOG_MSG_NO")]
public string LogMessageNumber { get; set; }
[RfcEntityProperty("MESSAGE_V1")]
public string MessageV1 { get; set; }
[RfcEntityProperty("MESSAGE_V2")]
public string MessageV2 { get; set; }
[RfcEntityProperty("MESSAGE_V3")]
public string MessageV3 { get; set; }
[RfcEntityProperty("MESSAGE_V4")]
public string MessageV4 { get; set; }
}
I am using BBP_VENDOR_GETLIST
bapi for example. This bapi returns the code and name of the vendors in the system according to the Company code it contains.
Input
public class VendorBapiInputParameter: IBapiInput
{
[RfcEntityProperty("COMP_CODE")]
public string CompanyCode { get; set; }
}
Output
public class VendorBapiOutputParameter:IBapiOutput
{
[RfcEntityProperty("RETURN")]
public RfcBapiOutputParameter BapiReturn { get; set; }
[RfcEntityProperty("VENDOR")]
public Vendor[] Vendors { get; set; }
}
Table Vendor Class
public class Vendor
{
[RfcEntityProperty("VENDOR_NO")]
public string VendorNo { get; set; }
[RfcEntityProperty("NAME")]
public string Name { get; set; }
}
Note that it derives from the IRfcBapiOutput
interface and has the attribute [RfcEntityProperty("RETURN")]
.
It happens instantly with the post RFC process. The IRfcClient
object is superimposed and executed by calling the ExecuteBapiAsync
function.
using IRfcClient sapClient = _serviceProvider.GetRequiredService<IRfcClient>();
return await sapClient.ExecuteBapiAsync<VendorBapiInputParameter, VendorBapiOutputParameter>("BBP_VENDOR_GETLIST", inputParameter);
companyCode
values set 200
The entire the code
public VendorBapiOutputParameter GetVerdorsByCompanyCode(string companyCode)
{
var inputParameter = new VendorBapiInputParameter
{
CompanyCode = companyCode
};
using IRfcClient sapClient = _serviceProvider.GetRequiredService<IRfcClient>();
return await sapClient.ExecuteBapiAsync<VendorBapiInputParameter, VendorBapiOutputParameter>("BBP_VENDOR_GETLIST", inputParameter);
return result;
}
Since we do not have direct access to the table objects, the system presented to us
Using the RFC_READ_TABLE
function, we perform the pull from the table.
RFC_READ_TABLE
has certain standard rules. We perform data extraction from the table by assigning the relevant data to those fields. We save you from all this trouble and produce our table function from the ISapTable
interface for a simpler use.
After the basic RFC process here, the WA
struct in the DATA
table, which is output by RFC_READ_TABLE, is split according to the shredding character you specify and assigned to the relevant class properties.
Since the transformation operations during this process are handled differently from the RFC Map operation, you must mark the object you will use as a table with RfcEntity
properties and RfcEntityProperty
attributes. Points to the table with the RrfcTable
name field. Again, it has the unsafe
feature to be found as information. This generally indicates that the RFC is not up to SAP's standards and is specifically overridden. For our systems, it defines the enhancements referred to as 'Z'. Likewise, it has TablePropertySapType
and Length
properties for transforming properties. In addition, unsafe is used to indicate whether this feature is an enhancement, in the same way as the table. SubTypePropertyName
shows linked tables that will not be pulled in the first place. For now, we fill them by writing the SetOption
method ourselves. We define these fields as virtual so that they are not taken into account in our normal data.
Instead of the input parameter, the TEXT
property of the OPTIONS
table of RFC_READ_TABLE
is written as text with ABAP
of the condition statements. You can create this either by creating a text list in the form of MATNR EQ '12344555'
in plain text, or with Abap Query
, which we can chain by analogy to Linq. I will add details about Abap Quey
in Project Wiki. FIELDS
fields are automatically assigned according to the attributes in the output class you created. Features such as ROWCOUNT
, ROWSKIPS
, ROWDATA
are optional and can be crushed when necessary.
Without further ado, let's look at the example.
For the example, I will pull the MARA
table and its sub-details (Material definition MAKT
and Material class ZMM24030
) from the tables.
NOTE: If the Material classes table in the sub-details is not included in your SAP system because it is a development table, please replace new MaterialQueryOptions { IncludeAll = true }
with new MaterialQueryOptions { IncludeDefinition = true }
. In this way, you only pull the MAKT
table.
[RfcEntity("MARA", Description = "Material General Table")]
public class Material : ISapTable
{
[RfcEntityProperty("MATNR", Description = "Material Code", SapDataType = RfcDataTypes.CHAR, Length = 18)]
public string Code { get; set; }
[RfcEntityProperty("MATNR", Description = "Material Definition", SapDataType = RfcDataTypes.CHAR, Length = 18, SubTypePropertyName = "Code")]
public virtual MaterialDefinition Definition { get; set; }
[RfcEntityProperty("ZZEXTWG", Description = "Material Category Code", SapDataType = RfcDataTypes.CHAR, Length = 25, Unsafe = true)]
public string MaterialCategoryCode { get; set; }
[RfcEntityProperty("ZZEXTWG", Description = "Ürün Sınıfı", SapDataType = RfcDataTypes.CHAR, Length = 25, SubTypePropertyName = "ZZEXTWG", Unsafe = true)]
public virtual MaterialCategoryDefinition MaterialCategory { get; set; }
}
Afterwards, we create the conditions for which we will draw our painting with AbapQuey
. We set the condition as MATNR
material definition starting with materialCodePrefix
and not marked as deleted.
List<string> whereClause = new AbapQuery().Set(QueryOperator.Equal("MATNR", materialCode))
.And(QueryOperator.NotEqual("LVORM", true, RfcDataTypes.BOOLEAN_X)).GetQuery();
We create our IRfcClient
object and with the GetTableDataAsync
method we specify whether we want the condition we want to retrieve, the number of records and the unsafe (included with Development) fields.
using IRfcClient sapClient = _serviceProvider.GetRequiredService<IRfcClient>();
List<Material> materials = await sapClient.GetTableDataAsync<Material>(whereClause, rowCount: recordCount);
return await SetOptionsAsync(materials, new MaterialQueryOptions { IncludeAll = true });
The entire the code
public async Task<List<Material>> GetMaterialsByPrefixAsync(string materialCodePrefix,
MaterialQueryOptions options = null, bool getUnsafeFields = true, int rowCount = 0)
{
options ??= new MaterialQueryOptions();
List<string> whereClause = new AbapQuery().Set(QueryOperator.StartsWith("MATNR", materialCodePrefix))
.And(QueryOperator.NotEqual("LVORM", true, RfcDataTypes.BOOLEAN_X)).GetQuery();
using IRfcClient sapClient = _serviceProvider.GetRequiredService<IRfcClient>();
List<Material> result = await sapClient.GetTableDataAsync<Material>(whereClause, rowCount: recordCount);
return await SetOptionsAsync(result, options, getUnsafeFields).ConfigureAwait(false);
}
Sub Table Operations
That's it based on the usage of your table reading event. to fetch child tables
We create a function called setoptions
and connect the sub-tables we get with it to our main MARA
table. This process was created as an example of using fields such as unsafe
, SubProperty
.
Sub Table Class
Material Definition (MAKT) Table Class
[RfcEntity("MAKT", Description = "Material Definition Table")]
public class MaterialDefinition : ISapTable
{
[RfcEntityProperty("MATNR", Description = "Material Code", SapDataType = RfcDataTypes.CHAR, Length = 18)]
public string Code { get; set; }
[RfcEntityProperty("MAKTX", Description = "Material Short Definition", SapDataType = RfcDataTypes.CHAR, Length = 40)]
public string Definition { get; set; }
}
Material Category (ZMM24030) Table Class
[RfcEntity("ZMM24030", Description = "Material Category Definition Table", Unsafe = true)]
public class MaterialCategoryDefinition : ISapTable
{
#region Properties
[RfcEntityProperty("ZZEXTWG", Description = "Material Category Code", SapDataType = RfcDataTypes.CHAR, Length = 25)]
public string Code { get; set; }
[RfcEntityProperty("TANIM", Description = "Material Category Definition", SapDataType = RfcDataTypes.CHAR, Length = 50)]
public string Definition { get; set; }
[RfcEntityProperty("ZZPARCASAYI", Description = "Material Category Unit Part Count", SapDataType = RfcDataTypes.INTEGER, Length = 10)]
public int PartCount { get; set; }
#endregion
#region Methods
public override string ToString()
{
return Definition;
}
#endregion
}
Sub Table Operations
private async Task<List<Material>> SetOptionsAsync(List<Material> materialList,
MaterialQueryOptions queryOptions, bool getUnsafeFields = true)
{
if (!materialList.Any())
return materialList;
var taskList = new List<Task>();
var definitionList = new ConcurrentQueue<MaterialDefinition>();
var materialCategoryDefinitionList = new ConcurrentQueue<MaterialCategoryDefinition>();
List<Task> materialDefinitionTaskList = SetMaterialDefinitionOptionAsync(queryOptions, materialList, definitionList);
if (materialDefinitionTaskList.Any())
taskList.AddRange(materialDefinitionTaskList);
List<Task> materialCategoryTaskList =
SetMaterialCategoryOptionAsync(queryOptions, materialList, materialCategoryDefinitionList);
if (materialCategoryTaskList.Any())
taskList.AddRange(materialCategoryTaskList);
if (!taskList.Any())
return materialList;
await Task.WhenAll(taskList).ConfigureAwait(false);
ILookup<string, MaterialDefinition> materialDefinitionLookup = definitionList.ToLookup(x => x.Code);
ILookup<string, MaterialCategoryDefinition> materialCategoryLookup =
materialCategoryDefinitionList.ToLookup(x => x.Code);
foreach (Material material in materialList)
{
material.Definition = materialDefinitionLookup.Contains(material.Code)
? materialDefinitionLookup[material.Code].FirstOrDefault()
: null;
material.MaterialCategory = materialCategoryLookup.Contains(material.MaterialCategoryCode)
? materialCategoryLookup[material.MaterialCategoryCode].FirstOrDefault()
: null;
}
return materialList;
}
Material Defitinion Operations
private List<Task> SetMaterialDefinitionOptionAsync(MaterialQueryOptions queryOptions, List<Material> materialList, ConcurrentQueue<MaterialDefinition> definitionList)
{
var taskList = new List<Task>();
if (!queryOptions.IncludeDefinition)
return taskList;
var list = materialList.Where(x => x.Code != null).Select(x => (object)x.Code).ToList();
List<object>[] parts = list.Partition(PartitionCount);
foreach (List<object> part in parts)
{
List<string> query = new AbapQuery()
.Set(QueryOperator.In("MATNR", part))
.GetQuery();
taskList.Add(Task.Run(async () =>
{
using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
List<MaterialDefinition> definitions = await client.GetTableDataAsync<MaterialDefinition>(query);
foreach (MaterialDefinition definition in definitions)
definitionList.Enqueue(definition);
}));
}
return taskList;
}
Material Category Operations
private List<Task> SetMaterialCategoryOptionAsync(MaterialQueryOptions queryOptions, List<Material> materialList, ConcurrentQueue<MaterialCategoryDefinition> materialCategoryDefinitionList)
{
var taskList = new List<Task>();
if (!queryOptions.IncludeMaterialCategory)
return taskList;
var materialCategoryList =
materialList.Where(x => x.MaterialCategoryCode != null)
.Select(x => (object)x.MaterialCategoryCode)
.Distinct()
.ToList();
List<object>[] materialCategoryParts = materialCategoryList.Partition(PartitionCount);
foreach (List<object> part in materialCategoryParts)
{
List<string> queryList = new AbapQuery()
.Set(QueryOperator.In("ZZEXTWG", part))
.GetQuery();
taskList.Add(Task.Run(async () =>
{
using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
List<MaterialCategoryDefinition> definitions = await client.GetTableDataAsync<MaterialCategoryDefinition>(queryList);
foreach (MaterialCategoryDefinition definition in definitions)
materialCategoryDefinitionList.Enqueue(definition);
}));
}
return taskList;
}
In order to get the metadata information of the function we are used to from SapNco, I prepared a method that will combine a Field and Parameter information in SapNetwareRFC. Method only works for BAPI and RFC. Unfortunately, we cannot access its metadata from here, as we get the tables via RFC_READ_TABLE.
In the future, I will have a study to get this metadata from tables where table definitions such as DL003 are kept.
Similar to other uses, we call the ReadFunctionMetaData
function after generating the IRfcClient
object. This method returns us a list of type ParameterMetaData
. This list holds objects in Import
,Export
,Tables
that we list with SE37. We take the elements of these objects under the Field
list.
public class ParameterMetaData
{
public string Name { get; set; }
public string Type { get; set; }
public string Direction { get; set; }
public int NumericLength { get; set; }
public int UcLength { get; set; }
public int Decimals { get; set; }
public string Description { get; set; }
public string DefaultValue { get; set; }
public bool Optional { get; set; }
public List<FieldMetaData> Fields { get; set; }
}
public class FieldMetaData
{
public string Name { get; set; }
public string Type { get; set; }
public int NucLength { get; set; }
public int NucOffset { get; set; }
public int UcLength { get; set; }
public int UcOffset { get; set; }
public int Decimals { get; set; }
}
Just give the name of the RFC or BAPI whose metadata you want to access.
using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
List<ParameterMetaData> result = client.ReadFunctionMetaData(functionName);
That's all for now.