Il presente articolo è stato tradotto automaticamente.

Elementi di base

Servizi dei flussi di lavoro per le comunicazioni locali

Matt Milner

Scaricare il codice di esempio

Nella colonna precedente (vedere “ Workflow Communications ” nel mese di settembre 2007 emettere di MSDN Magazine in msdn.microsoft.com/magazine/cc163365.aspx ), ho scritto l'architettura di comunicazione principale in Windows Workflow Foundation 3 (WF3). Le attività di comunicazioni locali sono un'astrazione in cima a questa architettura di comunicazione è un argomento che non coprire. Se si osserva 4 di .NET Framework Beta 1, non si noterà alcuna attività HandleExternalEvent. In effetti, con WF4, incluse le attività di comunicazione sono incorporate Windows Communication Foundation (WCF). Questo mese, mostrerò è l'utilizzo di WCF per la comunicazione tra un flusso di lavoro e un'applicazione host in Windows Workflow Foundation 3. Ottenendo questa conoscenza dovrebbe contribuire con il risultato delle attività di sviluppo utilizzando WF3 e prepararsi per WF4, in cui WCF è l'unica astrazione su code (definite “ segnalibri ” WF4) viene fornito con il framework. (Per informazioni su Servizi flussi di lavoro in WF3 di base, vedere mio articolo basi nel numero di Visual Studio 2008 avvio di MSDN Magazine, in msdn.microsoft.com/magazine/cc164251.aspx ).

Panoramica

Comunicazione tra applicazioni host e i flussi di lavoro si dimostra arduo per alcuni sviluppatori perché essi facilmente possibile trascurare il fatto che il flusso di lavoro e l'host viene spesso eseguita su un thread diverso. La progettazione dell'architettura di comunicazione ha lo scopo di rendere invisibile agli sviluppatori di dover preoccuparsi di gestire il contesto del thread, i dati di marshalling e altri dettagli di basso livello. Un'astrazione attraverso l'architettura Accodamento in WF è l'integrazione di messaggistica WCF è stato introdotto nella versione 3.5 di .NET Framework. La maggior parte degli esempi e i laboratori illustrano come utilizzare le attività e le estensioni di WCF per esporre un flusso di lavoro per i client esterni per il processo di hosting, ma questa stessa infrastruttura di comunicazione può essere utilizzata per comunicare all'interno dello stesso processo.

Implementare la comunicazione implica diversi passaggi, ma il lavoro non equivalgono a molto più di avrebbe a che fare con le attività di comunicazione locali.

Prima di effettuare qualsiasi altra operazione, è necessario definire (o almeno iniziare a definire in un approccio iterativo) contratti di comunicazione tramite i contratti di servizio WCF. Successivamente, è necessario utilizzare tali contratti nei flussi di lavoro per modellare i punti di comunicazione nella logica. Infine, per associare i vari componenti, il flusso di lavoro e altri servizi devono essere ospitato come servizi WCF con endpoint configurati.

Modellazione della comunicazione

Il primo passaggio nella comunicazione di modellazione consiste nel definire i contratti tra l'applicazione host e il flusso di lavoro. I servizi WCF utilizzano contratti per definire l'insieme di operazioni che compongono il servizio e i messaggi inviati e ricevuti. In questo caso, poiché si sta comunicando dall'host per il flusso di lavoro e dal flusso di lavoro per l'host, è necessario definire due contratti di assistenza e contratti di dati correlati, come illustrato in di Figura 1.

Figura 1 contratti per le comunicazioni da

[ServiceContract(
    Namespace = "urn:MSDN/Foundations/LocalCommunications/WCF")]
public interface IHostInterface
{
[OperationContract]
void OrderStatusChange(Order order, string newStatus, string oldStatus);
}

[ServiceContract(
    Namespace="urn:MSDN/Foundations/LocalCommunications/WCF")]
public interface IWorkflowInterface
{
    [OperationContract]
    void SubmitOrder(Order newOrder);

    [OperationContract]
    bool UpdateOrder(Order updatedOrder);
}

[DataContract]
public class Order
{
    [DataMember]
    public int OrderID { get; set; }
    [DataMember]
    public string CustomerName { get; set; }
    [DataMember]
    public double OrderTotal { get; set; }
    [DataMember]
    public string OrderStatus { get; set; }
    }

