February 7, 2012  
  You are here:  ArticlesASP.NET ArticlesCalling AS/400 (AS400) RPG Programs From ASP.NET

QTCommunicationSimplified50.gif


Try Radicomm's Quicktalk, the first enterprise-class Push-To-Talk application for your Motorola/Symbol rugged devices, in your distribution center for free.

Click here to enter the 21st Century.


Calling AS/400 (AS400) RPG Programs From ASP.NET
 

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.

 

 
AS400Program.cs - Source
 
using System;

namespace Interfaces.Source
{
    /// <summary>
    /// Summary description for AS400Program.
    /// </summary>
    public class AS400Program
    {
        private bool as400Configured = false;
        private cwbx.AS400System as400;
        private cwbx.Program program;

        // configuration format:
        // as400;userid;password;library;program
        public AS400Program(string configuration)
        {
            if(!as400Configured)
            {
                string[] settings = configuration.Split(';');
                if(settings.Length == 5)
                {
                    as400 = new cwbx.AS400SystemClass();
                    program = new cwbx.Program();

                    as400.Define(settings[0]);
                    program.system = as400;
                    program.system.UserID = settings[1];
                    program.system.Password = settings[2];
                    program.LibraryName = settings[3];
                    program.ProgramName = settings[4];
                    as400Configured = true;
                }
                else
                {
                    throw(new Exception(
                        string.Format("Invalid AS400Program configuration string : [{0}]", configuration)));
                }
            }
        }

        public bool Invoke(bool throwInsteadOfReturn, ref cwbx.ProgramParameters parameters)
        {
            bool success = false;

            try
            {
                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)
                    {
                        //  Log something here.
                    }
                }

                program.Call(parameters);
                success = true;
            }
            catch(Exception e)
            {
                // Capture to log the errors but then re-throw it to let caller know there was trouble.
                if(as400.Errors.Count > 0)
                {
                    foreach(cwbx.Error error in as400.Errors)
                    {
                        //  Log something here.
                    }
                }

                if(program.Errors.Count > 0)
                {
                    foreach(cwbx.Error error in program.Errors)
                    {
                        //  Log something here.
                    }
                }

                if(throwInsteadOfReturn)
                {
                    throw(e);
                }
            }

            return(success);
        }

        public void Close()
        {
            as400.Disconnect(cwbx.cwbcoServiceEnum.cwbcoServiceAll);
        }
    }
}
 
Using The AS400Program Class
 

Below is some code showing how one might invoke the AS400Program class.  Remember, in my web.config I have the following:

<appSettings>

...

   <add key="partsPricingConfig" value="server;userid;password;library;program" />

...

</appSettings>

 

 
Source Code Using AS400Program
 
//  Assume the RPG program being called takes one input paramater, PartId, and returns the PartPrice.

protected enum DataLengths : int
{
    PartId     = 12,
    PartPrice  = 15,
}


string partId = "3001891A";  // hardcoded for this example


AS400Program program = new AS400Program(ConfigurationSettings.AppSettings["partsPricingConfig"]);


cwbx.StringConverter stringConverter = new cwbx.StringConverterClass();
cwbx.PackedConverter packedConverter = new cwbx.PackedConverterClass();
packedConverter.DecimalPosition = 4;
packedConverter.Digits = (int)DataLengths.PartPrice;

cwbx.ProgramParameters parameters = new cwbx.ProgramParametersClass();

parameters.Append("PartId", cwbx.cwbrcParameterTypeEnum.cwbrcInout, (int)DataLengths.PartId);
stringConverter.Length = (int)DataLengths.PartId;
parameters["PartId"].Value = stringConverter.ToBytes(partId.PadRight((int)DataLengths.PartId, ' '));

parameters.Append("PartPrice", cwbx.cwbrcParameterTypeEnum.cwbrcInout, (int)DataLengths.PartPrice);
parameters["PartPrice"].Value = packedConverter.ToBytes("0");

program.Invoke(true, ref parameters);

string price = packedConverter.FromBytes(parameters["PartPrice"].Value);

program.Close();

return(Double.Parse(price));
 
Article Comments
 
