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.

Color-coded event graph animations for Mashups

Here is an idea to manually create color-coded event graph animations for Mashups leveraging my previous tool that automatically generates event graphs for Mashup. The idea is to color-code the events in the event graph of a Mashup, and move forward in time to see the animation, in order to have a visual cue of how the Mashup timeline works. See it like a time-lapse of the Mashup.

Let’s take the sample Mashup provided at Mashup Designer > Help > M3 Transactions > List & edit Customers. The sequence of events of that Mashup would be the following:

  1. The Mashup starts and loads the list of customers (Startup event).
  2. Optionally, the user can enter a Customer number and click Search (Click event).
  3. The user selects a customer in the list (CurrentItemChanged event), and the Mashup loads that customer’s details.
  4. Optionally, the user changes the values and clicks Save (Click event), and the Mashup changes the values of that customer record.
  5. The Mashup refreshes the list of customers (UpdateComplete event).

The event graph for this Mashup in plain black & white would be:

graph

The idea is to create a color-coded graph of each step of the sequence, save a colored image of each step, and render the result as an animated GIF.

Why it’s important

Color-coded event graph animations for Mashups will help developers control the quality of their Mashups, it will help users approve Mashup designs, it will be useful as a prototype for demos and useful for usability testing, and it will help new developers better understand how the user interacts with the Mashup. There is a lot of activity in the software industry around software mockups, for example the popular Balsamiq Mockups. This new idea I introduce for Mashups helps bring software mockups a bit closer to M3.

How to create it

Follow these steps to manually create a color-coded event graph animation for Mashups:

  1. Break down the sequence of Mashup events in numbered order, like I did above.
  2. Take the original DOT file of the event graph (refer to my previous tool), and duplicate the file by as many steps, for example MyMashup.gv would become:
    MyMashup1.gv
    MyMashup2.gv
    MyMashup3.gv
    etc.
  3. Open each file in a text editor, and add color to the node and the edges involved in that step. Use the following syntax, for example for color red:
    [color=red; fontcolor=red]

    MyMashup1.gv:

    <Global> -> CustomerList [label="Startup :: List"; color=red; fontcolor=red];
    <Global> [color=red; fontcolor=red];
    CustomerList [color=red; fontcolor=red];

    MyMashup2.gv:

    ButtonSearch -> CustomerList [label="Click :: List"; color=red; fontcolor=red];
    ButtonSearch [color=red; fontcolor=red];
    CustomerList [color=red; fontcolor=red];

    MyMashup3.gv:

    CustomerList -> CustomerDetail [label="CurrentItemChanged :: Get"; color=red; fontcolor=red];
    CustomerList [color=red; fontcolor=red];
    CustomerDetail [color=red; fontcolor=red];

    etc.

  4. Use Graphivz to generate an output file of each step. For example you will have:
    MyMashup1.png
    1
    MyMashup2.png
    2
    MyMashup3.png
    3
    etc.
  5. Use a graphic editor like Gimp to generate a GIF animation from the individual image files. In GIMP, open all the images as Layers, order the layers from last to first, and export the result as an animated GIF with 1000 millisecond delay between frames:
    GIMP4
  6. That’s it!

Result

Here below is the result, a color-coded animation of the event graph of the sample List & edit Customer Mashup of the Mashup Designer (click on the image to see the animation):

animation

Future work

In a future work, I would implement a breadth-first search graph traversal algorithm to automatically traverse the Mashup’s event graph, node by node, call Graphiz’ dot layout engine (C:\Program Files (x86)\Graphviz x.y.z\bin\dot.exe) to produce an image at each iteration, and use another tool to merge all the images into an animated GIF.

Related articles

Mashup – Adding a ContextMenu to the ListView

This week I will show you how you can add a ContextMenu to your ListView.
This is one of these small things that sometimes takes quite some time to figure out.
My major problem with getting the ContextMenu to work in the ListView was to get the actually ‘Click’ working.

Today I will provide you with a working .xaml sample containing some of the basic options from MMS001 using bookmark URI’s.
Related Options , Change, Copy, Delete, Display and Links.
You probably have to set your own values to ITGR and CONO to get it to run.
I also added a little bit of styling, in case this is unknown to some of you.

Here is a snapshot of the result:

ContextMenu

