Integrating With Fusion Application Using Services (.Net: Service Reference – de-serialization)

Fusion Applications provides Web services that allow external systems to integrate with Fusion Applications. There are two types of services: ADF services and composite services. ADF services are created for a logical business object and provide functionality to access and manipulate these objects. The composite services are mostly process oriented and provide an orchestration of multiple steps.

Information about the web services provided by Fusion Applications is hosted in Oracle Enterprise Repository (OER). The information provided by OER can be used to understand the functionality provided by the service and how the service can be called.

This series of articles describes how one can invoke SOAP web services provided by Fusion Applications using various technologies. In previous article we covered how to invoke a Fusion Application web service using Service Reference for .Net framework. In some cases the integration may fail with de-serialization issues like in exception:

Unhandled Exception: System.ServiceModel.CommunicationException: 
  Error in deserializing body of reply message for operation 'findOpportunity'. 
  ---> System.InvalidOperationException: There is an error in XML document (1, 4118).
  ---> System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(String str, NumberStyles options, 
     NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
An example of this scenario would be; where 2 EOs each of which has required attributes. A VO that is based on EO A and has a outer join on EO B. So are the attributes from EO B required or not ? In context of the outer join they are not, but when data is returned by the join what should happen when attributes in B are set to null ? For example calling OpportunityService may contain an empty value for element of type long:
  <ns3:PrimaryContactPartyId/>'
Which causes the exception, the definition for the element is as a number value is expected and empty value cannot be cast to a number:
  <xsd:element name="PrimaryContactPartyId" type="xsd:long" minOccurs="0" 
   sdoXML:dataType="sdoJava:LongObject"/>

If the problem element from is not included in the response or the element is defined as nillable="true" then the problem does not occur.

This article describes an example how to deal with de-serialization issues for .Net integration.

Prerequisites

.Net development tool such as Visual Studio 2012 needs to be installed and configured

Processing the Response Prior to De-serialization

To resolve the issue this example describes a method to manipulate the response from the Web Service prior of de-serialization in .Net. The processing will simply remove elements:

  • Node Type is XmlNodeType.Element
  • The element does NOT have any children
  • The element has no content
  • The element has no attributes

First we create a Service Reference for OpportunityService and use it to call the service as described in previous article:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel;
using ServiceReferenceExample2Client.FusionServiceReference;

namespace ServiceReferenceExample2Client
{
    /// <summary>
    /// Custom binding that can be used to invoke Fusion Application services secured with OWSM policy
    /// </summary>
    /// <remarks>
    /// This custom binding can be used to invoke Fusion Application services secured with 
    /// "oracle/wss_username_token_over_ssl_service_policy" OWSM policy
    /// </remarks>
    public class UsernameTokenOverSslBinding : CustomBinding
    {
        public override BindingElementCollection CreateBindingElements()
        {
            BindingElementCollection bindingElements = new BindingElementCollection();
            bindingElements.Add(SecurityBindingElement.CreateUserNameOverTransportBindingElement());
            MtomMessageEncodingBindingElement messageEncoding = new MtomMessageEncodingBindingElement();
            messageEncoding.MessageVersion = MessageVersion.Soap11;
            bindingElements.Add(messageEncoding);
            HttpsTransportBindingElement transport = new HttpsTransportBindingElement();
            bindingElements.Add(transport);
            return bindingElements.Clone();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            TestOpportunityService();
        }

        /// <summary>
        /// Tests a call to Opportunity Service
        /// </summary>
        static void TestOpportunityService()
        {
            Console.WriteLine("TestOpportunityService");
            System.ServiceModel.Channels.Binding binding = new UsernameTokenOverSslBinding();
           
 EndpointAddress endpointAddress = new EndpointAddress(new 
Uri("https://host/opptyMgmtOpportunities/OpportunityService"));
            OpportunityServiceClient opportunityServiceClient = new OpportunityServiceClient(binding, endpointAddress);
            opportunityServiceClient.ClientCredentials.UserName.UserName = "username";
            opportunityServiceClient.ClientCredentials.UserName.Password = "password";

            FindCriteria findCriteria = new FindCriteria();
            findCriteria.fetchSize = 2;
            findCriteria.fetchStart = 0;

            FindControl findControl = new FindControl();
            Opportunity[] result = opportunityServiceClient.findOpportunity(findCriteria, findControl);
            if (null != result && result.Length > 0)
            {
                foreach (Opportunity opportunity in result)
                {
                    Console.WriteLine("  OptyId = " + opportunity.OptyId);
                }
            }
            else
            {
                if (null == result)
                {
                    Console.WriteLine("  result null ");
                }
                else
                {
                    Console.WriteLine("  result empty ");
                }
            }
        }
    }
}  

When the code is executed an exception occurs:

Unhandled Exception: System.ServiceModel.CommunicationException: 
 Error in deserializing body of reply message for operation 'findOpportunity'. 
 ---> System.InvalidOperationException: There is an error in XML document (1, 4118). 
 ---> System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)

