21
Sat, Dec
3 New Articles

ASP.NET to iSeries Socket Server

Microsoft
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

You're probably already familiar with the concept of using TCP/IP sockets to communicate between a client and a server application. You may even have a socket server application on your iSeries now that communicates with client apps running on another iSeries or PC. In this article, I'll explain what it takes to create a client/server socket-based application that allows an Active Server Page .NET (ASP.NET) application to communicate with the iSeries.

Socket to Me

I'll start by taking a look at what TCP/IP sockets are and how they work. All applications running over TCP/IP communicate through the use of sockets. Sockets allow you to identify individual server applications at a specific IP address. Think of it this way: If you were comparing TCP/IP applications to a standard telephone call, the server's IP address would be the phone number, and the socket (or port number) would be the telephone extension. When people call your office, they probably dial a main number that gets them to a receptionist or automated attendant that can transfer them to your phone by identifying an extension. A TCP/IP server application simply waits in "listen" mode for a client application to try to connect to it. Once a connection is made, the server application will send data back and forth to the client until the socket is closed. On the client side, the socket is defined using the server's IP address and port number. The client application will then connect to the server and send data back and forth until the connection is closed. Figure 1 shows a graphic example of how typical client and server socket applications interact with each other.

http://www.mcpressonline.com/articles/images/2002/ASPSOcketsV400.png

Figure 1: This illustration shows how socket applications communicate. (Click images to enlarge.)

As I mentioned, all TCP/IP applications communicate using sockets. When you connect to a Web server on the Internet, your browser acts as the client and connects to port 80 on the server. When you connect to an FTP server running on the iSeries, your PC's FTP command is the client and is able to communicate with the iSeries FTP server through port 21. When you use iSeries Access to start a terminal session on the iSeries, PC5250 acts as the client and communicates with the iSeries server over port 23. There are many ports like these that are used for standard applications. The table in figure 2 contains a list of ports used by TCP/IP applications.

Port Number
Description
13
Network Time Protocol (NTP)
21
File Transfer Protocol (FTP)
23
Telnet Protocol
25
Simple Mail Transfer Protocol (SMTP)
53
Domain Name Service (DNS)
80
Hypertext Transfer Protocol (HTTP)
119
Network News Transfer Protocol (NNTP)
161
Simple Network Management Protocol (SNMP)
443
Hypertext Transfer Protocol over SSL (HTTPS)
563
Network News Transfer Protocol over SSL (NNTPS)

Figure 2: This table lists some commonly used TCP/IP ports.

When you are creating a custom socket application, it's best to avoid using any of these "well-known" port numbers to prevent conflicts with any of these applications.

ASPX to iSeries

The big draw to creating socket-based applications is that they allow applications written in different languages and running on different platforms to communicate with one another. This means that a socket server that is running on a Linux box can accept connections from and communicate with a client application running on Windows or even on the iSeries. To illustrate this, I've created a sample socket server application running on the iSeries that will interact with an ASP.NET client application.