And the 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" xmlns:m3="clr-namespace:MForms.Mashup;assembly=MForms">
    <Grid.Resources>
    </Grid.Resources>

    <Grid.ColumnDefinitions>
       <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
       <RowDefinition Height="*" />
       <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>



 <m3:MIListPanel Name="MMS200MIList">
       <m3:MIListPanel.Events>
          <mashup:Events>
             <mashup:Event SourceEventName="Startup">
                <mashup:Parameter TargetKey="ITGR" Value="03.01" />
             </mashup:Event>
          </mashup:Events>
       </m3:MIListPanel.Events>
       <m3:MIListPanel.DataSource>
          <m3:MIDataSource Program="MMS200MI" Transaction="LstItmByItmGr" Type="List" InputFields="ITGR" OutputFields="ITNO,ITTY,INDI" MaxReturnedRecords="20" />
       </m3:MIListPanel.DataSource>

       <ListView Name="MMS200MIListView" ItemsSource="{Binding Items}" Style="{DynamicResource styleListView}" ItemContainerStyle="{DynamicResource styleListViewItem}">

          <ListView.ContextMenu>
             <ContextMenu>

                <MenuItem Header="Related options">
                   <MenuItem.Style>
                      <Style x:Key="Triggers" TargetType="{x:Type MenuItem}">
                         <Style.Triggers>
                            <Trigger Property="MenuItem.IsMouseOver" Value="true">
                               <Setter Property="Foreground" Value="Red" />
                            </Trigger>
                         </Style.Triggers>
                      </Style>
                   </MenuItem.Style>

                   <MenuItem Header="Material plan" Command="mashup:MashupInstance.MashupCommand">
                      <MenuItem.CommandParameter>
                         <mashup:Events>
                            <mashup:Event SourceEventName="Click" LinkUri="mforms://bookmark?program=MMS001&amp;tablename=MITMAS&amp;keys=MMCONO%2c{CONO}%2cMMITNO%2c{ITNO}%2b%2b%2b%2b%2b%2b%2b%2b%2b&amp;option=34&amp;panel=E&amp;name=MMS001%2fE+Bookmark&amp;source=MForms">
                               <mashup:Parameter TargetKey="ITNO" Value="{Binding [ITNO]}" />
                               <mashup:Parameter TargetKey="CONO" Value="001" />
                            </mashup:Event>
                         </mashup:Events>
                      </MenuItem.CommandParameter>
                   </MenuItem>

                </MenuItem>

                <MenuItem Header="Change" Command="mashup:MashupInstance.MashupCommand">
                   <MenuItem.Style>
                      <Style x:Key="Triggers" TargetType="{x:Type MenuItem}">
                         <Setter Property="FontWeight" Value="Bold" />
                      </Style>
                   </MenuItem.Style>
                   <MenuItem.CommandParameter>
                      <mashup:Events>
                         <mashup:Event SourceEventName="Click" LinkUri="mforms://bookmark?program=MMS001&amp;tablename=MITMAS&amp;keys=MMCONO%2c{CONO}%2cMMITNO%2c{ITNO}%2b%2b%2b%2b%2b%2b%2b%2b%2b&amp;option=2&amp;panel=E&amp;name=MMS001%2fE+Bookmark&amp;source=MForms">
                            <mashup:Parameter TargetKey="ITNO" Value="{Binding [ITNO]}" />
                            <mashup:Parameter TargetKey="CONO" Value="001" />
                         </mashup:Event>
                      </mashup:Events>
                   </MenuItem.CommandParameter>
                </MenuItem>

                <MenuItem Header="Copy" Command="mashup:MashupInstance.MashupCommand">
                   <MenuItem.CommandParameter>
                      <mashup:Events>
                         <mashup:Event SourceEventName="Click" LinkUri="mforms://bookmark?program=MMS001&amp;tablename=MITMAS&amp;keys=MMCONO%2c{CONO}%2cMMITNO%2c{ITNO}%2b%2b%2b%2b%2b%2b%2b%2b%2b&amp;option=3&amp;panel=E&amp;name=MMS001%2fE+Bookmark&amp;source=MForms">
                            <mashup:Parameter TargetKey="ITNO" Value="{Binding [ITNO]}" />
                            <mashup:Parameter TargetKey="CONO" Value="001" />
                         </mashup:Event>
                      </mashup:Events>
                   </MenuItem.CommandParameter>
                </MenuItem>

                <MenuItem Header="Delete" Command="mashup:MashupInstance.MashupCommand">
                   <MenuItem.CommandParameter>
                      <mashup:Events>
                         <mashup:Event SourceEventName="Click" LinkUri="mforms://bookmark?program=MMS001&amp;tablename=MITMAS&amp;keys=MMCONO%2c{CONO}%2cMMITNO%2c{ITNO}%2b%2b%2b%2b%2b%2b%2b%2b%2b&amp;option=4&amp;panel=E&amp;name=MMS001%2fE+Bookmark&amp;source=MForms">
                            <mashup:Parameter TargetKey="ITNO" Value="{Binding [ITNO]}" />
                            <mashup:Parameter TargetKey="CONO" Value="001" />
                         </mashup:Event>
                      </mashup:Events>
                   </MenuItem.CommandParameter>
                </MenuItem>

                <MenuItem Header="Display" Command="mashup:MashupInstance.MashupCommand">
                   <MenuItem.CommandParameter>
                      <mashup:Events>
                         <mashup:Event SourceEventName="Click" LinkUri="mforms://bookmark?program=MMS001&amp;tablename=MITMAS&amp;keys=MMCONO%2c{CONO}%2cMMITNO%2c{ITNO}%2b%2b%2b%2b%2b%2b%2b%2b%2b&amp;option=5&amp;panel=E&amp;name=MMS001%2fE+Bookmark&amp;source=MForms">
                            <mashup:Parameter TargetKey="ITNO" Value="{Binding [ITNO]}" />
                            <mashup:Parameter TargetKey="CONO" Value="001" />
                         </mashup:Event>
                      </mashup:Events>
                   </MenuItem.CommandParameter>
                </MenuItem>

                <MenuItem Header="Links">
                   <MenuItem Header="https://thibaudatwork.wordpress.com" Command="mashup:MashupInstance.MashupCommand">
                      <MenuItem.CommandParameter>
                         <mashup:Events>
                            <mashup:Event SourceEventName="Click" LinkUri="https://thibaudatwork.wordpress.com" LinkIsExternal="True">
                                     </mashup:Event>
                         </mashup:Events>
                      </MenuItem.CommandParameter>
                   </MenuItem>

                </MenuItem>
             </ContextMenu>
          </ListView.ContextMenu>

          <ListView.View>
             <GridView ColumnHeaderContainerStyle="{DynamicResource styleGridViewColumnHeader}">
                <GridView.Columns>
                   <GridViewColumn Header="Item:" DisplayMemberBinding="{Binding [ITNO]}" />
                   <GridViewColumn Header="Item type:" DisplayMemberBinding="{Binding [ITTY]}" />
                   <GridViewColumn Header="Lot ctrl:" DisplayMemberBinding="{Binding [INDI]}" />
                </GridView.Columns>
             </GridView>
          </ListView.View>
       </ListView>
    </m3:MIListPanel>

    <ui:StatusBar Name="StatusBar" Grid.Row="1" Grid.Column="0" />
 </Grid>