To workaround this we will remove the problem elements from the response prior to de-serialization. First a "message inspector" is introduced; this class will intercept the reply message received from the service and removes the empty elements prior of de-serialization in .Net:

    /// <summary>
    /// This is a class used for custom processing removing empty elements from the WS response 
    /// </summary>
    /// <remarks>
    /// Calls to FA services include elements with no value are included in the response when the , this
    /// will cause the .Net de-serialization to fail for non-string data types. For example element defined as:
    ///   <xsd:element name="PrimaryContactPartyId" type="xsd:long" minOccurs="0" sdoXML:dataType="sdoJava:LongObject"/> 
    ///
    /// The response may contain an empty value:
    ///   <ns3:PrimaryContactPartyId/>'
    /// 
    /// Which causes exception:
    ///   Error in deserializing body of reply message for operation 'findOpportunity'. 
    ///   
    ///  .Net de-serialization expects "nillable="true"" to be defined for empty elements and since that is not
    ///  included the error occurs. This class is used to manipulate the response received from the service before
    ///  de-serialization by removing empty elements from the response. The element is removed from the reponse if
    ///  all of the following criteria are met:
    ///    - Node Type is XmlNodeType.Element 
    ///    - The element does NOT have any children
    ///    - The element has no content
    ///    - The element has no attributes
    /// </remarks>
    public class EmptyElementInspector : IClientMessageInspector
    {
        /// <summary>
        /// This will parse the response received from the service and remove all empty elements from it
        /// </summary>
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            MemoryStream memoryStream = new MemoryStream();
            XmlWriter xmlWriter = XmlWriter.Create(memoryStream);
            reply.WriteMessage(xmlWriter);
            xmlWriter.Flush();
            memoryStream.Position = 0;
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(memoryStream);

            XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(xmlDocument.NameTable);
            xmlNamespaceManager.AddNamespace("env", "http://schemas.xmlsoap.org/soap/envelope/");
            XmlNode header = xmlDocument.SelectSingleNode("//env:Header", xmlNamespaceManager);
            if (header != null)
            {
                header.ParentNode.RemoveChild(header);
            }

            XmlNodeList nodes = xmlDocument.SelectNodes("//node()");
            foreach (XmlNode node in nodes)
            {
               
 if (node.NodeType == XmlNodeType.Element && 
node.ChildNodes.Count == 0 && node.InnerXml == "" && 
node.Attributes.Count == 0)
                {
                    node.ParentNode.RemoveChild(node);
                }
            }
            memoryStream = new MemoryStream();
            xmlDocument.Save(memoryStream);
            memoryStream.Position = 0;
            XmlReader xmlReader = XmlReader.Create(memoryStream);
           
 System.ServiceModel.Channels.Message newMessage = 
System.ServiceModel.Channels.Message.CreateMessage(xmlReader, 
int.MaxValue, reply.Version);
            newMessage.Headers.CopyHeadersFrom(reply.Headers);
            newMessage.Properties.CopyProperties(reply.Properties);
            reply = newMessage;
        }

       
 public object BeforeSendRequest(ref 
System.ServiceModel.Channels.Message request, 
System.ServiceModel.IClientChannel channel)
        {
            return null;
        }
    }

