WCF Message Headers
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:
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( String culture, BranchesSearchQueryDto queryDto );
}
è decisamente più lineare. A questo punto si pone però il problema di come passare le informazioni accessorie…[ServiceContract]
public interface ISearchService
{
[OperationContract( Name = "FindBranchesByQuery" )]
BranchDto[] FindBranches( BranchesSearchQueryDto queryDto );
}
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;
possiamo prendere un qualsiasi DataContract e lo possiamo far viaggiare insieme alla nostra chiamata semplicemente in questo modo:[DataContract]
public class SearchRequestContext
{
[DataMember( IsRequired = true )]
public String TenantIdentifier { get; set; }
[DataMember( IsRequired = true )]
public String Culture { get; set; }
}
Con l’accortezza di eseguire la chiamata al servizio in un OperationContextScope:var header = new MessageHeader<SearchRequestContext>( context );
OperationContext.Current.OutgoingMessageHeaders.Add
(
header.GetUntypedHeader( "Header unique name", "My namespace" )
);
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.using( var client = new SearchServiceClient() )
{
using( var scope = new OperationContextScope( client.InnerChannel ) )
{
}
}
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…):
ma possiamo lavorare un po’ per semplificare drammaticamente le cose e arrivare ad una cosa come questa: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"
);
}
}
Figo , il tutto sarà disponibile a breve su Radical.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>();
}
}
Una nota molto importante, questo snippet:
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.var header = new SearchRequestContext();
manager.SetHeader( header );
header.TenantIdentifier = "Foo";
var outHeader = manager.GetOutgoingHeader<SearchRequestContext>();
var actual = header.TenantIdentifier == outHeader.TenantIdentifier;
.m