First, take a look at the iSeries server application. This sample application will accept an iSeries user ID and return the TEXT value from the corresponding user profile. Figure 3 contains the ILE RPG source code for the socket server application.

 H DFTACTGRP(*NO) ACTGRP('QILE') BNDDIR('QC2LE')
  *****************************************************************
  * Compile Command:
  *
  *      CRTBNDRPG  PGM(XXX/SKTSERVER) SRCFILE(XXX/QRPGLESRC)
  *
  *****************************************************************
  *  Copy Prototypes.
  *****************************************************************
  /COPY QRPGLESRC,RPGSOCKCPY
  ****************************************************************
  *  SOCKET DATA STRUCTURES
  *****************************************************************
 D sockaddr        DS
 D   sa_family                    5u 0
 D   sa_data                     14a
 D serveraddr      DS
 D   sin_family                   5i 0
 D   sin_port                     5u 0
 D   sin_addr                    10u 0
 D   sin_zero                     8a
  ****************************************************************
  *  Miscellaneous Field Definitions
  *****************************************************************
 D Msg             S             80a   Based(ptrMsg)
 D ErrMsg          S             52a   INZ
 D Errno           S             10i 0 Based(ptrErn)
 D UserName        S             30a   INZ
 D svaddrlen       S             10u 0 INZ
 D On              S             10u 0 INZ(1)
 D SDID            S             10i 0 INZ
 D SDID2           S             10i 0 INZ
 D TotCharRead     S             10i 0 INZ
 D rc              S             10i 0 INZ(0)
 D BufferIn        S             10A   INZ('          ')
 D BufferOut       S             50A   INZ(*ALL' ')
 D DataLen         S              5P 0 INZ
 D XLateTable      S             10A   INZ
 D XLateTblLib     S             10A   INZ
 C**********************************************************************
  * Obtain a socket descriptor ID
 C                   EVAL      SDID = Socket(AF_INET:SOCK_STREAM:UNUSED)
 C                   IF        (SDID < 0 )
 C                   EVAL      ptrErn = GetErrNo
 C                   EVAL      ptrMsg = StrError(Errno)
 C                   EVAL      ErrMsg = %Char(Errno) + ' ' + Msg
 C     ErrMsg        DSPLY
  *
 C                   EVAL      *INLR = *ON
 C                   RETURN
 C                   ENDIF

  *
  * Allow the socket descriptor to be reusable.
  *
 C                   EVAL      rc = SetSockOpt(SdId:SOL_SOCKET:
 C                             SOL_REUSEADDR:%ADDR(On):%SIZE(On))
 C                   IF        (rc < 0 )
 C                   EVAL      ptrMsg = StrError(Errno)
 C                   EVAL      ErrMsg = %Char(Errno) + ' ' + Msg
 C     ErrMsg        DSPLY
 C                   EVAL      *INLR = *ON
 C                   RETURN
 C                   ENDIF

  *
  * Bind the socket to the defined IP Address/Port
  *
 C                   EVAL      sin_family = AF_INET
 C                   EVAL      sin_port   = 2834
  * retrieves your as/400's ip address
 C                   EVAL      sin_addr = GetHostId
 C                   EVAL      sin_zero   = X'0000000000000000'
 C                   EVAL      svaddrlen = %SIZE(serveraddr)
 C                   EVAL      rc = Bind(SDID:%ADDR(serveraddr):
 C                             svaddrlen)
 C                   IF        (rc < 0 )
 C                   EVAL      ptrErn = GetErrNo
 C                   EVAL      ptrMsg = StrError(Errno)
 C                   EVAL      ErrMsg = %Char(Errno) + '-' + Msg
 C     ErrMsg        DSPLY
 C                   EVAL      *INLR = *ON
 C                   RETURN
 C                   ENDIF

  *
  * Listen on the socket for up to 5 clients
  *
 C                   EVAL      rc = Listen(SdId:5)
 C                   IF        (rc < 0 )
 C                   EVAL      ptrErn = GetErrNo
 C                   EVAL      ptrMsg = StrError(Errno)
 C                   EVAL      ErrMsg = %Char(Errno) + '-' + Msg
 C     ErrMsg        DSPLY
 C                   EVAL      *INLR = *ON
 C                   RETURN
 C                   ENDIF
  *
  * Accept new connection
  *
 C                   DOW       ( BufferIn <> '*END' )
 C                   EVAL      rc = Accept(SDID:%ADDR(serveraddr):%ADDR(
 C                             svaddrlen))
 C                   IF        ( rc < 0 )
 C                   EVAL      ptrErn = GetErrNo
 C                   EVAL      ptrMsg = StrError(Errno)
 C                   EVAL      ErrMsg = %Char(Errno) + '-' + Msg
 C     ErrMsg        DSPLY
 C                   EVAL      *INLR = *ON
 C                   RETURN
 C                   ELSE
 C                   EVAL      SdId2 = rc
 C                   ENDIF

 C                   DOW       ( rc > 0 )
  *
  * Read an process data from socket client
  *
 C                   EVAL      TotCharRead = *ZERO
 C                   EVAL      TotCharRead = Read(SdId2:%ADDR(BufferIn):
 C                             BufferLen)
  *
  * Convert incomming data from ASCII to EBCDIC
  *
 C                   IF        ( TotCharRead > *ZERO )
 C                   CALL      'QDCXLATE'                           45
 C                   PARM      10            DataLen
 C                   PARM                    BufferIn
 C                   PARM      'QEBCDIC'     XLateTable
 C                   PARM      'QSYS'        XLateTblLib

 C                   IF        ( *IN45 = *OFF AND BufferIn <> '*END')
  *
  * Call CL Program to get the defined system value
  *
 C                   Call      'GETUSRNAME'
 C                   Parm                    BufferIn
 C                   Parm                    UserName

  *
  * Convert outgoing data from EBCDIC to ASCII and send back
  *
 C                   Eval      DataLen=%Len(%Trim(UserName))
 C                   Eval      BufferOut=UserName

 C                   CALL      'QDCXLATE'                           45
 C                   PARM                    DataLen
 C                   PARM                    BufferOut
 C                   PARM      'QASCII'      XLateTable
 C                   PARM      'QSYS'        XLateTblLib

 C                   EVAL      rc = Write(SdId2:%ADDR(BufferOut): DataLen)
 C                   IF        ( rc < 0 )
 C                   EVAL      ptrErn = GetErrNo
 C                   EVAL      ptrMsg = StrError(Errno)
 C                   EVAL      ErrMsg = %Char(Errno) + '-' + Msg
 C     ErrMsg        DSPLY
 C                   EVAL      *INLR = *ON
 C                   RETURN
 C                   ENDIF
 C                   ELSE
  *
  * Close socket
  *
 C                   Eval      rc = Close(SdId2)
 C                   LEAVE
 C                   ENDIF

 C                   ELSE
  *
  * Close socket
  *
 C                   Eval      rc = Close(SdId2)
 C                   LEAVE
 C                   ENDIF
 C                   ENDDO
 C                   ENDDO

  *
  * Close socket
  *
 C                   Eval      rc = Close(SdId)
 C                   EVAL      *INLR = *ON
 C                   RETURN
 C*********************************************************************