Regards
Ken Eric

Mashup – GridView and CellTemplate

Today I will share some of my experience with the GridView in combination with MIListPanel and MIPanel. It’s a pretty simple solution, but I spent quite some time myself getting it to work the first time, so I hope it’s worth sharing.

My case scenario was to work with multiple lines. I wanted to be able to edit and update in a more “practical” way than having to select a line and then edit from a different panel followed by clicking a button. Sometimes it can be feel to “slow”, and sometimes it just takes too much space.

What I tried to do, was to create an all-in-one solution, by using the GridViewColumn.CellTemplate and actually open the cell in the GridView for editing and adding a button for update. It takes less space, and when processing multiple lines, it sure does feel a lot more user friendly than some of my previous solutions when I first started working with mashup.

The .xaml sample will look something like this:
GridView-Example1

The list is based on CRS620MI, LstSuppliers. The button is connected to the CRS620MI, UpdSupplier. Using the GridViewColumn.CellTemplate will allow you to create a DataTemplate inside the cell, and I think you can do pretty much anything you want when working with DataTemplate.

Here is also a few different scenarios as well using the same method.

Example one is built for speed, when you need to repeat the same task multiple times based on a list.

GridView-Example2

Example two allows you to provide more input. In this case I built a popup inside the CellTemplate. To learn how to create the popup see my previous post .


