1

I'm working on a business application modernization project where I need to call existing AS400 programs (RPG, CLP ...) from a .NET 8 app.

These RPG programs have I/O parameters with complex Data Structures (DS) and I need to create/parse these DS from/to POCOs.

Our server is AS400 V7.4 on Power 9 but that doesn't really matter here.

Assume the DS empl below and a program EMPLPGM taking this DS as first parameter :

dcl-ds empl qualified;
    name char(10) inz('Unknown'); 
    salary packed(9 : 2) inz(0);
end-ds;

The corresponding POCO in .NET for this DS would be:

public class Employee {
    public string Name {get; set;}
    public decimal Salary {get; set;}
}

Now what I'd like to achieve is the following:

var empl = new Employee() {
    Name = "Foo",
    Salary = 50000.0M
};

//Calling program in a "dapper way"
//Does this exist or can be achieved ?
someClass.SomeMethodToCallProgram("EMPLPGM", new[] {P1 = empl});

Console.WriteLine(empl.Name); // I/O Parameter, output data is relevant

Is it something achievable or do we need to reconsider the project?

EDIT 1

As suggested I'm investigating direct call through the network. Here is a list of TCP/IP ports for the different services. Do anybody know if the Remote Command service can be used to call a program from a remote client over the network ? Any idea to find a documentation of the underlying protocol ?

EDIT 2

I came across a 3rd party solution called Aumerial nti. It seems to provide remote program call. Has anyone ever tested this ? I cannot see any feedback out there.

This snippet on their website here caught my attention:

//Open connection
var conn = new NTiConnection(connectionString);
conn.Open();

//Create parameter list:
List<NTiProgramParameter> parms = new List<NTiProgramParameter>() {
  new NTiProgramParameter("Hello", 10).AsInput(), //CHAR(10) INPUT
  new NTiProgramParameter("World", 10).AsInput(), //CHAR(10) INPUT
  new NTiProgramParameter(new byte[] {0x00}).AsInput(), //CHAR(10) INPUT
  new NTiProgramParameter(128).AsInput(), //INTEGER (BYTE(4)) INPUT
  new NTiProgramParameter("", 128).AsOutput(), //CHAR(128) OUTPUT
  new NTiProgramParameter("", 50) //CHAR(50) I/O
};

//Program call
conn.CallProgram("MYLIB", "MYPGM", parms);

//Data retrieval from return variable (parameter n°5)
string message1 = parms[4].GetString(0, 64);
string message2 = parms[4].GetString(64, 64);

//Closing the connection
conn.Close();

Looks like I have a good candidate to solve my issue here, waiting for an answer from the editor...

10
  • I'm assuming you'll need some sort of network call here, since presumably .NET won't run on the AS400 O/S. I expect that will add substantial complication to any attempt to trigger a program on the AS400 from the .NET code. One option could be to have a webserver on the AS400 running a program which will accept HTTP requests containing data, and then will trigger the RPG program from its own code, if that's possible. That way, at least the RPG call is being made locally, which I'd imagine ought to be much easier. The .NET app would then just have to send a HTTP request, which is easy. Commented Jun 4 at 13:14
  • That's something I considered but was looking for something more direct. Running a webserver on the AS is somthing that I'd like to avoid... Don't get me wrong, I consider ODBC outdated from a .NET perspective where we have native drivers for almost everything. Also I'm not trying to run .NET on the AS400 itself but from another server. Commented Jun 4 at 13:18
  • I've removed the database related stuff from your post - as you said yourself that's a separate issue. Therefore it needs you to post it in a separate question. I would think that's probably an easier challenge than this one anyway Commented Jun 4 at 13:38
  • 1
    was looking for something more direct...well first you need to find out whether RPG programs on the AS400 can be invoked remotely over the network. Once you find out how that's done (assuming it's actually possible), then you can work out whether .NET can interact with that invocation protocol. So the .NET part of this question is still premature, I think. At a guess, maybe you can do something using SSH or similar. My knowledge of the AS400 is zero unfortunately - I saw this post because of the C# tag. Commented Jun 4 at 13:39
  • 1
    In interfacing with an AS400 we used a "staging area" where we would place files for the AS400 to "pickup" and vise versa. The files themselves represented the triggers. Commented Jun 4 at 16:23

3 Answers 3

3

