Allow me to explain that I am by no means an expert on this subject matter. In truth, I know very little about it. However, considering the extremely limited resources that seem to be available for calling AS/400 RPG programs from .NET, I thought it might be helpful to share what little I do know. Additionally, I am not an AS/400 developer, so if I misuse AS/400 terminology, please forgive me.
IBM's iSeries Client Access provides a library, cwbx.dll, that makes it possible to call RPG (probably other languages as well) programs running on an AS/400 from any Microsoft .NET aaplication. In this example, we will be calling it from ASP.NET. To find out if you have this ability, I would recommend you search your system for cwbx.dll.
In the steps below, I will present the code necessary to make the AS/400 RPG program call, in the simplest form possible. For this example, I will show how to make the call from an ASP.NET web application. I will also present my real code (minus application specific lines such as logging) below the article. I will reference this code as my real code in the article.
Be sure to check out part 2 in this series by clicking here.
Step 1 - Add the reference
First, make sure you add cwbx.dll to your ASP.NET web project's references. If you do this properly, you should see a file named Interop.cwbx.dll in your project's bin directory.
Step 2 - Get Your Configuration Settings
You are going to need 5 pieces of AS/400 specific information to make the call. They are:
- AS/400 server where the program exists
- AS/400 library where the program exists
- AS/400 user id that has access to the AS/400 library referenced above
- Password for that AS/400 user account
- Name of the program to call
In my real code, in web.config, I have added a key with all 5 of those specified, semi-colon delimited, as the value for the key. For example:
<
add key="partsPricingConfig" value="server;userid;password;library;program" />
In my web.config file, I have real values for the server, user id, etc. In this example though, I will not add the complexity of parsing that string, and will instead, hardocde these values where appropriate. In your real code, you should not do this though because you would not want to have to recompile and deploy your code if your password changes, or the program gets moved to a different library.
Step 3 - Initialize Objects
Create the objects to place the call:
cwbx.AS400System as400 = new cwbx.AS400SystemClass();
cwbx.Program program = new cwbx.Program();
as400.Define(server);
program.system = as400;
program.system.UserID = userid;
program.system.Password = password;
program.LibraryName = library;
program.ProgramName = program;
Step 4 - Verify The Connection (Optional)
Before invoking the call, you may want to check to insure the connection still exists. If you just instantiated and initialized them, there is no reason they should not. But, if you have code that gets reused, and the objects are initialized ahead of time, like in my real code, the server connection may have been lost.
if(as400.IsConnected(cwbx.cwbcoServiceEnum.cwbcoServiceRemoteCmd) == 0)
{
// Lost connection with the AS400. Disconnect, then reconnect.
as400.Disconnect(cwbx.cwbcoServiceEnum.cwbcoServiceAll);
as400.Connect(cwbx.cwbcoServiceEnum.cwbcoServiceRemoteCmd);
if(as400.IsConnected(cwbx.cwbcoServiceEnum.cwbcoServiceRemoteCmd) == 0)
{
// You have a real problem here and cannot reconnect. Handle this somehow.
}
}
It may look odd that I do a Disconnect() even though I already checked the connection. However, something was working incorrectly when trying to (re)Connect(). IBM support told me that I needed to manually call disconnect because a field was left set that prevented the reconnect from succeeding. This was a bug, and may have been fixed since then. You can read more about this here:
http://www-912.ibm.com/s_dir/slkbase.NSF/515a7ef1f8deef8c8625680b00020380/b5f7437961e391d986256e52005fcbf2?OpenDocument&ExpandSection=1
Step 5 - Parameter Construction
More than likely, the program you are going to call will need some arguments passed, and possibly some returned. To do this, we must construct the input and output parameters. Fortunately, the cwbx.dll provides helper classes for this purpose. Because the parameters are going to vary drastically from RPG program to program, in my real code I share below, the AS400Program class doesn't do any parameter construction. It expects the calling code to do this.
First, you should be aware that there are a handfull of converters, the helper classes, for this purpose. I will show two, cwbx.StringConverter and cwbx.PackedConverter. Since in my real code I use both, here is some code showing how to instantiate and use both. Obviously, the converters used, and number of parameters constructed, will depend on the program you are calling, so I will only show one of each. But, you can find them all documented in your iSeries Access Programmer's Toolkit. Open the Data Manipulation node in the tree at the left and select the ActiveX node. A popup will open describing the converter objects.
cwbx.StringConverter stringConverter = new cwbx.StringConverterClass();
cwbx.PackedConverter packedConverter = new cwbx.PackedConverterClass();
packedConverter.DecimalPosition = 4;
packedConverter.Digits = 5; cwbx.ProgramParameters parameters = new cwbx.ProgramParametersClass();
parameters.Append("Parameter1Name", cwbx.cwbrcParameterTypeEnum.cwbrcInout, 12);
stringConverter.Length = 12;
parameters["Parameter1Name"].Value = stringConverter.ToBytes("SomeString".PadRight(12, ' '));
parameters.Append("Parameter2Name", cwbx.cwbrcParameterTypeEnum.cwbrcInout, 15);
parameters["Parameter2Name"].Value = packedConverter.ToBytes("0");
Notice that when using the StringConverter for parameter 1, that I pad my input string to make sure it is 12 bytes, the size I specified. You will also notice that in my real code below, I don't hardcode the literal 12, I use an enum for consistency, and reference it everywhere I need to specify the size. That enum will contain the lengths for all my parameters.
Also notice that both of these parameters are specified to be both input and output (cwbrcInout). This is dictated by the way the RPG program has specified the parameters in the source code. In this program's case (my real code example below), it would be more correct for the first one to be input only (cwbrcInput) since it cannot change, and for the second one to be output only (cwbrcOutput) since it is only written to. But that is not the way the RPG code was written.
Step 6 - Invoke The Call
Make sure we do a try/catch and iterate through all the errors.
try
{
program.Call(parameters);
}
catch(Exception ex)
{
if(as400.Errors.Count > 0)
{
foreach(cwbx.Error error in as400.Errors)
{
// Log something
}
}
if(program.Errors.Count > 0)
{
foreach(cwbx.Error error in program.Errors)
{
// Log something
}
}
}
Step 7 - Cleanup
We need to close the connection. WARNING: In previous versions of this article, I had no cleanup code. Just recently, I found this left resources open on the AS/400. 11/16/2006. In my real code below, I added a Close method to the class. Make sure you call it when you are finished with the object.
as400.Disconnect(cwbx.cwbcoServiceEnum.cwbcoServiceAll);
Below is my real code that I use in my production system. This code is in a file named AS400Program.cs.