Figure 3: This is the ILE RPG socket server application.

This application uses the copy file source member RPGSOCKCPY, which is included with this article's source code. This member defines the prototypes for the functions used within the program as well as the constants used here. The functions defined here are contained within the QC2LE binding directory, which gives you access to C functions from within an ILE application. Figure 4 contains a list of the functions used here along with a description of their use.

Function
Returns
Description
Socket()
Socket Identifier
Creates a new socket
SetSockOpt()
Status Code
Sets options for the defined socket
Bind()
Status Code
Binds to the specified socket (port)
Listen()
Status Code
Waits for a connection request on the defined socket
Accept()
Incoming Socket ID
Accepts an incoming connection attempt
Read()
Characters Read
Reads in characters from the specified socket
Write()
Status Code
Writes data back to the client
Close()
Status Code
Ends the client connection

Figure 4: The functions listed here are used to create the socket server application.

As the table above explains, the socket() function creates a new socket. The socket identifier value returned when the function is called is used to identify the socket in the functions that follow. The first parameter supplied here is used to define the address family. AF_INET identifies that the address specified uses Internet Protocol (IP). The second parameter identifies the type of socket being defined. The SOCK_STREAM constant identifies that type to be socket streaming (TCP) protocol. The final option defines the protocol to be used with the socket. Since the other two parameters have defined that, set this option to UNUSED to cause the default for this value to be used.

The SetSockOpt() function defines options related to the socket. The first parameter on this function is socket ID of the socket created with the socket() function. The second parameter identifies the level at which the option is being set. Data communications using TCP/IP are processed on multiple levels or layers. The IP layer handles the lower-level network communications. This application uses the TCP layer to send data back and forth. In this case, the constant SOL_SOCKET identifies that the option being defined is at the socket level. The next parameter identifies the option being changed. In this case, the constant identifies that the socket definition is to be reusable. The next two parameters define the option data and the length of that data.

The bind() function binds the socket identifier defined on the first parameter to the IP address and port identified through the serveraddr variable. In this example, the IP address will be the IP address of your iSeries as returned by the GetHostId function. The port number is defined as 2834. The third parameter for the bind function is used to identify the length of the values stored in the serveraddr variable.

