siamo di nuovo qui… :-), ed è pure di nuovo lunedì…, abbiamo il nostro bel servizio di brokering basato su Wcf, o meglio abbiamo un’idea di quello che vogliamo e ci troviamo di fronte ad un interessante problema, questo è il contratto (non è più così ma poco importa adesso):
[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 );
}
Implementato “grezzamente” (siamo ancora in draft) in questo modo:
[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 :-)
    }
}
Probabilmente avete già notato l’oggetto del contendere: “recupera gli handler in base al tipo”; a naso direi che ci sono 2 soluzioni plausibili:
  • usare uno dei tanti mali del mondo: il service locator pattern;
  • cercare di iniettare qualcosa nel nostro servizio wcf;
n.d.r.:
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
.

siccome il primo approccio proprio non mi piace e il secondo mi sconquinfera parecchio diciamo che vogliamo essere in grado di supportare questo scenario:
[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )]
[ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall )]
public class WcfMessageBrokerService : IWcfMessageBrokerService
{
    readonly IMessageHandlerProvider provider;

    public WcfMessageBrokerService( IMessageHandlerProvider provider )
    {
        this.provider = provider;
    }
}
“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:
using( var host = new ServiceHost( typeof( MyService ) ) )
{
    host.Open();

    Console.WriteLine( "MyService is waiting for connections..." );
    Console.Read();

    host.Close();
}
n.d.r.:
Oppure usare un file “.svc” perchè siamo ospitati dentro IIS, boni che ci arriviamo :-)

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 ;-)
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.:
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.

Ma se siamo hostati in IIS…? Nulla di più semplice :-)
C’è semplicemente un attore in più: ServiceHostFactory, partiamo dal fondo:
Picture1
Aggiungiamo un file “.svc” senza nessun code-behind. Cosa ci vieta adesso di fare 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 di conseguenza questo:
public class WindsorStyxServiceHostFactory : StyxServiceHostFactory
{
    protected override IServiceBootstrapper CreateBoostrapper()
    {
        return new WindsorServiceBootstrapper();
    }
}
…e infine nel nostro file svc questo:
Picture2
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:
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 );
    }
}
Usando semplicemente il vostro service container preferito :-)
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