Un servizio è un componente come tanti e come tutti i componenti espone un contratto, questa cosa per inciso è decisamente eplicita in WCF che separa nettamente contratto e implementazione.
In generale trovo poco sensato che nel contratto vengano esplicitati elementi (tipicamente parametri) che hanno poco a che spartire con il contratto ma sono decisamente più legati alla tecnologia implementativa.
Un esempio potrebbe essere, ad esempio, questo:
[ServiceContract]
public interface ISearchService
{
[OperationContract( Name = "FindBranchesByQuery" )]
BranchDto[] FindBranches( String culture, BranchesSearchQueryDto queryDto );
}
ora… il primo parametro è stato esposto dal contratto del servizio perchè il client deve in qualche modo dire al servizio quale sia la culture del chiamante, ma nel caso specifico è un design sbagliato e legato all’implementazione e non al requisito perchè nei requisiti non c’è che il client possa visualizzare informazioni in una lingua diversa da quella corrente, nel qual caso avrebbe senso, ergo questo:
[ServiceContract]
public interface ISearchService
{

[OperationContract( Name = "FindBranchesByQuery" )]
BranchDto[] FindBranches( BranchesSearchQueryDto queryDto );
}
è decisamente più lineare. A questo punto si pone però il problema di come passare le informazioni accessorie…
Header(s)
Da che mondo è mondo i servizi supportano il concetto di MessageHeader, quando usiamo WCF da Visual Studio abbiamo una visione molto limitata di quello che in realtà succede dietro le quinte perchè l’infrastruttura di WCF fa di tutto per renderci la vita facile, però come sappiamo WCF è tutto tranne che chiamate a metodi remoti, in realtà WCF è messaggi che viaggiano tra il client e il server e viceversa e questi messaggi hanno una struttura ben precisa che come minimo è formata da:
  • una envelope;
    • un header;
    • un body;
L’header è la parte che noi non vediamo praticamente mai ma è ricca di funzionalità:
[DataContract]
public class SearchRequestContext
{
[DataMember( IsRequired = true )]
public String TenantIdentifier { get; set; }

[DataMember( IsRequired = true )]
public String Culture { get; set; }

}
possiamo prendere un qualsiasi DataContract e lo possiamo far viaggiare insieme alla nostra chiamata semplicemente in questo modo:
var header = new MessageHeader<SearchRequestContext>( context );
OperationContext.Current.OutgoingMessageHeaders.Add
(
header.GetUntypedHeader( "Header unique name", "My namespace" )
);
Con l’accortezza di eseguire la chiamata al servizio in un OperationContextScope:
using( var client = new SearchServiceClient() )
{
using( var scope = new OperationContextScope( client.InnerChannel ) )
{

}
}
in questo modo ci garantiamo che il ciclo di vita dell’header coincida con quello dello scope al fine di evitare collisioni con chiamate concorrenti.
Recuperare un header, ad esempio quando si è sul server o quando dal client si vogliono leggere gli header ritornati dal server, è un filino più complesso (o per meglio dire noioso…):
if( OperationContext.Current.IncomingMessageHeaders != null )
{
var index = OperationContext.Current.IncomingMessageHeaders.FindHeader( "My header name", "My Namespace" );
if( index != -1 )
{
var header = OperationContext.Current.IncomingMessageHeaders.GetHeader<SearchRequestContext>
(
"My header name", "My Namespace"
);
}
}
ma possiamo lavorare un po’ per semplificare drammaticamente le cose e arrivare ad una cosa come questa:
var factory = new DefaultClientMessageHeaderManagerFactory();

using( var client = new SearchServiceClient() )
{
using( var manager = factory.Create( client.InnerChannel ) )
{
var ctx = new SearchRequestContext()
{
Culture = "it-IT",
TenantIdentifier = "SampleTenantId",

};

manager.SetHeader( ctx );

var query = new ..Query..();
var result = client.FindBranchesByQuery( query );

var inHeaders = manager.GetIncomingHeader<SearchRequestContext>();
}
}
Figo Smile, il tutto sarà disponibile a breve su Radical.
Una nota molto importante, questo snippet:
var header = new SearchRequestContext();
manager.SetHeader( header );
header.TenantIdentifier = "Foo";

var outHeader = manager.GetOutgoingHeader<SearchRequestContext>();
var actual = header.TenantIdentifier == outHeader.TenantIdentifier;
non produce il risultato atteso, non tanto per come funzione l’header manager piuttosto per come funzionano proprio gli header di WCF che vengono immediatamente serizalizzati all’atto dell’aggiunta.
.m