Autore: Fabio Cozzolino
L’architettura
di Windows Communication Foundation (WCF) è stata realizzata per
consentire un elevato grado di estensibilità. In effetti ogni
applicazione ha le sue esigenze e spesso la scelta dell’utilizzo di un
framework piuttosto che di un altro si basa sulla valutazione di come
esso può esserci d’aiuto nei differenti scenari. WCF consente di realizzare applicazioni
utilizzando sia un set di configurazioni di base (o comunque guidate),
sia concedendo la possibilità di personalizzare il comportamento del
nostro servizio. Una di queste possibilità è fornita dai behaviors. In questo articolo vedremo come costruire un semplice Interceptor che si occupa di tracciare tutti quei messaggi che rispettano determinate caratteristiche.
Cosa sono i Behaviors
Nel primo screencast della serie su WCF (Address, Binding, Contract)
abbiamo parlato dell’ABC di WCF. Questi tre elementi, che costituiscono l'endpoint del servizio, nel 99% dei casidevono essere noti
al client per poter avviare una corretta comunicazione tra le parti. Oltre all'endpoint, i behaviors consentono
di intervenire nel channel stack per modificare il comportamento del
nostro servizio o, in alcuni casi, anche del proxy client, sempre se sviluppato con WCF. Possiamo, ad esempio,
intercettare il messaggio e verificare se sono state fornite le
credenziali richieste, oppure possiamo impostare il comportamento del DataContractSerializer.
WCF consente lo sviluppo di quattro tipologie di behaviors, ognuna con
le proprie caratteristiche e realizzabile attraverso l’implementazione della rispettiva
interfaccia:
- IServiceBehavior: l’implementazione
di questa interfaccia consente di intervenire sull’intero service
runtime, modificandone il comportamento. Non è utilizzabile lato
client;
- IEndpointBehavior: agisce unicamente a livello di endpoint, personalizzandone il comportamento sia lato client che lato server;
- IContractBehavior: consente la personalizzazione sia del ClientRuntime che del DispatchRuntime rispettivamente sulle applicazioni client e service applications;
- IOperationBehavior: consente la personalizzazione sia del ClientOperation che del DispatchOperation rispettivamente sulle applicazioni client e service;
In particolare, in questo articolo, implementeremo un IServiceBehavior che si occuperà di tenere traccia dei messaggi recapitati al servizio inviati da uno specifico client.
Primo passo: sviluppiamo il nostro Inspector
Prima
di realizzare il nostro custom behavior dobbiamo sviluppare il
componente che si occuperà di ispezionare il messaggio in ingresso, di
valutarne il contenuto ed infine prendere la decisione adeguata. Per
questo scopo WCF introduce il concetto di MessageInspector. Implementando l’interfaccia IDispatchMessageInspector, siamo in grado di intervenire nel DispatchRuntime,
e quindi eslusivamente nel contesto di esecuzione del servizio,
iniettando del nostro codice. Nel nostro caso specifico realizziamo il LoggerMessageInspector come di seguito:
public class LoggerMessageInspector : IDispatchMessageInspector
{
public LoggerMessageInspector()
{
}
#region IDispatchMessageInspector Members
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message
request, System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext)
{
int headerIndex = request.Headers.FindHeader("Requester", "http://dotnetside.org");
if (headerIndex == -1)
return null;
string content = request.Headers.GetHeader<string>(headerIndex);
if (content == "fabio")
{
// Leggiamo il messaggio e ne inseriamo il contenuto
MessageBuffer messageBuffer = request.CreateBufferedCopy(int.MaxValue);
// copiamo il contenuto del messaggio in uno stream
MemoryStream ms = new MemoryStream();
messageBuffer.WriteMessage(ms);
// dallo stream ricaviamo un array di byte rappresentante il messaggio
byte[] rawData = new byte[ms.Length];
ms.Write(rawData, 0, rawData.Length);
// scriviamo il risultato nell'EventLog
EventLog.WriteEntry("MessageLog", "Nuovo messaggio da fabio.",
EventLogEntryType.Information, 60000, short.MinValue,
rawData);
}
return null;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply,
object correlationState)
{
// do nothing
}
#endregion
}
Come potete vedere dal codice, l’interfaccia IDispatchMessageInspector richiede l’implementazione di due metodi che intervengono in due momenti differenti:
· AfterReceiveRequest
· BeforeSendReply
Il primo, come è facile capire dal nome, viene eseguito al
ricevimento della richiesta, mentre il secondo prima di inviare una
risposta al client. Nel nostro esempio stiamo utilizzando solo il
primo, ma avremmo potuto migliorare l’esempio, tracciando nell’EventLog
anche l’invio di una risposta a seguito della richiesta pervenuta da
quello specifico Uri.
Dopo aver creato il MessageInspector, passiamo alla
realizzazione del behavior. Lo scopo del behavior è principalmente
quello di “agganciare” determinati comportamenti al contesto di
esecuzione, rappresentato dal ServiceHost in esecuzione e dal ServiceDescription associato a quello specifico servizio. L’implementazione dell’interfaccia IServiceBehavior, infatti, richiede la realizzazione di tre metodi:
· AddBindingParameters: consente di aggiungere uno o più parametri personalizzati e richiesti dall’implementazione del channel in uso;
· ApplyDispatchBehavior: è il metodo principale e
consente di eseguire la maggior parte del lavoro del behavior. E’
possibile aggiungere qui i message interceptor, come nel nostro
esempio, gestori di evento, estensioni per la gestione della sicurezza
o qualsiasi altra implementazione personalizzata;
· Validate: è il primo metodo richiamato dal runtime e
permette la validazione del contesto di esecuzione sulla base della
valutazione delle proprie necessità;
Sulla base di quanto detto, proviamo ad implementare il nostro
behavior. Dei metodi sopra elencati, implementeremo per il nostro scopo
solo l’ApplyDispatchBehavior:
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.MessageInspectors.Add(new LoggerMessageInspector());
}
}
}
Il codice mostrato aggiunge un’istanza del LoggerMessageInspector ad ogni EndpointDispatcher presente in tutti i ChannelDispatcher in ascolto sul service host in uso.
Utilizziamo il LoggerBehavior
Il codice mostrato realizza il behavior che ci eravamo
prefissati. Al momento, però, possiamo solo utilizzare la classe
agganciandola al relativo service host. Per utilizzare il LoggerBehavior anche nel file di configurazione, possiamo estendere la classe base BehaviorExtensionElement ed implementare gli override richiesti:
public class LoggerBehaviorElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(LoggerBehavior); }
}
protected override object CreateBehavior()
{
return new LoggerBehavior();
}
}
Infine utilizziamo il file .config per registrare il behavior
sviluppato ed associarne l’uso allo specifico endpoint/servizio
attraverso l’infrastruttura di WCF:
<system.serviceModel>
...
<behaviors>
<serviceBehaviors>
<behavior name="helloServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<dataContractSerializer
ignoreExtensionDataObject="true"/>
<loggerMessageInspector/>
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="loggerMessageInspector"
type="extensions.LoggerBehaviorElement, extensions, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
</system.serviceModel>
Conclusioni
In questo articolo abbiamo visto come l’architettura di WCF
permette di inserire codice personalizzato direttamente nel channel
stack utilizzando i Message Inspector. Il codice mostrato
nell’articolo può essere senz’altro migliorato ed integrato con
ulteriori aspetti, ma costituisce sicuramente una base su cui
sviluppare le nostre estensioni. E’ molto importante, oltre all’esempio
in sé, comprendere le caratteristiche di WCF e soprattutto i punti in
cui intervenire per personalizzarne il comportamento. Potete ovviamente
chiedere chiarimenti o dubbi lasciando un commento in fondo a questa
pagina, oppure utilizzando il form contact del mio blog.