<TabItem Header="Attached items">
 <StackPanel>

    <m3:MIListPanel Name="MOS256List">
                <m3:MIListPanel.Events>
                   <mashup:Events>
                      <mashup:Event SourceEventName="CurrentItemChanged" SourceName="MMS240List2">
                         <mashup:Parameter TargetKey="MTRL" SourceKey="ITNO" />
                         <mashup:Parameter TargetKey="SERN" SourceKey="SERN" />
                         <mashup:Parameter TargetKey="EXPA" Value="Y" />
                      </mashup:Event>
                   </mashup:Events>
                </m3:MIListPanel.Events>
                <m3:MIListPanel.DataSource>
                   <m3:MIDataSource Program="MOS256MI" Transaction="LstAsBuildLevel" Type="List" InputFields="MTRL,SERN" OutputFields="MTRL,SERN,CFGL,ITNO,SER2,ITDS,LVLS,TX40,MES1,MES2,MES3,MES4,MVA1,MVA2,MVA3,MVA4,STAT" MaxReturnedRecords="50" />
                </m3:MIListPanel.DataSource>

                <ListView ItemsSource="{Binding Items}" Style="{DynamicResource styleListView}" ItemContainerStyle="{DynamicResource styleListViewItem}">
                   <ListView.View>
                      <GridView ColumnHeaderContainerStyle="{DynamicResource styleGridViewColumnHeader}">
                         <GridView.Columns>
                            <GridViewColumn Header="Lvl:" DisplayMemberBinding="{Binding [LVLS]}" />
                            <GridViewColumn Header="Configuration:" DisplayMemberBinding="{Binding [CFGL]}" />
                            <GridViewColumn Header="Position:" DisplayMemberBinding="{Binding [TX40]}" />
                            <GridViewColumn Header="S/N" DisplayMemberBinding="{Binding [SER2]}" />
                            <GridViewColumn Header="Item" DisplayMemberBinding="{Binding [ITNO]}" />
                            <GridViewColumn Header="Descripton:" DisplayMemberBinding="{Binding [ITDS]}" />
                            <GridViewColumn Header="Days:" DisplayMemberBinding="{Binding [MVA1]}" />
                            <GridViewColumn Header="Runs:" DisplayMemberBinding="{Binding [MVA2]}" />
                            <GridViewColumn Header="Hours:" DisplayMemberBinding="{Binding [MVA3]}" />
                            <GridViewColumn Header="Remove">
                               <GridViewColumn.CellTemplate>
                                  <DataTemplate>
                                     <StackPanel>
                                        <ToggleButton Name="button">
                                           <ToggleButton.Template>
                                              <ControlTemplate TargetType="ToggleButton">
                                                 <TextBlock Text="Remove" Foreground="#FFE10101" TextDecorations="Underline" />
                                              </ControlTemplate>
                                           </ToggleButton.Template>
                                        </ToggleButton>

                                        <Popup IsOpen="{Binding IsChecked, ElementName=button, Mode=OneWay}" Width="300" Height="200" Popup.StaysOpen="False">
                                           <Border Background="White" BorderBrush="Black" BorderThickness="2">
                                              <Grid>
                                                 <Grid.RowDefinitions>
                                                    <RowDefinition Height="200" />
                                                 </Grid.RowDefinitions>
                                                 <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="300" />
                                                 </Grid.ColumnDefinitions>
                                                 <StackPanel>
                                                    <GroupBox Header="Remove unit" Style="{DynamicResource styleGroupLineMashup}">
                                                       <Grid Margin="0,0,0,8">
                                                          <Grid.ColumnDefinitions>
                                                             <ColumnDefinition Width="100" />
                                                             <ColumnDefinition Width="200" />
                                                          </Grid.ColumnDefinitions>
                                                          <Grid.RowDefinitions>
                                                             <RowDefinition Height="32" />
                                                             <RowDefinition Height="32" />
                                                             <RowDefinition Height="32" />
                                                             <RowDefinition Height="32" />
                                                             <RowDefinition Height="32" />
                                                          </Grid.RowDefinitions>

                                                          <TextBlock Text="Serial number:" Grid.Row="0" Grid.Column="0" Margin="8,8,0,0" />
                                                          <TextBox Text="{Binding [SER2]}" Grid.Row="0" Grid.Column="1" MinWidth="115" MaxWidth="115" HorizontalAlignment="Left" IsEnabled="False" />
                                                          <TextBlock Text="Item number:" Grid.Row="1" Grid.Column="0" Margin="8,8,0,0" />
                                                          <TextBox Text="{Binding [ITNO]}" Grid.Row="1" Grid.Column="1" MinWidth="80" MaxWidth="80" HorizontalAlignment="Left" IsEnabled="False" />
                                                          <TextBlock Text="To location:" Grid.Row="2" Grid.Column="0" Margin="8,8,0,0" />
                                                          <TextBox Name="RemoveLocation" Text="" Grid.Row="2" Grid.Column="1" MinWidth="115" MaxWidth="80" HorizontalAlignment="Left" />
                                                          <TextBlock Text="Warehouse:" Grid.Row="3" Grid.Column="0" Margin="8,8,0,0" />
                                                          <TextBox Name="RemoveWhs" Text="100" Grid.Row="3" Grid.Column="1" MinWidth="40" MaxWidth="40" HorizontalAlignment="Left" />

                                                          <Button Name="Install" Content="Remove" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left" Margin="0,0,50,0">
                                                             <Button.CommandParameter>
                                                                <mashup:Events>
                                                                   <mashup:Event SourceEventName="Click" TargetName="RemoveInstall" TargetEventName="Update" Debug="True">
                                                                      <mashup:Parameter TargetKey="RITP" Value="R" />
                                                                      <mashup:Parameter TargetKey="RESP" Value="202231" />
                                                                      <mashup:Parameter TargetKey="TRDT" Value="20130927" />
                                                                      <mashup:Parameter TargetKey="TRTM" Value="090909" />
                                                                      <mashup:Parameter TargetKey="WHLO" Value="{Binding Path=Text, ElementName=RemoveWhs}" />
                                                                      <mashup:Parameter TargetKey="RSC4" Value="CHG" />
                                                                      <mashup:Parameter TargetKey="ITNR" Value="{Binding [ITNO]}" />
                                                                      <mashup:Parameter TargetKey="BANR" Value="{Binding [SER2]}" />
                                                                      <mashup:Parameter TargetKey="TWSL" Value="{Binding Path=Text, ElementName=RemoveLocation}" />
                                                                      <mashup:Parameter TargetKey="NHAR" Value="{Binding [MTRL]}" />
                                                                      <mashup:Parameter TargetKey="NHSR" Value="{Binding [SERN]}" />
                                                                      <mashup:Parameter TargetKey="CFGR" Value="{Binding [CFGL]}" />
                                                                   </mashup:Event>
                                                                </mashup:Events>
                                                             </Button.CommandParameter>
                                                          </Button>
                                                       </Grid>
                                                    </GroupBox>
                                                 </StackPanel>
                                              </Grid>
                                           </Border>
                                        </Popup>
                                     </StackPanel>
                                  </DataTemplate>
                               </GridViewColumn.CellTemplate>
                            </GridViewColumn>
                         </GridView.Columns>
                      </GridView>
                   </ListView.View>
                </ListView>
             </m3:MIListPanel>

             <m3:MIPanel Name="RemoveInstall">
                <m3:MIPanel.Events>
                   <mashup:Events>
                      <mashup:Event SourceName="RemoveInstall" TargetName="MOS256List" SourceEventName="UpdateComplete" TargetEventName="Refresh" />
                   </mashup:Events>
                </m3:MIPanel.Events>

                <m3:MIPanel.DataSource>
                   <m3:MIDataSource Program="MOS125MI" Transaction="RemoveInstall" InputFields="RITP,RESP,TRDT,TRTM,WHLO,RSC4,ITNR,BANR,TWSL,NHAR,NHSR,CFGR" />
                </m3:MIPanel.DataSource>
             </m3:MIPanel>

    </StackPanel>
    </TabItem>

 

