How to dynamically consume a M3 Web Service in C#

Previous posts have dealt successfully with how to consume a web service and how to use the SmartOffice DynamicWs classes.  This post will help if you need something which is dynamic, yet decoupled from SmartOffice.

This is a sample based on the WCF Dynamic Proxy classes available under a Microsoft Public License in the msdn archive.

For initial reference we have a standard C# invocation using a generated service reference.  The web service we are using is API_MNS150MI_GetUserData.

ScreenShot2390

The only tricky part here is ensuring the http authentication is set so that Web services accepts you as a valid user.

Here is the code for the static call against the generated service reference.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using WebServiceStatic.API131;
using System.ServiceModel;

namespace WebServiceStatic
{
class Program
{
static void Main(string[] args)
{
// Create a client with basic http credentials
API_MNS150MI_GetUserDataClient client = new API_MNS150MI_GetUserDataClient();
System.ServiceModel.BasicHttpBinding binding = new System.ServiceModel.BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.MaxReceivedMessageSize = 25 * 1024 * 1024;
client.Endpoint.Binding = binding;

// show endpoint address
Console.WriteLine(client.Endpoint.Address);
Console.WriteLine(client.Endpoint.Name);

// ask for UserID and password
Console.Write("User ID : ");
client.ClientCredentials.UserName.UserName = Console.ReadLine().Trim();
Console.Write("Password: ");
client.ClientCredentials.UserName.Password = Console.ReadLine().Trim();

// Create LWS header
lws header = new lws();
header.user = client.ClientCredentials.UserName.UserName;
header.password = client.ClientCredentials.UserName.Password;

// Create a requests item
GetUserDataItem item1 = new GetUserDataItem();
item1.USID = client.ClientCredentials.UserName.UserName;

// construct a collection for the request item (only 1 accepted?)
GetUserDataCollection collection = new GetUserDataCollection();
collection.GetUserDataItem = new GetUserDataItem[] { item1 };

try
{
// execute the web service
GetUserDataResponseItem[] response = client.GetUserData(header, collection);
// loop through the response items (only 1) and output to console
foreach (GetUserDataResponseItem responseItem in response)
{
Console.WriteLine("User '{0}' description '{1}'", responseItem.USID, responseItem.TX40);
}
}
catch (Exception e)
{
// catch and display any errors
Console.WriteLine(e.Message);
}

// wait for user to press a key
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
}

The result shows we have connected to the web service and retrieved the Users ID (USID) and description (TX40).

ScreenShot2391

Now we have the basic hard-coded example code as a template, we can use the DynamicProxyLibrary to do the same.

This dynamically creates an Assembly (dll) containing the service reference which we can use in place of a hard-coded Service reference.

This can be done without the DynamicProxyLibrary however as the DynamicProxy handles most of the Assembly/Reflection plumbing it is much easier to read and work with.

First create a project referencing the DynamicProxyLibrary in Visual Studio.

ScreenShot2393

Now it is possible to use the Dynamic proxy to call the web service without using a hard-coded service references, all field/property/class references can be coded as text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WebServiceDynamic
{
using WcfSamples.DynamicProxy;
using System.ServiceModel.Description;
using System.ServiceModel;
using System.Reflection;

class Program
{
static void Main(string[] args)
{
string serviceWsdlUri = "https://m3app-2013.gdeinfor2.com:41964/mws-ws/services/API_MNS150MI_GetUserData?wsdl";
if (args.Length > 0)
serviceWsdlUri = args[0];

// create the dynamic proxy factory, that downloads the service metadata
// and create the dynamic factory.
Console.WriteLine("Creating DynamicProxyFactory for " + serviceWsdlUri);
DynamicProxyFactory factory = new DynamicProxyFactory(serviceWsdlUri);

// list the endpoints.
int count = 0;
foreach (ServiceEndpoint endpoint in factory.Endpoints)
{
// create proxy client
Console.WriteLine("Service Endpoint[{0}]", count);
Console.WriteLine("\tAddress = " + endpoint.Address);
Console.WriteLine("\tContract = " + endpoint.Contract.Name);
Console.WriteLine("\tBinding = " + endpoint.Binding.Name);
DynamicProxy clientProxy = factory.CreateProxy(endpoint.Contract.Name);

// Create a client with basic http credentials
System.ServiceModel.BasicHttpBinding binding = new System.ServiceModel.BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.MaxReceivedMessageSize = 25 * 1024 * 1024;
ServiceEndpoint clientEndpoint = (ServiceEndpoint)clientProxy.GetProperty("Endpoint");
clientEndpoint.Binding = binding;

// ask for UserID and password
ClientCredentials credentials = (ClientCredentials)clientProxy.GetProperty("ClientCredentials");
Console.Write("User ID : ");
credentials.UserName.UserName = Console.ReadLine().Trim();
Console.Write("Password: ");
credentials.UserName.Password = Console.ReadLine().Trim();

// Create LWS header
Type lwsType = clientProxy.ProxyType.Assembly.GetType("lws");
DynamicObject header = new DynamicObject(lwsType);
header.CallConstructor();
header.SetProperty("user", credentials.UserName.UserName);
header.SetProperty("password", credentials.UserName.Password);

// Create a requests item
Type itemType = clientProxy.ProxyType.Assembly.GetType("GetUserDataItem");
DynamicObject item = new DynamicObject(itemType);
item.CallConstructor();
item.SetProperty("USID", credentials.UserName.UserName);

// Add the user request item to an array of 1
Array itemArray = Array.CreateInstance(item.ObjectType, 1);
itemArray.SetValue(item.ObjectInstance, 0);

// construct a collection for the request item (only 1 accepted?)
Type collectionType = clientProxy.ProxyType.Assembly.GetType("GetUserDataCollection");
DynamicObject collection = new DynamicObject(collectionType);
collection.CallConstructor();
collection.SetProperty("GetUserDataItem", itemArray);

try
{
// execute the web service
Array responseCollection = (Array)clientProxy.CallMethod("GetUserData", new object[] { header.ObjectInstance, collection.ObjectInstance });
// loop through the response items (only 1) and output to console
foreach (object responseItemObject in responseCollection)
{
DynamicObject responseItem = new DynamicObject(responseItemObject);
Console.WriteLine("User '{0}' description '{1}'",
responseItem.GetProperty("USID"),
responseItem.GetProperty("TX40"));
}
}
catch (Exception e)
{
// catch and display exceptions
Console.WriteLine(e.Message);
// catch and display inner exception (this is the real error from the web service call)
if (e.InnerException != null)
{
Console.WriteLine(e.InnerException.Message);
}
}
// close the connection
clientProxy.Close();
}

Console.WriteLine("Press any key...");
Console.ReadKey();
}

}
}

The result shows that we can use the DynamicProxyFactory to get some basic information about the web service, then consume the web service.

ScreenShot2392

Regards,

Lee Flaherty

UPDATE: This was tested against M3 10.1 and M3 13.1 both running on the grid.  It may, or may not, work on other versions.

Data conversion techniques

Here below is an old slide I found in my archives where I list my known techniques for data conversion, i.e. how to push data into Infor M3, also known as data entry. This list intends to remind readers there are more solutions than the traditional techniques.

Data conversionTechniques

Traditional entry points

The two traditional entry points are:

  1. API – The traditional entry point is to call M3 API. Advantages: it’s the fastest and most reliable technique, and the most widespread in terms of platforms supported, libraries, tools, and documentation. Disadvantages: there aren’t M3 API available for every program/field/operation in M3, as given by the M3 API Repository – MRS001.
  2. MDP – When there’s no M3 API available, we use the other traditional entry point, Lawson Web Services (LWS) of type M3 Display Program (MDP) to simulate a user going through the screens at the middleware level in M3 Net Extension (MNE). Advantages: with the Lawson Web Services Designer we can create the equivalent of an M3 API, for most M3 Programs, in almost no time. Disadvantage: it’s less efficient to run than M3 API as there are more layers to traverse.

Those are the traditional techniques. And we massively call them with for example M3 Data Import (MDI), Smart Data Tool (SDT), M3 E-Collaborator (MeC), Visual Basic macros in Microsoft Excel, ProcessFlow Integrator (PFI), Infor Process Automation (IPA), Tibco, WebMethods, or custom Java/C#/VB programs, with the data coming from a source like for example a Microsoft Excel spreadsheet, a CSV or plain text file, or a staging database.

Alternate techniques

If the traditional entry points fail, there are two alternate techniques.

  1. Manual entry – We can always do manual data entry. Advantage: it requires almost no skills, no programming, and no tools. Disadvantage: it can become humanly impossible to manually enter large amounts of data.
  2. MAK – Alternatively, we can write an M3 modification with MAK, to create a new API or modify an existing one. Advantages: it’s the ultimate solution. Disadvantages: it requires an MAK developer, it can take time, and M3 mods create a maintenance problem.

Despair techniques

Then, there are the following techniques which are less know and which I use when I’m at a loss of ideas:

  1. MForms Automation – When there are no M3 API available, and when Lawson Web Services of type MDP fail for rare M3 programs, we can try to reproduce the steps with MForms Automation and write a Smart Office Script that loops thru a data source and executes the MForms Automation at each iteration. This is a proven technique and Seth will soon write a post illustrating this solution. Advantage: It’s the last card on the deck when you lost hope. Disadvantage: It’s less efficient because it’s at the user interface level.
  2. Bookmarks – Similarly, we can write a Smart Office Script to execute Bookmarks in a loop of the form mforms://bookmark?program=CRS620&tablename=CIDMAS&keys=IDCONO…
  3. MNEAI – Likewise, we can inject a piece of JavaScript in M3 Workplace to simulate a user’s data entry, and loop through a data source we get with JavaScript.
  4. H5 Client – We can do the same JavaScript injection for H5 Client.
  5. Macro – We can record the mouse movement and click events, and the keyboard keystrokes, and use a Windows program to replay them. Advantages: It’s the last solution available out of desperation. Disadvantage: it will break at the slightest change in window position or popup, and it will be slow.

Forbidden techniques

Finally, as a reminder, we never use SQL INSERT/UPDATE/DELETE to M3, as that would break the integrity of the ERP, it would bypass the cache of the data abstraction layer, and it would void warranty for support.

That’s it! Thanks for reading. Subscribe below.

Using Dynamic WS to consume a LWS in a script

Here is a new solution to call SOAP Web Services from a Smart Office script that complements the previous known solutions. It’s a very easy and fast way to call LWS and does not require you to write any C# or XML.

It’s using a private API in Smart Office so it might change in future releases, without any announcement.

Continue reading Using Dynamic WS to consume a LWS in a script

How to call an M3 Web Service using jQuery

Here’s a simple example of calling an M3 web service using jQuery. In this example, my web service has two input fields and 3 output fields. You’ll obvously need to change the URL to the web service and the format of your soap request to match your WSDL.

<html>
<head>
    <title>example m3 soap web service with jquery</title>
    http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
    
        $(document).ready(function () {
            jQuery.support.cors = true;

            $("#submitBtn").click(function (event) {
                var wsUrl = "http://ussplu124.lu123train.lawson.com:20005/lws-ws/learning/JK-CustomerService";

                var soapRequest = '';
                soapRequest += '' + $("#cusno").val() + '' + $("#addressId").val();
                soapRequest += '';
                $.ajax({
                    type : "POST",
                    url : wsUrl,
                    contentType : "text/xml",
                    dataType : "xml",
                    data : soapRequest,
                    success : processSuccess,
                    error : processError
                });
            });
        });