Con i contratti in luogo di modellazione del flusso di lavoro mediante le attività Send e Receive funziona come avviene per la comunicazione remota. Che è uno dei vantaggi bellissimi di WCF: locale o remoto, il modello di programmazione è lo stesso. Ad esempio, di Figura 2 illustra un flusso di lavoro con due attività di ricezione e l'attività Invia una modellazione della comunicazione tra il flusso di lavoro e l'host. Le attività di ricezione sono configurate con il contratto di assistenza IWorkflowInterface e l'attività di invio utilizza contratto IHostInterface.

Finora, l'uso di WCF per le comunicazioni locali non è molto diverso dall'utilizzo di WCF per le comunicazioni remote ed è molto simile all'utilizzo di servizi e le attività di comunicazioni locali. La differenza principale è disponibile in modalità di scrittura di codice host per avviare il flusso di lavoro e gestire le comunicazioni provenienti dal flusso di lavoro.

Nella figura 2 Workflow modellato dai contratti

I servizi di hosting

Poiché desideriamo che la comunicazione tramite WCF in entrambe le direzioni di flusso, dobbiamo host due servizi, ovvero il servizio del flusso di lavoro per eseguire il flusso di lavoro e un servizio nell'applicazione host di ricevere messaggi da flusso di lavoro. Nel mio esempio creato una semplice applicazione Windows Presentation Foundation (WPF) di agire come l'host e utilizzato OnStartup e OnExit metodi della classe App per gestire gli host. Il primo inclination, è possibile creare la classe di WorkflowServiceHost e aperto a destra nel metodo OnStartup. Poiché il metodo Open non si blocca dopo che l'host è stato aperto, è possibile continuare l'elaborazione, caricare l'interfaccia utente e iniziare l'interazione con il flusso di lavoro. Poiché WPF (e altre tecnologie client) utilizza un singolo thread per l'elaborazione, ciò presto comporta problemi poiché il servizio e la chiamata client non è possibile utilizzare lo stesso thread in modo che il client si verifica il timeout. Per evitare questo problema, viene creato il WorkflowServiceHost in un altro thread tramite ThreadPool, come illustrato in Figura 3.

Nella figura 3 Hosting Workflow Service

ThreadPool.QueueUserWorkItem((o) =>
{

//host the workflow
workflowHost = new WorkflowServiceHost(typeof(
    WorkflowsAndActivities.OrderWorkflow));
workflowHost.AddServiceEndpoint(
    "Contracts.IWorkflowInterface", LocalBinding, WFAddress);
try
{
    workflowHost.Open();
}
catch (Exception ex)
{
    workflowHost.Abort();
    MessageBox.Show(String.Format(
        "There was an error hosting the workflow as a service: {0}",
    ex.Message));
}
});

La sfida successiva che si verificano riguarda la scelta associazione appropriata per le comunicazioni locali. Attualmente, non esiste nessun in memoria o nel processo di associazione che è estremamente leggero per questi tipi di scenari. L'opzione migliore per un canale leggero consiste nell'utilizzare il NetNamedPipeBinding con protezione disattivato. Sfortunatamente, se si tenta di utilizzare questa associazione e host il flusso di lavoro come servizio, si verifica un errore che informa che l'host richiede un binding con il canale di contesto presente perché il contratto di assistenza potrebbe richiedere una sessione. Inoltre, non c'è Nessun NetNamedPipeContextBinding incluso in .NET Framework, che viene fornito con solo tre associazioni di contesto: BasicHttpContextBinding NetTcpContextBinding e WSHttpContextBinding. Fortunatamente, è possibile creare i propri binding personalizzati da includere il canale di contesto. Nella figura 4 viene illustrato un binding personalizzato che deriva dalla classe NetNamedPipeBinding e inserisce il ContextBindingElement nell'associazione. Comunicazione in entrambe le direzioni ora possibile utilizzare questa associazione nella registrazione endpoint utilizzando indirizzi diversi.

Nella figura 4 NetNamedPipeContextBinding

public class NetNamedPipeContextBinding : NetNamedPipeBinding
{
    public NetNamedPipeContextBinding() : base(){}

    public NetNamedPipeContextBinding(
        NetNamedPipeSecurityMode securityMode):
        base(securityMode) {}

    public NetNamedPipeContextBinding(string configurationName) :
        base(configurationName) {}

    public override BindingElementCollection CreateBindingElements()
    {
        BindingElementCollection baseElements = base.CreateBindingElements();
        baseElements.Insert(0, new ContextBindingElement(
            ProtectionLevel.EncryptAndSign,
            ContextExchangeMechanism.ContextSoapHeader));

        return baseElements;
    }
}

Con questa nuova associazione, è possibile creare un endpoint sul WorkflowServiceHost e aprire l'host senza ulteriori errori. Il flusso di lavoro è pronto a ricevere dati da host utilizzando il contratto di assistenza. Per inviare tali dati, è necessario creare un proxy e richiamare l'operazione, come illustrato in di Figura 5.

Nella figura 5 codice host per l'avvio di un flusso di lavoro

App a = (App)Application.Current;
    IWorkflowInterface proxy = new ChannelFactory<IWorkflowInterface>(
    a.LocalBinding, a.WFAddress).CreateChannel();

    proxy.SubmitOrder(
        new Order
        {
            CustomerName = "Matt",
            OrderID = 0,
            OrderTotal = 250.00
        });

Poiché si condividono i contratti, non vi è alcuna classe proxy in modo che sia necessario utilizzare ChannelFactory < TChannel > per creare proxy del client.

Mentre il flusso di lavoro è ospitato e pronto a ricevere messaggi, è necessario che sia configurato per l'invio di messaggi per l'host. Più importanti, deve essere in grado di ottenere un endpoint client quando si utilizza l'attività Invia il flusso di lavoro. L'attività Invia consente di specificare il nome dell'endpoint, che in genere un mapping a un endpoint di tipo denominato nel file di configurazione. Sebbene funzioni inserendo le informazioni di endpoint in un file di configurazione, è anche possibile utilizzare ChannelManagerService (come descritto nel mio articolo dell'agosto 2008 in msdn.microsoft.com/magazine/cc721606.aspx ) per contenere gli endpoint client utilizzati per le attività di invio del flusso di lavoro. Nella figura 6 viene illustrato il codice di hosting per creare il servizio, fornire con un endpoint di tipo denominato e aggiungerlo a WorkflowRuntime ospitato nel WorkflowServiceHost.

Nella figura 6 aggiunta ChannelManagerService a Runtime

ServiceEndpoint endpoint = new ServiceEndpoint
(
    ContractDescription.GetContract(typeof(Contracts.IHostInterface)),
        LocalBinding, new EndpointAddress(HostAddress)
);
endpoint.Name = "HostEndpoint";

WorkflowRuntime runtime =
    workflowHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().
WorkflowRuntime;

ChannelManagerService chanMan =
    new ChannelManagerService(
        new List<ServiceEndpoint>
        {
            endpoint
        });

runtime.AddService(chanMan);

Con il servizio flusso di lavoro ospitato offre la possibilità di inviare messaggi da host per il flusso di lavoro, ma per i messaggi di nuovo l'host, è necessario disporre di un servizio WCF può ricevere messaggi da flusso di lavoro. Questo servizio è un servizio WCF standard self-hosted nell'applicazione. Poiché il servizio non è un servizio flusso di lavoro, è possibile utilizzare NetNamedPipeBinding standard o riutilizzare NetNamedPipeContextBinding illustrato in precedenza. Infine, poiché questo servizio viene richiamato dal flusso di lavoro, può essere ospitato sul thread dell'interfaccia utente, quindi più semplice l'interazione con gli elementi dell'interfaccia utente. Nella figura 7 viene illustrato il codice di hosting per il servizio.

Nella figura 7 hosting servizio host

ServiceHost appHost = new ServiceHost(new HostService());
appHost.AddServiceEndpoint("Contracts.IHostInterface",
LocalBinding, HostAddress);

try
{
    appHost.Open();
}
catch (Exception ex)
{
    appHost.Abort();
    MessageBox.Show(String.Format(
        "There was an error hosting the local service: {0}",
    ex.Message));
}

Con entrambi i servizi ospitati, è possibile ora eseguire il flusso di lavoro, inviare un messaggio e ricevere nuovamente un messaggio. Tuttavia, se si tenta di inviare un messaggio secondo utilizzando questo codice al secondo ricevere attività del flusso di lavoro, si riceverà un errore sul contesto.

La gestione di correlazione di istanza

Un modo per gestire il problema di contesto consiste nell'utilizzare lo stesso proxy di client per ogni chiamata del servizio. In questo modo, il proxy client gestire gli identificatori di contesto (utilizzando il NetNamedPipeContextBinding) e l'invio del servizio con le richieste successive.

In alcuni scenari, non è possibile mantenere lo stesso proxy intorno per tutte le richieste. Si consideri il caso in cui si avvia un flusso di lavoro, mantenere a un database e chiuderla l'applicazione client. Quando l'applicazione client avvio nuovamente, è necessario un modo per riprendere il flusso di lavoro mediante l'invio di un altro messaggio per quell'istanza specifica. L'altro use case comuni è quando si desidera utilizzare un proxy client singolo, ma è necessario interagire con le istanze del flusso di lavoro diversi, ciascuno con un identificatore univoco. Ad esempio, l'interfaccia utente fornisce un elenco di ordini, ognuno con un flusso di lavoro corrispondente, e quando l'utente richiama un'azione in un ordine selezionato, è necessario inviare un messaggio all'istanza del flusso di lavoro. Consentendo l'associazione di gestire l'identificatore di contesto non funzioneranno in questo scenario, in quanto verrà sempre essere utilizzando l'identificatore del l'ultimo flusso di lavoro con cui interagire.

Per il primo scenario, ovvero utilizzando un nuovo proxy per ogni chiamata, ovvero è necessario impostare manualmente l'identificatore del flusso di lavoro nel contesto utilizzando l'interfaccia IContextManager. IContextManager è accessibile tramite il metodo GetProperty < TProperty > sull'interfaccia IClientChannel. Dopo aver configurato il IContextManager, è possibile utilizzare per ottenere o impostare il contesto.

Il contesto di se stesso è un dizionario delle coppie nome / valore, più importanti dei quali è il valore instanceId. Nel codice riportato di seguito viene illustrato come è recuperare l'ID dal contesto in modo che può essere archiviata dall'applicazione client per la versione successiva, quando è necessario interagire con la stessa istanza del flusso di lavoro. In questo esempio, l'ID è visualizzato nell'interfaccia utente del client anziché vengano archiviate in un database:

IContextManager mgr = ((IClientChannel)proxy).GetProperty<IContextManager>();
      
string wfID = mgr.GetContext()["instanceId"];
wfIdText.Text = wfID;

Dopo aver eseguito la prima chiamata al servizio del flusso di lavoro, il contesto viene automaticamente compilato con l'ID dell'istanza del flusso di lavoro per l'associazione di contesto nell'endpoint del servizio.

Quando si utilizza un proxy appena creato per comunicare con un'istanza del flusso di lavoro è stata creata in precedenza, è possibile utilizzare un metodo simile per impostare l'identificatore nel contesto per garantire che il messaggio viene indirizzato all'istanza del flusso di lavoro corretto, come illustrato di seguito:

IContextManager mgr = ((IClientChannel)proxy).GetProperty<IContextManager>();
  mgr.SetContext(new Dictionary<string, string>{
    {"instanceId", wfIdText.Text}
  });

Quando si dispone di un proxy appena creato, questa funziona codice correttamente la prima volta, ma non se si tenta di impostare il contesto di una seconda volta per richiamare un'altra istanza del flusso di lavoro. L'errore che è ottenere indica che non è possibile modificare il contesto quando viene abilitata la gestione automatica del contesto. In pratica, viene indicato che non è possibile avere la torta ed esso mangiare troppo. Se si desidera che il contesto di gestita automaticamente, non è possibile modificare manualmente. Sfortunatamente, se si desidera gestire manualmente il contesto, Impossibile ottenere la gestione automatica, che significa che non è possibile recuperare l'ID dell'istanza del flusso di lavoro dal contesto come ho illustrato in precedenza.

Per far fronte a questa mancata corrispondenza, è possibile gestire separatamente ciascun caso. Per la chiamata iniziale a un flusso di lavoro, utilizzare un nuovo proxy, ma per tutte le chiamate successive a un'istanza del flusso di lavoro esistente, è possibile utilizzare un proxy client singolo e gestire manualmente il contesto.

