piątek, 11 października 2013

.NET Client consuming PHP ZendFramework Web Service - solution no.1 (WPF)

 Dostałem za zadanie zintegrowanie się z kilkoma web servisami. Naturalnym rozwiązaniem było wybranie do tego celu technologii WPF, szczególnie, że korzystanie z jego poprzednika zwanego potocznie "(SOAP) Web Services" jest niezalecane przez firmę Microsoft od 2009r:
 W książce "Professional-ASP-NET-4-5-in-C-and-VB.productCd-1118311825.html" jest napisane:

In 2009, ASMX Web Services were marked as legacy. Therefore, the code found within will notbe updated with future ASP.NET releases (the exception would be a security update).

 O ile zintegrowanie się z serwisami wystawionymi przez inne WCF poszło gładko, to pewne problemy napotkałem podczas integracji z web serwisem napisanym w PHP przy pomocy Zend Framework. Początkowo chciałem wykonać standardową operację połączenia, która powinna wyglądać tak samo, jak w przypadku korzystania z serwisu napisanego w WCF (link do artykułu, w którym programista łączy się z php za pomocą wcf). Niestety, w moim przypadku, WCF wykrył usługę, ale wyrzucił błąd:

There was an error downloading 'http://xxx.yyy.zzz.pl/Swdsoapserver/query?wsdl/_vti_bin/ListData.svc/$metadata'.
The request failed with the error message:
--
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>Sender</faultcode><faultstring>Invalid XML</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>

--.
Metadata contains a reference that cannot be resolved: 'http://xxx.yyy.zzz.pl/Swdsoapserver/query?wsdl'.
The content type text/html; charset=UTF-8 of the response message does not match the content type of the binding (application/soap+xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly. The first 1024 bytes of the response were: '<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://xxx.yyy.zzz.pl/Swdsoapserver/query" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" name="SwdQuerySoap"
    targetNamespace="http://xxx.yyy.zzz.pl/Swdsoapserver/query">
    <wsdl:types>
        <xsd:schema 
 Jak zwykle w takich przypadkach nieodzowne okazało się forum stackoverflow, gdzie zadałem pytanie: "consuming-php-web-service-by-wcf-client".
 Dzięki otrzymanej tam pomocy, zapisałem plik .wsdl na dysku i za jego pomocą utworzyłem klasy proxy (Add Service Reference, tylko zamiast linku, wskazujemy lokalizację pliku na dysku).

Tak wygenerowane klasy proxy potrafiły się połączyć i wykonać zapytanie, ale miały problemy z odczytaniem odpowiedzi. Błędy były dwa:

a) Wielkość przesyłanej odpowiedzi była większa, niż długość standardowej odpowiedzi
"Maksymalny przydział rozmiaru wiadomości dla wiadomości przychodzących (65536) został przekroczony. Aby zwiększyć przydział, użyj właściwości MaxReceivedMessageSize we właściwym elemencie wiązania."
Obejście tego problemu jest stosunkowo proste i polega na modyfikacji ustawień powiązania (ang. binding) wewnątrz pliku web.config/app.config naszej aplikacji:

        <binding name="Logic_DzSoapServer_QueryKoBinding" maxBufferSize="2147483647"
          maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
        </binding>
b) Problem mappera z parsowaniem odpowiedzi wysyłanej przez PHP na obiekty .NET
"Nie można przypisać obiektu typu System.Int32 do obiektu typu System.Int32[]."
Sposobem aby to obejść okazała się ręczna modyfikacja wygenerowanych klas proxy, aby zamiast spodziewanego typu odpowiedzi, odpowiadały typem 'object':

        public object getParcel(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode) {
            return base.Channel.getParcel(parameters, hashCode);
        }

        public object getParcelAsync(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode)
        {
            return base.Channel.getParcelAsync(parameters, hashCode);
        }

     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ServiceModel.ServiceContractAttribute(Namespace="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko", ConfigurationName="ko.Logic_DzSoapServer_QueryKoPort")]
    public interface Logic_DzSoapServer_QueryKoPort {
      
        [System.ServiceModel.OperationContractAttribute(Action="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko#getParcel", ReplyAction="*")]
    [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)]
        [return: System.ServiceModel.MessageParameterAttribute(Name="return")]
        object getParcel(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode);
      
        [System.ServiceModel.OperationContractAttribute(Action="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko#getParcel", ReplyAction="*")]
        [return: System.ServiceModel.MessageParameterAttribute(Name="return")]
        object getParcelAsync(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode);
    }

 Co spowodowało, że wywołanie odpowiedzi było stosunkowo proste:

            List<MigDzKo> migDzKoList = new List<MigDzKo>();
            try
            {
                ko.Logic_DzSoapServer_QueryKoPortClient client = new ko.Logic_DzSoapServer_QueryKoPortClient();
                ko.Logic_DzSoapServer_Request_BodyKo request = new ko.Logic_DzSoapServer_Request_BodyKo();
                request.sy = "12345678";
                client.Open();
                var response = client.getParcel(request, Constraints.MIGDZ.MigDzHashBase64);
                XmlNode[] xmlNodes = (XmlNode[])response;
                for (int i = 0; i < xmlNodes.Length; i++)
                {
                    try
                    {
                        string organization = string.Empty;
                        if (xmlNodes[i].ChildNodes[0].HasChildNodes)
                        {
                            MigDzKo migDzKo = new MigDzKo();
                            migDzKo.operation_id = Convert.ToInt32(xmlNodes[i].ChildNodes[0]["operation_id"].InnerXml);
                            migDzKo.hashpublic = xmlNodes[i].ChildNodes[0]["hashpublic"].InnerXml;
                            migDzKoList.Add(migDzKo);
                        }
                    }
                    catch (Exception ex)
                    {
                        log.Error(ex.Message.ToString());
                    }
                }
                client.Close();
            }
            catch (Exception ex)
            {
                log.Error(ex.Message.ToString());
            }
            return migDzKoList;


Dla spójności podaję też zawartość klasy MigDzKo

    public class MigDzKo
    {
        public int operation_id { get; set; }
        public string hashpublic { get; set; }
    }

Brak komentarzy:

Prześlij komentarz