        function processSuccess(data, status, req) {
            if (status == "success") {
                var ois002 = $(req.responseText).find('OIS002');
                var response = ois002.find('Name').text() +
                                "
" + ois002.find('AddressLine1').text() + "
" + ois002.find('AddressLine2').text(); $("#response").html(response); } } function processError(data, status, req) { alert(req.responseText + " " + status); } </head> <body> <h3>Calling Web Services with jQuery/AJAX</h3> <h4>Input</h4> Customer Number / Address ID <input id="cusno" type="text" /> <input id="addressId" type="text" /> <input id="submitBtn" value="Submit" type="button" /> <h4>Output</h4> <div id="response"/> </body> </html>

Here’s the example HTML page, with my input fields and the response I get when I submit the form:

jquery example

/Jessica

Related articles

Geocoding of Stock Locations in MMS010

Here is a video that illustrates the process to set the Geo Codes XYZ of Stock Locations in MMS010 in Smart Office, i.e. to set the latitude, longitude, and altitude of Stock Locations, a.k.a. geocoding. In my example I determined the coordinates based on an 3D model built in Google SketchUp and geo-located in Google Earth; a GPS receiver with good indoor accuracy would work as well. With geocoded information, we can present data from the Warehouse Management System in a graphical way. This is important for applications such as showing Stock Locations on a map, or finding the shortest path for a picking list.

Demo video

How to proceed

These are the steps I followed in the video to geolocate the Stock Locations in MMS010:

  1. I used this SketchUp model of a 3D warehouse that I had previously geo-located:
  2. I also used this other SketchUp model of the Stock Locations that I had previously uniquely identified:
  3. Then, I used this Ruby script to get the geocoding of the floor plan:
  4. Then, I used this other Ruby script to get the geocoding of each Stock Location:
  5. The result is this CSV file of the floor plan’s geocodes and each Stock Location’s geocodes:
  6. Then, I used this Lawson Web Service of type Display Program to set the values for the fields Geo Code X (GEOX), Geo Code Y (GEOY), and Geo Code Z (GEOZ) in MMS010/F for a specified Warehouse (WHLO) and Stock Location (WHSL):
  7. Then, I used a Visual Basic macro for Microsoft Excel to call the Web Service for all Stock Locations:
  8. Finally, I used this script to display the Geo Codes XYZ in MMS010/B1:

Result

The result is the list of Stock Locations in MMS010/B1 displaying all the Geo Codes XYZ:

Resources

  • Download the SketchUp model of the geo-located 3D warehouse.
  • Download the SketchUp model of the uniquely identified Stock Locations.
  • Download the Ruby script to get the geocoding of the floor plan.
  • Download the Ruby script to get the geocoding of each Stock Location.
  • Download the resulting CSV file of all Stock Locations and their Geo Codes.
  • Download the Lawson Web Service to set the Geo Codes XYZ of a Stock Location.
  • Download the script to display the Geo Codes XYZ in MMS010/B1.
  • Watch the video of the entire process.

Related articles

UPDATE

2012-09-28: I had a bug in the Ruby script that miscalculated the Y and Z geocodes for the Stock Locations. I corrected the script and the resulting CSV file and I updated the links above.

How to get the URL to Lawson Web Services in a Mashup

Here’s a technique for a Mashup to call a Lawson Web Service (LWS) using the correct Lawson Web Service server and the correct environment (DEV, EDU, PRD, TST, etc.).

The problem

By default, when we use the Web Service wizard in Mashup Designer, the URL to the Lawson Web Service server is hard-coded in the two Parameters WS.Wsdl and WS.Address.

For instance, in the following example we’re using a Lawson Web Service that calls the M3 API CRS610MI.LstByNumber, but the server (hostname), the port number (10000), and the environment (TST) are hard-coded in the URL:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI">
    <mashup:DataPanel Name="WS">
       <mashup:DataPanel.Events>
          <mashup:Events>
             <mashup:Event SourceEventName="Startup" />
          </mashup:Events>
       </mashup:DataPanel.Events>
       <mashup:DataPanel.DataService>
          <mashup:DataService Type="WS">
             <mashup:DataService.Operations>
                <mashup:DataOperation Name="Read">
                   <mashup:DataParameter Key="WS.Wsdl" Value="http://hostname:10000/LWS_TST/svc/CRS610MI.wsdl" />
                   <mashup:DataParameter Key="WS.Address" Value="http://hostname:10000/LWS_TST/services/CRS610MI" />
                   <mashup:DataParameter Key="WS.Operation" Value="LstByNumber" />
                   <mashup:DataParameter Key="WS.Contract" Value="CRS610MI" />
                   <mashup:DataParameter Key="mws.user" Value="LSO.USER" />
                   <mashup:DataParameter Key="mws.password" Value="LSO.PASSWORD" />
                </mashup:DataOperation>
             </mashup:DataService.Operations>
          </mashup:DataService>
       </mashup:DataPanel.DataService>
    </mashup:DataPanel>
 </Grid>

Because the server and environment are hard-coded in the XAML, it will be difficult to deploy the Mashup on other servers, and on other environments (DEV, EDU, PRD, etc.). The workaround would be to make one copy of the Mashup per target server and per target environment. But it would quickly become a maintenance nightmare.

The goal is to make that URL dynamic, based on the server and on the environment we are currently running.

The solution

The solution is to dynamically read at runtime the URL to Lawson Web Services that’s defined in the Smart Office Profile:

Step 1 – Get the value

We get the URL to Lawson Web Services in the Mashup with:

{mashup:ProfileValue Path=M3/WebService/url}

For example:

Step 2 – Create a parameter

Then, we create an Event parameter with a SourceKey and the value, and we call it for example BaseUri:

<mashup:Event SourceEventName="Startup" >
    <mashup:Parameter SourceKey="BaseUri" Value="{mashup:ProfileValue Path=M3/WebService/url}" />
</mashup:Event>

Step 3 – Move it to an Event

Then, we move the two WS parameters from the DataPanel to the Event as TargetKeys:

<mashup:Event SourceEventName="Startup" >
    <mashup:Parameter SourceKey="BaseUri" Value="{mashup:ProfileValue Path=M3/WebService/url}" />
    <mashup:Parameter TargetKey="WS.Wsdl" Value="http://hostname:10000/LWS_TST/svc/CRS610MI.wsdl" />
    <mashup:Parameter TargetKey="WS.Address" Value="http://hostname:10000/LWS_TST/services/CRS610MI" />
</mashup:Event>

Step 4 – Variable substitution

Finally, we use Karin’s solution for variable substitution and markup extension to un-hard-code the URL:

<mashup:Event SourceEventName="Startup" >
    <mashup:Parameter SourceKey="BaseUri" Value="{mashup:ProfileValue Path=M3/WebService/url}" />
    <mashup:Parameter TargetKey="WS.Wsdl" Value="{}{BaseUri}/svc/CRS610MI.wsdl" />
    <mashup:Parameter TargetKey="WS.Address" Value="{}{BaseUri}/services/CRS610MI" />
</mashup:Event>

Final code

Here’s the resulting source code:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI">
    <mashup:DataPanel Name="WS">
       <mashup:DataPanel.Events>
          <mashup:Events>
             <mashup:Event SourceEventName="Startup" >
                <mashup:Parameter SourceKey="BaseUri" Value="{mashup:ProfileValue Path=M3/WebService/url}" />
                <mashup:Parameter TargetKey="WS.Wsdl" Value="{}{BaseUri}/svc/CRS610MI.wsdl" />
                <mashup:Parameter TargetKey="WS.Address" Value="{}{BaseUri}/services/CRS610MI" />
             </mashup:Event>
          </mashup:Events>
       </mashup:DataPanel.Events>
       <mashup:DataPanel.DataService>
          <mashup:DataService Type="WS">
             <mashup:DataService.Operations>
                <mashup:DataOperation Name="Read">
                   <mashup:DataParameter Key="WS.Operation" Value="LstByNumber" />
                   <mashup:DataParameter Key="WS.Contract" Value="CRS610MI" />
                   <mashup:DataParameter Key="mws.user" Value="LSO.USER" />
                   <mashup:DataParameter Key="mws.password" Value="LSO.PASSWORD" />
                </mashup:DataOperation>
             </mashup:DataService.Operations>
          </mashup:DataService>
       </mashup:DataPanel.DataService>
    </mashup:DataPanel>
</Grid>

Conclusion

With this solution we learned how to create a Mashup that calls a Lawson Web Service such that the Mashup will use the correct Lawson Web Service server and the correct environment (DEV, EDU, PRD, TST, etc.).

Maybe LPD should make this a native feature of Mashups so that developers don’t have to implement it themselves.

For more examples on how to call a Lawson Web Service from a Mashup, refer to Karin’s post.

That’s it!

How to call Lawson Web Services from PHP

Here are examples to call Lawson Web Services from PHP for all three adapters: API, M3 Display Program (MDP), and SQL.

Which SOAP client ?

In the past, I used the external NuSOAP toolkit to make SOAP calls in PHP.

Now, PHP 5 comes built-in with SoapClient.

To determine which SOAP client your PHP server provides, use:

phpInfo();

It will show:

What’s the Endpoint ?

To determine the endpoint or the WSDL to our Lawson Web Service open the Lawson Web Services Runtime Management Page which you can launch from LifeCycle Manager or from Lawson Web Services Designer.

Select List, select the Service Context, and select the Web Service.

The bottom right corner will show the WSDL Address:

How’s the SOAP ?

I recommend using tools like Fiddler or soapUI to determine the exact structure of the SOAP Request and SOAP Response to call our Lawson Web Services.

Fiddler can intercept HTTP calls from most SOAP clients, for example from Lawson Web Services Designer, from Microsoft InfoPath, from PocketSOAP, or from Microsoft Visual C# Express, and we can use that SOAP Request and SOAP Response as a reference to write our PHP code:

Similarly, soapUI will create a sample SOAP Request from a WSDL, it will show the SOAP Response after the web service is executed, and we can use that SOAP Request and SOAP Response as a reference to write our PHP code:

M3 API adapter

Here is a sample PHP code to call a Lawson Web Service of type API.

The Web Service name is Customers, the operation name is LstByNumber. It calls the API CRS610MI.LstByNumber. It accepts Company and CustomerNumber as input fields; note the suffix Item in LstByNumberItem for the collection of input fields. It returns CustomerNumber and CustomerName as output fields; note the suffix ResponseItem in LstByNumberResponseItem for the collection of output fields.

<?php
	try {
		$client = new SoapClient("http://hostname:10000/LWS_DEV/svc/Customers.wsdl",
		array(
			'login'=>'M3SRVADM',
			'password'=>'*******'
		)
		);
		$response = $client->LstByNumber(array("LstByNumberItem"=>array(
			"Company"=>"001",
			"CustomerNumber"=>"00100001"
		)));
		foreach ($response->LstByNumberResponseItem as $item) {
			print($item->CustomerNumber." ".$item->CustomerName."\n");
		}
	} catch (Exception $e) {
		echo 'Message: ' .$e->getMessage();
	}
?>

M3 Display Program adapter

Here is a sample PHP code to call a Lawson Web Service of type M3 Display Program (MDP).

The Web Service name is Customers, the operation name is GetName. It works in CRS610/A/E, it accepts W1CUNO as an input field, and returns WRCUNM as an output field.

<?php
	try {
		$client = new SoapClient("http://hostname:10000/LWS_DEV/svc/Customers.wsdl",
		array(
			'login'=>'M3SRVADM',
			'password'=>'*******'
		)
		);
		$response = $client->GetName(array("CRS610"=>array(
			"W1CUNO"=>"0010001"
		)));
		print($response->CRS610->WRCUNM);
	} catch (Exception $e) {
		echo 'Message: ' .$e->getMessage();
	}
?>

SQL adapter

Here is a sample PHP code to call a Lawson Web Service of type SQL (JDBC).

The Web Service name is Customers, the operation name is Search. It works by doing a SELECT FROM WHERE on OCUSMA, it accepts CustomerName as an input field. And it returns OKCUNO and OKCUNM as output fields; note the new1Collection and new1Item automatically generated.

<?php
	try {
		$client = new SoapClient("http://hostname:10000/LWS_DEV/svc/Customers.wsdl",
		array(
			'login'=>'M3SRVADM',
			'password'=>'******'
		)
		);
		$response = $client->Search(array("CustomerName"=>"%ARMY%"));
		foreach ($response->new1Collection->new1Item as $item) {
			print($item->OKCUNO.", ".$item->OKCUNM."\n");
		}
	} catch (Exception $e) {
		echo 'Message: ' .$e->getMessage();
	}
?>

That’s it!

Related articles

How to call M3 API with REST & JSON

With the Lawson Grid, M3 introduced a new REST endpoint for calling M3 API where the response is returned in XML or in JSON.

Background

Historically, software clients that called M3 API needed to use proprietary libraries, such as MvxSockJ.class for Java and MvxSockX_SVR.dll for Microsoft languages. Over the years, Intentia introduced Movex Web Services to call M3 API using the SOAP standard. Then, Lawson introduced the Grid for cloud computing. In the process, Lawson replaced IBM WebSphere Application Server by open source software, it also replaced the Core Web Services for Lawson Smart Office, and it added Jersey, an open source implementation of JAX-RS (JSR 311). The positive outcome is that now, software clients can call M3 API using REST/JSON.

Why does it matter?

REST is increasingly popular, and years ago it started supplanting SOAP for web services [1].

Also, JSON is becoming the de facto data-interchange format, nicknamed “The Fat-Free Alternative to XML” [2].

Most importantly, it’s now possible to call M3 API without needing the proprietary libraries, and without needing Lawson Web Services. Software clients can now make simple HTTP Requests to call M3 API. This makes it easier to call M3 API from third-party software (such as Tibco, Business Objects, Web Methods, etc.) and from previously unsupported or difficultly supported programming languages (such as JScript.NET, JavaScript, Ruby, PHP, etc.).

What about Lawson Web Services?

The new REST endpoint for M3 API doesn’t preclude using Lawson Web Services. Lawson Web Services is still necessary for calling M3 API with the SOAP protocol, and for using the unavoidable M3 Display Program adapter which cannot be found in any other Lawson product. Also, Lawson Web Services is still necessary for Lawson Smart Data Tool.

REST endpoint

To access the new REST endpoint for M3 API, open a Grid Information page, which is accessible from any participating Host in the Grid:

For example: http://ussplu123:20005/grid/info.html

At the bottom of the page is the Application Name M3-API-WS, and it has a Link to the Rest Service‘s WADL:

For example: http://ussplu123:20005/m3api-rest/application.wadl

This WADL gives us the resource path:

execute/{program}/{transaction}

Where {program} is the M3 API Program, like MNS150MI, CRS610MI, MMS200MI, etc. And where {transaction} is that API’s transaction, like GetBasicData, AddAddress, LstByNumber, etc.

Let’s try to call the API CRS610MI.LstByNumber. The URL in my example would be: http://ussplu123:20005/m3api-rest/execute/CRS610MI/LstByNumber

The server will challenge us for HTTP Basic Authentication, we need to provide a valid M3 userid and password:

And the result in XML is a collection of MIRecord elements with Name/Value pairs:

JSON

We can get the result in JSON by adding the HTTP Header field:

Accept: application/json

The result is:

REST GUI

RESTClient is a great REST graphical user interface which we can use to test M3 API; you can download it at http://code.google.com/p/rest-client/

Paste the URL, set the M3 userid and password in the Authentication tab, optionally set the Header to get the response in JSON, and click Go!

Conclusion

With the Lawson Grid, software clients can now natively call M3 API from third-party software, and from previously unsupported or difficultly supported programming languages, without the need for proprietary libraries, and without the need for Lawson Web Services.

Updates

Input parameters

UPDATE 2012-07-17: You can set input parameters in the URL, for example: http://hostname:port/m3api-rest/execute/MMS200MI/GetItmBasic?CONO=531&ITNO=ABCDEF . Also, you can hook RESTClient to use Fiddler as a proxy in Tools >;; Options.

Max number of records

UPDATE 2012-07-31: You can set the maximum number of records returned with the matrix parameter maxrecs, for example: http://hostname:port/m3api-rest/execute/CRS610MI/LstByNumber;maxrecs=20?CONO=1

Output fields

UPDATE 2012-08-02: You can set the output fields returned with the parameter returncols, for example: http://hostname:port/m3api-rest/execute/CRS610MI/LstByNumber;returncols=CUNO,CUNM,CUA1,STAT,PHNO

Related articles

How to consume a Lawson Web Service from a Personalized Script in Smart Office

Calling Lawson Web Service (LWS) from a Personalized Script in Lawson Smart Office is very useful as LWS has three adapters: M3 API, SQL, and M3 Display Program (MDP).

As of today, there are three known solutions to call LWS from a script: 1) the “Big string”, 2) the XML writer, and 3) the C# proxy written with Microsoft Visual Studio. Each of these solutions has its advantages and disadvantages.

In the paper How to call LWS from a JScript I illustrate a new solution which complements the other three known solutions. This new solution is interesting as it minimizes code source surface while still ensuring SOAP validation. And the solution does not involve any C# coding, nor does it require Microsoft Visual Studio. For this new solution, we will use the free Microsoft Web Services Description Language Tool (wsdl.exe) to generate a proxy class in C# that we’ll use from JScript.NET.

As an addendum to the paper, I write two additions. First, it’s possible to make the C# proxy re-usable for multiple environments (ex: DEV, PRD, TST) by setting the variable Url of the proxy instance. Second, I have successfully tested this technique with all three LWS adapters: M3 API, SQL, and MDP.

Related articles

Web Service pretty print

Here is a technique that uses XSLT to pretty print the XML metadata of a Lawson Web Service. The output shows each operation’s details (name, input/output parameters, type, length, constraint, SQL statement, etc.) in a human readable HTML format. This technique is useful for creating template spreadsheets in Excel where we input data without having to manually enter the headers in the spreadsheet which is error prone. The result is similar to the template spreadsheets generated by Smart Data Tool.

