APIs – don’t forget to read the fine print!

Well that was an interesting surprise.

As I mentioned in a previous post, we are in the process of migrating from MoveX Apps 5.2 to M3 Apps 10.1 – though I may have made a point of dragging out the versions a little 😉

Credit where credit is due, Smart Office is worlds apart from MoveX Explorer and Workplace – truly a work of art. But, that’s not what this post is about…

In my world

In my world all sorts of odd things happen, code works, people can describe their queries in a sensible and accurate fashion, and APIs remain static.

Sometimes my world and everyone elses worlds are, well…worlds apart.

So, a number of years ago we had the opportunity to take a look at the APIs that were exposed by MoveX, quite a few and many providing useful features. Intentia even provided an API toolkit which included a very helpfuly program called MiTest and another quaint wee thing that generates code that you can use to wrap the APIs.

It is this later mentioned program (StructBuilder I think) which I thought was pretty nifty, sure the code it spat out was VB6, but it was pretty straight forward to migrate in to C# or VB.Net. So, out I go and write some software which makes calls to the APIs, and I take advantage of this tool.

Now for the uninitiated, the Intentia crew did something really quite smart when it came down to the APIs, your application would create a plain text string and send it to the MoveX Business Engine via the MvxSock.dll, and the MoveX Business engine would be kind enough to send back a plain string which you then needed to break down in to it’s appropriate fields.

So, an example is in MRS001MI, you send the string “LstTransactionsOIS100MI” which will go out and list all of the transactions that are available in the OIS100MI interface. MoveX returns the string “REP OIS100MI AddBatchAddressAdd batch order address 71 0 13.145V0T0S200009170948202008072354 12071 ” which then needs to be split in to specific fields – in this instance the “Add batch order address” is a specific field, the description of the Transaction which starts at 40 characters in to the returned string and is 60 characters in length. Each field starts at a specific location and has a specific length, information which is stored in the table CMIFLD or in MRS001.

The beauty in this method is that it is really quick and efficient to process and relatively easy to troubleshoot visually.

The Problem

In my world of bliss with the little wee application that Intentia provided which generated wrappers for the API calls all was great – until I tried a new version of MoveX. Lawson had decided to increase the length of several fields that OIS100MI used, that’s all well and good – until you realise that the code generated that wrapped the API calls is set to look at static locations. So, when we are looking for the Line Number (PONR) returned by AddBatchLine it is at position 16 and is now 5 characters in length rather than 3. This of-course means that the Line Suffix which follows the Line Number (POSX) is now 2 characters out – oh dear, how sad, never mind…

Basically it means that my code needs to be adjusted and then recompiled and then deployed, not so fun.

Confounded by my own Laziness

Now because of the existence of a program which did most of the heavy lifting I never really dug too deeply in to the Library/Program GENERAL or more importantly the Library/Program MRS001MI. It would appear that the Intentia developers had thought recognised that there would be a need to change field lengths and to this end they had an interface specifically designed to provide information about the API field positions (and even a table!).

So really, if you want to be correct and want your code to work in future versions you should at the very least call MRS001MI.LstFields to get the In and Out field positions for the API that you want to use. I guess if you really want to be sure then you should also check that the API (program and transaction) still exists and is at a usable status – again with transactions in MRS001MI.

My Solution

In order to get things moving again, I have written a series of classes for each of the Transactions, they inherit much functionality from a base class which stores a List of field definitions for that specific transaction. The class also allows us to set and retrieve values against a specific field name. The list of fields and their positions get populated on the fly from MRS001MI.

My code has been adjusted so it will use the Field name that is present in the data returned by MRS001MI.ListFields so in theory, as long as mandatory fields don’t change then my code will stand a very good chance of just working; users will be able to flick backwards and forwards between versions when testing without needing separately compiled programs.

Because I am a big fan of LINQ and .Net3.5, the code that I have created is well, LINQ and .Net3.5 based – but if you aren’t using .Net then you should be able to adapt it to suit your purposes. It’s in VB though I will be looking at doing it in C# in the near future and clean it all up too!

To Start with my class mvxAPIField will store information about the field returned by MRS001MI, it will also have an additional field which we can use to store a value.

Public Class mvxAPIField

