HodStudio / XitSoap

An alternative way to do SOAP Requests without use the WSDL
MIT License
11 stars 3 forks source link

SOAP Exception #13

Open hemawst opened 4 years ago

hemawst commented 4 years ago

Hi, i use your very good project in my source. I call a soap services, the response give me an error. The exception is "The remote server returned an error: (500) ."
SOAPUI show we, that is a soap fault:

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
         <faultcode>S:Server</faultcode>
         <faultstring>No open Order</faultstring>
         <detail>
            <Result:Result xmlns:Result="http://example.com/AkkordlohnWS" xmlns="http://example.com/AkkordlohnWS">
               <messageCode xmlns:ns7="http://example.com/AkkordlohnWS" xmlns="">-12</messageCode>
               <messageType xmlns:ns7="http://example.com/AkkordlohnWS" xmlns="">Error</messageType>
               <messageText xmlns:ns7="http://example.com/AkkordlohnWS" xmlns="">No open Order</messageText>
               <messageSource xmlns:ns7="http://example.com/AkkordlohnWS" xmlns="">Application</messageSource>
               <MessageDetails xmlns:ns7="http://example.com/AkkordlohnWS" xmlns="">
                  <Message>
                     <messageCode>-12</messageCode>
                     <messageType>Error</messageType>
                     <messageText>Identifiers: BundleopBarcode=</messageText>
                     <messageSource>Application</messageSource>
                  </Message>
                  <Message>
                     <messageCode>tlbctstgen</messageCode>
                     <messageType>Information</messageType>
                     <messageText>Error occurred in Method 'registerAkkordlohn' on component 'AkkordlohnWS' of Object 'amweb200 AkkordlohnWS'.</messageText>
                     <messageSource>Protected Layer</messageSource>
                  </Message>
               </MessageDetails>
            </Result:Result>
         </detail>
      </S:Fault>
   </S:Body>
</S:Envelope> 

But in my application i can only get the error code "The remote server returned an error: (500) ." Is there a way to evaluate the soap fault in my application?

For a quick reply, I thank you in advance.

Cussa commented 4 years ago

Hello,

The problem with it is that every project handles the exception in one/their way. This brings me an idea: I can configure a way to parse the response if the execution is an error. I'll study it a little and try to come with a solution for it.

hemawst commented 4 years ago

Hi, i have provisionally solved the problem for myself as follows:

var wsCon = new HodStudio.XitSoap.WebService("https://example.com/AkkordlohnWS/test?wsdl", "http://example.com/AkkordlohnWS");
try
{
    wsCon.AddParameter("reportthrombexinRequest", rpTrom);
    rpResponse = wsCon.Invoke<reportthrombexinResponseType>("reportthrombexin");

    if (rpResponse != null)
    {
        // do anything
    }
}
catch(System.Net.WebException exW)
{
    string strResponsStream = "";

    string exMessage = exW.Message;

    if (exW.Response != null)
    {
        using (System.IO.StreamReader responseReader = new System.IO.StreamReader(exW.Response.GetResponseStream()))
        {
            strResponsStream = responseReader.ReadToEnd();
        }
    }

    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(strResponsStream);
    XmlNodeList xnList;
    xnList = xmlDoc.GetElementsByTagName("faultstring");
    string node = "";
    if(xnList != null && xnList.Count > 0)
    {
        foreach(XmlNode xn in xnList)
        {
            node = xn.InnerText;
        }
    }

} 

I'll get the response as string and create a xml document. Then i search for the faultstring tag and then the value of it. Would that be an idea for solving the problem?

Cussa commented 4 years ago

Yes. We are in a quite similar approach. I'm just trying to create a good way to configure the exception returns and configure them automatically. But thanks for the contribution. I'll use this as a base for improvement.

Just one thing: I recommend you to use two using for the stream, to avoid any possible problem.

hemawst commented 4 years ago

Hello, i have another problem with the very bad web service which i have to use.

My Code:

try
{
    var wsCon = new HodStudio.XitSoap.WebService("https://example.de:8445/c4ws/services/AkkordlohnWS/test?wsdl", "http://example.com/AkkordlohnWS");
    wsCon.AddParameter("report.bundle.operationRequest", rpBundOp);
    rpBundOpResponse = wsCon.Invoke<reportbundleoperationResponseType>("report.bundle.operation");

    if (rpBundOpResponse != null)
    {
        retStr = rpBundOpResponse.DataArea[0].InfoMessage.ToString();
    }
}
catch (Exception e)
    {
        string ret1 = e.StackTrace.ToString();
        retStr = e.Message;
    }