GridView-Example4

The source code of the sample:

<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" xmlns:m3="clr-namespace:MForms.Mashup;assembly=MForms">
    <Grid.Resources>
    </Grid.Resources>

    <Grid.ColumnDefinitions>
       <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
       <RowDefinition Height="*" />
       <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

       <m3:MIListPanel Name="CRS620MIList">
       <m3:MIListPanel.Events>
          <mashup:Events>
             <mashup:Event SourceEventName="Startup" />
          </mashup:Events>
       </m3:MIListPanel.Events>

       <m3:MIListPanel.DataSource>
          <m3:MIDataSource Program="CRS620MI" Transaction="LstSuppliers" Type="List" OutputFields="SUNO,SUNM,PHNO,CUCD,BUYE" MaxReturnedRecords="4" />
       </m3:MIListPanel.DataSource>

       <ListView ItemsSource="{Binding Items}" Style="{DynamicResource styleListView}" ItemContainerStyle="{DynamicResource styleListViewItem}">
          <ListView.View>
             <GridView ColumnHeaderContainerStyle="{DynamicResource styleGridViewColumnHeader}">
                <GridView.Columns>
                   <GridViewColumn Header="Supplier:" DisplayMemberBinding="{Binding [SUNO]}" />

                   <GridViewColumn Header="Supplier Name:">
                      <GridViewColumn.CellTemplate>
                         <DataTemplate>
                            <TextBox Name="Name" Text="{Binding [SUNM]}" />
                         </DataTemplate>
                      </GridViewColumn.CellTemplate>
                   </GridViewColumn>

                   <GridViewColumn Header="Phone:">
                      <GridViewColumn.CellTemplate>
                         <DataTemplate>
                            <TextBox Name="Phone" Text="{Binding [PHNO]}" />
                         </DataTemplate>
                      </GridViewColumn.CellTemplate>
                   </GridViewColumn>

                   <GridViewColumn Header="Currency:">
                      <GridViewColumn.CellTemplate>
                         <DataTemplate>
                            <TextBox Name="Currency" Text="{Binding [CUCD]}" />
                         </DataTemplate>
                      </GridViewColumn.CellTemplate>
                   </GridViewColumn>

                      <GridViewColumn Header="Buyer:">
                      <GridViewColumn.CellTemplate>
                         <DataTemplate>
                            <TextBox Name="Buyer" Text="{Binding [BUYE]}" />
                         </DataTemplate>
                      </GridViewColumn.CellTemplate>
                   </GridViewColumn>

                   <GridViewColumn>
                      <GridViewColumn.CellTemplate>
                         <DataTemplate>
                            <Button Content="Update">
                               <Button.CommandParameter>
                                  <mashup:Events>
                                     <mashup:Event SourceEventName="Click" TargetName="CRS620Update" TargetEventName="Update">
                                        <mashup:Parameter TargetKey="SUNO" Value="{Binding [SUNO]}" />
                                        <mashup:Parameter TargetKey="SUNM" Value="{Binding [SUNM]}" />
                                        <mashup:Parameter TargetKey="PHNO" Value="{Binding [PHNO]}" />
                                        <mashup:Parameter TargetKey="CUCD" Value="{Binding [CUCD]}" />
                                        <mashup:Parameter TargetKey="BUYE" Value="{Binding [BUYE]}" />
                                     </mashup:Event>
                                  </mashup:Events>
                               </Button.CommandParameter>
                            </Button>
                         </DataTemplate>
                      </GridViewColumn.CellTemplate>
                   </GridViewColumn>
                </GridView.Columns>
             </GridView>
          </ListView.View>
       </ListView>

    </m3:MIListPanel>
    <m3:MIPanel Name="CRS620Update">
       <m3:MIPanel.DataSource>
          <m3:MIDataSource Program="CRS620MI" Transaction="UpdSupplier" Type="Update" InputFields="SUNO,SUNM,PHNO,CUCD,BUYE" MandatoryInputFields="SUNO" />
       </m3:MIPanel.DataSource>
    </m3:MIPanel>

    <ui:StatusBar Name="StatusBar" Grid.Row="1" Grid.Column="0" />
 </Grid>