DateNameComment
3/13/2007 vue Can I use this to call a CL program in the AS/400 environment? examples?
2/7/2007 Dan Gratton Hello, Very interesting article. if your willing to use the Microsoft .NET OLE DB wrapper and the IBMDA400 OLE DB provider there is a way of calling programs and getting data back. you would have something like dbcmd.commandtext = "{{ Call MYPGM (?,?,?,?)}}" This would be for calling MYPGM using the current users Library List and the program has four parameters. The parameter could be I,O or both, you define their direction and type on the add parameter function. You add the parameters to the command object DbCmd.Parameters.Add(...) And then you use DbCmd.Prepare() DbCmd.ExecuteNonQuery() I did not include any of the variable declaration information or the connection and disconnect code but that stuff is easy to do and easy to find sample code. I'm looking to do the same thing under the IBM.Data.DB2.iSeries provider have not been able to find the information. Have you seen anything?
1/26/2007 Jim Good Stuff, one question. How do I get it to use the library list instead of the .LibraryName to find the program?
1/20/2007 Joe Rattz (Article Author) Be sure to check out part 2 of this series which shows how to use structures and arrays as arguments, as well as calling an AS/400 API. There is a link near the top of this article. Or, you can find it here: http://www.netsplore.com/PublicPortal/Blog/tabid/284/EntryID/13/Default.aspx
12/27/2006 Joe Rattz (Article Author) Carmel and Isidro Santana, I just noticed in the IBM documentation that it is supposedly possible to have parameters that are arrays and structures. It looks like you will need to use the Structure object, StructureFields collection, and StructureField objects to do this. Unforunately, I have no way to test this at the current time. You can find these documented in your iSeries Access Programmer's Toolkit. Open the Data Manipulation node in the tree at the left and select the ActiveX node. On the page that comes up, there will be a link for "iSeries Access for Windows ActiveX Automation Objects - data manipulation". Click that link and a popup will open describing the converter objects, including the Structure and StructureField objects.
12/7/2006 Roger Great Article! Would this same technique work if I just want to call an AS/400 command such as "WRKUSRJOB"? (returns a list of AS/400 jobs) If so, what would I need to change to in the code? Thanks! Roger
11/22/2006 Dean Hey, thanks for posting information on this topic it is rare stuff and to those of us using it welcome to see. I have a bit of an addendum to your posting which some may find helpful. If you are running multiple commands on the host in a single session, and you are calling programs that use job level commit, you will want to disconnect with cwbcoServiceEnum.cwbcoServiceRemoteCmd and NOT cwbcoSeviceEnum.cwbcoServiceAll The reason for this is obscure, if you use cwbcoServiceAll all communications and data flow is terminated, including any messaging back to the AS/400 telling it that your remote command connection has terminated! This results in hanging transactions, and the next time you connect you may find your application hanging and timing out with exclusive locks, etc. cwbcoServiceEnum.cwbcoServiceRemoteCmd works fine for this (assuming you are only using remote commands!) Good luck thanks again!
11/17/2006 Derek Thanks for the heads up on the close routine. I now use this method you've documented to access all my RPG programs now. Works very well.
11/16/2006 Joe Rattz (Article Author) Just wanted to say that since posting the original article, I have used this same class (AS400Program) to call yet another AS/400 program. That is when I discovered the resource leak and made the article/code modification to disconnect. Other than that issue, this code worked flawlessly for calling the new program.
11/16/2006 Joe Rattz (Article Author) Sorry Isidro Santana, I don't know how to return structs either. If you figure it out though, please let me know and I will update the article.
11/16/2006 Joe Rattz (Article Author) Derek, I too have used a stored procedure to call an AS/400 program. In fact, the program that I researched this topic for and wrote this code for, I initially tried to call via a stored proc. It sounds like you actually created a stored proc on the AS/400 for the program you wanted to call, which is doable. In fact, you can actually call a program on the AS/400 as though it were a stored proc, without actually creating a stored proc on the AS/400. I have actually done this and it works. However, just as you discovered, the argument types get tricky. I found that I could call AS/400 programs as stored procs without actually creating stored procs if there were no arguments, or the arguments' type were simple enough. I then tried actually creating a stored proc on the AS/400 to call the program, and that worked too, but again with trouble due to argument types. This solution was the result for me. Others have posted comments asking about returning arrays and structs, and I don't know how to do that currently, so there may still be some issues this way. But, that is what this library is designed for.
11/16/2006 Joe Rattz (Article Author) mohan, I am not AS/400 savvy enough to know what an 'AS400 ServiceProgram' is. If it is a normal AS/400 program, the example I show above works from an ASP.net page, as that is what I am calling it from.
11/16/2006 Joe Rattz (Article Author) Thanks Ganesh for the additional info.
11/16/2006 Joe Rattz (Article Author) carmel, no I have not returned an array. Sorry.
10/23/2006 Isidro Santana I need get a struct output parameter from my RPG Program.
10/4/2006 Derek I've managed to do the same by using a normal OleDB connection and by wrapping the RPG programs with a AS400SQL stored procedure. Then it's a simple process of calling a "SQL stored procedure". The only hassle I had was that it doesn't handle packed numerics. So I changed all the parameters to strings. I'm going to try this option though. It looks a lot slicker and I won't have to create wrapper stored procs.
9/19/2006 mohan Hi, i am a novice to AS400. do you know how to call a AS400 ServiceProgram from ASP.net page?
9/14/2006 Ganash Very good stuff. If any body wants to add some libraries in the library list, they can use the following codes in the correct context.
private cwbx.Command command;
……………
command = new cwbx.CommandClass();
command.system=as400;
command.system.UserID=settings[1];
command.system.Password=settings[2];
……………
……………
command.Run("CHGLIBL LIBL(*NONE)" ); //before calling the program
command.Run("ADDLIBLE QGPL " ); //before calling the program
……………
program.Call(parameters); // this would be called with QGPL
8/24/2006 kamal This article really augments my confidence to accept challenge for the integration of Dot net web App;ication to AS400 RPG Program.
8/22/2006 carmel thanks for the usefull example, have u tried get back array of data?
6/19/2006 Ken Very informative! We have looked at other options such as WebSphere messaging servers, MS Host Integration Server and Java wrappers to accomplish a similar task but this seems to be the simplest (and cheapest!) solution yet. Thanks!

Add Your Comment

 

 
    

  Home|Freebies|Blog|Services|Articles|ASP.NET Depot|DotNetNuke Central|Contact Us
  Copyright 2005 Netsplore Terms Of Use Privacy Statement