The response from web service (web service did his job successful). I only interested in InfoMessage

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <report.bundle.operationResponse xmlns="http://www.infor.com/businessinterface/MEDIAkkordlohnWS">
         <report.bundle.operationResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="">
            <DataArea>
               <MEDIAkkordlohnWS>
                  <OutData>0</OutData>
                  <InfoMessage>Noch 1 Packen zum AG offen</InfoMessage>
               </MEDIAkkordlohnWS>
            </DataArea>
         </report.bundle.operationResponse>
      </report.bundle.operationResponse>
   </S:Body>
</S:Envelope>

I get an exception:

System.NullReferenceException: Object reference not set to an instance of an object.

exception stack trace: 
  at HodStudio.XitSoap.Helpers.WebHelpers.ExtractResult (HodStudio.XitSoap.WebService service, System.String methodName) [0x00045] in <b1bbdb60aaff4ab7bcfc89bc944b4cce>:0 
  at HodStudio.XitSoap.Helpers.WebHelpers.InvokeService (HodStudio.XitSoap.WebService service, System.String methodName, System.Boolean encode, System.String soapActionComplement) [0x001c3] in <b1bbdb60aaff4ab7bcfc89bc944b4cce>:0 
  at HodStudio.XitSoap.WebService.Invoke[ResultType] (System.String methodName, System.Boolean encode, System.String soapActionComplement) [0x0007f] in <b1bbdb60aaff4ab7bcfc89bc944b4cce>:0 
  at HodStudio.XitSoap.WebService.Invoke[ResultType] (System.String methodName, System.Boolean encode) [0x00000] in <b1bbdb60aaff4ab7bcfc89bc944b4cce>:0 
  at HodStudio.XitSoap.WebService.Invoke[ResultType] (System.String methodName) [0x00000] in <b1bbdb60aaff4ab7bcfc89bc944b4cce>:0 
  at AppXamarinTest.Droid.ModelWebServiceLnNeu+<>c.<AppXamarinTest.IWebserviceLN.GetPersonalDataNeu>b__0_0 () [0x000cd] in C:\VSProjekteXamarin\AppXamarinTest\AppXamarinTest\AppXamarinTest.Android\ModelWebServiceLnNeu.cs:63 

The relevant parts of wsdl file:

<xsd:complexType name="report.bundle.operationResponseType">
<xsd:sequence>
<xsd:element minOccurs="0" name="DataArea">
<xsd:complexType>
<xsd:sequence>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="MEDIAkkordlohnWS">
<xsd:complexType>
<xsd:sequence>
<xsd:element minOccurs="0" name="OutData" type="xsd:long"/>
<xsd:element minOccurs="0" name="InfoMessage" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>

<wsdl:message name="report.bundle.operationResponse">
<wsdl:part name="report.bundle.operationResponse" type="bo:report.bundle.operationResponseType"/>
</wsdl:message>

<wsdl:operation name="report.bundle.operation">
<wsdl:input message="bo:report.bundle.operationRequest"/>
<wsdl:output message="bo:report.bundle.operationResponse"/>
<wsdl:fault message="bo:Result" name="FaultName"/>
</wsdl:operation>

So i debugged your code. The exception appeared at ExtractResult. It seems, the problem is the StringConstants.XmlResultResultFormat. In my case the "Result" have to chance with Response". (Just like that in ExtractResultClass); But when i chance this, i got another error at var result = (ResultType)serializer.Deserialize(rdr);

Have you any idea?

Cussa commented 4 years ago

Provide me please the class reportbundleoperationResponseType so I can take a look please.

hemawst commented 4 years ago