The listen() function instructs the program to wait for a connection attempt on the socket defined by the socket identifier on the first parameter. The second parameter defines the number of connections to be accepted at one time. Program execution will stall at this statement until a client application attempts to make a connection to this socket.

The accept() function is executed once a connection attempt is made. This function accepts the same three parameters used for the bind() function: the socket ID, the server address, and the length of the server address value. The value returned by this function uniquely identifies each of the possible incoming connections for future statements.

The read() function reads data coming in through the socket. The first parameter is the socket identifier returned by the accept() function. The second parameter is the pointer to the BufferIn variable, which will contain the incoming data. This function returns a value that contains the number of characters read into the BufferIn variable. The sample application then uses the QDCXLATE API to convert the incoming data from ASCII to EBCDIC. The value from BufferIn, which should contain an iSeries user ID, is then sent to the CL program GetUserName. This program uses RTVUSRPRF to retrieve the TEXT value related to the user profile. This value is sent back to the client socket application. The QDCXLATE API is again used to translate the EBCDIC data back to ASCII.

The write() function returns data to the client application. The first parameter used with this function is the socket ID created by the accept() function. The second parameter is the pointer to the BufferOut variable, which contains the outgoing data. The third parameter defines the length of the outgoing data. The sample application continues this process until there is no more data to be read in. At that point, the remote socket is closed.

The close() function ends the socket. The single parameter on this function is the socket identifier for the socket to be closed. In this sample application, both the primary socket identifier and the secondary identifiers that relate to each client connection are used along with this function.

The server application will remain active until it receives a value of *END from the client. At that point, the server application will close the socket and end. Use the CRTBNDRPG program to create this program, as shown here:

CRTRPGMOD PGM(lib/SOCKSRVR) SRCFILE(lib/QRPGLESRC) SRCMBR(SOCKSRVR)

You'll also need to create the CL Program GETUSRNAME, which can be found with this article's companion code.

Now that the socket server application is in place, I'll explain what you need to do on the client side.

ASP.NET Socket Client