Regards
Ken Eric

Grid MobileUI Management Pages

I recently came across Grid MobileUI, a mobile version of the Infor ION Grid Management Pages to efficiently monitor Grid Applications such as M3 on tablets and mobile phones like iPads and iPhones. I believe the Grid MobileUI was released with the new M3 13.1 in July. Here are some screenshots of the Grid MobileUI to spread the goodness.

The Grid MobileUI strips out the extra fat of the Grid Management Pages, keeping only the necessary information for an efficient user experience on a mobile device: you can easily see the list of Grid applications, you can check the errors and warning logs of each application, and you can automatically compose an email and it will paste the link to the page application details.

The Grid MobileUI is important to continue monitoring your M3 when you are on the move, for example when you are away from your office, commuting in the train, or waiting at the boarding gate at the airport. You can now pull up your tablet or your phone, and quickly address issues until you get back to your computer. It helps address issues faster and helps make more efficient use of your downtime.

In order to access the URL from your mobile device, you will have to use a VPN client on your mobile device to access your office network, or setup the web server in your DMZ and setup some form of encrypted authentication.

Here is a screenshot of the Infor ION Grid Management Pages for M3 showing the link to the Grid MobileUI:

2_

Here are two screenshots of the Grid MobileUI on an iPad, showing the main page, and one of the page application details:

5 6

Here is a screenshot of my iPhone’s Home Screen with the shortcut to the Grid MobileUI:

IMG_2487

Here are four screenshots of the Grid MobileUI on my iPhone, showing the main page, one of the page application details, the logs, and composing an email:

IMG_2489 IMG_2490 IMG_2491 IMG_2461

And if the Grid MobileUI is not enough for your administration needs, you can always request the desktop site on the iPad of the Grid Management Pages to get the full user interface, with the entire set of options, and all the detailed information:

7

That’s it!

Related articles:

  • H5 Client, a new HTML5 user interface for M3 that runs on mobile devices and modern browsers like Google Chrome.

Mashup – Browsing with F4 through a popup

Last week Thibaud invited me to share some of our M3 ideas from a customer’s point of view.
The invitation was accepted, hoping to make the community grow beyond just Infor.
I will be sharing some of our knowledge and experience on the subject mashup, and this is my first post.