Per la chiamata iniziale, si dovrebbe utilizzare un singolo ChannelFactory < TChannel > per creare tutti i proxy. I risultati migliori prestazioni poiché la creazione di ChannelFactory presenta un certo overhead che non si desidera duplicare per ogni chiamata al primo. Utilizzando codice simile a tale illustrato precedentemente in di Figura 5, è possibile utilizzare un singolo ChannelFactory < TChannel > per creare il proxy iniziale. Nel codice chiamante, dopo aver utilizzato il proxy, è consigliabile seguire le procedure consigliate per la chiamata del metodo Close per rilasciare il proxy.

Questo è codice WCF standard per la creazione del proxy utilizzando il metodo factory di canale. Poiché l'associazione è una contesto di associazione, è possibile ottenere gestione del contesto automatico per impostazione predefinita, il che significa che è possibile estrarre l'identificatore dell'istanza del flusso di lavoro dal contesto dopo aver apportato la prima chiamata al flusso di lavoro.

Per le chiamate successive, è necessario gestire personalmente il contesto e questo comporta utilizzando codice client WCF non come frequentemente utilizzato dagli sviluppatori. Per impostare manualmente il contesto, è necessario utilizzare un OperationContextScope e creare personalmente il MessageContextProperty. L'invio, che equivale all'utilizzo di IContextManager per impostare il contesto, con l'eccezione che utilizzando direttamente la proprietà funziona anche quando la gestione del contesto è disattivata il MessageContextProperty è impostata sul messaggio. Nella figura 8 viene visualizzato il codice per creare il proxy utilizzando la stessa ChannelFactory < TChannel > è stato utilizzato per il proxy iniziale. La differenza è che in questo caso, il IContextManager viene utilizzato per disattivare la funzionalità di gestione automatica del contesto e viene utilizzato un proxy nella cache invece di crearne uno nuovo ogni richiesta.

Nella figura 8 disattivazione Automatic Management Context

App a = (App)Application.Current;

if (updateProxy == null)
{
    if (factory == null)
        factory = new ChannelFactory<IWorkflowInterface>(
            a.LocalBinding, a.WFAddress);

        updateProxy = factory.CreateChannel();
        IContextManager mgr =
            ((IClientChannel)updateProxy).GetProperty<IContextManager>();
        mgr.Enabled = false;
        ((IClientChannel)updateProxy).Open();
}

Una volta creato il proxy, è necessario creare un OperationContextScope e aggiungere il MessageContextProperty per le proprietà dei messaggi in uscita sull'ambito. In questo modo la proprietà da includere nei messaggi in uscita durante la durata dell'ambito. Nella figura 9 viene visualizzato il codice per creare e impostare la proprietà messaggio utilizzando il OperationContextScope.

Nella figura 9 utilizzo OperationContextScope

using (OperationContextScope scope =
    new OperationContextScope((IContextChannel)proxy))
{
    ContextMessageProperty property = new ContextMessageProperty(
        new Dictionary<string, string>
        {
            {“instanceId”, wfIdText.Text}
        });

OperationContext.Current.OutgoingMessageProperties.Add(
    "ContextMessageProperty", property);

proxy.UpdateOrder(
    new Order
        {
            CustomerName = "Matt",
            OrderID = 2,
            OrderTotal = 250.00,
            OrderStatus = "Updated"
        });
}

Questo potrebbe sembrare parecchio lavoro semplicemente per parlare tra l'host e il flusso di lavoro. La buona notizia è che è possibile incapsulare gran parte di questa logica e la gestione degli identificatori, in alcune classi. Tuttavia, implica la codifica del client in modo particolare per assicurarsi che il contesto è gestito correttamente nei casi in cui è necessario inviare più di un messaggio all'istanza del flusso di lavoro. Nel download del codice in questo articolo, ho incluso un host di esempio per un flusso di lavoro utilizzando comunicazioni locale tenta di incapsulare gran parte della complessità e l'applicazione di esempio viene illustrato come utilizzare l'host.

Word About interazione dell'interfaccia utente