''' Is this In (I) or Out (O)
Public Property Direction As String

''' the value that we will be submitting
Public Property Value As String

''' the name of the field this is associated with
Public Property FieldName As String

''' the from position
Public Property From As String

''' length of the field
Public Property Length As String

''' The version that this pertains to
Public Property Version As String

''' this is the constructor – to make things easy we can populate the base information from here
''' astrDirection is the direction (I = In, O = Out - ie. In is us submitting to MoveX, O is the result from MoveX)
''' astrFieldName is the name of the field, eg. PONR
''' astrFrom is the position that the field starts at
''' astrLength is the length of the field
''' astrVersion is the version that we are using
Sub New(ByVal astrDirection As String, ByVal astrFieldName As String, ByVal astrFrom As String, ByVal astrLength As String, ByVal astrVersion As String)
 Direction = astrDirection
 FieldName = astrFieldName
 From = astrFrom
 Length = astrLength
 Version = astrVersion
End Sub

''' return the length as a int
Public Function LengthAsInt() As Int32
 Dim result As Int32 = -1

 If Length <> Nothing Then
  If IsNumeric(Length) = True Then
   result = CInt(Length)
  End If
 End If
 LengthAsInt = result
End Function

''' return the From as an int
Public Function FromAsInt() As Int32
 Dim result As Int32 = -1
 If From <> Nothing Then
  If IsNumeric(From) = True Then
   result = CInt(From)
  End If
 End If
 FromAsInt = result
End Function

End Class

Then we have our actual helper class – it groups all of fields together and allows us to generate the string to submit to the API and functions that allow us to decode the results.

Public Class BaseAPIMI

' this is where we will store the fields themselves
Dim lsFields As List(Of mvxAPIField)

' the program name eg. OIS100MI
Public Property ProgramName As String

' the name of the transaction eg. AddBatchHead
Public Property TransactionName As String

' the version that we are playing with
Public Property Version As String = Nothing

''' This is the largest position that we have found (determines the size of the Space() we do)
Dim giMaxPosition As Int32 = 0

''' Create a new Input String to send to MoveX based on the values stored within the lsFields list
Public Function CreateInString(ByVal astrVersion As String) As String
 CreateInString = Nothing

 ' create a big string filled with spaces
 Dim strPopulatedString = Space(giMaxPosition)

 ' set the transaction name
 Mid(strPopulatedString, 1) = TransactionName

 ' retrieve all of the fields for this version that are In fields (ie. fields that we will send to MoveX)
 Dim vFields = From field In lsFields Where field.Version = astrVersion And field.Direction = "I" Select field
  If Not vFields Is Nothing Then
   If Enumerable.Count(vFields) >= 1 Then
    For Each apifCurrentField As mvxAPIField In vFields
     If Not apifCurrentField Is Nothing Then
      Dim iFrom As Int32 = apifCurrentField.FromAsInt()
      Dim iLength As Int32 = apifCurrentField.LengthAsInt()

      ' retrieve the value of the field at a specific location and for a specific length
      Mid(strPopulatedString, iFrom, iLength) = apifCurrentField.Value

     End If
    Next
   End If
  End If
 CreateInString = strPopulatedString
End Function

''' Get a value from a result for a specific fieldname
Public Function GetOutValue(ByVal astrFieldName As String, ByVal astrVersion As String) As String
 GetOutValue = Nothing
 Dim apifField As mvxAPIField = GetOutField(astrFieldName, astrVersion)

 If Not apifField Is Nothing Then
  GetOutValue = apifField.Value
 End If
End Function

' this function will look for a specific fieldname and retrieve the mvxAPIField associated with it
Public Function GetOutField(ByVal astrFieldName As String, ByVal astrVersion As String) As mvxAPIField
 GetOutField = Nothing
 If astrFieldName <> Nothing Then
  If Not lsFields Is Nothing Then
   Dim vField = From field In lsFields Where field.FieldName = astrFieldName And field.Version = astrVersion Select field
   If Not vField Is Nothing Then
    If Enumerable.Count(vField) >= 1 Then
     GetOutField = vField(0)
    End If
   End If
  End If
 End If
End Function

''' Set the value for a specific INPUT field
' when we want to create a string we should use this sub to
' set the values - it will look through our fields looking for
' the appropriate field to set
Public Sub SetInputValue(ByVal astrFieldName As String, ByVal astrValue As String)
 If astrFieldName <> Nothing Then
  If Not lsFields Is Nothing Then
   Dim vField = From field In lsFields Where field.FieldName = astrFieldName Select field
   If Not vField Is Nothing Then
    If Enumerable.Count(vField) >= 1 Then
     For Each apifCurrentField As mvxAPIField In vField
      If Not apifCurrentField Is Nothing Then
       apifCurrentField.Value = astrValue
      End If
     Next
    End If
   End If
  End If
 End If
