Technologie pro výstavbu distribuovaných řešení na platformě .NET, Windows Communication Foundation (WFC) není žádnou novinkou, přichází s verzí .NET Framework 3.0. Novinkou není tato technologie ani na českém internetu, kde se s novými technologiemi setkáváme často s jistým zpožděním, viz http://www.vyvojar.cz/Series/2-uvod-do-wcf.aspx. Rád bych se připojil k lidem představující tuto technologii u nás  a věnoval se některým důležitým souvisejícím tématům, mezi něž nepochybně patří možnost zpětných volání (callbacks), událostí, eventů – chcete-li.

 

Synchronní / asynchronní volání

   Při klasickém objektově orientovaném programování má klient k dispozici pouze jeden způsob volání metody – klient zavolá metodu, do té doby zůstává blokován, a jakmile je metoda vykonána, může klient pokračovat ve vykonávání dalšího kódu. Tento způsob volání zpravidla nazýváme synchronním voláním metody. Tento model volání je defaultně podporován i v technologii WCF. Technologie WFC ovšem navíc nabízí tzv. One-Way způsob volání, kdy volající blokován nezůstává a smí pokračovat ve vykonávání kódu. Volání metody s tímto přístupem zpravidla nazýváme asynchronním, a právě na One-Way způsobu volání jsou nejčastěji zpětná (callbacks, events) volání ze služby zpět ke klientovi založena.

Pozn.: S One-Way voláními se setkáváme už u .NET Remoting. Později upřesním, kdy zavolání způsobem One Way skutečně asynchronní je a za jakých okolností být nemusí.

Callback Contract

   Události ve WCF jsou definovány pomocí callback rozhranní (česky rozhraní příjemce) s operacemi, které reprezentují jednotlivé události. Toto rozhraní příjemce není nic jiného než ServiceContract definovaný na straně služby, implementovaný bude ovšem na straně klienta.

Definice rozhraní příjemce může vypadat následovně:

public interface IOperationCallback

{

    [OperationContract( IsOneWay = true )]

    void OnNewMessage( string message );

}

   O tomto rozhraní implementovaném příjemcem uvědomíme kontrakt služby pomocí parametru CallbackContract (nutno podotknout, že kontrakt služby může v parametru specifikovat nanejvýše jeden CallbackContract):

[ServiceContract( CallbackContract = typeof( IOperationCallback ) )]

public interface IWCFService

{

    [OperationContract]

    void SubscribeMessages();

 

    [OperationContract]

    void SendMessage( string message );

}

Pozn.: Právě díky vazbě pomocí parametru CallbackContract již rozhraní příjemce neoznačujeme atributem ServiceContract, je jím automaticky.

Definice zmíněných dvou rozhraní představuje velmi zjednodušený příklad chatovací služby, kterou postupně v tomto článku implementuji.

One-Way operace detailněji

   Pravděpodobně je zřejmé, že One-Way operace se specifikuje parametrem IsOneWay=true atributu OperationContract. Je důležité vědět, že tato vlastnost je defaultně vypnuta. V takovém případě se jedná o volání požadavek – odpověď. One-Way operace je ovšem založena jen na požadavku – jde o jednosměrné volání.

Z faktu, že jde pouze o jednosměrné volání, zde budou zřejmě platit jistá omezení:

-        One-Way operace nemůže vracet žádnou hodnotu, v případě, že se o to pokusí, skončí pokus výjimkou InvalidOperationException.

-        Argumenty One-Way operace nemohou použít klíčová slova out nebo ref.

-        V případě, že vykonávání One-Way operace na straně klienta skončí výjimkou, nebo se vůbec nezrealizuje, volající se o tom nic nedozví.

   Slíbil jsem, že se vrátím k otázce asynchronnosti One-Way operací, One-Way totiž není synonymem pro asynchronní volání. Pravdou je, že když volající zavolá One-Way metodu/metody, nemusí být okamžitě zpracovány, ale mohou být umístěny do fronty, aby byly zpracovány postupně. Vše záleží od nastavení Concurency a Session a od počtu zpráv, které jsou umístěny do fronty, na nastavení kanálu. Může tedy nastat případ, že fronta požadavků je plná a v takové situaci se bohužel volající strana zablokuje do doby, než se ve frontě uvolní místo – bude vyřízen jeden z předchozích požadavků.

Vygenerovaná proxy třída

   Definice těchto dvou rozhraní i bez implementace je postačující, abychom mohli vygenerovat pomocí utility svcutil proxy třídu, kterou bude vyžadovat klient. Pro informaci je můj post-build-step projektu služby následovný:

svcutil.exe $(TargetFileName)

svcutil.exe *.wsdl *.xsd /language:C# /out:$(ProjectName)Client.cs

    Ve vygenerované proxy třídě si lze všimnout hlavně dvou hlavních rozdílů mezi proxy služby s callbacky a proxy služby bez callbacků:

-        zatímco vygenerovaná třída služby bez callbacků dědí od třídy ClientBase, vygenerovaná třída služby s callbacky dědí od třídy DuplexClientBase,

-        prvním argumentem konstruktoru vygenerované proxy třídy je instance InstanceContext referující instanci přijímacího rozhraní implementovaného na straně klienta.

   Ve vygenerovaném souboru je dále možné si všimnout, že k názvu přijímacího callback rozhraní přibyl postfix Callback, pokud již nebyl součástí názvu rozhraní.

Implementace služby

   Domnívám se, že poté, co je zmíněn alespoň první argument konstruktoru proxy třídy, mohu uvést implementaci služby:

[ServiceBehavior( InstanceContextMode = InstanceContextMode.Single ) ]

public class WCFService : IWCFService

{

    private List<IOperationCallback> callbacks;

 

    public WCFService()

    {

        callbacks = new List<IOperationCallback>();

    }

 

    public void SubscribeMessages()

    {

        callbacks.Add( OperationContext.Current.GetCallbackChannel<IOperationCallback>() );

    }

 

    public void SendMessage( string message )

    {

 

        foreach ( IOperationCallback callback in callbacks )

        {

            callback.OnNewMessage( message );

        }

    }

}

 

class Program

{

    static void Main( string[] args )

    {

        Uri baseAddress = new Uri( "net.tcp://localhost:1234/WCFService" );

        Type contractType = typeof( IWCFService );

        NetTcpBinding binding = new NetTcpBinding();

 

        ServiceHost serviceHost = new ServiceHost( typeof( WCFService ) );

        serviceHost.AddServiceEndpoint( contractType, binding, baseAddress );

 

        serviceHost.Open();

 

        Console.ReadLine();

 

        serviceHost.Close();

    }

}

   Nejdůležitější je, podle mého názoru, metoda SubscribeMessages a volání GetCallbackChannel<T>, která vrací proxy třídu vzdáleného implementovaného přijímacího rozhraní typu T, kterou jsme předali pomocí InstanceContextu jako argument konstruktoru vygenerované proxy třídy. Proxy třída vzdáleného implementovaného rozhraní na straně klienta může být službou získána v rámci jakéhokoliv volání z jeho strany.

Implementace klienta

Zbývá uvést implementaci klienta:

public class OperationCallback : IWCFServiceCallback

{

    public void OnNewMessage( string message )

    {

        Console.WriteLine( "New message: " + message );

    }

}

 

class Program

{

    static void Main( string[] args )

    {

        NetTcpBinding binding = new NetTcpBinding();

        EndpointAddress remoteAddress = new EndpointAddress( "net.tcp://localhost:1234/WCFService" );

        InstanceContext context = new InstanceContext( new OperationCallback() );

 

        WCFServiceClient client = new WCFServiceClient( context, binding, remoteAddress );

        client.SubscribeMessages();

 

        string message = "";

        while ( !( message = Console.ReadLine() ).Equals( "exit" ) )

        {

            client.SendMessage( message );

        }

    }

}

 

Pozn.  Nenechte se zmást tím, že pro obsluhu události rozhraní IWCFServiceCallback jsem použil zvláštní třídu. V praxi zpravidla obsluhuje události třída, jejíž metoda si o jejich zasílání vyžádala – v našem zkráceném příkladě je tato metoda bohužel statická, což nám neumožní vytvořit InstanceContext s parametrem this.

Binding

   Článek se blíží k závěru, nicméně bych nerad opomenul jednu stěžejní podmínku úspěšného použití callback rozhraní a tou je nutnost podpory duplexní komunikace ze strany bindingu. Podporu duplexní komunikace nabízí následující čtyři druhy bindingů:

-        WSDualHttpBinding,

-        NetTcpBinding,

-        NetNamedPipeBinding,

-        NetPeerTcpBinding.

Závěr

   Tak toto jsou mé postřehy, které jsem shromáždil během asi dvoudenního studia WCF a především WCF událostí. Jako programátorovi desktopových aplikací přecházejícímu od nativního C++ a technologií COM/DCOM na platformu .NET s požadavkem vývoje distribuovaných řešení, jsem nespatřil v podpoře "událostí" technologie WCF žádné velké nedostatky, vyjma jednoho zásadního omezení. Tímto omezením je skutečnost, že kontrakt služby, jak již bylo řečeno, umožňuje specifikovat jen jedno callback rozhraní, což v důsledku znamená akumulaci všech potencionálních událostí, které spolu vůbec nemusí funkčně souviset, v jednom callback rozhraní. Jak si s tímto poradit, to nechám už na vás.

Zdrojové kódy celého projektu jsou ke stažení zde: WCF Events Example Code.zip (7,86 kb)

Zdroj: http://msdn.microsoft.com/en-us/magazine/cc163537.aspx