If you're unfamiliar with ASP.NET, let me explain: This extension to Microsoft's Internet Information Server (IIS) is part of the .NET (that's "dot net") framework, which can be downloaded from Microsoft's web site here. ASP.NET extends Microsoft's Active Server Pages (ASP) by extending the language options available. Within ASP, you have the option of using Visual Basic Scripting (VBScript) language or JavaScript. ASP.NET supports any language that is compliant with the .NET framework. This currently includes Visual Basic.NET, Visual C#, Visual C++, and Visual J#. Once you have downloaded the .NET framework from the link above, install the framework on a machine running Microsoft IIS. This sample client application allows the user to key in an iSeries user ID and receive back the corresponding user's name. Figure 5 contains the source for the Web page that contains the client application.

<%@ Page Language="VisualBasic" Debug="true" %>


    
    Socket Client ASP.NET Application


    




Value="Get User Name" onserverclick="GetUserName">

    




    Dim sock As New System.Net.Sockets.Socket(Net.Sockets.AddressFamily.InterNetwork, _ 
                             Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)
    Dim RecvBytes(256) As Byte, x AS Long
    Dim hostadd As System.Net.IPAddress
    Dim bytes As Int32
    Dim Str As String
    
    
    Sub GetUserName(sender As Object, e As System.EventArgs)
 If UserID.Value = "" Then 
Response.Write("Please Enter The User ID")
 Else
         hostadd = System.Net.IPAddress.Parse("192.168.0.1")
     Dim EPhost As New System.Net.IPEndPoint(hostadd, 2834)
     Dim UIDBytes(11) AS Byte
         sock.Connect(EPhost)
 Response.Write("Connecting!
")

 Response.Flush
                 
                 If Not sock.Connected()
                 Response.Write("Unable to Connect!
")

                 Response.End
                 End If
 Response.Write("Connected!
")

 Response.Flush
                 
 UIDBytes = Encoding.ASCII.GetBytes(LEFT(UserID.Value & "          ",10))
 Response.Write("Sending " & UIDBytes.Length & " bytes!
")

 Response.Flush
             sock.Send(UIDBytes,10,System.Net.Sockets.SocketFlags.None)
 
 
         bytes = sock.Receive(RecvBytes, RecvBytes.Length, 0)
         If UserID.Value="*END" Then
 Response.Write("Socket Server Ended!
")

 Response.Flush
 Else
 Response.Write(bytes & " bytes received!
")

 Response.Flush
 
             For x = 0 To bytes - 1
                 Str = Str & Chr(RecvBytes.GetValue(x))
             Next
             Response.Write("User Name=" & Str & "
")

             str="" 
         End If
 Response.Write("Complete!
")

 Response.Flush
             sock.close
End If
    End Sub


    



Figure 5: This is the source for SOCKCLIENT.ASPX.

Save this application to the root Web directory on your IIS server, which will usually be C:INETPUBWWWROOT. This page starts by creating an HTML for containing a single HTML Input Text box and a command button. Each of these contain the RUNAT=SERVER modifier, which allows these objects to be accessed directly from within the ASP.NET server script. Within that script, define the socket object as a System.Net.Sockets.Socket object. You'll notice that this object is defined using the same three parameters that the ILE RPG application used to define the socket on its end. The first parameter here identifies the address family as an IP address. The second parameter identifies the type as a TCP socket stream. Finally, the third parameter defines the protocol as TCP.

After defining some additional variables used within the application, you'll find the GetUserName() Visual Basic.NET user-defined function. This function is executed when the onserverclick event fires on the button within your HTML form. The ability to directly call a Web server-side function from a client button is a functionality that has been added with ASP.NET. The GetUserName() function defines the destination system by first converting the system IP address from the string 192.168.0.1 to a valid IP address value.

Next, you use this value with the System.Net.IPEndPoint() along with the port number 2834 to arrive at a valid IP end-point value that corresponds to your iSeries socket server. This function can be compared to the bind() function in the socket server application. The .connect method is executed on your socket object named sock to attempt to make a connection to the socket server. Throughout this application, the Response.Write method will be used to write messages out to the client browser window. Response.Flush is used immediately after Response.Write to cause the data to be sent to the browser immediately.

The sock.Connected() property is read to determine whether or not you made a successful connection. If not, an error message is displayed. Otherwise, the value of the USERID text box is converted to a byte array using the Encoding.ASCII.GetBytes() function. This is the data type required when sending data through the socket.

The sock.send function is used with the socket object to send the data to the socket server. The byte array created previously is used as the first parameter for this function. The second parameter identifies the length of the data defined in the first parameter. The third parameter sets options related to the data being sent.

The sock.Recieve function allows you to receive the value back from the socket server application. The first parameter here identifies the name of a byte array to contain the data that is sent back. The second parameter identifies the length of that data. The third again is used to define flags or options related to the socket. In this case, the value of 0 identifies that no flags should be set. If you enter the value of *END into the text box, you'll display a message indicating that the server was ended. Otherwise, a routine is run to convert the values from the byte array into a string that is then written out to the browser as the user name.

Finally, the sock.close method closes the socket. Figures 6, 7, and 8 show the progression of this application within the browser.

http://www.mcpressonline.com/articles/images/2002/ASPSOcketsV401.jpg

Figure 6: This is the initial display from the ASP.NET socket client.

http://www.mcpressonline.com/articles/images/2002/ASPSOcketsV402.jpg

Figure 7: Data is returned by the iSeries socket server to the client application.

http://www.mcpressonline.com/articles/images/2002/ASPSOcketsV403.jpg

Figure 8: This data is displayed when *END is used to end the socket server application.

While this sample application performs only a simple task, you could easily take what you've done here and extend it to perform more complex tasks. The values passed through the socket connection can be used as parameters for any required job on the iSeries.

This example shows you how to get data from an ASP.NET to the iSeries, execute a job on the iSeries using that data, and return a value back to the application. This same technique can be used to create a socket-based application to perform whatever task you may require.

Now that you have the concept down, it's time to "plug in" to sockets on your iSeries.

Mike Faust is MIS Manager for The Lehigh Group in Macungie, Pennsylvania. Mike is also the author of the books The iSeries and AS400 Programmer's Guide to Cool Things and Active Server Pages Primer from MC Press. You can contact Mike at This email address is being protected from spambots. You need JavaScript enabled to view it..

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: