Announcement

Collapse
No announcement yet.

WCF-Dienst kann nicht von Client benutzt werden (CommunicationException)

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • WCF-Dienst kann nicht von Client benutzt werden (CommunicationException)

    Ich habe einen WCF-Dienst geschrieben und möchte diesen mit einer einfachen WinForm-Anwendung testen. Ich habe es mit dem in VS 2008 integrierten WCFTextClient probiert, aber dieser unterstützt scheinbar meine Methoden nicht.

    Ich erhalte die folgende Fehlermeldung:
    "System.ServiceModel.CommunicationException: Die zugrunde liegende Verbindung wurde geschlossen: Die Verbindung wurde unerwartet getrennt.. ---> System.Net.WebException: Die zugrunde liegende Verbindung wurde geschlossen: Die Verbindung wurde unerwartet getrennt.. bei System.Net.HttpWebRequest.GetResponse()"

    Ich habe nach dieser Fehlermeldung gegooglet aber nichts Hilfreiches gefunden. Ich bin neu im Bereich WCF-Server, daher kann ich Anfängerfehler nicht ausschließen.

    Ich habe nach dem ABC-Prinzip eine DataContract-Schnittstelle "ITour4UDBDienst" erstellt, welche zwei OperationContract-Methoden beinhaltet. Mein Dienst implementiert diese Schnittstelle.
    Der Dienst nimmt komplexe Datentypen entgegen oder gibt diese zurück.

    Meine Datenklassen implementieren alle eine simple Schnittstelle. Alle Datenklassen (z. B. clsKunde & clsMitarbeiter) wurden als DataContract gekennzeichnet und ihre Properties als DataMember.

    Ich habe die Tutorials im Netz zu WCF-Services gesehen und verstehe nun nicht, warum meine Klassen nicht transportiert werden können, da die Connection scheinbar nicht zu stande kommt.

    Hier die Vertragsschnittstelle mit den Basisklassen und deren Schnittstelle:
    [highlight=c#]
    [ServiceContract]
    public interface ITour4UDBDienst
    {
    [OperationContract]
    bool SpeichereObjekt(ITour4UObjekt Objekt);

    [OperationContract]
    ITour4UObjekt LadeObjekt<T>(int ID);
    }


    public interface ITour4UObjekt
    {
    [DataMember(IsRequired=true)]
    int ID
    {
    get;
    set;
    }
    }

    [DataContract(Name = "Person")]
    public abstract class clsPerson : ITour4UObjekt
    {
    private string nachname = default(string);
    private string vorname = default(string);
    private int id;

    [DataMember]
    public string Vorname
    {
    get { return vorname; }
    set { vorname = value; }
    }

    [DataMember]
    public string Nachname
    {
    get { return nachname; }
    set { nachname = value; }
    }

    [DataMember(IsRequired=true)]
    public int ID
    {
    get { return id;}
    set { id = value; }
    }
    }

    [DataContract(Name="Kunde")]
    public class clsKunde : clsPerson
    {
    }

    [DataContract(Name="Mitarbeiter")]
    public class clsMitarbeiter : clsPerson
    {
    private string rolle;
    [DataMember]
    public string Rolle
    {
    get { return rolle; }
    set { rolle = value; }
    }
    }
    [/highlight]

    Mein Dienst implementiert die Vertrags-Schnittstelle und sieht wie folgt aus:
    [highlight=c#]public class Tour4UDBDienst : ITour4UDBDienst
    {
    public ITour4UObjekt LadeObjekt<T>(int ID)
    {
    return clsDBService.LadeAusDB<T>(ID);
    }
    public bool SpeichereObjekt(ITour4UObjekt MeinObjekt)
    {
    return clsDBService.Speichern(Objekt);
    }
    }[/highlight]

    Ich habe den WCF-Dienst mit VS 2008 publiziert und in meinen lokalen IIS 5 als virtuelles Verzeichnis gehängt. Wenn ich die URL im Browser aufrufe, wird mir angezeigt, dass es einen funktionierenden Dienst gibt.

    In einer simplen WinForm-Anwendung habe ich einen Dienstverweis auf den WCF-Dienst erstellt und diesen "Tour4UDBDienstRef" genannt. Der Code im Client um die Methoden zu testen sieht wie folgt aus:

    [highlight=c#]private void testeDienst()
    {
    Tour4UDBDienstClient client = new Tour4UClient.Tour4UDBDienstRef.Tour4UDBDienstClien t();
    clsKunde kunde = (clsKunde)client.LadeObjekt(1);
    MessageBox.Show(kunde.Nachname);
    client.Close();
    }[/highlight]

    Da sowohl Dienst als auch Client bei mir lokal laufen, schließe ich Netzwerkprobleme aus. Auch ein Deaktivieren der FW ändert nichts.

    Was mache ich falsch? Warum bricht die Verbindung mit der genannten Meldung ab?
    Zuletzt editiert von TheoFontane; 18.09.2009, 14:02.

  • #2
    Hallo,

    mit IIS-Hosting kenne ich mich nicht aus - nur mit Self-Hosting. Probier mal dieses Beispiel aus bzw. passe das so an dass der ServiceHost darin deinen Service hostet (d.h. Typen austauschen).

    Wenn dies funktioniert wissen wir dass der Service klappt und das Problem beim IIS-Hosting liegt (was ich vermute).


    mfG Gü
    "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

    Comment


    • #3
      Hallo Gü,

      vielen Dank für dein Beispiel - jetzt habe ich erneut das Gefühl, mein Entwicklerleben verschlafen zu haben...

      Sag mal - muss man die Umsetzung wirklich über Callbacks machen? Ich habe in der vergangenen Stunde die Struktur und das Vorgehen analysiert, doch ich schaffe es noch nicht, meine Struktur sinnvoll in deine einzufügen.

      Client, Host und Dienst sind soweit klar. Was aber mache ich exakt mit der Callback-Schnittstelle und was in der "normalen" Schnittstelle? Und wo definiere ich meine Basisklassen sowie deren Schnittstelle? Leider enthält das Beispielprojekt keinen komplexen Datentyp der übergeben oder zurück gegeben wird.

      Wenn ich es richtig verstanden habe, muss in der Callback-Schnittstelle definiert werden, welche Methoden der Client besitzt um die Serverantwort zu verarbeiten.
      Da ich vorhabe, den WCF-Dienst später in einen Workflow an eine SendActivity-Aktivität zu binden, weiß ich nicht, ob ich da gerade noch den richtigen Weg mit diesem Duplex-Callback-Verfahren einschlage oder ob das nicht (in diesem Fall) zuviel des Guten ist.

      Nichtsdestotrotz werde ich jetzt weiterhin versuchen, dein Beispielprojekt mit meinen Methoden zu "infiltrieren" - danach poste ich den Code - bestimmt kommen wir so schneller zu einer Lösung, die etwas einfacher ist.

      Auf jeden Fall danke ich dir schon einmal für dieses funktionierende Beispiel.

      Comment


      • #4
        Hallo,

        das mit dem Callback ist nicht nötig. Es war nur das Beispiel das ich schon mal gepostet hatte und aus Zeitnot nicht genau auf dein Problem eingehen konnte.

        Ich werde aber in Kürze dein Problem anschauen und dafür ein passendes Beispiel posten.

        Callback ist nur für Mediator-Szenarien (einfaches Beispiel dazu: Chat) sinnvoll.

        Bis bald


        mfG Gü
        "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

        Comment


        • #5
          Hallo,

          jetzt habe ich meine gesamte Struktur in das Callback-Szenario-Projekt implementiert.

          Ich nehme an, dass ich mittlerweile 80% von dem, was dort passiert, verstanden habe.

          Drei Probleme stechen hervor:
          1.) Ich kann im Clientprojekt der Projektmappe keinen Dienstverweis erstellen. Weder die automatische Suche in der Projektmappe noch das manuelle Eingeben der Dienstadresse führt dazu, dass der Dienst in dem VS-Assistenten gefunden wird.

          2.) Ich habe also einen eigenen WinForm-Client geschrieben. Dieser findet den Dienst mit der Dienstadresse aus der App.config. Der Dienstclient macht jedoch aus meinen komplexen Datentypen "object" - das ist nicht schön.

          3.) Wenn ich den WinForm-Client starte, so erhalte ich die Fehlermeldung, dass der Port 80 bereits belegt sei. ("HTTP konnte den URL http://+:80/Temporary... nicht registrieren, weil der TCP-Port 80 von einer anderen Anwendung verwendet wird.")

          Im Anhang ist der um meine Klassen und Methoden erweiterte Code. Ich habe die alten Klassen so belassen, um einen Vergleich zu ermöglichen.

          Hast du eine Idee, was ich falsch gemacht habe? Oder ist es so gedacht, dass komplexe Typen nach dem Transport über WCF zu object-Instanzen werden? Das wäre sehr traurig...
          Attached Files

          Comment


          • #6
            Nach einigen Debug-Sessions hatte sich heraus gestellt, dass im WCF-Dienst ein Fehler in einer Methode war. Statt einer ordentlichen Fehlermeldung erhielt ich deshalb nur die "Information", dass ein Timeout aufgetreten sei.

            Mittlerweile habe ich aber ein anderes Problem (was auch sonst):

            Die im WCF-Server veröffentlichten Methoden kommen bei meinem Client mit einer falschen Signatur an.

            Aus
            "bool SpeichereObjekt(ITour4UObjekt Objekt);"
            wird
            "bool SpeichereObjekt(object Objekt)".

            Und aus
            "ITour4UObjekt LadeObjekt<T>(int ID);"
            wird das völlig unbrauchbare
            "object LadeObjekt(int ID)"

            Ich habe mittlerweile auch Dummy-Methoden veröffentlicht, die simple Typen erwarten und die eigentlichen Methoden (SpeichereObjekt und LadeObjekt) aufrufen - nur um zu testen, ob diese funktionieren.

            Die Dummy-Methoden (und damit auch die "richtigen" Methoden) geben das Erwartete zurück. Die Kommunikation mit dem Client generell (trotz Hosting über IIS) scheint also zu funktionieren.

            Rufe ich die Methode "LadeObjekt" auf, so erhalte ich eine bombastische Fehlermeldung. Hier der Anfang:
            "System.ServiceModel.CommunicationException: Fehler beim Deserialisieren von Parameter http://tempuri.org/:Objekt. Die InnerException-Nachricht was "Der Typ "Tour4UDBWCF.clsKunde" mit dem Dienstvertragsnamen "Kunde:http://schemas.datacontract.org/2004/07/Tour4UDBWCF" wurde nicht erwartet. Fügen Sie alle nicht bekannten Typen der Liste der bekannten Typen hinzu. Verwenden Sie dazu z. B. das Attribut "KnownTypeAttribute", oder fügen Sie die Typen der an DataContractSerializer übergebenen Liste von bekannten Typen hinzu.""

            Ich habe versucht, meiner Klasse clsKunde sowie deren Oberklasse das Serializeable-Attribut zu geben, doch das hat die Meldung nicht geändert. Das KnownTypeAttribute habe ich auch zu den beiden Klassen hinzu gefügt:

            [KnownType(typeof(clsKunde))]
            public class clsKunde : ITour4UObjekt (...)

            Aber entweder habe ich das falsch angewandt oder es bringt generell keine Änderung.

            Im Internet finde ich zu diesem Fehler nur Postings bei denen jemand zu große Daten versandt hat.

            Bitte, weiß jemand, was ich falsch gemacht haben könnte?

            Comment


            • #7
              Hallo,

              hab mir das mal genau angeschaut. Folgendes ist mir aufgefallen:

              Die Datenübertragung bei WCF basiert auf (XML)-Serialisierung. Sollen abgeleitete Typen serialisiert werden so muss dies dem Serialisierer bekannt gemacht werden. Dies geschieht über das KnownType-Attribut.
              [highlight=c#]
              [DataContract(Name="Person")]
              [KnownType(typeof(Kunde))]
              [KnownType(typeof(Mitarbeiter))]
              public abstract class Person : ITour4UObject
              {
              [DataMember]public string Vorname { get; set; }
              [DataMember]public string Nachname { get; set; }
              [DataMember(IsRequired = true)]public int ID { get; set; }
              }
              [/highlight]

              Ein weiteres Problem ist dass bei den abgeleiteten Typen beim Hinzufügen eines Dienstverweisen (VS-Assistent oder svcutil.exe - dies ruft VS auf) der Typ System.Object anstatt des angegeben Typs im ServiceContract verwendet wird. Wird zB im ServiceContract eine Methode SpeicherKunde(Kunde kunde) angegeben so wird der korrekte Typ verwendet. Durch Angabe der Basisklasse funktioniert das nicht mehr.

              Dies ist deshalb begründet weil der Serialisierer (der DataContract-Serialisierer) nicht weiß wie er ein Objekt in eine Schnittstelle wandeln soll - irgendwie klar. Für "normale" Basisklassen kann dem Service das ServiceKnownType-Attribut hinzugefügt werden. Dies hilft jedoch bei abstrakten Basisklasse oder Schnittstellen auch nicht. Weiters ist dies dadurch begründet dass die Serialisierung plattforumunabhängig sein soll. Der Client kann also JAVA, irgendein HTTP, oder sonst was sein.

              Bei der DeSerialisierung ist es generell so dass keine Schnittstellen verwendet werden können als Rückgabetyp - wie auch, es wird keine Typinformation gespeichert.

              Der gesamt ServiceContract sieht dann so aus:
              [highlight=c#]
              using System.Runtime.Serialization;
              using System.ServiceModel;

              namespace gfoidl.Test.WCF.Contract
              {
              [ServiceContract]
              [ServiceKnownType(typeof(Person))]
              [ServiceKnownType(typeof(Kunde))]
              [ServiceKnownType(typeof(Mitarbeiter))]
              public interface ITour4UDbDienst
              {
              [OperationContract]
              bool SpeichereObject(ITour4UObject tour4UObject);

              [OperationContract]
              ITour4UObject LadeObjekt(int ID, KlassenArten klassenArt);

              [OperationContract]
              bool SpeichereKunde(Kunde kunde);

              [OperationContract]
              Kunde LadeKunde(int ID);
              }
              //-------------------------------------------------------------------------
              [DataContract]
              public enum KlassenArten
              {
              [EnumMember]Kunde,
              [EnumMember]Mitarbeiter
              }
              //-------------------------------------------------------------------------
              [ServiceKnownType(typeof(Person))]
              [ServiceKnownType(typeof(Kunde))]
              [ServiceKnownType(typeof(Mitarbeiter))]
              public interface ITour4UObject
              {
              [DataMember(IsRequired=true)]
              int ID { get; set; }
              }
              //-------------------------------------------------------------------------
              [DataContract]
              [KnownType(typeof(Kunde))]
              [KnownType(typeof(Mitarbeiter))]
              public abstract class Person : ITour4UObject
              {
              [DataMember]public string Vorname { get; set; }
              [DataMember]public string Nachname { get; set; }
              [DataMember(IsRequired = true)]public int ID { get; set; }
              }
              //-------------------------------------------------------------------------
              [DataContract]
              public class Kunde : Person { }
              //-------------------------------------------------------------------------
              [DataContract]
              public class Mitarbeiter : Person
              {
              [DataMember]public string Rolle { get; set; }
              }
              }
              [/highlight]

              Hab mein Testprojekt angehängt.

              mfG Gü
              Attached Files
              "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

              Comment


              • #8
                Du bist die Rettung in der Not... ich leide schon unter einem WCF-Wahn...

                Ich habe deine Antwort verstanden. Demzufolge wäre es also bei WCF und anderen Diensten generell besser, Datenklassen unter einer gemeinsamen Oberklasse als unter einer Schnittstelle zu vereinen, richtig?

                Dann werde ich dem Graus jetzt ein Ende setzen und meine abstrakten Klassen von einer Oberklasse clsTour4UOberklasse o.ä. erben lassen. Damit sollte eine halbwegs typsichere Rückgabe möglich sein, oder?

                Ich kann dir gar nicht sagen, wie dankbar ich dir für deine Unterstützung und deine wertvolle Zeit bin - ich wünschte ich könnte dir auch irgendwie "unter die Arme greifen", aber das wird wohl noch ein Weilchen dauern...

                Comment


                • #9
                  einer gemeinsamen Oberklasse als unter einer Schnittstelle zu vereinen, richtig?
                  So ist es. Dann mit KnowType und ServiceKnownType deklarieren und es funktioniert so wie es soll.


                  mfG Gü
                  "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                  Comment


                  • #10
                    Einen Trick musst du mir bitte noch verraten: Wie bekommst du die Dienstverweise in das Client-Projekt, wenn du den Service über einen Host hostest?

                    Benutzt du den Assistenten oder hackst du das selber in die Config? Bei mir findet er den Dienst weder über die lokale Suche in der Projektmappe, noch wenn ich den svc-Pfad angebe.

                    Vielleicht habe ich auch das Umstellen des Namespaces versemmelt. Der restliche Code (also alles bis auf den Client) wird problemlos kompiliert (im Anhang) und ich kann den Dienst tatsächlich von einem anderen Client aus verwenden.

                    Verrätst du mir deinen Trick? *gg*
                    Attached Files
                    Zuletzt editiert von TheoFontane; 18.09.2009, 16:43.

                    Comment


                    • #11
                      Einen Trick musst du mir bitte noch verraten: Wie bekommst du die Dienstverweise in das Client-Projekt, wenn du den Service über einen Host hostest?
                      Durch die Konfigurationsdatei des ServiceHost -> MetadataExchange (MEX).

                      Für die Konfiguration gibt es auch ein Tool: Configuration Editor Tool (ist kein separater Download, sondern beim SDK dabei).

                      Benutzt du den Assistenten oder hackst du das selber in die Config?
                      Anfangs hab ich zu Lernzwecken die Konfig auf Clientseite selber getippt. Jetzt mach ich es über 'Dienstverweis hinzufügen'. Dabei gebe ich die BaseAdress in das Textfeld ein (geht übrigens auch im Browser).

                      Probier das mal, wenn es nicht geht schau ich mir den Anhang an (Zeitmangel).


                      mfG Gü
                      "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                      Comment


                      • #12
                        Hallo Gü,

                        danke für den Hinweis. Ich habe das Tool zwar ausprobiert, aber mehr als eine App.config ist dabei nicht entstanden. Wie erstelle ich den benötigten Dienstverweis, nachdem ich mit dem Config-Tool eine Anwendungskonfigurationsdatei erzeugt habe?


                        Bisher bin ich wie folgt vorgegangen:
                        1.) Konfig-Editor öffnen.
                        2.) Konfiguration des Service-Hosts öffnen.
                        3.) App.Config im Client-Projekt speichern.
                        4.) Im Config-Editor neuen Client hinzufügen und App.Config im Client-Projekt als Quelle auswählen.
                        5.) Neue Bindung (MEX) erstellen und dem Client zuweisen.
                        6.) Neues Endpunktverhalten mit webHttp erzeugen und dem Dienst hinzu fügen.
                        7.) App.Config im Client-Projekt speichern.
                        --
                        8.) Im Client-Projekt auf "Dienstverweis hinzufügen" klicken.
                        9.) Versuch, den Dienst über "Ermitteln" und Eingabe der Service-Adresse zu finden, damit ich im Client-Code eine Dienstclientinstanz erzeugen kann. OHNE ERFOLG.

                        Die Hilfe des Tools sowie die MSDN-Webseite schweigt sich diesbezüglich aus. Im Netz finde ich dazu nur Inhaltsverzeichnisse von WCF-Büchern.

                        Wenn es dazu eine kostenlose Anleitung gibt, arbeite ich mich gerne in diese ein, aber bisher bin ich nicht fündig geworden. Das Projekt befindet sich im Anhang.

                        Kannst du mir hier bitte weiter helfen? Habe ich bisher die richtigen Schritte zur Erstellung der Config getan?
                        Attached Files

                        Comment


                        • #13
                          Ahoi,

                          Ich habe das Tool zwar ausprobiert, aber mehr als eine App.config ist dabei nicht entstanden.
                          Mehr kann auch nicht entstehen Ist ja ein Konfiguraitons-Editor und kein Dienstverweis-Tool

                          Wie erstelle ich den benötigten Dienstverweis, nachdem ich mit dem Config-Tool eine Anwendungskonfigurationsdatei erzeugt habe?
                          Selbst erstellen (ob mit oder ohne dem Konfig-Editor) brauchst du nur die app.config des ServiceHosts.

                          Nachdem diese und die paar Zeilen Code des ServiceHost erstellt wurden starte den ServiceHost damit auch der Service startet.

                          Gehe zum Client-Projekt und dort auf Dienstverweis hinzufügen. Als Adresse verwende diejenige welche in der app.config des ServiceHost als BaseAdress eingetragen wurde. Das wars schon.

                          Die Konfiguration auf Client-Seite brauch i.d.R. nicht geändert werden. Die von VS (eigentlich vom svcutil-Tool) erstellte app.config des Clients passt meistens (außer Timeouts, maxQuotas).

                          D.h. heißt auch dass die config des ServerHosts nichts mit der des Clients zu tun hat.

                          Für dein angehängtest Projekt:
                          1. Starte den ServiceHost
                          2. Lösche die app.config des Client
                          3. gehe auf Dienstverweis hinzufügen im Client
                          4. gibt die BaseAdresse des Service ein
                          5. OK
                          6. fertig


                          mfG Gü
                          "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                          Comment

                          Working...
                          X