Code Monkey home page Code Monkey logo

yanco's Introduction

YaNco - Yet another .NET Connector

.NET Connector for SAP Netweaver RFC

Stable Latest
NuGet stable NuGet pre

This the README for UNSTABLE version v5!

The latest stable version can be found here: https://github.com/dbosoft/YaNco/tree/support/4.3

Description

This library provides an alternative SAP .NET Connector based on the SAP NetWeaver RFC Library.

Features:

  • thin, modern layer above native SAP Netweaver RFC SDK
  • DI container friendly API
  • Functional programming friendly API (using Language.Ext)
  • ABAP callbacks support (not possible with sapnco, see SAP note 2297083).

Platforms & Prerequisites

.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.

Getting started

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!

Using

Prepare connection

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.

RfcContext

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.

Calling ABAP Function Modules

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:

  • IMPORTING parameters are passed in the Input function
  • EXPORTING parameters are retured in the Output function
  • CHANGING and TABLES parameters can be used in both functions
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);
}

Functional programming usage

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

Calling functions from SAP to .NET

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:

  • Input mapping
    Extraction of values from the incoming function call and return extracted value for further processing. Any error here will stop the chain. Mapping features are the same as in CallFunction output mapping.
  • Processing
    The extracted value will be passed as argument to the process function. In this example the input is just written to the console. The process function can return a output value, that is passed to last step in chain.
  • Reply mapping
    The reply step sets the values of the response (same as Input mapping in 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:

  1. Check tRFC
    In that step you save the incoming tRFC and verify if it was not allready saved before

  2. 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.

  3. Commit or Rollback
    In case of a commit data can now be processed further or it has to be rolled back.

  4. 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.

Build

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.

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

Authors

  • Frank Wagner - Initial work - fw2568

See also the list of contributors who participated in this project.

Commercial support

The creators of YaNco, dbosoft, offer professional support plans which we strongly recommend for any organization using YaNco on a commercial basis.

They includes:

  • Prioritised resolution of any bugs. If you find a bug thatโ€™s blocking you, weโ€™ll prioritise it and release a hot fix as soon as itโ€™s ready.
  • Prioritised resolution and escalation of issues. If thereโ€™s a possible issue or question, weโ€™ll prioritise dealing with it.
  • Prioritised feature requests: Get new features that are important to you added first.
  • Personalised support and guidance via email, telephone or video. Speak to one of our team for advice and best practices on how to best manage deployments.
  • Discounts on training and coaching services

License

This project is licensed under the MIT License - see the LICENSE file for details

Trademark notice

SAP, Netweaver are trademarks of SAP SE

yanco's People

Contributors

christophermann avatar dbosoft-fw avatar dependabot[bot] avatar fw2568 avatar prodehghan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

yanco's Issues

YaNCo 3.x is incompatible with latest dbosoft.Functional package

If dbosoft.Functional 2.0 or higher is referenced in a project that use YaNco < 4.0 errors like this will occur:

One or more errors occurred. (Method not found: 'System.Threading.Tasks.Task1<!!1> LanguageExt.CondAsyncExt.Apply(System.Threading.Tasks.Task1<!!0>, System.Func2<System.Threading.Tasks.Task1<!!0>,System.Threading.Tasks.Task1<!!1>>)'.)`

Workaround: ensure that package Dbosoft.Functional 1.0 is used.

like this:
https://github.com/dbosoft/sap-dmsclient/blob/0b5e97a2111572e7c76a184826f67b53be7ced4c/src/SAPDms.Primitives/SAPDms.Primitives.csproj#L16

Add support for RFC servers

Currently YaNco only implements RFC clients.

To be used as full replacement for sapnco we have to implement also RFC servers.

Draft solution:

  • create a class FunctionBuilder around RfcCreateFunctionDesc and similar methods to specify the function from .NET
  • add a callback registration to IConnection to register a handler for the function.

The already build in RFC server function AllowStartOfPrograms could be refactored as reference implementation for this use case.

Add IRfcContext.ReadTable method

Using RFC_READ_TABLE is a typical request, even if it should be used carefully.
However we could add a method ReadTable that calls RFC_READ_TABLE and returns ABAPValue for datatype returned from RFC_READ_TABLE fieldlist.
This would allow users to use the build in mapping for fields supported by RFC_READ_TABLE and to add custom mappings.

var clientTable = await context.CallFunction("RFC_READ_TABLE",
    Input: f => f.SetField("QUERY_TABLE", "T000"),
    Output: f => 
        from fields in f.MapTable("FIELDS", s=>
            from fieldname in s.GetField<string>("FIELDNAME")
            from offset in s.GetField<int>("OFFSET")
            from length in s.GetField<int>("LENGTH")
            select new { fieldname, offset, length }
            )
        from lines in f.MapTable("DATA", s=>s.GetField<string>("WA"))
        select lines.Map(line =>
            fields.ToDictionary(field => field.fieldname, field =>
                {
                    //special handling for last field, as nwrfc truncates string at end
                    var length = field.offset + field.length > line.Length
                        ? line.Length - field.offset
                        : field.length;

                    return line.Substring(field.offset, length);
                })
        )
).IfLeftAsync(l=>throw new Exception(l.Message));

foreach (var row in clientTable)
{
    foreach (var column in row)
    {
        Console.Write($"{column.Value}\t");
        
    }
    Console.WriteLine();
}

Issue occuring in Azure function app but not in webapp

Hi,

I am trying to use Yanco connector in my azure function app in .netcore and I am getting exception on System.Threading.Tasks.Datafow when calling BAPI in SAP. But when I use the same code as webapp, i am getting results correctly.

Could you please help.
issue

Async deprecation in Language.Ext

This will have a big impact as we currently use EitherAsync heavily:

louthy/language-ext#1269

Looks like we need to migrate any async code to an async run effect (Eff with RunAsync).
However, it has been announced that EitherAsync will be supported for some years to come...

AB#627

dotnet core 3.1

Hi there,

I seem to have trouble getting it to run from a dotnet core 3.1 docker image.
If I run the app it works, but as soon as it is being dockerized, it doesn't work.

Missing dependencies or any ideas?

simplify build pipeline

it is no longer necessary to build x86 / x64 build.
Recreate entire build pipeline, could be much more simpler now.

BAPI_SALESORDER_CREATEFROMDAT2

I tried to call BAPI_SALESORDER_CREATEFROMDATA no data is inserted into SAP tables, whereas call returns success and even if I call commit transaction after BAPI_SALESORDER_CREATEFROMDATA function without error ..

thanks

Add a HandleReturn support for tables

HandleReturn currently only works for structures. We should add a logic to detect, if it is a structure or table and handle tables also.

/// <summary>
/// This methods extracts the value of a RETURN structure (BAPIRET or BAPIRET2) and processes it's value as
/// left value if return contains a non-successful result (abort or error).
/// This method accepts a <see cref="EitherAsync{RfcErrorInfo,IFunction}"/> with <see cref="IFunction"/> as right value and returns it if return contains a successful result.
/// </summary>
/// <returns>A <see cref="EitherAsync{RfcErrorInfo,IFunction}"/> with the function as right value or the left value.</returns>
public static EitherAsync<RfcErrorInfo, IFunction> HandleReturn(this EitherAsync<RfcErrorInfo, IFunction> self)
{
return self.ToEither().Map(f => f.HandleReturn()).ToAsync();
}

Add input mapping example

In input list you pass a IEnumerable of any type. The mapping function map will then be called for any entry in inputList and with a new structure entry for the input table.

here a sample that returns all users that starts with A, B, or C.

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);
}

Originally posted by @fw2568 in #84 (comment)

CallFunction: add a table row iterator function

Getting values from table rows is currently quite complicated:

using (var context = new RfcContext(ConnFunc))
{
    await context.CallFunction("BAPI_COMPANYCODE_GETLIST",
        Output: func => func.BindAsync(f =>
            from companyTable in f.GetTable("COMPANYCODE_LIST")

            from row in companyTable.Rows.Map(s =>
              from code in s.GetField<string>("COMP_CODE")
              from name in s.GetField<string>("COMP_NAME")
              select (code, name)).Traverse(l => l)

        select row))
    .ToAsync().Match(
        r =>
            {
                foreach (var (code, name) in r)
                {
                    Console.WriteLine($"{code}\t{name}");
                }
            },
        l=> Console.WriteLine($"Error: {l.Message}"));
}

We should add a "MapStructure" method to simplify this:

using (var context = new RfcContext(ConnFunc))
{
    await context.CallFunction("BAPI_COMPANYCODE_GETLIST",
        Output: func => func.BindAsync(f =>
            from companyTable in f.GetTable("COMPANYCODE_LIST")

            from row in companyTable.MapStructure(s =>
              from code in s.GetField<string>("COMP_CODE")
              from name in s.GetField<string>("COMP_NAME")
              select (code, name))

        select row))
    .ToAsync().Match(
        r =>
            {
                foreach (var (code, name) in r)
                {
                    Console.WriteLine($"{code}\t{name}");
                }
            },
        l=> Console.WriteLine($"Error: {l.Message}"));
}

Consider removing the C++/CLI implementation

As we have now a working interop implementation that works cross platform the project could be simplified a lot when we remove the C++/CLI implementation.

However when there is any advantage (performance?) it should be keeped.

In any case this would be a breaking change.

Issue with commit not "kicking in"

Hi,

I am trying to post some data to SAP.

I'm doing a "_context.CallFunction" for updating the values, it responds with success, but when I sequentially call "_context.CommitAndWait" nothing happens.

The call does not fail, but in SAP it seems like the commit never took place - any ideas?

Inject converters

For custom type convertion it should be possible to inject a converter.

.NET Standard deprecation

next major version of language.ext will require .NET 8, see also louthy/language-ext#1303

Currently we are based on .NET Standard to support both .NET framework and .NET Core.
As there is a alternative from SAP (sapnco) it should be save to assume that all users are on .NET core, but jumping directly to .NET 8 could be a hard move for some users.
Therefore .NET 8 migration should be scheduled for a version after 5, where version 5 is in between to close the gap to current language-ext features.

AB#639

Translate field parameter names to upper case

Field names should be passed in UPPER CASE to the rfcapi as it lookups them case sensitive.

Therefore, all methods that accept a parameter or a fieldname should translate it to upper case.

Call bapis direct from .net framework and .net core

I am trying to use your package to call bapis directly from .net projects. I'm trying to do it from .net framework 4.7.2 web project, and .net core web project.
I cannot get it to work from either. I've downloaded and installed all the prerequisites. I am also dropping all the SAP Netweaver DLLs into the bin folder (not sure if that's what I'm supposed to do). I feel like there's something very obvious I'm missing. Or perhaps your package is not intended for what i'm trying to do. But I can't even seem to make a connection to our SAP environment. Any help would be appreciated.

Add support for .NET Core

As .NET Core 3.1 C++/CLI support is available now for some time we should add support for .NET core.

Add interopt library for Linux and MacOs support

We should add a native implementation using interopt for support of non windows platforms.

It would be also interesting to do some performance testing, how fast a interopt implementation is compared to the current c++ / CLI implementation.

Table iterator fails on empty tables

If a table is empty the iterators tries to access a non existing table row.

TRACE   move to first table row by table handle, Data: {}
DEBUG   received error from rfc call, Data: {"Code":24,"Group":6,"Key":"RFC_TABLE_MOVE_BOF","Message":"No first row","AbapMsgClass":"","AbapMsgType":"","AbapMsgNumber":"","AbapMsgV1":"","AbapMsgV2":"","AbapMsgV3":"","AbapMsgV4":""}
TRACE   reading current table row by table handle, Data: {}
TRACE   received result value from rfc call, Data: {"Ptr":{"value":0}}
TRACE   reading type description by container handle, Data: {"Ptr":{"value":0}}
DEBUG   received error from rfc call, Data: {"Code":13,"Group":5,"Key":"RFC_INVALID_HANDLE","Message":"An invalid handle 'DATA_CONTAINER_HANDLE' was passed to the API call","AbapMsgClass":"","AbapMsgType":"","AbapMsgNumber":"","AbapMsgV1":"","AbapMsgV2":"","AbapMsgV3":"","AbapMsgV4":""}
TRACE   move to next table row by table handle, Data: {}
DEBUG   received error from rfc call, Data: {"Code":25,"Group":6,"Key":"RFC_TABLE_MOVE_EOF","Message":"No next row","AbapMsgClass":"","AbapMsgType":"","AbapMsgNumber":"","AbapMsgV1":"","AbapMsgV2":"","AbapMsgV3":"","AbapMsgV4":""}
An invalid handle 'DATA_CONTAINER_HANDLE' was passed to the API call

two issues come together in this case: even if the api reports thats there is no table row (RFC_TABLE_MOVE_BOF) the iterator moves to the first row. The second problem is that a null pointer response is not detected and used for the next call.

`NullReferenceException` when calling `Dbosoft.YaNco.Table.GetTypeDescription()` method

Call stack:

at Dbosoft.YaNco.Internal.Api.GetTypeDescription(IDataContainerHandle dataContainer, RfcErrorInfo& errorInfo) in /src/YaNco.Core/Internal/Api.cs:line 79
at Dbosoft.YaNco.RfcRuntime.GetTypeDescription(IDataContainerHandle dataContainer) in /src/YaNco.Core/RfcRuntime.cs:line 167
at Dbosoft.YaNco.DataContainer.GetTypeDescription() in /src/YaNco.Core/DataContainer.cs:line 53

What is null?

The dataContainer that is passed to Api.GetTypeDescription is null.

Why?

In RfcRuntime.cs, line 167 we have:

ITypeDescriptionHandle handle = Api.GetTypeDescription(dataContainer as Internal.IDataContainerHandle, out var errorInfo);

Both StructureHandle and FunctionHandle classes implement IDataContainerHandle, but TableHandle class does not. When the GetTypeDescription() method is called on a Table object, since its _handle field does not implement Internal.IDataContainerHandle, the above line of code passes null to the Api.GetTypeDescription.

empty connection settings cause missleading rfc error

If connection settings are empty the API responds with following error in RfcErrorInfo:

Invalid parameter 'unsigned paramCount' was passed to the API call

This is missleading as it looks like a library error but is caused by the application that passes the empty connection dictionary.
YaNco should check if there are any options in settings.

Output all Fields from RFC Response

The examples show how we can output specific Fields from an RFC request. How can I achieve outputting every fields that get returned from a RFC request?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.