I won't say it's impossible, but it's going to be very, very difficult to deal with a RPG Data structure containing packed decimal from .NET.

Basically, for your {"Foo", 50000.0M}, the RPG is expecting x'C6969640404040404040005000000F' to be passed in. If all the DS subfields were character or zoned numeric it be a bit easier as it'd be basically one long string of "Foo 005000000"
( x'C6969640404040404040F0F0F5F0F0F0F0F0F0')

(Technically the RPG's getting the value by reference, but that shouldn't really matter here)

From an external invocation perspective, it's much better to have individual parameters on the RPG program. It would be worthwhile to see if a wrapper program with individual parameters can be created on the IBM i side.

If you don't already have it, you should install the IBM i Access Client Solution (ACS) package include the Windows Application Package. ACS is Java based, but the Windows Application Package provides "Connectivity to Db2® for i using ODBC, .Net, OLE DB and XDA" for Windows machines. There's also a Linux and Mac application package that provide ODBC and unixODBC connectivity.

Then take a look a Windows Application Package: Programming

One of the comments mentioned ssh , which is available on the IBM i. But again, passing a single data structure parameter is going to be complicated. A wrapper with individual parameters preferably with a *CMD front end would be considerably easier to invoke from the outside.

If I were doing this, I'd setup a web service with the RPG wrapper than then invokes the actual program.

EDIT

Depending on where the data originates, you might be able to remove .NET from the picture altogether. Apache Camel for instance has a JT400 connector that "supports data queues, message queues, or program call." But again, there's likely to be work on the IBM i side.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for these info, I might consider the wrapper alternative if that's the only way to go but extra work on the IBM i and overhead just for an external call.. Meanwhile I came across a 3rd party solution called aumerial nti do you know it or have any experience with it ? (see my edit in the OP)
never heard of it. Looks interesting, and the NTiProgramParameter class has an Append() method for "when a parameter has a complex structure made up of several pieces of data, possibly of different types."
I just had a discussion with them, this Append() method was neatly designed to create datastructures, I have it working now. I'll self-answer my question. I really appreciate your help 🙂
2

Solution (self-answer)

With some further research I came across Aumerial.Data.Nti on NuGet. It's a fully standalone .NET driver to connect to DB2 and call programs remotely from .NET without the need to install anything on the AS400 nor writing wrapper (ie exactly what I was looking for, just a nuget package).

It's a 3rd party solution so I contacted the vendor and explained my use case, they provided me with a free trial and some sample code very quickly.

Here is what I came up with:

//Employee instance (POCO)
var empl = new Employee() {
    Name = "Foo",
    Salary = 50000.0M
};

//Opening the connection
var conn = new NTiConnection("server=...;user=...;password=...");
conn.Open();

//Creating the parameter according to the datastructure
var parms = new List<NTiProgramParameter>() {
    new NTiProgramParameter(empl.Name, 10).Append(empl.Salary, 9, 2)
};

//Program call
conn.CallProgram("PGMLIB", "EMPLPGM", parms);

//Retreive returned data from the DS to the POCO
empl.Name = parms[0].GetString(0, 10); //0 offset, 10 in length
empl.Salary = parms[0].GetPackedDecimal(9, 2, 10); //PACKED(9, 2), 10 offset

...et voilà, almost too easy.

It seems to have many features including Entity Framework Core, we'll buy a license for sure. Hope this and my feedback can help somebody with similar use case.

2 Comments

Hello, we've been looking into reading a dataqueue from a .net 8/9 (preferably an azure function on linux) and this nuget might help us getting there. Did you need a license to use this package? Thank you
Sorry, missed the "they provided me with a free trial" part :)
0

AFAIR you can call any application program from SQL as a stored procedure call, as long as the respective user's authority permits. This removes http requests and complicated remote start of applications from the equation. Not sure how parameters are passed in such circumstances, I'm not very fluent in the SQL world of IBM i. But maybe this is a start.

With a project I'm involved, ODBC was used as transport because that's what was established for accessing data in the i before .NET became involved in that project at all. "Outdated" (as you call ODBC) should not be a reason to make your life more miserable by trying to eliminate it. Because "almost" is not 100%. 😊

2 Comments

thanks so maybe a combination of a wrapper program called by an SQL stored procedure. I'll try to work something out 🤔
SQL Stored procs are discussed in the docs I linked to. And yes, a wrapper would make working with the params easier.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.