One of the first thing the end-users requested after releasing our first mashups was the F4/browse functionality missing.
A few request for help was sent, but the main feedback was that this is not possible from a mashup and had to be part of a SDK application.
Unfortunately this was out of my range and a work around had to be made.
After spending quite some time googling/reading I came across the popup class.
“A Popup control displays content in a separate window relative to an element or point on the screen. ”

This is a simple example using the F4 command to open the popup and then allowing you to browse and write back with CurrentItemChanged.

Before:
F4

After:
F4Browse

Real world example, but this time with a button controlling the popup.

ExampleBrowse

And here is the 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" xmlns:m3="clr-namespace:MForms.Mashup;assembly=MForms">
	<Grid.Resources>
	</Grid.Resources>

	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="400" />
		<ColumnDefinition Width="*" />
	</Grid.ColumnDefinitions>
	<Grid.RowDefinitions>
		<RowDefinition Height="40" />
		<RowDefinition Height="Auto" />
	</Grid.RowDefinitions>

	<StackPanel Grid.Row="0" Margin="8,8,0,0" Orientation="Horizontal">
			<StackPanel.InputBindings>
        		 <KeyBinding Key="F4" Command="mashup:MashupInstance.MashupCommand">
					 <KeyBinding.CommandParameter>
						<mashup:Events>
							<mashup:Event Target="{mashup:SetProperty ElementName=BrowseLocation, Path=IsOpen}">
								<mashup:Parameter TargetKey="Value" Value="True" />
							</mashup:Event> 	
						</mashup:Events>
					</KeyBinding.CommandParameter>
				</KeyBinding>
      	</StackPanel.InputBindings>

			<TextBlock Text="Location:" />
			<TextBox Name="Location" Width="50" Height="20" Margin="5,0,0,0" />	

	</StackPanel>

	<StackPanel HorizontalAlignment="Left" Grid.Column="0" Grid.Row="0">

		<Popup Name="BrowseLocation" Width="600" Height="450" Popup.StaysOpen="False">
			<Border Background="White">
				<Grid>
					<Grid.RowDefinitions>
						<RowDefinition Height="400" />
						<RowDefinition Height="50" />
					</Grid.RowDefinitions>
					<Grid.ColumnDefinitions>
						<ColumnDefinition Width="600" />

					</Grid.ColumnDefinitions>
					<m3:ListPanel Name="ListLocation" Grid.Column="0" Grid.Row="0" EnableBasicOptions="False" IsDefaultActionEnabled="False" EnableRelatedOptions="False">
						<m3:ListPanel.Events>
							<mashup:Events>
								<mashup:Event SourceEventName="Startup">
									<mashup:Parameter TargetKey="MLCONO" />
									<mashup:Parameter TargetKey="MLFACI" DefaultValue="100" />
									<mashup:Parameter TargetKey="MLWHSL" />
								</mashup:Event>

								<mashup:Event SourceName="ListLocation" SourceEventName="CurrentItemChanged" Target="{mashup:SetProperty ElementName=Location, Path=Text}">
										<mashup:Parameter TargetKey="Value" SourceKey="WHSL" />
								</mashup:Event>
							</mashup:Events>
						</m3:ListPanel.Events>
						<m3:ListPanel.Bookmark>
							<m3:Bookmark Program="MMS010" Table="MITPCE" KeyNames="MSCONO,MSWHLO,MSWHSL" SortingOrder="1" />
						</m3:ListPanel.Bookmark>
					</m3:ListPanel>

					<Button Name="Close" Content="Close" Grid.Row="1" Grid.Column="0">
								<Button.CommandParameter>
									<mashup:Events>
										<mashup:Event Target="{mashup:SetProperty ElementName=BrowseLocation, Path=IsOpen}" SourceEventName="Click">
											<mashup:Parameter TargetKey="Value" Value="False" />
										</mashup:Event>
									</mashup:Events>
								</Button.CommandParameter>
					</Button>	
				</Grid>
			</Border>
		</Popup>
	</StackPanel>
</Grid>

Regards
Ken Eric

Event graphs for Mashups

Today I introduce a new home-made tool that automatically generates event graphs from a Mashup‘s source code.

Motivation

I am currently doing some maintenance on a monster Mashup that has 20 data controls choreographed by 27 events where the height of the event tree is greater than 3, and I needed to understand the sequence of events so I can implement several new requirements in the Mashup without breaking the entire Mashup.

The tool

To assist me, I implemented a home-made tool with XSLT and XPath that automatically transforms the <mashup:Event> nodes of the Mashup XAML source code into a directed graph in the DOT graph description language that I rendered in GraphViz, an open source graph visualization software. I used what I learned from two of my previous tools: dependency graphs for data conversion, and Web Service pretty print.

