Wcf self-service hosting: IoC-2-the-max
Implementato “grezzamente” (siamo ancora in draft) in questo modo:[ServiceContract( SessionMode = SessionMode.NotAllowed, Namespace = "…" )] public interface IWcfMessageBrokerService { [OperationContract( IsOneWay = false )] [ServiceKnownType( "GetServiceKnownTypes", typeof( ServiceKnownTypesProvider ) )] ResponseMessage[] Dispatch( TwoWayRequestMessage[] requests ); [OperationContract( IsOneWay = true )] [ServiceKnownType( "GetServiceKnownTypes", typeof( ServiceKnownTypesProvider ) )] void Broadcast( OneWayRequestMessage[] requests ); }
Probabilmente avete già notato l’oggetto del contendere: “recupera gli handler in base al tipo”; a naso direi che ci sono 2 soluzioni plausibili:[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )] [ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall )] public class WcfMessageBrokerService : IWcfMessageBrokerService { public ResponseMessage[] Dispatch( TwoWayRequestMessage[] requests ) { var tmp = new List<ResponseMessage>(); foreach( var message in requests ) { //recupera il tipo degli handler in base al messaggio //recupera gli handler in base al tipo //esegue gli handler passando il messaggio //accumula le risposte } //ritorna le risposte return tmp.ToArray(); } public void Broadcast( OneWayRequestMessage[] requests ) { //idem con patate :-) } }
- usare uno dei tanti mali del mondo: il service locator pattern;
- cercare di iniettare qualcosa nel nostro servizio wcf;
n.d.r.:siccome il primo approccio proprio non mi piace e il secondo mi sconquinfera parecchio diciamo che vogliamo essere in grado di supportare questo scenario:
Prima che qualcuno lo sottolinei c’è anche l’opzione MEF, ma in questo caso abbiamo bisogno di sfruttare, per cose che vedremo, le facility e la pluggabilità che un service container ci offre. Inoltre conosco ancora troppo poco MEF per metterlo in produzione adesso e per quel che ho capito MEF ragiona sulle “istanze” (inietta istanze) mentre noi per molte cose dobbiamo ragionare sui tipi (fare discovery e altre belle cosette), cosa che con MEF credo non ci sia mezzo di fare.
“semplicemente” essere in grado di fare DI in un servizio Wcf. Per arrivare li è necessario capire come gira il fumo della genesi di un servizio Wcf, tipicamente per hostare un sevizio in una applicazione potremmo scrivere una cosa del tipo:[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )] [ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall )] public class WcfMessageBrokerService : IWcfMessageBrokerService { readonly IMessageHandlerProvider provider; public WcfMessageBrokerService( IMessageHandlerProvider provider ) { this.provider = provider; } }
using( var host = new ServiceHost( typeof( MyService ) ) ) { host.Open(); Console.WriteLine( "MyService is waiting for connections..." ); Console.Read(); host.Close(); }
n.d.r.:La prima cosa da notare è che non siamo noi responsabili della creazione dell’istanza del servizio, e ci mancherebbe :-), ma lo possiamo diventare? decisamente si, non solo: possiamo diventare responsabili della crezione dell’istanza senza doverci prendere carico anche della gestione del ciclo di vita, il che non ci fa certo schifo ;-)
Oppure usare un file “.svc” perchè siamo ospitati dentro IIS, boni che ci arriviamo :-)
Wcf ragiona più o meno così (semplificando al massimo, mamma mia che macello…):
- Wcf è dotato di una feature interessantissima: i behavior;
- All’apertura di un “ServiceHost” potete agganciare un behavior al nuovo host, la stessa cosa si può fare anche da configurazione con qualche passaggio in più;
- Il behavior avrà poi la possibilità, “ApplyDispatchBehavior”, di iniettare logica/cambiare proprietà/aggiungere extension in fase di startup;
- Quindi un behavior potrebbe iterare tutti gli endpoint, o quelli che gli interessano, e cambiare un robo che si chiama: IInstanceProvider;
- un IInstanceProvider è a tutti gli effetti un service container (GetInstance/ReleaseInstance) che verrà invocato da Wcf, per gli endpoint a cui è stato assegnato (ergo potreste avere comportamenti diversi per endpoint diversi), nel momento in cui Wcf ha effettivamente bisogno di creare un’istanza del servizio vero e proprio;
n.d.r.:Ma se siamo hostati in IIS…? Nulla di più semplice :-)
Qui nasce subito un primo problema… la documentazione su Wcf è decisamente poca, San Google non aiuta e se vi dannate a cercare trovate almeno 3 soluzioni diverse per lo stesso problema il che altro non fa che aumentare il disordine.
C’è semplicemente un attore in più: ServiceHostFactory, partiamo dal fondo:
Aggiungiamo un file “.svc” senza nessun code-behind. Cosa ci vieta adesso di fare questo?
e di conseguenza questo:public abstract class StyxServiceHostFactory : ServiceHostFactory { protected abstract IServiceBootstrapper CreateBoostrapper(); protected virtual StyxServiceHost OnCreateServiceHost( Type serviceType, Uri[] baseAddresses ) { var boostrapper = this.CreateBoostrapper(); boostrapper.Boot(); var host = boostrapper.CreateHost( serviceType, baseAddresses ); return host; } protected override sealed ServiceHost CreateServiceHost( Type serviceType, Uri[] baseAddresses ) { var host = this.OnCreateServiceHost( serviceType, baseAddresses ); return host; } public override sealed ServiceHostBase CreateServiceHost( string constructorString, Uri[] baseAddresses ) { return base.CreateServiceHost( constructorString, baseAddresses ); } }
…e infine nel nostro file svc questo:public class WindsorStyxServiceHostFactory : StyxServiceHostFactory { protected override IServiceBootstrapper CreateBoostrapper() { return new WindsorServiceBootstrapper(); } }
Agganciamo cioè il nostro servizio e una nostra factory; una factory è esattamente quello che pensate: un singleton, il cui ciclo di vita è regolato dall’application pool. Singleton a cui IIS si rivolge (una ed una sola volta all’avvio) per avere istanze di un ServiceHost.
E’ ovvio che adesso è tutto facilissimo perchè avete “in mano il telecomando” e potete controllare qualsiasi cosa e quindi iniettare quello che volete dove volete:
Usando semplicemente il vostro service container preferito :-)public sealed class WindsorServiceBootstrapper : IServiceBootstrapper { IWindsorContainer container; protected virtual IWindsorContainer CreateContainer() { return new WindsorContainer(); } public virtual void Boot() { this.container = this.CreateContainer(); Ensure.That( container ).Named( () => container ).IsNotNull(); this.container.AddFacility<FactorySupportFacility>( "factory.support" ); this.container.AddFacility<StartableFacility>(); this.container.Register( Component.For<KharonConfigurationSectionHandler>().UsingFactoryMethod( () => { var configHandler = ( KharonConfigurationSectionHandler )ConfigurationManager.GetSection( "brokerServiceConfiguration" ); return configHandler; } ).LifeStyle.Is( LifestyleType.Singleton ) ); this.container.Register( Component.For<IServiceProvider, IWindsorContainer>().Instance( this.container ) ); this.container.Register( Component.For<IInstanceProvider>().ImplementedBy<WindsorStyxInstanceProvider>().LifeStyle.Is( LifestyleType.Singleton ) ); this.container.Register( Component.For<IMessageHandlerProvider>().ImplementedBy<WindsorMessageHandlerProvider>().LifeStyle.Is( LifestyleType.Singleton ) ); this.container.Register( Component.For<IWcfMessageBrokerService>().ImplementedBy<WcfMessageBrokerService>().LifeStyle.Is( LifestyleType.Transient ) ); this.container.Register( Component.For<IKharonConfiguration>().ImplementedBy<WindsorConfiguration>().LifeStyle.Is( LifestyleType.Singleton ) ); } public StyxServiceHost CreateHost( Type serviceType, params Uri[] baseAddresses ) { return new WindsorStyxServiceHost( this.container, serviceType, baseAddresses ); } }
Abbiamo messo ancora più carne al fuoco e probabilmente lo scenario si è per ora solo complicato, al prossimo giro vediamo come usarlo e perchè sperando di fare luce su ogni dubbio. Purtroppo non posso rilasciare un sample perchè il “robo” è di Gaia e sinceramente non ho il tempo di mettere in piedi una code-base parallela, ma, sempre che vi interessi, non disperate… chi vivrà… compilerà e probabilmene mangerà anche un bel po’ di sana carnazza:-)
.m