The class was created automatically via a web reference from the wsdl file. Here are the two relevant classes from the reference.cs file

   /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.8.3761.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(TypeName="report.bundle.operationResponseType", Namespace="http://example.com/MEDIAkkordlohnWS")]
    public partial class reportbundleoperationResponseType {

        private reportbundleoperationResponseTypeMEDIAkkordlohnWS[] dataAreaField;

        /// <remarks/>
        [System.Xml.Serialization.XmlArrayAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
        [System.Xml.Serialization.XmlArrayItemAttribute("MEDIAkkordlohnWS", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)]
        public reportbundleoperationResponseTypeMEDIAkkordlohnWS[] DataArea {
            get {
                return this.dataAreaField;
            }
            set {
                this.dataAreaField = value;
            }
        }
    }

and

   [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.8.3761.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://example.com/MEDIAkkordlohnWS")]
    public partial class reportbundleoperationResponseTypeMEDIAkkordlohnWS {

        private long outDataField;

        private bool outDataFieldSpecified;

        private string infoMessageField;

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public long OutData {
            get {
                return this.outDataField;
            }
            set {
                this.outDataField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public bool OutDataSpecified {
            get {
                return this.outDataFieldSpecified;
            }
            set {
                this.outDataFieldSpecified = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public string InfoMessage {
            get {
                return this.infoMessageField;
            }
            set {
                this.infoMessageField = value;
            }
        }
    }

I hope, this will help.

Cussa commented 4 years ago

So, the idea of the library is exactly to avoid use this WSDL to not have so "ugly" classes. Most of them throw several warnings in code analyzers, like SonarQube.

What I can recommend to you is to recreate the same structure of the response to your class. There was an idea about an improvement to "cut" part of the responses that were not used. That would be useful in your situation now... 🤔

hemawst commented 4 years ago

Sorry, I'm a bit confused today. I tried it with my own class. However, I always get the error in the same place:

   internal static void ExtractResult(this WebService service, string methodName)
        {
            // Selects just the elements with namespace http://tempuri.org/ (i.e. ignores SOAP namespace)
            XmlNamespaceManager namespMan = new XmlNamespaceManager(new NameTable());
            namespMan.AddNamespace(StringConstants.XmlResultDummyNamespace, service.Namespace);
            var methodNameResult = string.Format(StringConstants.XmlResultResultFormat, methodName);

            XElement webMethodResult = service.Result.SoapResponse.XPathSelectElement(string.Format(StringConstants.XmlResultXPathSelectorFormat, methodNameResult), namespMan);
            // If the result is an XML, return it and convert it to string
            if (webMethodResult.FirstNode.NodeType == XmlNodeType.Element)
            {
                var xmlResult = XDocument.Parse(webMethodResult.ToString());
                service.Result.XmlResult = XmlHelpers.RemoveNamespaces(xmlResult);
                service.Result.StringResult = service.Result.XmlResult.ToString();
            }
            // If the result is a string, return it and convert it to XML (creating a root node to wrap the result)
            else
            {
                service.Result.StringResult = webMethodResult.FirstNode.ToString();
                service.Result.XmlResult = XDocument.Parse(string.Format(StringConstants.XmlResultXDocumentFormat, service.Result.StringResult));
            }
        }

var methodNameResult = string.Format(StringConstants.XmlResultResultFormat, methodName); This adds always "Result" on my methode name (report.bundle.operation -> report.bundle.operationResult).

XElement webMethodResult = service.Result.SoapResponse.XPathSelectElement(string.Format(StringConstants.XmlResultXPathSelectorFormat, methodNameResult), namespMan); This search for the name report.bundle.operationResult, but by method named report.bundle.operationResponse.

And this if (webMethodResult.FirstNode.NodeType == XmlNodeType.Element) falls into an NullPointerException because the webMethodResult is null.

Cussa commented 4 years ago

Hello @hemawst ,

After some digging here, I was able to figure out the basic problem.

First, I should say that in the case, we are using this issue to talk about two problems. One is related to the FaultException, which will probably be an improvement for the next version. The second one is related to the exception during the invoke.

This answer is most about the second question, as we already know the "problem" and even some solution to the first one.

The first thing that called my attention was that the returned XML does not follow the same structure as the class. Creating an instance and serializing it already produces a different result. (Name is report.bundle.operationResponseType instead of report.bundle.operationResponse)

<report.bundle.operationResponseType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <DataArea>
    <MEDIAkkordlohnWS>
      <InfoMessage>Noch 1 Packen zum AG offen</InfoMessage>
    </MEDIAkkordlohnWS>
  </DataArea>
</report.bundle.operationResponseType>

This looks a little bit strange to me, as the element that is the return and the object have the same name. Because of that, the XPathSelectElement is getting not only the object that is returned but the enclosed element too.

ACTUAL

<report.bundle.operationResponse>
  <report.bundle.operationResponse>
    <DataArea>
      <MEDIAkkordlohnWS>
        <OutData>0</OutData>
        <InfoMessage>Noch 1 Packen zum AG offen</InfoMessage>
      </MEDIAkkordlohnWS>
    </DataArea>
  </report.bundle.operationResponse>
</report.bundle.operationResponse>

EXPECTED

<report.bundle.operationResponse>
  <DataArea>
    <MEDIAkkordlohnWS>
      <OutData>0</OutData>
      <InfoMessage>Noch 1 Packen zum AG offen</InfoMessage>
    </MEDIAkkordlohnWS>
  </DataArea>
</report.bundle.operationResponse>

Because of this situation, all the following code will have some problems. The library is not prepared to have two elements with the same name. I was even thinking about some improvement where it could receive the name of the "result type," but given the fact that the name is the same from the enclosing type, it would not work.

Another point is that, as I said, the idea of the library is to use it without generating the classes from the WSDL, as they have a lot of garbage. So, because of the XmlTypeAttribute that is on the reportbundleoperationResponseType, the library does a substitution that will fail too.

What I can recommend to you: 1) if you have some control (or can report this situation), I would say to change the name of the returning "class." Change it from report.bundle.operationResponse to report.bundle.operationResponseType would fix part of the problem. I say part because this would mean that the improvement of informing the result type name would be necessary.

2) the library follows the pattern that the "result type" is always method name + Result. If there is a possibility to change to this pattern, that will solve everything. This seems to be the general followed approach. To tell the truth is the first time I see a web service that does not follow it.

3) Latest recommendation: don't use the auto-generated classes from WSDL. They create a lot of things that are not really used and make some mess on the code. We have already a WsMapper attribute that you can use to have better property names on the project, mapping them to the properties returned by the web service.

I hope that the answer helps you. If you need some information, please, reach out.

hemawst commented 4 years ago

Your answer helped me a lot to understand a lot better. Thank you for that and for the quick response. I am only surprised that "method name + Result" seems to be the standard, that I hear the first time. Unfortunately, the web service I have to use is not mine and I can not change it. Maybe you could add an additional parameter to the library, which makes this behavior more variable.

Cussa commented 4 years ago

Your answer helped me a lot to understand a lot better. Thank you for that and for the quick response.

Happy to hear that!

I am only surprised that "method name + Result" seems to be the standard, that I hear the first time.

This is something that I saw basically everywhere, even that it's not a rule. If you create a web service in .net, for example, it will use this pattern automatically.

Unfortunately, the web service I have to use is not mine and I can not change it. Maybe you could add an additional parameter to the library, which makes this behavior more variable.

If you can not change it, the parameter to provide the name of the response will not work, as it still will have the same name two times. In that case, maybe the solution would be:

However, this still would require that you stop to use the XmlTypeAttribute in the class, to not have problems when doing the substitution/deserialization. Do you think that it would be a problem?

hemawst commented 4 years ago

Ok, I think I'm just getting a bit of a headache. I can not quite understand what exactly happened there. I catch the responsReader (string result = responseReader.ReadToEnd ()) and replace it with the (right) my one response and create with this the service.Result.SoapResponse XDocument.

<report.bundle.operationResponse>
  <DataArea>
    <MEDIAkkordlohnWS>
      <OutData>0</OutData>
      <InfoMessage>My message</InfoMessage>
    </MEDIAkkordlohnWS>
  </DataArea>
</report.bundle.operationResponse>

Then in the method "ExctracResults" an XmlNamespaceManager is created with the dummy prefix "guisgjkjtlcenzpabjfm" and the method name is added a "Result" (in my case a "Response").

Then the service.Result.SoapResponse is searched for the element "//guisgjkjtlcenzpabjfm:report.bundle.operationResponse". XElement webMethodResult = service.Result.SoapResponse.XPathSelectElement (string.Format (StringConstants.XmlResultXPathSelectorFormat, methodNameResult), namespMan); And that always gives me back null! How can the element "guisgjkjtlcenzpabjfm" be found in service.Result.SoapResponse? It does not exist. I have a problem with understanding, I do not get it.

Cussa commented 4 years ago

The point is that the response is not exactly what the library is expecting.

In the WebHelper, line 17, we are creating a "mapper" for the guisgjkjtlcenzpabjfm to the service name that you have. So, we kind of map this line from the response: <report.bundle.operationResponse xmlns="http://www.infor.com/businessinterface/MEDIAkkordlohnWS">

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <report.bundle.operationResponse xmlns="http://www.infor.com/businessinterface/MEDIAkkordlohnWS">
         <report.bundle.operationResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="">
            <DataArea>
               <MEDIAkkordlohnWS>
                  <OutData>0</OutData>
                  <InfoMessage>Noch 1 Packen zum AG offen</InfoMessage>
               </MEDIAkkordlohnWS>
            </DataArea>
         </report.bundle.operationResponse>
      </report.bundle.operationResponse>
   </S:Body>
</S:Envelope>

After the mapping, we try to find the "response object" inside this mapper. Problem is that we have the report.bundle.operationResponse two times. This makes the XPathSelectElement find the first one, which will cause subsequent problems.

"Right" way to fix the problem would be to "replace" the second report.bundle.operationResponse with the "expected" name. If that happens (plus removing the XmlTypeAttribute from the class), the library will work as expected.