Uno dei motivi principali che si invia i dati dal flusso di lavoro per l'host è che si desidera presentare all'utente nell'interfaccia dell'applicazione. Fortunatamente, con questo modello si sono disponibili alcune opzioni per usufruire di funzionalità dell'interfaccia utente, tra cui associazione dati in WPF. Un semplice esempio, se si desidera che l'interfaccia utente per utilizzare l'associazione dati e aggiornare l'interfaccia utente quando vengono ricevuti i dati dal flusso di lavoro, è possibile associare l'interfaccia utente direttamente all'istanza del servizio dell'host.

La chiave utilizzando l'istanza del servizio, come il contesto dati per la finestra è che l'istanza deve essere ospitato come singleton. Quando si host del servizio come singleton, hanno accesso all'istanza e può essere utilizzato nell'interfaccia utente. Il servizio host semplice mostrato in di Figura 10 aggiorna una proprietà quando riceve informazioni dal flusso di lavoro e utilizza il INotifyPropertyChangedInterface per facilitare l'infrastruttura di associazione dati sollevare immediatamente le modifiche. Si noti l'attributo ServiceBehavior che indica che questa classe deve essere ospitata come singleton. Se si osserva il postback a di Figura 7, è possibile visualizzare ServiceHost creata un'istanza non con un tipo, ma con un'istanza della classe.

Nella figura 10 Implementation Service con INotifyPropertyChanged

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
internal class HostService : IHostInterface, INotifyPropertyChanged
{
    public void OrderStatusChange(Order order, string newStatus,
        string oldStatus)
    {
        CurrentMessage = String.Format("Order status changed to {0}",
            newStatus);
    }

private string msg;

public string CurrentMessage {
get { return msg; }
set
    {
        msg = value;
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(
                "CurrentMessage"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Per associare a questo valore, il DataContext della finestra o un particolare controllo nella finestra, può essere impostato con l'istanza. L'istanza può essere recuperato utilizzando la proprietà SingletonInstance sulla classe ServiceHost, come illustrato di seguito:

HostService host = ((App)Application.Current).appHost.SingletonInstance as HostService;
  if (host != null)
    this.DataContext = host;

A questo punto è possibile semplicemente associare elementi nella tua finestra per le proprietà dell'oggetto, come mostrato con questo TextBlock:

<TextBlock Text="{Binding CurrentMessage}" Grid.Row="3" />

Come detto in precedenza, questo è un semplice esempio di operazioni che è possibile eseguire. In un'applicazione reale è probabile che sarebbe non associare direttamente per l'istanza del servizio ma invece associato ad alcuni oggetti a cui sia la finestra e l'implementazione del servizio aveva accesso.

Ricerca quantità send-ahead per WF4

WF4 introduce diverse funzionalità che rendono comunicazioni locali su WCF ancora più semplice. La funzione primaria è correlazione del messaggio che non si basa sul protocollo. L'utilizzo di un identificatore di istanza del flusso di lavoro continuerà a essere un'opzione, ma una nuova opzione consentirà di messaggi di essere correlato in base al contenuto del messaggio. In questo modo, ognuno dei messaggi di posta contiene un ID ordine, un ID cliente o insieme di dati, è possibile definire delle correlazioni tra tali messaggi e non è necessario utilizzare un'associazione che supporta la gestione del contesto.

Inoltre, il fatto che WPF e WF di generazione del nucleo stessa API XAML in .NET Framework versione 4 potrebbe aprire alcune possibilità interessanti per integrare le tecnologie in modi nuovi. Come ci si avvicina al rilascio di .NET Framework 4, fornirà ulteriori dettagli sull'integrazione di WF con WCF e WPF, insieme ai meccanismi interni di WF4 altri contenuti.

Matt Milner è un membro dello staff tecnico all'indirizzo di Pluralsight, dove lavora in particolare sulle tecnologie di sistemi connessi (WCF, Windows Workflow Foundation, BizTalk, “ Dublin ” e la piattaforma servizi Azure). Matt è anche un consulente indipendente specializzato nello sviluppo e progettazione dell'applicazione Microsoft. NET. Per condividere il suo amore della tecnologia regolarmente egli a conferenze locali, regionali e internazionali, ad esempio Tech·ed. Microsoft ha riconosciuto Milner come un MVP per la sua funzione community intorno a sistemi connessi tecnologia. È possibile contattarlo sul suo blog all'indirizzo pluralsight.com/community/blogs/matt .