  1. Suppose we have the following Thibaudweb service with three operations, one of each type, API, MDP, and SQL:

  2. Save the XML metadata into a file somewhere in your computer. For that, go to the Lawson Web Service server view (for example: http://hostname/LWS_DEV/), select List Services, expand your web service (in my case Thibaud), right-click the Meta Data link, select Save Target As, and save the XML file somewhere in your computer:
  3. Then, open the XML file in a text editor, insert the following processing instruction at the top of the file, and save the file:
    <?xml-stylesheet type="text/xsl" href="WebServicePrettyPrint.xslt"?>
  4. Then, save a copy of the following XSLT file to somewhere in your computer, in the same folder as the XML file: http://ibrix.info/lws/WebServicePrettyPrint.xslt
  5. Then, open the XML file in Microsoft Internet Explorer. The XSLT processor of Internet Explorer’s MSXML will convert the XML metadata into HTML using the XSLT file above. The HTML output shows each operation’s details (name, input/output parameters, type, length, constraint, SQL statement, etc.) in a human readable HTML format. The result looks like this:
  6. Internet Explorer will show a security warning asking if you want to run the script. Click Allow blocked content. The blocked content is a small piece of JavaScript code that transposes the HTML tables.
  7. The little button at the top right transposes the HTML tables. Click the button. Copy/paste the transposed table in an Excel spreadsheet. That will serve as the header. Now just enter the data. That’s useful to create the template spreadsheets in Excel similar to Smart Data Tool.

That’s it!

 

UPDATE 2012-08-14: Added support for MDP Output fields and Related Programs.