No, non sono in silenzio stampa …
, è che, insomma, il periodo… è un po’ così….
Comunque, nel frattempo giro il post di Andrea. Davvero un ottimo tool per testare le famigerate Regular Expression pubblicato da BucketSoft. Ha anche una funzione per fare un minimo di benchmark. Me lo segno per la prossima volta che mi servirà … 
… per il prossimo evento
. Come è intuibile dal titolo e dall’agenda, gli argomenti che tratteremo sono temi caldi e molto discussi su cui spesso è necessario il confronto aperto, e questo workshop è un’occasione assolutamente da non perdere. E poi, non capita molto spesso di avere qui Lorenzo Barbieri … approfittatene !!!
Infine, ci saranno premi decisamente interessanti per i partecipanti !!!
Ps.: durante l’evento se avete dubbi o questioni su WCF, chiedetemi pure … 
Oggi mi sono imbattuto in questo errore:
The 'Microsoft.Jet.OLEDB.4.0' provider is not registered on the local machine
L’errore si verifica, almeno nel mio caso, eseguendo un’applicazione .NET su un server x64. La soluzione è semplice, basta forzare il target dell’applicazione su x86 e tutto torna poi a funzionare come dovrebbe.
Ciauz
”The Times They Are A-Changin'”, sulle note di una canzone di Bob Dylan annunciamo il prossimo workshop .netSide: “Sviluppare applicazioni di qualità in .NET”.
In questo incontro parliamo di patterns, best practices, testing, etc,.. tutti ingredienti che portano allo “sviluppo di applicazioni moderne che puntano ad alti livelli di affidabilità e stabilità, oltre a portare all'abbattimento dei costi di manutenzione”: Dopo l’intervento del già collaudato MVP Vito Arconzo, vedremo il debutto di Mario Ferrante, collaboratore importante nelle dinamiche del gruppo che ora ha finalmente deciso di mettersi in prima linea. Grande Mario !!!
Infine, ma assolutamente non per ultimo, chiuderà l’evento Lorenzo Barbieri di Microsoft, che di sicuro non ha bisogno di presentazioni grazie anche ai suoi numerosi contributi dati in passato alla community.
Fissate quindi la data: 15 Maggio 2009 ore 14:30, presso la sede di Tecnopolis a Valenzano (BA). Non vi resta che consultare l’agenda e registrarvi!!
Ciauz …
.
Uno degli usi più comuni ed allo stesso tempo più errati di un client WCF è l’utilizzo di un blocco using. Vediamo perchè un codice come questo è errato:
1: using (MyClient client = new MyClient())
2: {
3: client.RemoteOperation();
4: }
che, quando compilato, viene tradotto in questo:
1: MyClient client = new MyClient();
2: try
3: {
4: client.RemoteOperation();
5: }
6: finally
7: {
8: ((IDisposable)client).Dispose();
9: }
Qui, però, abbiamo un problemino. Se il client termina correttamente la chiamata, infatti, la dispose tenta di chiudere il channel, e questo è giusto. Ma se la comunicazione va in eccezione, allora la dispose tenterà comunque di chiudere il channel, generando quindi una nuova eccezione proprio perchè un channel in stato Faulted non può passare in stato Closed. L’eccezione generata è infatti:
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
Come fare quindi? Semplice non usare lo using e “tornare” al classico “try..catch” esplicito:
1: MyClient client = new MyClient();
2: try
3: {
4: client.RemoteOperation();
5: client.Close();
6: }
7: catch (Exception ex)
8: {
9: client.Abort();
10: }
in questo modo chiudiamo correttamente il channel mettendolo in Closed, effettuando però una serie di operazioni come il disposing dei vari channel utilizzati, la chiusura della sessione corrente (se utilizzata), la chiusura di eventuali transazioni ancora in piedi, etc….
Queste stesse operazioni vengono eseguite anche dalla close, ma chiaramente con le differenze del caso dovute alle specifiche implementazioni dei vari channel coinvolti. Tenendo presente come è composto il channel layer di WCF, è chiaro che quando viene invocato l’Abort o il Close sul client, questo viene propagato verso tutti i vari channels, generando quindi comportamenti differenti sulla base dello specifico channel.
Alcuni riferimenti:
http://msdn.microsoft.com/en-us/library/aa355056.aspx
http://msdn.microsoft.com/en-us/library/aa354510.aspx
Rilasciata da poche ore la CTP3 di "Velocity", il tool per la cache distribuita sviluppato da Microsoft:
For community technology preview 3 (CTP3), Microsoft project code named "Velocity" offers several enhancements and revised APIs. The enhancements include cache notifications feature, performance improvements, security enhancements, and new cluster management options. There have also been updates to the installation program.
L'annuncio ufficiale sul blog del team: http://blogs.msdn.com/velocity/
Download Microsoft Project Code Named "Velocity" Community Technology Preview 3
Gli Extension Methods mi piacciono parecchio. Li ritengo molto utili e comodi con il fine di rendere immediate e più semplici determinate operazioni che potrei definire “ripetitive”. Oggi, dopo l’ennesimo MessageInterceptor creato ed aggiunto ad un service host, mi sono deciso a creare un Extension Method per semplificarmi la vita:
1: public static class ServiceModelExtensions
2: {
3: public static void AttachInterceptor(this ServiceHostBase host, IDispatchMessageInspector inspector)
4: {
5: foreach (ChannelDispatcher channelDispatcher in host.ChannelDispatchers)
6: {
7: foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
8: {
9: endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
10: }
11: }
12: }
13:
14: public static void AttachInterceptor(this ServiceHostBase host, Type inspectorType)
15: {
16: if (inspectorType.HasInterface(typeof(IDispatchMessageInspector)))
17: {
18: throw new Exception("Il tipo richiesto non implementa l'interfaccia 'IDispatchMessageInspector'.");
19: }
20:
21: IDispatchMessageInspector inspector = (IDispatchMessageInspector) Activator.CreateInstance(inspectorType);
22: foreach (ChannelDispatcher channelDispatcher in host.ChannelDispatchers)
23: {
24: foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
25: {
26: endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
27: }
28: }
29: }
30:
31: public static bool HasInterface(this Type type, Type interfaceType)
32: {
33: return type.GetInterfaces().Any(t => t == interfaceType);
34: }
35: }
L’extension method può poi essere utilizzato così:
1: public class LoggerServiceBehavior : IServiceBehavior
2: {
3: public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
4: {
5: }
6:
7: public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
8: {
9: }
10:
11: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
12: {
13: serviceHostBase.AttachInterceptor(typeof(LoggerMessageInterceptor));
14: }
15: }
A mio parere viene semplificata notevolmente la leggibilità del codice. Un’altro esempio dell’utilità degli extension methods. Ovvio, come in ogni cosa, non bisogna abusarne… 
C’è una battaglia in atto ... chi la spunterà ?? 
Simpatica, vero??
L’ho notato solo oggi, probabilmente è attivo solo da qualche giorno, ma la nuova low-band view del sito MSDN mi piace parecchio.
Più fluida, semplice, diretta ai contenuti. Non so … mi ci trovo meglio e mi sa che la renderò persistente … 
Se vi siete persi l’evento, Francesco Renzo ha registrato la sessione e pubblicato il video su Vimeo.
Ringrazio davvero Francesco per la registrazione, promosso a pieni voti come reporter ufficiale DotNetSide
. Io intanto mi scuso con tutti per la mia mancata partecipazione, purtroppo per problemi lavorativi non ho potuto prendere parte all’evento. Cercherò di farmi perdonare…
…
Dalle puntate precedenti:
Abbiamo visto come poter sviluppare un semplice sistema diIoC con WCF sfruttando Unity. La stessa logica è in realtà applicabile a tutti gli altri framework .NET che forniscono funzionalità di IoC.
Vorrei però arricchire quanto già detto per consentire di sfruttare lo UnityServiceBehavior anche in situazioni in cui tutte le impostazioni vengono gestite direttamente dal file di configurazione, e non da codice come abbiamo precedentemente visto.
Per ottenere questo risultato abbiamo bisogno di sviluppare una particolare estenzione, una classe che eredita dal BehaviorExtensionElement. Sviluppiamo quindi il nostro UnityServiceBehaviorExtensionElement.
1: public class UnityServiceBehaviorExtensionElement : BehaviorExtensionElement
2: {
3: [ConfigurationProperty("containerName")]
4: public string ContainerName
5: {
6: get
7: {
8: return (string) base["containerName"];
9: }
10: }
11:
12: protected override object CreateBehavior()
13: {
14: return new UnityServiceBehavior(this.ContainerName);
15: }
16:
17: public override Type BehaviorType
18: {
19: get { return typeof(UnityServiceBehavior); }
20: }
21: }
La classe dichiara semplicemente due metodi: il primo restituisce l’istanza del behavior, mentre il secondo identifica il tipo di behavior che stiamo costruendo. Nella creazione dell’istanza dell’UnityServiceBehavior viene passato il valore della proprietà ContainerName che contiene il nome della sezione di configurazione relativa al container che vogliamo utilizzare.
Immaginiamo di avere questa configurazione di Unity:
1: <unity>
2:
3: <containers>
4:
5: <container name="BookServiceContainer">
6: <types>
7:
8: <type type="DotNetSide.WCF.IoC.Service.InnerBookService, DotNetSide.WCF.IoC.Service"
9: name="inner"/>
10:
11: <type type="DotNetSide.WCF.IoC.Service.IBookService, DotNetSide.WCF.IoC.Service"
12: mapTo="DotNetSide.WCF.IoC.Service.BookService, DotNetSide.WCF.IoC.Service">
13: <typeConfig>
14: <constructor>
15: <param name="innerService" parameterType="DotNetSide.WCF.IoC.Service.InnerBookService, DotNetSide.WCF.IoC.Service">
16: <dependency name="inner" />
17: </param>
18: </constructor>
19: </typeConfig>
20: </type>
21:
22: </types>
23: </container>
24:
25: </containers>
26:
27: </unity>
28:
La proprietà ContainerName dovrà riportare il nome BookServiceContainer. Prima di utilizzare lo UnityServiceBehaviorExtensionElement, creiamo il costruttore dello UnityServiceBehavior che accetta come parametro il nome del container:
1: public UnityServiceBehavior(string containerName)
2: : this(GetContainer(containerName))
3: {
4: }
5:
6: private static IUnityContainer GetContainer(string containerName)
7: {
8: IUnityContainer unityContainer = null;
9: var configurationSection =
10: (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
11:
12: UnityContainerElement unityContainerElement = null;
13: if (!string.IsNullOrEmpty(containerName))
14: {
15: unityContainerElement = configurationSection.Containers[containerName];
16: }
17:
18: if (unityContainerElement == null)
19: {
20: unityContainerElement =
21: configurationSection.Containers.Default;
22: }
23:
24: unityContainer = new UnityContainer();
25: unityContainerElement.Configure(unityContainer);
26:
27: return unityContainer;
28: }
Il costruttore invoca il metodo privato GetContainer per ottenere l’istanza del container e successivamente chiama il costruttore che accetta il container come parametro passando, appunto, l’istanza appena ottenuta.
Ora possiamo utilizzare la nostra estensione nel file di configurazione. Aggiungiamo un nuovo elemento nella sezione extensions del system.serviceModel:
1: <system.serviceModel>
2: ...
3: <extensions>
4: <behaviorExtensions>
5: <add name="unityService"
6: type="DotNetSide.WCF.IoC.Unity.UnityServiceBehaviorExtensionElement, DotNetSide.WCF.IoC.Unity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
7: </behaviorExtensions>
8: </extensions>
9: ...
10: </system.serviceModel>
Inseriamo quindi la configurazione del behavior:
1: <system.serviceModel>
2: ...
3: <behaviors>
4: <serviceBehaviors>
5: <behavior name="DotNetSide.WCF.IoC.Service.BookServiceBehavior">
6: <serviceMetadata httpGetEnabled="true"/>
7: <serviceDebug includeExceptionDetailInFaults="false"/>
8: <unityService containerName="BookServiceContainer" />
9: </behavior>
10: </serviceBehaviors>
11: </behaviors>
12: ...
13: </system.serviceModel>
Ora possiamo creare semplicemente il nostro ServiceHost (non lo UnityServiceHost) ed utilizzarlo normalmente. Questo utilizzo di pone anche come alternativa a quanto visto nella seconda parte (Inversion of Control con WCF e Unity (Parte II)) perchè in questo modo non è più necessario utilizzare il ServiceHostFactory per istanziare il ServiceHost.
Se avete dubbi, chiedete pure …
bye
Questo post è basato sul precedente Inversion of Control con WCF e Unity.
Nel post precedente abbiamo visto in che modo è possibile abilitare l’uso dell’IoC anche per i servizi WCF. Nell’esempio abbiamo visto però come creare il ServiceHost in applicazioni self-hosted. Per utilizzare il tutto anche in applicazioni ASP.NET e quindi in hosting su IIS dobbiamo implementare un ulteriore componente: il ServiceHostFactory.
Implementiamo quindi il nostro UnityServiceHostFactory:
1: public class UnityServiceHostFactory : ServiceHostFactory
2: {
3: protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
4: {
5: IUnityContainer unityContainer = null;
6: var configurationSection =
7: (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
8:
9: string configurationName = GetConfigurationName(serviceType);
10: var unityContainerElement =
11: configurationSection.Containers[configurationName];
12: if (unityContainerElement == null)
13: {
14: unityContainerElement =
15: configurationSection.Containers.Default;
16: }
17:
18: if (unityContainerElement != null)
19: {
20: unityContainer = new UnityContainer();
21: unityContainerElement.Configure(unityContainer);
22: }
23:
24: return new UnityServiceHost(unityContainer, serviceType, baseAddresses);
25: }
26:
27: private string GetConfigurationName(Type serviceType)
28: {
29: ServiceBehaviorAttribute serviceBehaviorAttribute = serviceType.GetCustomAttributes(true)
30: .OfType<ServiceBehaviorAttribute>()
31: .FirstOrDefault();
32:
33: return (serviceBehaviorAttribute != null &&
34: !string.IsNullOrEmpty(serviceBehaviorAttribute.ConfigurationName))
35: ? serviceBehaviorAttribute.ConfigurationName
36: : serviceType.FullName;
37: }
38: }
Il metodo CreateServiceHost è quello invocato dal ServiceHostingEnvironment per la creazione, appunto, del ServiceHost. In questo metodo creiamo l’istanza dell’UnityContainer e poi la configuriamo sulla base di quanto indicato nel file di configurazione (web.config, in questo caso). La soluzione qui implementata cerca inizialmente, tra i containers, uno specifico il cui nome corrisponde al ConfigurationName eventualmente indicato nell’attributo ServiceBehavior dell’implementazione del servizio. Se non specificato, viene utilizzato il nome completo del tipo del servizio. Se però il container specifico non viene trovato, lo unityContainer viene configurato con il container di default (senza nome). Infine, viene creata l’istanza del ServiceHost passando lo unityContainer.
A questo punto specifichiamo nel file .svc l’utilizzo dello UnityServiceHostFactory:
1: <%@ ServiceHost Language="C#" Debug="true"
2: Service="DotNetSide.WCF.IoC.Service.BookService, DotNetSide.WCF.IoC.Service"
3: Factory="DotNetSide.WCF.IoC.Unity.UnityServiceHostFactory, DotNetSide.WCF.IoC.Unity" %>
Ora siamo pronti per eseguire la nostra applicazione. In allegato troverete il codice completo dell’esempio.
Anche qui il codice è reso volutamente molto semplice per evitare di essere troppo prolisso.
bye 
Nei giorni scorsi mi è capitato spesse volte di parlare con amici e colleghi di Inversion of Control (IoC) e WCF. Perciò mi sono deciso a pubblicare un breve post per spiegare i passaggi necessari per ottenere l’utilizzo di IoC con un servizio WCF. Nel caso specifico ho scelto Unity come framework per l’IoC.
Sono innanzitutto necessari 3 componenti:
- InstanceProvider: è il componente che si incarica di creare l’istanza del servizio. In definitiva è quello che userà direttamente Unity;
- ServiceBehavior: ci consente di creare l’istanza dell’InstanceProvider e di “agganciarla” al servizio corrente;
- ServiceHost: è il gestore del nostro servizio. Il ServiceHost ha il compito di configurare il servizio per l’utilizzo e quindi si occuperà di applicare il ServiceBehavior creato;
Avremo quindi rispettivamente: UnityInstanceProvider, UnityServiceBehavior e UnityServiceHost.
UnityInstanceProvider
Il primo passo è, come detto, quello di creare l’InstanceProvider. Il codice è in realtà molto semplice, almeno in questa prima fase:
1: internal class UnityInstanceProvider : IInstanceProvider
2: {
3: private readonly IUnityContainer container;
4: private readonly Type contractType;
5:
6: public UnityInstanceProvider(IUnityContainer container, Type contractType)
7: {
8: this.container = container;
9: this.contractType = contractType;
10: }
11:
12: public object GetInstance(InstanceContext instanceContext)
13: {
14: return GetInstance(instanceContext, null);
15: }
16:
17: public object GetInstance(InstanceContext instanceContext, Message message)
18: {
19: return container.Resolve(contractType);
20: }
21:
22: public void ReleaseInstance(InstanceContext instanceContext, object instance)
23: {
24: container.Teardown(instance);
25: }
26: }
Il costruttore accetta in input l’istanza del container ed il tipo di contratto dello specifico endpoint. Entrambi verranno poi utilizzati dal GetInstance per risolvere l’istanza sulla base del contesto. Il metodo può chiaramente essere migliorato.
UnityServiceBehavior
Il ServiceBehavior, come detto, si occupa di creare l’InstanceProvider per gli specifici endpoint del servizio:
1: public class UnityServiceBehavior : IServiceBehavior
2: {
3: private readonly IUnityContainer container;
4:
5: public UnityServiceBehavior(IUnityContainer container)
6: {
7: this.container = container;
8: }
9:
10: public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
11: {
12: }
13:
14: public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
15: {
16: }
17:
18: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
19: {
20: foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
21: {
22: foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
23: {
24: if (endpointDispatcher.ContractName != "IMetadataExchange")
25: {
26: string contractName = endpointDispatcher.ContractName;
27: ServiceEndpoint serviceEndpoint = serviceDescription.Endpoints.FirstOrDefault(e => e.Contract.Name == contractName);
28: endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(this.container, serviceEndpoint.Contract.ContractType);
29: }
30: }
31: }
32: }
33: }
Il costruttore riceve dal ServiceHost l’istanza del container che viene poi assegnata al DispatchRuntime dell’EndpointDispatcher, selezionando anche il tipo di contratto che deve essere utilizzato per la risoluzione dell’istanza del servizio.
UnityServiceHost
Il passo finale è la creazione del ServiceHost, il componente che ci consente di configurare il servizio:
1: public class UnityServiceHost : ServiceHost
2: {
3: private IUnityContainer unityContainer;
4:
5: public UnityServiceHost(IUnityContainer unityContainer, Type serviceType) : base(serviceType)
6: {
7: this.unityContainer = unityContainer;
8: }
9:
10: protected override void OnOpening()
11: {
12: base.OnOpening();
13:
14: if (this.Description.Behaviors.Find<UnityServiceBehavior>() == null)
15: {
16: this.Description.Behaviors.Add(new UnityServiceBehavior(this.unityContainer));
17: }
18: }
19: }
Anche qui l’implementazione è davvero molto semplice. Il ServiceHost riceve nel costruttore l’istanza del container e contemporaneamente il tipo che identifica il servizio. Nell’override del metodo OnOpening, eseguito prima dell’apertura dei channels, il servicehost si occupa di verificare l’esistenza dell’UnityServiceBehavior e, se non trovato, lo istanzia passando a sua volta l’istanza del container.
Utilizzo in una applicazione console self-hosted
Ora siamo pronti per provare. Questa è la configurazione di esempio che utilizzeremo per il nostro servizio:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <configSections>
4: <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
5: </configSections>
6:
7: <unity>
8:
9: <containers>
10:
11: <container>
12: <types>
13:
14: <type type="DotNetSide.WCF.IoC.Console.InnerBookService, DotNetSide.WCF.IoC.Console"
15: name="inner"/>
16:
17: <type type="DotNetSide.WCF.IoC.Console.IBookService, DotNetSide.WCF.IoC.Console"
18: mapTo="DotNetSide.WCF.IoC.Console.BookService, DotNetSide.WCF.IoC.Console">
19: <typeConfig>
20: <constructor>
21: <param name="innerService" parameterType="DotNetSide.WCF.IoC.Console.InnerBookService, DotNetSide.WCF.IoC.Console">
22: <dependency name="inner" />
23: </param>
24: </constructor>
25: </typeConfig>
26: </type>
27:
28: </types>
29: </container>
30:
31: </containers>
32:
33: </unity>
34:
35: </configuration>
Molto semplicemente deve essere creata un’istanza dell’InnerBookService e passata come parametro nel costruttore del nostro servizio BookService, implementazione del nostro contratto IBookService:
1: [ServiceContract]
2: internal interface IBookService
3: {
4: [OperationContract]
5: Book ReadBook(string bookId);
6: }
7:
8: class BookService : IBookService
9: {
10: private readonly InnerBookService innerService;
11:
12: public BookService(InnerBookService innerService)
13: {
14: this.innerService = innerService;
15: }
16:
17: public Book ReadBook(string bookId)
18: {
19: return this.innerService.ReadBook(bookId);
20: }
21: }
22:
23: class InnerBookService
24: {
25: public Book ReadBook(string bookId)
26: {
27: return new Book()
28: {
29: Title = "WCF",
30: Description = "A book on Windows Communication Foundation"
31: };
32: }
33: }
Costruiamo quindi l’istanza del ServiceHost passando il container UnityContainer:
1: var unityContainer = new UnityContainer();
2: var configurationSection = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
3: configurationSection.Containers.Default.Configure(unityContainer);
4:
5: Uri serviceAddress = new Uri("http://localhost:9001/BookService");
6: Uri mexAddress = new Uri("http://localhost:9001/BookService/mex");
7: using (UnityServiceHost host = new UnityServiceHost(unityContainer, unityContainer.Resolve<IBookService>().GetType()))
8: {
9: host.Description.Behaviors.Add(new ServiceMetadataBehavior());
10: host.AddServiceEndpoint(typeof(IBookService), new BasicHttpBinding(), serviceAddress);
11: host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), mexAddress);
12:
13: host.Open();
14:
15: System.Console.Read();
16:
17: host.Close();
18: }
Abbiamo anche aggiunto un endpoint di tipo WS-Metadata Exchange, per la creazione del quale abbiamo anche evitato di utilizzare l’IoC nel UnityServiceBehavior, così possiamo anche creare il nostro client e testare il tutto.
Davvero molto semplice, non credete ?? 
Nei prossimi post aggiungerò qualche tassello per rendere più completo il discorso, nel frattempo aspetto i vostri feedback.
bye
Su ioProgrammo di Aprile è stato pubblicato un mio articolo che descrive come utilizzare Workflow Foundation per gestire uno scheduler completamente personalizzato ma soprattutto facilmente estendendibile.
Magari, se lo leggete, fatemi sapere che ne pensate ... 
Sarà forse l'attesa del nuovo sistema operativo, sarà forse la curiosità che c'è dietro, sarà forse la primavera, fatto sta che l'installation fest del 26 Marzo a Bari è già SOLD OUT. Tutti i posti sono prenotati
.
Se però già sapete di non poter partecipare, vi chiedo di liberare il posto già prenotato così da consentire la partecipazione a chi è in attesa.
Grazie, ci vediamo il 26!!!
More Posts
Next page »