End Sub

''' interpret the result string
' basically loop through the fields that we have and
' extract the data at each field position from the astrResult
' string and store it in the Value
Public Sub SetResult(ByVal astrResult As String)
 If Not lsFields Is Nothing Then
  If astrResult <> Nothing Then
   For Each apifCurrentField As mvxAPIField In lsFields
    If Not apifCurrentField Is Nothing Then
     If astrResult.Length >= (apifCurrentField.FromAsInt() + apifCurrentField.LengthAsInt()) Then
      Dim strTemporary As String = astrResult.Substring(apifCurrentField.FromAsInt() - 1, apifCurrentField.LengthAsInt())
      If strTemporary <> Nothing Then
       apifCurrentField.Value = strTemporary.Trim()
      End If
     End If
    End If
   Next
  End If
 End If
End Sub

' the constructors requires a list of the fields that were retrieved from MRS001MI
' and then populated in to a list of mvxAPIField objects
' it probably would have been easier to just extract the full results from MRS001MI
' directly in to mvxAPIField but I was pressed for time
Public Sub New(ByVal alsAPIPositions As List(Of mvxAPIField), ByVal astrVersion As String, Optional ByVal astrResult As String = Nothing)
 Version = astrVersion
 lsFields = alsAPIPositions

 ' we want to do a count up to determine the maximum size of our string
 If Not lsFields Is Nothing Then
  For Each apifCurrentField As mvxAPIField In lsFields
   If Not apifCurrentField Is Nothing Then
    Dim iFrom As Int32 = apifCurrentField.FromAsInt()
    Dim iLength As Int32 = apifCurrentField.LengthAsInt()
    If iFrom <> -1 Then
     If iLength <> -1 Then
      If (iFrom + iLength) > giMaxPosition Then
       giMaxPosition = iFrom + iLength
      End If
     End If
    End If
   End If
  Next
 End If
 If astrResult <> Nothing Then
  SetResult(astrResult)
 End If
End Sub

End Class

And finally, overtop of that we have the class which stores the field names – making the code a little bit more readable and clear.

Public Class OIS100_Confirm
 Inherits BaseAPIMI
 Dim lsFields As List(Of mvxAPIField)
 
 Public Sub New(ByVal alsAPIPositions As List(Of mvxAPIField), ByVal astrVersion As String, Optional ByVal astrResult As String = Nothing)
  MyBase.New(alsAPIPositions, astrVersion, astrResult)
  ProgramName = "OIS100MI"
  TransactionName = "Confirm"
End Sub

' I have done this to help make my code more readable (CONO just
' doesn't mean that much to people outside of MoveX)
Public Sub SetCompany(ByVal astrCompany As String)
 SetInputValue("CONO", astrCompany)
End Sub

' if we are sending this to MoveX, then we should try to
' set the Order Number
Public Sub SetOrderNumber(ByVal astrOrderNumber As String)
 SetInputValue("ORNO", astrOrderNumber)
End Sub

' if we are initialised with a result then this will go out and try to extract
' the Order Number (ORNO)
Public Function GetOrderNumber() As String
 GetOrderNumber = GetOutValue("ORNO", Version)
End Function

End Class

And this is the function that will do the setting of the variables and submitting it to MoveX, it’s adapted from the Intentia supplied programs code


   ' Confirm an order that has been submitted via AddBatchHead and is in
   ' OIS275
   ' alsConfirmInfFields should be a list of mvxAPIFields that have been populated from MRS001MI
   Private Function Confirm(byval alsConfirmInFields as List(Of mvxAPIField), byval astrAPIVersion as string, byval astrTemporaryOrderNumber as string, byval astrCompanyNumber)
as string) As String
        Confirm = Nothing
        Dim transStr As String = Nothing
        Dim Result As String = Nothing
        Dim rc As Long

        ' create our handler class and set the fields and version
        Dim conConfirm As New OIS100_Confirm(alsConfirmInFields, astrAPIVersion )
        If Not conConfirm Is Nothing Then
            ' set the company
            conConfirm.SetCompany(astrCompanyNumber)
            ' set the order number we want to confirm
            conConfirm.SetOrderNumber(astrTemporaryOrderNumber)

            ' create the string that we will send to MoveX
            transStr = conConfirm.CreateInString(astrAPIVersion)
        End If

        ' submit to MoveX
        rc = MoveXSocket.MvxSockTrans(transStr, Result)
        If rc <> 0 Then
            MoveXSocket.MvxSockShowLastError("")
            Exit Function
        End If
        If Left(Result, 5) = "REP  " Then
            Do
                'Handle the returned string here
                rc = MoveXSocket.MvxSockReceive(Result)
                If Left(Result, 5) = "NOK   " Then
                    Exit Function
                End If
            Loop Until Left(Result, 5) <> "REP  "
        End If
        ' return our result
        Confirm = Result
    End Function

And this is basically the code which will do the actual confirm.

 ' where ConfirmInFields is a List of mvxAPIFields that we have populated based on the In values for OIS100MI.Confirm from
 ' MRS001MI
 ' Version is the version that we have extracted from GENERAL.GetMIBuild.
 ' astrTemporaryOrderNumber was generated from our AddBatchHead command
 ' astrCompanyNumber was the company number of the order is for
 Dim strConfirmResult As String = Confirm(ConfirmInFields, Version, astrTemporaryOrderNumber, astrCompanyNumber)
  If strConfirmResult <> Nothing Then
   ' if the string starts with OK then we should be all good
   If strConfirmResult.StartsWith("OK") = True Then
    ' create a new OIS100_Confirm object and populate it with the OutFields we have populated from
    ' MRS001MI
    ' set the version
    ' and supply the result string we got when we submitted the confirm to MoveX
    Dim conConfirmResult As New OIS100_Confirm(ConfirmOutFields, Version, strConfirmResult)
     If Not conConfirmResult Is Nothing Then
      ' and finally we extract the order number
      Dim strOrderNumber As String = conConfirmResult.GetOrderNumber()
     End If
    End If
   End If
  End If

The code to extract the fields from MRS001MI are missing, but if you have had any experience with the APIs it should be fairly trivial for you to create – unfortunately due to the structure of my existing application there is a lot of interdependencies on other code making it difficult to extract for the purposes of illustration. Though in saying that I do plan on restructuring my code in the near future so may include it in a future post.

Anyways, I hope that this gives others a bit of a heads up or that the code is of some use to reduce compatibility problems in the future…

This entry was posted in Development, M3 / MoveX. Bookmark the permalink.

7 Responses to APIs – don’t forget to read the fine print!

  1. Lars Nyström says:

    How do you use the MvxSock.dll in .Net? DllImport? I mostly use WebServices but I’d like to use the APIs to gain some speed and also I’d like to use it from .Net.

    • potatoit says:

      You’ll need to add a reference to MvxSockX_SVR.dll, I have had issues if the API toolkit isn’t installed first.

      If you’re using Visual Studio, right click on References and then Add References, then browse to MvxSockX_SVR.dll

      • Lars Nyström says:

        Yes, tried that, but it won’t add in VS2010… Tried both in a C# and VB project.

      • potatoit says:

        Interesting, most of my projects were migrated from Visual Studio 2008 which seemed to work ok. If I try with VS2010 it complains but still creates the interop assembly. That may work just like that.

      • Lars Nyström says:

        I read your comment again. The problem was that I didn’t install the API toolkit correctly. I guess the dll wasn’t registered correctly on my computer. After installing I found the MvxSockX_SVR.dll as a COM (ActiveX) in VS2010.

        Thanks!

      • potatoit says:

        Good to hear you got it sorted 🙂

  2. Just found this post. Cross-referencing this other post just in case: http://m3ideas.org/2012/02/23/how-to-call-m3-api-from-net/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s