Suppose we have a Mashup with a Search button that triggers a search on a Customer list. We would have the following XAML code:

<mashup:Event
    SourceName="BtnSearch"
    SourceEventName="Click"
    TargetName="CustomerList"
    TargetEventName="Search" />

The idea is to take each event’s properties SourceName, SourceEventName, TargetName, and TargetEventName, and display them in a directed graph with nodes, edges, and labels using this DOT syntax:

digraph g {
    BtnSearch -> CustomerList [label="Click > Search"];
}

The result will look like:

2
We can automatically transform the XAML code into that DOT code with the following XSLT code:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI">
    <xsl:template match="/">
        digraph g {
            <xsl:apply-templates select="//mashup:Event"/>
        }
    </xsl:template>
    <xsl:template match="mashup:Event">
        <xsl:value-of select="@SourceName"/> -> <xsl:value-of select="@TargetName"/> [label="<xsl:value-of select="@SourceEventName"/> > <xsl:value-of select="@TargetEventName"/>"];
    </xsl:template>
</xsl:stylesheet>

The problem is that not all Events are fully qualified with all the properties SourceName, SourceEventName, TargetName, and TargetEventName. For instance the Mashup has an implicit SourceName <Global> that does not need to be explicitly qualified in the code. And the Button has the implicit SourceEventName “Click” that does not need to be explicitly qualified either. Thus, we need to handle those cases in the XSLT code. The resulting XSLT code is long and complicated with many if-then-else to test if the properties are blank, and, if they are, to test if the control has a known implicit property.

Finally, we will need an XSLT processing engine in order to get the result. Most major browsers have a built-in XSLT engine, for instance Microsoft Internet Explorer, Google Chrome, Safari, and Mozilla Firefox have a built-in XSLT engine. To test my tool on your computer, you can use Internet Explorer, Safari, and Opera as they will process the XSLT file locally from the disk with file://… On the other hand, Firefox and Chrome for security reasons will only process the file if it’s served from a web server with http:// so you would have to setup your localhost.

You can download the final XSLT file at http://thibaudlopez.net/Mashups/EventGraph.xslt

Preparation

Before using my tool, follow these steps:

  1. Download Graphviz from http://www.graphviz.org/ and start it from Windows > Start > Graphviz > gvedit.exe.
  2. Download my XSLT file from http://thibaudlopez.net/Mashups/EventGraph.xslt and save it somewhere in your file system.

How to use

To use my tool, follow these steps:

  1. Get some Mashup XAML code, for example from the Mashup Designer built-in examples, and save the XAML in the same folder as the XSLT file you saved previously:4
  2. Rename the file extension from XAML to XML so we can open it in one of the browsers later:
    5
  3. Add the following XSLT processing instruction at the top of the XML file:
    <?xml-stylesheet type=”text/xsl” href=”EventGraph.xslt”?>
    6
  4. Open the file in one of the browsers, for instance Internet Explorer:
    7
  5. In Graphviz, select File > New.
  6. Copy/paste the code that was generated in the browser:
    10
  7. Select Graph > Layout (F5) to generate the graph:
    11
  8. That’s it!

Results

Here are the resulting event graphs for five of the Mashup Designer’s built-in examples:

  1. REST Lists:
    213
  2. Item list & details:
    1 3
  3. Customer Addresses & Map:
    23
  4. Item list & visualizers:
    1 2
  5. List & edit Customers:
    1 10

Future work

In a future work, the XSLT code would have to be refined to cover all possible scenarios (blanks and implicit properties).

Also, we could include the Bookmark’s Keys or the Event’s Parameter Keys in the event graph, for example CONO, CUNO, ADRT, ADID.

7

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

Progress indicator adorner

Last week in Stockholm norpe showed me how to add a progress indicator Adorner to a ListView when I call an M3 API in a background thread in a Personalized Script for Lawson Smart Office. When I execute a time consuming operation I like to maintain good usability by indicating activity to the user. For that, I used to display a message to the user in a Label like “Loading please wait…”, but that doesn’t catch the eye very well, and/or I used to changed the mouse cursor to Cursor.Wait. but I think that’s a shared resource. So I prefer this new technique.

It’s only two lines of code from Mango.UI.Controls:

ProgressIndicatorAdorner.AddAdorner(element, useOpacity, scaleFactor);
ProgressIndicatorAdorner.RemoveAdorner(element);

Here’s the method’s signature:
Reflector

Here’s the result:
1_ 2_

That’s it.