Dienstag, 18. März 2014

Implementierung eines asynchronen WcfProxy

Vor einiger Zeit zeigte mir ein Kollege eine interessante Implementierung für einen Wcf Client Proxy, die er im Buch Professionell entwickeln mit Visual C# 2012: Das Praxisbuch. von Matthias Geirhos gefunden hat. Ziel war es das Verbindungsmanagement und ExceptionHandling an einer zentralen Stelle (einer statischen Klasse) abzuwickeln.

Leider war die Implementierung speziell für die synchronen Methodenaufrufe über den WebService gedacht, aus diesem Grund habe ich eine TAP-variante implementiert, die  mit async und await arbeitet:
Bitte bedenkt, dass dieser Code nur als Ansatz zu verstehen ist, hier fehlt noch einiges (Exception Handling, Logging, Kann man wirklich con.Close() aufrufen (?), retry connect,...)

    public static class WcfProxy
    {
        public static async Task CallAsync<T>(Action<T> webServiceMethod) where T : ICommunicationObject, new()
        {
            T con = new T();
            try
            {
                // open the connection
                await Task.Factory.FromAsync(con.BeginOpen, con.EndOpen, null);

                // call the method
                await Task.Factory.FromAsync<T>(webServiceMethod.BeginInvoke, webServiceMethod.EndInvoke, con, null);
            }
            catch (CommunicationException comEx)
            {
                // .. do something .. //
                System.Diagnostics.Debug.WriteLine(comEx.ToString());
            }
            finally
            {
                con.Close();
            }
        }

        public static async Task<TResult> CallAsync<T, TResult>(Func<T, TResult> webServiceFunction) where T : ICommunicationObject, new() where TResult : class
        {
            T con = new T();
            TResult res = null;
            try
            {
                // open the connection
                await Task.Factory.FromAsync(con.BeginOpen, con.EndOpen, null);

                // call the method
                res = await Task.Factory.FromAsync<T, TResult>(webServiceFunction.BeginInvoke, webServiceFunction.EndInvoke, con, null);
            }
            catch (CommunicationException comEx)
            {
                // .. do something .. //
                System.Diagnostics.Debug.WriteLine(comEx.ToString());
            }
            finally
            {
                con.Close();
            }
            return res;
        }

    }

Montag, 17. März 2014

Async Methoden im WCF Stub in Portable Class Library (PCL)

Vorbetrachtung

Wenn man mit dem Tool svcutil.exe einen Webservice Stub erstellt, gibt es (wenn man es genau nimmt) 3 verschiedene Arten, wie eine Methode erstellt werden kann:
  1. als synchroner Auftruf, da entspricht der Methodenname dem Namen im WCF-ServiceContract
  2. als asynchroner Aufruf im EAP-Pattern (Event-based Asynchronous Pattern)
    Hierbei gibt es zum einen eine Methode mit dem Suffix -Async, und ein event mit dem Methodennamen und dem Suffix -Completed, außerdem gibt es für jedes Ergebnis einen eigenen EventArgs-Typ 
  3. als asynchroner Aufruf im TAP-Pattern (Task-based Asynchronous Pattern)
    Hier wird auch der Methode ein -Async angehängt, jedoch ist das Ergebnis ein Task mit einem Resulttyp, der dem des Webservice-Result entspricht. Ist die Methode void, gibt es natürlich auch keinen Resulttyp. Der Vorteil dieser Methode ist, dass man kein umständliches Event-Housekeeping machen muss, sondern die Methode fast wie eine synchrone Methode aufrufen kann, ohne dass die Anwendung einfriert

Liest man sich die Beschreibungen durch, erkennt man sicher meine Vorliebe für die TAP-Variante. Diese Variante wird auch ab dem Zielframework .NET 4.5 standardmäßig anstelle von EAP erzeugt. Das ist auch gut so, aber an der Überschrift erkennt man, dass es wohl nicht immer so ist, denn in einer PCL-Bibliothek, die verschiedene Zielplattformen haben kann, wird automatisch Variante 2 erzeugt, was sehr unschön ist, denn für EAP muss man nicht nur unmengen mehr Code schreiben, sondern bei mehreren parallelen Aufrufen auch noch Housekeeping mit State-Objekten

Wenn svcutil.exe uns hier nicht helfen will, müssen wir uns eben selbst helfen:
Nehmen wir folgende WebService:
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Person GetNextInterviewPartner();
      
        [OperationContract]
        Person GetNextInterviewPartnerGeneric(string typeFullName);

        [OperationContract]
        void AddPersonToList(Person person);
    }

Unabhängig von der Implementierung, oder was der Service eigentlich macht, erhalten wir folgenden Stub (gekürzt):
 public partial class Service1Client {
        
        public void GetNextInterviewPartnerAsync()
        {...}
        
        public System.IAsyncResult BeginGetNextInterviewPartner(System.AsyncCallback callback, object asyncState)
        {...}
        
        public object EndGetNextInterviewPartner(System.IAsyncResult result)
        {...}
        
        public void GetNextInterviewPartnerGenericAsync(string typeFullName)
        {...}
        
        public System.IAsyncResult BeginGetNextInterviewPartnerGeneric(string typeFullName, System.AsyncCallback callback, object asyncState)
        {...}
        
        public object EndGetNextInterviewPartnerGeneric(System.IAsyncResult result)
        {...}
        
        public void AddPersonToListAsync(object person)
        {...}
        
        public System.IAsyncResult BeginAddPersonToList(object person, System.AsyncCallback callback, object asyncState)
        {...}
        
        public void EndAddPersonToList(System.IAsyncResult result)
        {...}

    //[...]
    }

Ich habe den Stub stark gekürzt, und nur die fürs Weitere wichtigen Methoden herausgestellt. Die Events, die bei der standardmäßigen Implementierung notwendig sind, werden nicht benötigt. Auf Basis dieses Stubs kann man einen Proxy implementieren, der auf das TAP-Pattern aufbaut. Dabei hilft die Funktion Task.
  public class Service1ClientTAP : Service1Client
    {
        public new Task<object> GetNextInterviewPartnerAsync()
        {
            return Task.Factory.FromAsync<object>(BeginGetNextInterviewPartner, EndGetNextInterviewPartner, TaskCreationOptions.PreferFairness);
        }

        public new Task<object> GetNextInterviewPartnerGenericAsync(string typeFullName)
        {
            return Task.Factory.FromAsync<string,object>(BeginGetNextInterviewPartnerGeneric, EndGetNextInterviewPartnerGeneric, typeFullName,  TaskCreationOptions.PreferFairness);
        }

        public new Task AddPersonToListAsync(object person)
        {
            return Task.Factory.FromAsync<object>(BeginAddPersonToList, EndAddPersonToList, person, TaskCreationOptions.PreferFairness);
        }
    }
Das Wichtige sind die beiden delegates Begin... und End..., diese werden in der Funktion Task.Factory.FromAsync als erstes übergeben, danach können bis zu 3 Übergabeparameter folgen. Danach werden die TaskCreationOptions übergeben. Normalerweise muss die Methode nicht explizit typisiert werden, wenn es sich bei den Parametern und Resultaten nicht unbedingt um object-Typen handelt. Hier nochmal zusammengefasst, wie die einzelnen Funktionssignaturen überführt werden:
1. TResult TestFunktion1(T1 parameter1, T2 parameter2, T3 parameter3)
2. TResult TestFunktion2()
3. void TestFunktion3(T1 parameter1, T2 parameter2, T3 parameter3)
// 1.
public Task<TResult> TestFunktion1Async(T1 parameter1, T2 parameter2, T3 parameter3)
{
   return Task.Factory.FromAsync<T1, T2, T3, TResult> (BeginTestFunktion1, EndTestFunktion2, parameter1, parameter2, parameter3, TaskCreationOptions.PreferFairness);
}

// 2.
public Task<TResult> TestFunktion2Async()
{
   return Task.Factory.FromAsync<TResult> (BeginTestFunktion1, EndTestFunktion2, TaskCreationOptions.PreferFairness);
}

// 3.
public Task TestFunktion3Async(T1 parameter1, T2 parameter2, T3 parameter3)
{
   return Task.Factory.FromAsync<T1, T2, T3> (BeginTestFunktion1, EndTestFunktion2, parameter1, parameter2, parameter3, TaskCreationOptions.PreferFairness);
}

Montag, 24. Februar 2014

Generische Methoden in WCF Webservice

Manchmal wünscht man sich von einem WCF Service, dass er die Eier legende Wollmilchsau ist, die er niemal sein kann. Daher für alle Leser, die hier eine Step by Step Anleitung für das erwarten, was im Titel steht, sage ich gleich: ES GEHT NICHT! Und ich sag das nich einfach so, sondern möchte hier kurz 3 Gründe nennen, die mir spontan einfallen, warum es nicht geht. Aber erst möchte ich kurz erörtern, wozu man generische Methoden verwenden könnte, mir ist spontan nichts konkretes eingefallen, daher habe ich in der Suchmaschine meines Vertrauens, folgende Methode gefunden:

public T Get<T> Max(List<T> listOfObjects) where T: IComparable<T>

Diese Funktion gibt ein Objekt vom Typ T zurück, welches das Größte aus der gegebenen Liste ist. Klingt erstmal ganz gut, aber wenn wir in Richtung einer WCF-Implementierung denken, kommen ziemlich schnell Fragen auf, die eine Implementierung so unmöglich machen.


  1. Wie serialisieren wir den Typparameter? Schließlich soll der Service auch aus anderen Betriebssystemen / Programmiersprachen bedient werden können als C#.
  2. Interfaces werden über WCF Webservices ignoriert (im Beispiel IComparable), da sie ja nicht serialisiert/deserialisiert werden können. In gegebenem Fall beschreibt das Interfaces nur Methoden, die implementiert sein müssen, hat also nichts mit den Daten zu tun.
  3. und wenn das noch nicht überzeugt hat: Im wsdl (bzw. xsd) für den Service müssen alle Datentypen beschrieben sein, die über den Transportweg als Xml gesendet werden können. Im Fall des Parametertyps T, wären das alle Typen des .NET-Frameworks. Wer sich schonmal die Definition eines normalen WCF-Services angesehen hat, wird zugeben, dass das vermutlich den Rahmen sprengt.

Überzeugt? Dann will ich Euch gern einen Weg zeigen, wie man in einem konkreten Beispiel einen auch über Webservices ähnlichen erfolg erzielen kann. 

Das Beispiel

Nehmen wir einen Webservice, der es einem Abteilungsleiter ermöglichen soll, seine Mitarbeitergespräche zu verwalten (inkl. Vorstellungsgespräche). Dazu gibt es eine Funktion für das Seketariat, welches einen Mitarbeiter/Bewerber in eine Liste aufnimmt. In einer generischen Methode würde das so aussehen:

void AddPersonToList<T>(T person) where T:IPerson;

Für den Abteilungsleiter soll der Service eine Funktion zur Verfügung stellen, welche es ihm ermöglicht einen bestimmten Typ aus der Liste abzufragen. So ist es möglich zum Beispiel, den nächsten Bewerber zu erhalten, oder eben den nächsten Angestellten, oder gar einen speziellen Angestellten, zum Beispiel einen Teamleiter:

T GetNextPerson<T>() where T:IPerson;

Wrapper-Funktionen im ServiceContract

Ein Webservice kann intern eine generische Funktion verwenden, jedoch nicht nach außen transportieren. Eine Möglichkeit wäre also für jeden Typ eine Funktion nach außen zur Verfügung zu stellen, die entsprechend intern diese generische Methode aufruft. Je nachdem, wie viele Typen es gibt, muss eine separate Funktion zur Verfügung gestellt werden:

// secretary functions
public void AddApplicant(Applicant person)
{
   AddPersonToList<Applicant>(person);
}
public void AddEmployee(Employee person) {..}
public void AddSapEmployee(SapEmployee person) {..}
public void AddNetDeveloper(NetDeveloper person) {..}
[..]

// manager functions
public Applicant GetNextApplicant()
{
   return GetNextPerson<Applicant>();
}

public Employee GetNextEmployee() {..}
public SapEmployee GetNextSapEmployee() {..}
public NetDeveloper GetNextNetDeveloper() {..}
[..]
Für den ersten Schuss ist die Idee ganz gut, aber mit Sicherheit nicht ideal, zumindest muss man dadurch keine Code-Dubletten schreiben, sondern kann die bestehende Implementierung verwenden.

Verwendung der Basistypen im OperationContract

Wie man schnell sieht, implementieren in unserem Fall alle Personen das Interface IPerson. Unsere erste generische Funktion können wir also unter Verwendung des Interface etwas generalisieren:

void AddPersonToList(IPerson person);

In diesem Fall entspricht der Funktionsaufruf syntaktisch sogar dem Original. Es gibt aber immer noch ein Problem, denn wir wissen nicht, welche Implementierungen von IPerson in Frage kommen. Damit kann der Webservice auch keine valide Schnittstellenbeschreibung für eine mögliche Clientimplementierung zur Verfügung stellen. Um genau zu sein, würde es so erst zur Laufzeit zu einem Fehler kommen. Ruft man zum Beispiel in der Clientanwendung AddPersonToList(new SapDeveloper()) auf, bekommt man folgenden Fehler:





Der Fehlerdetails an sich erschließen sich einem im ersten Moment nicht:
Zusätzliche Informationen: Fehler beim Deserialisieren von Parameter http://tempuri.org/:person. Die InnerException-Nachricht war "Der Typ 'Test.Model.SapDeveloper' mit dem Datenvertragsnamen 'SapDeveloper:http://schemas.datacontract.org/2004/07/Test.Model' wird nicht erwartet. Verwenden Sie ggf. einen DataContractResolver, oder fügen Sie alle unbekannten Typen statisch der Liste der bekannten Typen hinzu, beispielsweise mithilfe des KnownTypeAttribute-Attributs oder indem Sie sie zur Liste der bekannten Typen hinzufügen, die an DataContractSerializer übergeben wird.".  Weitere Details finden Sie unter "InnerException".


Der Client könnte theoretisch ein Objekt der Klasse SapDeveloper serialisieren, dies würde sogar der Schnittstellenbeschreibung entsprechen, denn der Soap-Service definiert den Übergabeparameter als anyType


Jedoch gibt die Schnittstellenbeschreibung nicht wirklich vor, welche Implementierung verwendet werden soll, bzw. welche denn der Webservice verstehen würde. Auch wenn wir es also schaffen würden, die Serialisierung des Objekts der Klasse SapDeveloper auf Client Seite hinzubekommen, könnte der Server damit nicht viel anfangen. 

Der Akteur ist also immer derjenige, der die Schnittstelle vorgibt - also der Webservice. Aber die Fehlermeldung gibt noch mehr Anhaltspunkte, wie wir weiter machen können,es gibt ein KnownType-Attribut, welches man verwenden kann, um eine Liste möglicher Implementierungen anzugeben. Dieses Attribut wird verwendet, um einem DataContractSerializer zu sagen, welche Implementierungen dieser serialisieren bzw. deserialisieren "darf". Leider verwenden wir den DataContractSerializer nur implizit, denn das macht der WCF Service für uns. Daher gibt es das Attribut ServiceKnownType, welches man direkt am Service-Interface definieren kann, um mögliche Implementierungen Service-weit zu definieren. In unserem Fall sieht die fertige Servicedefinition so aus:


    [ServiceKnownType(typeof(Test.Model.Person))]
    [ServiceKnownType(typeof(Test.Model.Employee))]
    [ServiceKnownType(typeof(Test.Model.SAPEmployee))]
    //[ServiceKnownType("GetKnownTypes", typeof(Helper))]
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        IPerson GetNextInterviewPartner();

        
        // so geht das nicht
        //[OperationContract]
        //T GetNextInterviewPartnerGeneric() where T : IPerson;

        [OperationContract]
        IPerson GetNextInterviewPartner(string typeFullName);

        [OperationContract]
        void AddPersonToList(IPerson person);

     
    }
Wie man leicht sieht, gibt es zwei Varianten das Attribut zu verwenden, entweder man gibt die Typen direkt hintereinander an, oder einen Verweis auf eine Methode, die ein IEnumerable<Type> zurückgibt.

Eigentlich ist damit die Aufgabe gelöst, nur gibt es einen unschönen Nebeneffekt. Da es in der Schnittstellenbeschreibung keine Interfaces gibt, werden diese immer als anyType interpretiert, in einem C#-Stub ist dies dann der Typ object. Das führt dazu, dass jede Art von Typisierungsfehlern erst zur Laufzeit auffallen. Um das einzudämmen, sollte man stattdessen entweder mit einer Basisimplementierung als Parametertyp arbeiten oder eine abstrakte Klasse zur Schnittstellenbeschreibung verwenden.