Next a Behaviour that can be added on the client is introduced. This class adds the Inspector created above for the client to hook into the response processing:

    /// <summary>
    /// This class is used to add the custom inspector to process the response before de-serialization
    /// </summary>
    public class EmptyElementBehavior : System.ServiceModel.Description.IEndpointBehavior
    {
       
 public void 
AddBindingParameters(System.ServiceModel.Description.ServiceEndpoint 
endpoint, BindingParameterCollection bindingParameters)
        {
            //no-op
        }

        public void ApplyClientBehavior(System.ServiceModel.Description.ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            EmptyElementInspector inspector = new EmptyElementInspector();
            clientRuntime.MessageInspectors.Add(inspector);
        }

       
 public void 
ApplyDispatchBehavior(System.ServiceModel.Description.ServiceEndpoint 
endpoint, EndpointDispatcher endpointDispatcher)
        {
            //no-op
        }

        public void Validate(System.ServiceModel.Description.ServiceEndpoint endpoint)
        {
            //no-op
        }
    }

Finally the custom Behavior is added to the client:

opportunityServiceClient.Endpoint.Behaviors.Add(new EmptyElementBehavior());

With this custom processing the exception does not occur and call succeeds.
For complete code listing refer to the Appendix A.

Summary

In this article we covered an example of how to implement custom processing for Web Service response to deal with de-serialization issue for Service Reference integration for .Net framework.
In future articles other technologies for invoking Fusion Applications web services will be covered.

References


Appendix A - Complete code listing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel;
using ServiceReferenceExample2Client.FusionServiceReference;
using System.Xml;
using System.IO;
using System.ServiceModel.Dispatcher;

namespace ServiceReferenceExample2Client
{
    /// <summary>
    /// Custom binding that can be used to invoke Fusion Application services secured with OWSM policy
    /// </summary>
    /// <remarks>
    /// This custom binding can be used to invoke Fusion Application services secured with
    /// "oracle/wss_username_token_over_ssl_service_policy" OWSM policy
    /// </remarks>
    public class UsernameTokenOverSslBinding : CustomBinding
    {
        public override BindingElementCollection CreateBindingElements()
        {
            BindingElementCollection bindingElements = new BindingElementCollection();
            bindingElements.Add(SecurityBindingElement.CreateUserNameOverTransportBindingElement());
            MtomMessageEncodingBindingElement messageEncoding = new MtomMessageEncodingBindingElement();
            messageEncoding.MessageVersion = MessageVersion.Soap11;
            bindingElements.Add(messageEncoding);
            HttpsTransportBindingElement transport = new HttpsTransportBindingElement();
            bindingElements.Add(transport);
            return bindingElements.Clone();
        }
    }

    /// <summary>
    /// This is a class used for custom processing removing empty elements from the WS response
    /// </summary>
    /// <remarks>
    /// Calls to FA services include elements with no value are included in the response when the , this
    /// will cause the .Net de-serialization to fail for non-string data types. For example element defined as:
    ///   <xsd:element name="PrimaryContactPartyId" type="xsd:long" minOccurs="0" sdoXML:dataType="sdoJava:LongObject"/>
    ///
    /// The response may contain an empty value:
    ///   <ns3:PrimaryContactPartyId/>'
    ///
    /// Which causes exception:
    ///   Error in deserializing body of reply message for operation 'findOpportunity'.
    ///  
    ///  .Net de-serialization expects "nillable="true"" to be defined for empty elements and since that is not
    ///  included the error occurs. This class is used to manipulate the response received from the service before
    ///  de-serialization by removing empty elements from the response. The element is removed from the reponse if
    ///  all of the following criteria are met:
    ///    - Node Type is XmlNodeType.Element
    ///    - The element does NOT have any children
    ///    - The element has no content
    ///    - The element has no attributes
    /// </remarks>
    public class EmptyElementInspector : IClientMessageInspector
    {
        /// <summary>
        /// This will parse the response received from the service and remove all empty elements from it
        /// </summary>
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            MemoryStream memoryStream = new MemoryStream();
            XmlWriter xmlWriter = XmlWriter.Create(memoryStream);
            reply.WriteMessage(xmlWriter);
            xmlWriter.Flush();
            memoryStream.Position = 0;
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(memoryStream);

            XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(xmlDocument.NameTable);
            xmlNamespaceManager.AddNamespace("env", "http://schemas.xmlsoap.org/soap/envelope/");
            XmlNode header = xmlDocument.SelectSingleNode("//env:Header", xmlNamespaceManager);
            if (header != null)
            {
                header.ParentNode.RemoveChild(header);
            }

            XmlNodeList nodes = xmlDocument.SelectNodes("//node()");
            foreach (XmlNode node in nodes)
            {
                if (node.NodeType == XmlNodeType.Element && node.ChildNodes.Count == 0 && node.InnerXml == "" && node.Attributes.Count == 0)
                {
                    node.ParentNode.RemoveChild(node);
                }
            }
            memoryStream = new MemoryStream();
            xmlDocument.Save(memoryStream);
            memoryStream.Position = 0;
            XmlReader xmlReader = XmlReader.Create(memoryStream);
            System.ServiceModel.Channels.Message newMessage = System.ServiceModel.Channels.Message.CreateMessage(xmlReader, int.MaxValue, reply.Version);
            newMessage.Headers.CopyHeadersFrom(reply.Headers);
            newMessage.Properties.CopyProperties(reply.Properties);
            reply = newMessage;
        }

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            return null;
        }
    }

    /// <summary>
    /// This class is used to add the custom inspector to process the response before de-serialization
    /// </summary>
    public class EmptyElementBehavior : System.ServiceModel.Description.IEndpointBehavior
    {
        public void AddBindingParameters(System.ServiceModel.Description.ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            //no-op
        }

        public void ApplyClientBehavior(System.ServiceModel.Description.ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            EmptyElementInspector inspector = new EmptyElementInspector();
            clientRuntime.MessageInspectors.Add(inspector);
        }

        public void ApplyDispatchBehavior(System.ServiceModel.Description.ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            //no-op
        }

        public void Validate(System.ServiceModel.Description.ServiceEndpoint endpoint)
        {
            //no-op
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            TestOpportunityService();
        }

        /// <summary>
        /// Tests a call to Opportunity Service
        /// </summary>
        static void TestOpportunityService()
        {
            Console.WriteLine("TestOpportunityService");
            System.ServiceModel.Channels.Binding binding = new UsernameTokenOverSslBinding();
            EndpointAddress endpointAddress = new EndpointAddress(new Uri("https://host/opptyMgmtOpportunities/OpportunityService"));
            OpportunityServiceClient opportunityServiceClient = new OpportunityServiceClient(binding, endpointAddress);
            opportunityServiceClient.ClientCredentials.UserName.UserName = "username";
            opportunityServiceClient.ClientCredentials.UserName.Password = "password";
            opportunityServiceClient.Endpoint.Behaviors.Add(new EmptyElementBehavior());

            FindCriteria findCriteria = new FindCriteria();
            findCriteria.fetchSize = 2;
            findCriteria.fetchStart = 0;

            FindControl findControl = new FindControl();
            Opportunity[] result = opportunityServiceClient.findOpportunity(findCriteria, findControl);
            if (null != result && result.Length > 0)
            {
                foreach (Opportunity opportunity in result)
                {
                    Console.WriteLine("  OptyId = " + opportunity.OptyId);
                }
            }
            else
            {
                if (null == result)
                {
                    Console.WriteLine("  result null ");
                }
                else
                {
                    Console.WriteLine("  result empty ");
                }
            }
        }
    }
}



 

        
    
Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

Follow us on twitter Fusion Applications Extensibility, Customizations and Integration forum Fusion Applications Dev Relations YouTube Channel
This blog offers news, tips and information for developers building extensions, customizations and integrations for Oracle Fusion Applications.

Search

Categories
Archives
« March 2015
SunMonTueWedThuFriSat
1
2
4
6
7
8
11
12
14
15
17
19
20
21
22
24
25
27
28
29
30
31
    
       
Today