I was trolling through some older posts on Karin's blog and spotted a reference to a LSO discussion forum in the comments.
The address is http://www.lawsonguru.com/forums/ux/lso/ Thanks Karin for pointing this out :-)
For generic M3 / Movex issues the discussion forum http://erp.ittoolbox.com/groups/technical-functional/intentia-l/ is a great place to ask questions and contribute to collective community knowledge base.
Friday, November 23, 2012
Friday, November 16, 2012
Lawson Web Services and .Net - a guest post
My friend and colleague Paul Grooby at Resene Paints in Wellington was kind enough to contribute the post below about his trials and tribulations with consuming Lawson Web Services in .Net.
Using Visual Studio .Net and Lawson Web Services.
Paul Grooby – 2012-11-02.
Using Lawson WebServices is a doddle, once you understand how it fits together. And if you want to generate simpler data entry and display screens (without SQL and wrapping API’s then this is the ticket). Once you've created your web-services in Lawson Web Services and published these you can see them in a browser before you start testing them using SOAPUI.
Click on the WSDL file link above – this will load the XML file in the browser that you can then save.
Choose File ->Save from the menu to save the file as a WSDL file on your system.
Now a caveat – while Lawson Web Services generated xsd:datetime definitions for some of the API fields, it doesn't accept accept xsd:datetime inputs to them – so open the saved WSDL file in a text editor and do a find and replace on any datetime fields.
Replace the datetime fields with a string datatype. Once you've checked the remainder of the WSDL file its time to do the cool stuff and fire up Visual Studio (we’ll need this WSDL file later). I'm going to create a blank web application for this demo (it could as easily be a console application or windows application).
I normally add the external WSDL files to the application and place them in their own folder in the project. Locate the file and add to the project.
From here select the file and in the properties box of the studio project select and copy the file path.
With the file path copied to the clipboard, select the option to add a service reference to the application..
The following dialog box will be displayed:
Paste the file path into the Address bar – Don’t click Ok just yet – click the advanced button to check a couple of important options.
Ensure that these two check boxes are checked and then click the OK button.
Give the webservice a relevant namespace. As a naming convention I tend to leave these the same name as the API / Webservice so that these can be traced back.
Once you click the Ok button a list of service endpoints will be displayed.
Click Ok – Visual Studio will do its thing and generate the necessary references and stubs for the program.
The project will now look like the following
If your project doesn't show all of the files then click on the button highlighted to see them.
The two files with the extension 'svcinfo' contain details of where the endpoint of the service is located. This is important in that you’d normally develop, test and then deploy in the different environments. If you don’t remember that the endpoints are embedded in these files you could be pointing at the wrong environment.
However to save a little hassle, and this is why we copied the file locally we can edit our WSDL file that's contained in the project.
The endpoint is embedded in the WSDL – if you change this to point to say PROD and regenerate the files, the files with the svcinfo extension will be updated – follow this process here:
1: Alter the end point in the WSDL file. Save.
2: Against the service select the option to configure the service reference
This will display the follow dialogue box:
Make sure the address is pointing to your local file then click Ok.
From the project menu for the service select the option to update the service reference:
You can check that the service has taken by opening the svcinfo files and checking the end point.
This process also updates the web.config file in this case so we just need to check that this has been updated.
If we see two endpoints with the same contract we’re in trouble – delete the incorrect one as the process will not work.
So that’s all there is to it for the adding of a reference. If you want to use the interactions you’ll need to write a bit of code. The following section has a simple class that consumes the objects used above.
The following class is used to interact with the webservice (I won’t teach you about programming – suffice to say this works a treat). It creates a 'Customer' object, calls the various methods to populate the relevant bits of information and retains that information.
using System;
using SampleLWSApplication.CRS610MI;
namespace SampleLWSApplication
{
public class Customer
{
#region PrivateVariables
private String _company = "";
// also based on this customer we should be able to also use the GetBasicData
private String _customername = "";
private String _customernumber = "";
private String _customerpassword = "";
private String _customersordernumber = "";
private String _division = "";
private String _facility= "";
// get the other details such as order number , etc
private String _ordertype = "";
private String _password = "";
private String _payer = "";
private DateTime _requesteddeliverydate ;
private String _transactionreason= "";
private String _username = "";
private String _warehouse= "";
private String _emailaddress = "";
private String _specialnstructions = "";
#endregion
public Customer()
{
// create the class
}
/// <summary>
/// Get CustomerData is to pull the data through for the customer
/// </summary>
/// <param name="CustomerNumber"></param>
public void GetBasicData(String CustomerNumber)
{
// code to do a lookup against the customer and retrieve the basic data from the system
// always need a client
CRS610MI.CRS610MIClient crs610MIClient = new CRS610MIClient();
// create the soap header
CRS610MI.headerType mwsheader = new CRS610MI.headerType();
mwsheader.user = _username; // user name for the API from the configuration file
mwsheader.password = _password; // password for the API from the configuration file
mwsheader.company = _company;
mwsheader.division = "xxx";
// create a new object to hold the details
CRS610MI.GetBasicDataItem getBasicData = new GetBasicDataItem();
// set the variables for the process
getBasicData.Company = 100;
// set to the passed
getBasicData.CustomerNumber = CustomerNumber.ToUpper(); // force to upper case
CRS610MI.GetBasicDataCollection collection = new GetBasicDataCollection();
collection.maxRecords = 1000;
collection.GetBasicDataItem = new GetBasicDataItem[1];
collection.GetBasicDataItem[0] = getBasicData;
CRS610MI.GetBasicDataRequest crs610MIRequest = new GetBasicDataRequest(mwsheader, collection);
try
{
// pul back the data responses
CRS610MI.GetBasicDataResponse responseItem = crs610MIClient.GetBasicData(crs610MIRequest);
// turn this into an array
CRS610MI.GetBasicDataResponseItem[] ri = responseItem.GetBasicDataResponse1;
// loop through the array
for (int i = 0; i < ri.Length; i++)
{
// output the data
// set the values
_customername = ri[i].CustomerName;
_transactionreason = ri[i].FreeField3;
_division = ri[i].Division;
}
}
catch (System.ServiceModel.FaultException ex)
{
Console.Write(ex.Message);
}
}
/// <summary>
/// Get CustomerData is to pull the data through for the customer
/// </summary>
/// <param name="CustomerNumber"></param>
public void GetOrderInfo(String CustomerNumber)
{
// code to do a lookup against the customer and retrieve the basic data from the system
// always need a client
CRS610MI.CRS610MIClient crs610MIClient = new CRS610MIClient();
// create the soap header
CRS610MI.headerType mwsheader = new CRS610MI.headerType();
mwsheader.user = _username; // user name for the API from the configuration file
mwsheader.password = _password; // password for the API from the configuration file
mwsheader.company = _company;
mwsheader.division = "xxx";
// create a new object to hold the details
CRS610MI.GetOrderInfoItem getOrderInfo = new GetOrderInfoItem();
// set the variables for the process
getOrderInfo.Company = 100;
// set to the passed
getOrderInfo.CustomerNumber = CustomerNumber.ToUpper(); // force to upper case
CRS610MI.GetOrderInfoCollection collection = new GetOrderInfoCollection();
collection.maxRecords = 1000;
collection.GetOrderInfoItem = new GetOrderInfoItem[1];
collection.GetOrderInfoItem[0] = getOrderInfo;
CRS610MI.GetOrderInfoRequest crs610MIRequest = new GetOrderInfoRequest(mwsheader, collection);
try
{
// pull back the data responses
CRS610MI.GetOrderInfoResponse responseItem = crs610MIClient.GetOrderInfo(crs610MIRequest);
// turn this into an array
CRS610MI.GetOrderInfoResponseItem[] ri = responseItem.GetOrderInfoResponse1;
// loop through the array
for (int i = 0; i < ri.Length; i++)
{
// output the data
// set the values
_facility = ri[i].Facility;
_warehouse = ri[i].Warehouse;
_payer = ri[i].Payer;
_division = ri[i].Division;
_ordertype = ri[i].CustomerOrderType;
}
}
catch (System.ServiceModel.FaultException ex)
{
Console.Write(ex.Message);
}
}
// Properties here
#region Properties
public String SpecialInstructions
{
get { return _specialnstructions; }
set {_specialnstructions = value;}
}
public String Company
{
get { return _company; }
set { _company = value; }
}
public String EmailAddress
{
get { return _emailaddress; }
set { _emailaddress = value; }
}
public String CustomerName
{
get { return _customername; }
set { _customername = value; }
}
public String CustomerNumber
{
get { return _customernumber; }
set { _customernumber = value; }
}
public String CustomerOrderNumber
{
get { return _customersordernumber; }
set { _customersordernumber = value; }
}
public String CustomerPassword
{
get { return _customerpassword; }
set { _customerpassword = value; }
}
public String Division
{
get { return _division; }
set { _division = value; }
}
public String Facility
{
get { return _facility; }
set { _facility = value; }
}
public String OrderType
{
get { return _ordertype; }
set { _ordertype = value; }
}
public String Password
{
get { return _password; }
set { _password = value; }
}
public String Payer
{
get { return _payer; }
set { _payer = value; }
}
public DateTime RequestedDeliveryDate
{
get { return _requesteddeliverydate; }
set { _requesteddeliverydate = value; }
}
public String TransactionReason
{
get { return _transactionreason; }
set { _transactionreason = value; }
}
public String UserName
{
get { return _username; }
set { _username = value; }
}
public String Warehouse
{
get { return _warehouse; }
set { _warehouse = value; }
}
#endregion Properties
}
}
A note or two about the code.
The basic structure for each call is the following:
- Create a client
- Create a mwsheader object (this is the SOAP Header)
- Create a base item for the method to pass parameter
- Create a collection of correct return types
- Add the base item as array 0 to pass
- Add a request of the correct type
- Call the method and return a collection of responseitems of the correct type
- Check for any errors and if none use the returned collection of types in any manner that you like – for 'Get' methods its likely that there is only one object returned, for List methods there is likely to be 0 to many.
Importantly – You’ll also find problems with the service if using a List type operation where the amount of data returned by the service exceeds the file size (not sure where this is set but it seems to be around 64K) – to get around this problems use the following syntax when creating the client.
Note that I've also added complexity by having this endpoint embedded in the web.config file – its' my style of programming.
String endPoint = System.Configuration.ConfigurationManager.AppSettings["INTERNETORDERSENDPOINT"];
WebOrders.InternetOrdersClient orderClient = new InternetOrdersClient(new asicHttpBinding(BasicHttpSecurityMode.None) { MaxReceivedMessageSize = 2147483647, MaxBufferSize = 2147483647 }, new EndpointAddress(endPoint));
In the above example we've set the return size to a huge number – this get around the limitation but still may not be enough – in which case you’d probably want to look at the fields being output from the Web service and whether you need them all.
Enjoy :-)
Open source alternative to MvxSock for API interactions with M3
I've recently come across an open source alternative to Infor's MvxSock library called MvxLib (http://mvxlib.sourceforge.net/). I haven't had the time yet to look into this in detail but there are a few potential benefits I see from this:
- Firstly it's a native .Net library, so no more wrapping the old style MvxSock dll whenever we need to use it in a .Net application;
- Secondly it's open source, so with some work this could be used as the basis for building (for example) applications in Windows Mobile, Android or IOS;
- Thirdly it's open source. I know I already mentioned that one but it's worth reiterating. There is an ecosystem of 3rd party consultants and software solutions that interact with M3. Wouldn't it be nice to be able to do so without being beholden to Infor for access to the underlying library that allows this?
For my next project I'm certainly going to look at this as an option. And and when I do (or someone else does and lets me know about it) I'll post my conclusions on its suitability as a MvxSock replacement.
Friday, October 12, 2012
Another JScript and Mashups blog
I've just spotted another JScripts and Mashups blog over at http://www.joshgeving.com
It appears to be really focussed on S3, but would be worth checking out.
It appears to be really focussed on S3, but would be worth checking out.
Friday, January 13, 2012
New Lawson S3 Technology Blog
There's a new Lawson S3 Technology Blog at lawsons3tech.wordpress.com. As there is lots of technology (like LSO, PFI and LBI) shared between S3 and M3 this may be an interesting blog to follow for M3 users as well.
<Edit>
Note that the blog has now moved to Infor's site http://blogs.infor.com/s3tech/
</Edit>
<Edit>
Note that the blog has now moved to Infor's site http://blogs.infor.com/s3tech/
</Edit>
Tuesday, January 10, 2012
JScripts and toolbox programs
Thibaud has posted a entry on how JScripts that can be programmed to be generic across multiple screens. This reminded me of a solution I've used for dealing with M3 toolbox screens where the columns can change based on the panel version selected by the user.
Columns in LSO can be queried by looking at the controller.RenderEngine.ListControl.Columns list. The following JScript parses through the list of columns (in my case in mms080) to find the columns that store the ORCA (order category) and RIDN (order number) columns.
var category_column = 100;
var orderno_column = 100;
var listControl = controller.RenderEngine.ListControl;
var columns = listControl.Columns;
for(var i=0; i < columns.Count; i++) {
if(columns[i] == "ORCA") { category_column = i;}
}
From there I can check to see if I have the columns I need to run my JScript logic, and if I do not then advise the user accordingly.
if (qty_column != 100 && category_column != 100') {
//apply logic
} else {
//advise the user that we don't have the required information to run the script
}
This approach allows robust JScripts that work with user-configurable toolbox screens.
Saturday, January 7, 2012
Screen design changes using JScript
I continue to be impressed with what can be achieved with JScript and LSO. The other day I wanted to improve the readability of a screen. Using personalisations I hid unnecessary fields, but I was then left with fields scattered across a screen that I wanted to group together. JScript allows me to find controls on the screen and move these around with the Grid.SetColumn and Grid.SetRow functions:
After running the script the Item number field has been moved:
Combined with the ability to set the Tab Order in LSO we're able to create streamlined panels that show just the information we're interested in, and group them together into business or user-specific groupings.
import System;Prior to running this script the screen looks like this:
import System.Windows;
import System.Windows.Controls;
import MForms;
package MForms.JScript {
class MMS001_ChangeItemFieldPos {
public function Init(element: Object, args: Object, controller : Object, debug : Object) {
var content : Object = controller.RenderEngine.Content;
//Find the control we want to move
var textBoxItem = ScriptUtil.FindChild(content, "MMITNO");
//Specify where we want to move the control to
Grid.SetColumn(textBoxItem, 50);
Grid.SetRow(textBoxItem, 1);
}
}
}
After running the script the Item number field has been moved:
Labels:
JScript,
M3,
Mforms,
Scripting,
Smart Client,
Smart Office
Subscribe to:
Posts (Atom)