Hello,
We are developping an user interface for Infor EAM that enables users to manipulate data through the use of the existing webservices. Within the application one can only edit a single row (eg. purchase-orderline) at a time in their screen. Using a .NET CORE MVC application we enable them to, for example edit all orderlines of a purchaseorder in one table and hit 'save'.
The application then uses the existing webservices from Infor to save thoses lines within the application.
Eventhough we are able to do this, we believe we are missing something and do not use the webservices to its full potential.
What do we want:
- we want to use the webservice for sending the SOAP message
- we want to use a OAUTH token for authorization
What do we have:
- we have a working OAUTH authentication/authorization structure
- we are able to use the classes from the webservice reference to construct a SOAP message.
- this however in a slightly less robust fashion. First we simply created the SOAP message by 'stringing' together some hardcoded strings. Now we do it with a custom "Envelope" class that we serialize. The downside of this is that for each webservice we need another custom envelope class as the body is different.
- the SOAP WSDL within SOAP UI with coded username and password in the message works
- the SOAP message combined with a custom httpclient works
- a HTTP POST with an added Authorization header with Bearer token and SOAP in the body
- setting up the client within the webservice reference does not work
Question:
- How do we initiliaze/declare/start the client in the webservice such that it can directly communicate with Infor?
Codesnippets
I will use de MP0118 service as example as I guess everyone would be using that in whichever application you have made, as it is used to call grid/tabledata from Infor to your application.
We implemented an OAUTH authentication scheme in se startup.cs that allows the user to login and provides a token to communicate with Infor.
We used Visual Studio 2019 standard method to import MP0118 WSDL file as a connected services.
Working code to communicate with Infor
"Stringing" the xml SOAP message:
<span style="font-family:'courier new', courier;">string organization = "Organization";
<span style="font-family:'courier new', courier;">string tenant = "Tenant";
<span style="font-family:'courier new', courier;">string gridname = "LURCUS";
<span style="font-family:'courier new', courier;">string gridtype = "LOV";
<span style="font-family:'courier new', courier;">string soapenv = "<soapenv:Envelope xmlns:soapenv='<a href="http://schemas.xmlsoap.org/soap/envelope/'">schemas.xmlsoap.org/.../'</a> xmlns:mp='<a href="http://schemas.datastream.net/MP_functions'">schemas.datastream.net/MP_functions'</a> xmlns:sec='<a href="http://schemas.xmlsoap.org/ws/2002/04/secext'">schemas.xmlsoap.org/.../secext'</a> xmlns:mp0='<a href="http://schemas.datastream.net/MP_functions/MP0118_GetGridHeaderData_001'">schemas.datastream.net/.../MP0118_GetGridHeaderData_001'</a> xmlns:grid='<a href="http://schemas.datastream.net/MP_functions/GridRequest'>">">schemas.datastream.net/.../GridRequest'>";</a>
<span style="font-family:'courier new', courier;">string soapenvHeader = "<soapenv:Header><mp:Tenant>" + tenant + "</mp:Tenant><mp:MessageConfig/><Organization>" + organization + "</Organization></soapenv:Header>";
<span style="font-family:'courier new', courier;">string soapenvBody = "<soapenv:Body><mp0:MP0118_GetGridHeaderData_001><mp0:FUNCTION_REQUEST_INFO REQUEST_TYPE='LOV.HEAD_DATA.STORED'><grid:GRID GRID_NAME='" + gridname + "' CURSOR_POSITION='1' TAB_NAME='?' RESULT_IN_SAXORDER='?' TERSERESPONSE='?' LOCALIZE_RESULT='?'/><grid:GRID_TYPE TYPE='" + gridtype + "'/>";
<span style="font-family:'courier new', courier;">string soapenvend = "</mp0:FUNCTION_REQUEST_INFO></mp0:MP0118_GetGridHeaderData_001></soapenv:Body></soapenv:Envelope>";
<span style="font-family:'courier new', courier;">string xmlSOAP = soapenv + soapenvHeader + soapenvBody + soapenvend;
Create a httpclient and message for API communication:
string accessToken = await HttpContext.GetTokenAsync("access_token");
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
Uri uri = new Uri("<a href="https://mingle-ionapi.eu1.inforcloudsuite.com/TENANT/EAM/APIServices">">mingle-ionapi.eu1.inforcloudsuite.com/.../APIServices");</a>
HttpContent httpContent = new StringContent(xmlSOAP);
HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = httpContent
};
httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/xml; charset=utf-8");
HttpResponseMessage httpresponse;
httpresponse = await httpClient.SendAsync(httpRequest);
//This actually writes the correct response, similar to the response we would get using SOAP UI
Debug.WriteLine(httpresponse.Content.ReadAsStringAsync());
As an alternative to the stringing we created this class for serialization, this works als an alternative for xmlSOAP in the previous code.
[XmlType(Namespace = SOAPRequest.soapenv, IncludeInSchema = true)]
public class SOAPRequest
{
private const string soapenv = "">schemas.xmlsoap.org/.../";
private const string mp = "">schemas.datastream.net/MP_functions";
private const string sec = "">schemas.xmlsoap.org/.../secext";
private const string mp0 = "">schemas.datastream.net/.../MP0118_GetGridHeaderData_001";
private const string grid = "">schemas.datastream.net/.../GridRequest";
// [XmlAttribute(AttributeName = "id")]
// public string Id { get; set; }
// [XmlAttribute(AttributeName = "root", Namespace = c)]
// public int Root { get; set; }
[XmlRoot(Namespace = soapenv)]
public class Envelope
{
public Header Header { get; set; }
public Body Body { get; set; }
static Envelope()
{
staticxmlns = new XmlSerializerNamespaces();
staticxmlns.Add("soapenv", soapenv);
staticxmlns.Add("mp", mp);
staticxmlns.Add("sec", sec);
staticxmlns.Add("mp0", mp0);
staticxmlns.Add("grid", grid);
}
private static XmlSerializerNamespaces staticxmlns;
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlns { get { return staticxmlns; } set { } }
}
[XmlType(Namespace = mp)]
public class Header
{
[XmlElement(ElementName = "Tenant")]
public string Tenant { get; set; }
[XmlElement(ElementName = "Organization")]
public string Organization { get; set; }
[XmlElement(ElementName = "MessageConfig")]
public string MessageConfig { get; set; }
}
[XmlType(Namespace = mp0)]
public class Body
{
public MP0118_GetGridHeaderData_001 MP0118_GetGridHeaderData_001 { get; set; }
}
}
//Using the class present in the Reference.cs file from the connected services
MP0118_GetGridHeaderData_001 GGHD = new MP0118_GetGridHeaderData_001
{
FUNCTION_REQUEST_INFO = new FUNCTION_REQUEST_INFO
{
GRID = new GRID
{
GRID_NAME = "LURCUS",
NUMBER_OF_ROWS_FIRST_RETURNED = "50",
CURSOR_POSITION = "1",
TAB_NAME = "?",
RESULT_IN_SAXORDER = "?",
TERSERESPONSE = "?",
LOCALIZE_RESULT = "?",
},
GRID_TYPE = new GRID_TYPE
{
TYPESpecified = true,
TYPE = GRID_TYPE_type.LOV,
},
REQUEST_TYPESpecified = true,
REQUEST_TYPE = FUNCTION_REQUEST_TYPE.LOVHEAD_DATASTORED,
}
};
var msg = new SOAPRequest.Envelope
{
Header = new SOAPRequest.Header()
{
Tenant = "Tenant",
Organization = "Organization",
MessageConfig=""
},
Body = new SOAPRequest.Body()
{ MP0118_GetGridHeaderData_001 = GGHD}
};
var serializer = new XmlSerializer(typeof(SOAPRequest.Envelope));
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Indent = true,
OmitXmlDeclaration = true,
};
var builder = new StringBuilder();
using (var writer = XmlWriter.Create(builder, settings))
{
serializer.Serialize(writer, msg, msg.xmlns);
}
//Similar use of the HttpClient / Uri and Token only replaced the xmlSOAP with builder.ToString().
//Results in a working communication and gives us the requested SOAP return message.
Working SOAP UI version:
- Import WSDL MP0118
- Endpoint:
eam-webservice.eu1.inforcloudsuite.com/.../EWSConnector- Add your credentials to the message
<soapenv:Envelope xmlns:soapenv="<a href="http://schemas.xmlsoap.org/soap/envelope/" />">schemas.xmlsoap.org/.../" xmlns:mp="<a href="http://schemas.datastream.net/MP_functions">">schemas.datastream.net/MP_functions"</a> xmlns:sec="<a href="http://schemas.xmlsoap.org/ws/2002/04/secext">">schemas.xmlsoap.org/.../secext"</a> xmlns:mp0="<a href="http://schemas.datastream.net/MP_functions/MP0118_GetGridHeaderData_001">">schemas.datastream.net/.../MP0118_GetGridHeaderData_001"</a> xmlns:grid="<a href="http://schemas.datastream.net/MP_functions/GridRequest">">schemas.datastream.net/.../GridRequest"></a>
<soapenv:Header>
<mp:Tenant>Tenant</mp:Tenant>
<mp:MessageConfig/>
<Organization>Organization</Organization>
<sec:Security>
<UsernameToken>
<Username>Usercredentials</Username>
<Password>Userpassword</Password>
</UsernameToken>
</sec:Security>
</soapenv:Header>
<soapenv:Body>
<mp0:MP0118_GetGridHeaderData_001>
<mp0:FUNCTION_REQUEST_INFO REQUEST_TYPE="LOV.HEAD_DATA.STORED">
<grid:GRID GRID_NAME="LURCUS" NUMBER_OF_ROWS_FIRST_RETURNED="5000" CURSOR_POSITION="1" TAB_NAME="?" RESULT_IN_SAXORDER="?" TERSERESPONSE="Y" LOCALIZE_RESULT="?"/>
<grid:GRID_TYPE TYPE="LOV"/>
<grid:MULTIADDON_FILTERS>
<!--1 or more repetitions:-->
<grid:MADDON_FILTER ALIAS_NAME="evt_date" OPERATOR=">" VALUE="01-01-2018 00:00:00.0" SEQNUM="1" />
</grid:MULTIADDON_FILTERS>
</mp0:FUNCTION_REQUEST_INFO>
</mp0:MP0118_GetGridHeaderData_001>
</soapenv:Body>
</soapenv:Envelope>
- Hit send, and the response is the exact same content as the content from the previous httpresponse
Issue / Not working, back in the C# (.NET CORE) application:
BasicHttpsBinding binding = new BasicHttpsBinding();
EndpointAddress endpointAddress = new EndpointAddress(new Uri("<a href="https://eam-webservice.eu1.inforcloudsuite.com/axis/services/EWSConnector">">eam-webservice.eu1.inforcloudsuite.com/.../EWSConnector"));</a>
GetGridHeaderDataPTClient client = new GetGridHeaderDataPTClient(binding, endpointAddress);
string organization = "Organization";
string tenant = "Tenant";
Security security = new MP0118GetGridHeaderData.Security();
string sessionScenario = "";
MessageItemConfigType[] messageConfig = new MessageItemConfigType[0] {};
SessionType session = new SessionType
{
sessionId = "1"
};
MP0118_GetGridHeaderData_001 GGHD = new MP0118_GetGridHeaderData_001
{
FUNCTION_REQUEST_INFO = new FUNCTION_REQUEST_INFO
{
GRID = new GRID
{
GRID_NAME = "LURCUS",
NUMBER_OF_ROWS_FIRST_RETURNED = "50",
CURSOR_POSITION = "1",
TAB_NAME = "?",
RESULT_IN_SAXORDER = "?",
TERSERESPONSE = "?",
LOCALIZE_RESULT = "?",
},
GRID_TYPE = new GRID_TYPE
{
TYPESpecified = true,
TYPE = GRID_TYPE_type.LOV,
},
REQUEST_TYPESpecified = true,
REQUEST_TYPE = FUNCTION_REQUEST_TYPE.LOVHEAD_DATASTORED,
}
};
// Below function requires all inputs, eventhough the "security" part is not actually required for the SOAP communication to work as it is not included in the previous 'string' example.
GetGridHeaderDataResponseMsg response = await client.GetGridHeaderDataOpAsync(organization, security,sessionScenario,session, messageConfig, tenant, GGHD);
Now we tried the configuration of the Security class, SessionType and binding in a dozen different combinations but we always get stuck on either:
- C# / Visual Studio doesnt want to build because the GetGridHeaderDataOpAsync() isnt correctly called
- The response sais "not authorized"
Hopefully someone can point us in the correct direction. Thanks in advance.