ASP.NET

Topshelf e Katana: architettura unificata per Web e servizi

Wes McClure

Scaricare il codice di esempio

Utilizzo di IIS per applicazioni Web host ASP.NET è stato lo standard de facto per oltre un decennio. Creazione di tali applicazioni è un processo relativamente semplice, ma distribuirli non è. Distribuzione richiede conoscenza a cura di gerarchie di configurazione applicazione e sfumature della storia di IIS e il provisioning noioso di siti, applicazioni e directory virtuali. Molti dei pezzi dell'infrastruttura critici spesso finiscono per vivere di fuori dell'applicazione in componenti di IIS configurati manualmente.

Quando troppo grande per applicazioni semplici Web richieste e necessità di sostenere le richieste di lunga durata, lavori ricorrenti e altri lavori di trasformazione, diventano molto difficili da sostenere all'interno di IIS. Spesso, la soluzione è creare un servizio Windows separata per ospitare questi componenti. Ma questo richiede un processo di distribuzione interamente separate, raddoppiando lo sforzo coinvolti. L'ultima goccia è sempre il Web e comunicare i processi di servizio. Quello che potrebbe essere un'applicazione molto semplice rapidamente diventa estremamente complesso.

Figura 1 Mostra l'aspetto questa architettura in genere. Il livello Web è responsabile per la gestione delle richieste veloci e fornendo un'interfaccia utente per il sistema. Le richieste di lunga durata sono delegate al servizio, che gestisce anche l'elaborazione e lavori ricorrenti. Inoltre, il servizio fornisce lo stato attuale e futuro lavoro al livello Web per essere incluso nell'interfaccia utente.

Traditional Separated Web and Service Architecture
Figura 1 tradizionale separato servizio architettura e Web

Un nuovo approccio

Fortunatamente, le nuove tecnologie stanno emergendo che può rendere lo sviluppo e la distribuzione di Web e applicazioni molto più semplice di servizio. Grazie al progetto di Katana (katanaproject.codeplex.com) e le specifiche fornite da OWIN (owin.org), è ora possibile di self-hosting di applicazioni Web, prendendo IIS dall'equazione e ancora molti dei componenti ASP.NET onnipresente come WebApi e SignalR supporto. Web hosting indipendente può essere integrato in un'applicazione console rudimentale insieme a Topshelf (topshelf-project.com) per creare un servizio Windows con facilità. Di conseguenza, Web e componenti del servizio possono vivere by-side nello stesso processo, come mostrato Figura 2. Questo elimina la necessità di sviluppare livelli di comunicazione estranei, progetti separati e procedure di distribuzione separata.

Unified Web and Service Architecture with Katana and Topshelf
Figura 2 unificata Web e architettura di servizio con la Katana e Topshelf

Questa funzionalità non è completamente nuova. Topshelf è stato intorno per anni, contribuendo a semplificare lo sviluppo dei servizi di Windows, e ci sono molti open source self-hosting Web Framework, come Nancy. Tuttavia, finché OWIN sbocciato nel progetto Katana, nulla ha dimostrato tanto promessa di diventare lo standard de facto alternativa per l'hosting di applicazioni Web in IIS. Inoltre, Nancy e molti componenti open source funzionano con il progetto Katana, consentendo di curare un quadro eclettico, elastico.

Topshelf può sembrare opzionale, ma non è questo il caso. Senza la possibilità di semplificare lo sviluppo dei servizi, sviluppo Web indipendente può essere estremamente ingombrante. Topshelf semplifica lo sviluppo del servizio trattandolo come un'applicazione console e astrarre il fatto che esso sarà ospitato come un servizio. Quando è il momento di distribuire, Topshelf gestisce automaticamente l'installazione e avvio dell'applicazione come servizio Windows — tutto senza l'overhead di trattare con InstallUtil; le sfumature di un progetto di servizio e componenti del servizio; e associare i debugger per servizi quando qualcosa va storto. Topshelf permette anche di molti parametri, quali il nome del servizio, per essere specificato nel codice o configurata durante l'installazione tramite riga di comando.

Per illustrare come unificare Web e componenti con Katana e Topshelf di servizio, creerò un semplice SMS applicazione di messaggistica. I'll iniziare con un'API per ricevere messaggi e li in coda per l'invio. Ciò dimostrerà come è facile per gestire le richieste con esecuzione prolungata. Poi, aggiungerò un metodo API di query per restituire il numero di in attesa di messaggi, mostrando che è anche facile per lo stato del servizio query da componenti Web.

Successivamente, aggiungerò un'interfaccia amministrativa per dimostrare self-hosted Web components ancora permettersi di costruire interfacce Web ricche. Per completare l'elaborazione del messaggio, aggiungerò un componente per inviare messaggi come sei accodati, Showcase, compresi i componenti del servizio.

E per evidenziare una delle parti migliori di questa architettura, creerò uno script psake per esporre la semplicità delle distribuzioni.

Per concentrarsi sui benefici combinati di Katana e Topshelf, io non entrerò nei dettagli o progetto. Check-out "Ottenere iniziato con la Katana progetto" (bit.ly/1h9XaBL) e "Creare Windows servizi facilmente con Topshelf" (bit.ly/1h9XReh) per saperne di più.

Un'applicazione Console è tutto ciò che serve per iniziare

Topshelf esiste per sviluppare e distribuire un servizio Windows dal punto di partenza di una semplice applicazione console facilmente. Per iniziare con l'applicazione di messaggistica SMS, creo un'applicazione console c# e quindi installare il pacchetto Topshelf NuGet dalla Console Gestione pacchetti .

All'avvio dell'applicazione di console, è necessario configurare il Topshelf HostFactory per astrarre l'applicazione come una console nello sviluppo e come servizio nella produzione di hosting:

private static int Main()
{
  var exitCode = HostFactory.Run(host =>
  {
  });
  return (int) exitCode;
}

Il HostFactory restituirà un codice di uscita, che è utile durante l'installazione del servizio per rilevare e fermare in caso di errore. Il configuratore di host fornisce un metodo di servizio per specificare un tipo personalizzato che rappresenta il punto di ingresso al codice dell'applicazione. Topshelf si riferisce a questo come il servizio che ospita, perché Topshelf è un framework per semplificare la creazione di servizi di Windows:

host.Service<SmsApplication>(service =>
{
});

Successivamente, creare un tipo di Management per contenere la logica di spin up self-hosted Web server e i componenti del servizio Windows tradizionali. Come minimo, questo tipo personalizzato conterrà il comportamento da eseguire quando l'applicazione si avvia o arresta:

public class SmsApplication
{
  public void Start()
  {
  }
  public void Stop()
  {
  }
}

Perché ho scelto di utilizzare un oggetto CLR vecchio normale (POCO) per il tipo di servizio, fornire un'espressione lambda a Topshelf per costruire un'istanza del tipo Management, e specificare l'inizio e fermare i metodi:

service.ConstructUsing(() => new SmsApplication());
service.WhenStarted(a => a.Start());
service.WhenStopped(a => a.Stop());

Topshelf permette molti servizio param­eters essere configurati nel codice, quindi utilizzare SetDescription, SetDisplayName e SetServiceName per descrivere e il nome del servizio che verrà installato in produzione:

host.SetDescription("An application to manage
     sending sms messages and provide message status.");
  host.SetDisplayName("Sms Messaging");
  host.SetServiceName("SmsMessaging");
  host.RunAsNetworkService();

Infine, io uso RunAsNetworkService per indicare Topshelf per configurare il servizio per l'esecuzione come account servizio di rete. È possibile modificare questo a qualunque conto si adatta il vostro ambiente. Per ulteriori opzioni di servizio, vedere la documentazione di configurazione Topshelf a bit.ly/1rAfMiQ.

In esecuzione il servizio come un'applicazione console è semplice come lanciare l'eseguibile. Figura 3 illustrato l'output dell'avvio e arresto dell'eseguibile SMS. Perché questa è un'applicazione console, quando si lancia l'applicazione in Visual Studio, potrete sperimentare il comportamento stesso.

Running the Service as a Console Application
Figura 3 in esecuzione il servizio come un'applicazione Console

Incorporando un'API

Con l'impianto idraulico di Topshelf in luogo posso cominciare il lavoro su API dell'applicazione. Il progetto di Katana fornisce pipeline di componenti per auto-ospitare un OWIN, quindi installare il pacchetto Microsoft.Owin.SelfHost per incorporare i componenti di servizio indipendente. Questo pacchetto fa riferimento a diversi pacchetti, di cui due sono importanti per il self-hosting. In primo luogo, Microsoft.Owin.Hosting fornisce un insieme di componenti per ospitare ed eseguire un gasdotto OWIN. In secondo luogo, Microsoft.Owin.Host.HttpListener fornisce un'implementazione di un server HTTP.

All'interno del Management, creare un'applicazione Web indipendente utilizzando il tipo di WebApp fornito dal pacchetto Hosting:

protected IDisposable WebApplication;
public void Start()
{
  WebApplication = WebApp.Start<WebPipeline>("http://localhost:5000");
}

Il metodo WebApp Start richiede due parametri, un parametro generico per specificare un tipo che configurerà la pipeline OWIN e un URL per ascoltare le richieste. L'applicazione Web è una risorsa monouso. Quando l'istanza del Management viene fermato, smaltire l'applicazione Web:

public void Stop()
{
  WebApplication.Dispose();
}

Uno dei vantaggi dell'utilizzo di OWIN è che posso sfruttare la una varietà di componenti familiari. In primo luogo, si utilizzerà WebApi per creare l'API. Ho bisogno di installare il pacchetto Microsoft.AspNet.WebApi.Owin per incorporare WebApi nella pipeline OWIN. Poi, creerò il tipo WebPipeline per configurare la pipeline OWIN e iniettare il middleware WebApi. Inoltre, potrai configurare WebApi per utilizzare l'attributo di routing:

public class WebPipeline
{
  public void Configuration(IAppBuilder application)
  {
    var config = new HttpConfiguration();
    config.MapHttpAttributeRoutes();
    application.UseWebApi(config);
  }
}

E ora posso creare un metodo API per ricevere i messaggi e li invio in coda:

public class MessageController : ApiController
{
  [Route("api/messages/send")]
  public void Send([FromUri] SmsMessageDetails message)
  {
    MessageQueue.Messages.Add(message);
  }
}

SmsMessageDetails contiene il payload del messaggio. L'azione di invio aggiunge il messaggio a una coda per essere elaborate in modo asincrono in un secondo momento. MessageQueue è un BlockingCollection globale. In un'applicazione reale, questo potrebbe significare che è necessario fattore in altre preoccupazioni come durabilità e scalabilità:

public static readonly BlockingCollection<SmsMessageDetails> Messages;

In un Web separate e architettura del servizio, consegnare fuori l'elaborazione asincrona delle richieste di lunga durata, come l'invio di un messaggio, richiede la comunicazione tra il Web e il servizio processi. E aggiungendo metodi API per interrogare lo stato del servizio significa anche più comunicazione sopra la testa. Un approccio unificato rende condivisione informazioni sullo stato tra Web e componenti semplici. Per dimostrare questo, aggiungere una query PendingCount all'API:

[Route("api/messages/pending")]
public int PendingCount()
{
  return MessageQueue.Messages.Count;
}

Costruire un'interfaccia utente ricca

Le API sono convenienti, ma ancora bisogno di applicazioni Web indipendenti a supporto di un'interfaccia visiva. In futuro, ho il sospetto, il framework MVC ASP.NET o un derivato sarà disponibile come OWIN middleware. Per ora, Nancy è compatibile e ha un pacchetto per sostenere il nucleo del motore di visualizzazione rasoio.

Potrai installare il pacchetto Nancy.Owin per aggiungere il supporto per Nancy e la Nancy.Viewengines.Razor di integrare il motore di visualizzazione Razor. Per collegare la pipeline OWIN Nancy, è necessario registrarlo dopo la registrazione per WebApi così esso non acquisisce le rotte che mappato all'API. Per impostazione predefinita, Nancy restituisce un errore se una risorsa non viene trovata, mentre WebApi passa le richieste non può gestire torna alla pipeline:

application.UseNancy();

Per ulteriori informazioni sull'utilizzo di Nancy con una pipeline OWIN, consultare "Hosting di Nancy con OWIN" sul bit.ly/1gqjIye.

Per costruire un'interfaccia status amministrativo, aggiungo un modulo di Nancy e mappare un itinerario di stato per il rendering di una visualizzazione di stato, passando il numero di messaggi in sospeso come il modello di visualizzazione:

public class StatusModule : NancyModule
{
  public StatusModule()
  {
    Get["/status"] =
      _ => View["status", MessageQueue.Messages.Count];
  }
}

La vista non è molto glamour a questo punto, basta un semplice conteggio dei messaggi in sospeso:

<h2>Status</h2>
There are <strong>@Model</strong> messages pending.

Io vado a jazz fino la vista un po ' con una semplice barra di navigazione Bootstrap, come mostrato Figura 4. L'utilizzo di Bootstrap richiede contenuto statico per il foglio di stile Bootstrap di hosting.

Administrative Status Page
Figura 4 pagina Status amministrativo

Potrei usare Nancy per ospitare il contenuto statico, ma il vantaggio di OWIN è miscelazione e la congruenza middleware, così invece che ho intenzione di utilizzare il pacchetto Microsoft.Owin.StaticFiles appena rilasciato, che fa parte del progetto Katana. Il pacchetto StaticFiles fornisce file di servizio middleware. Potrai aggiungere all'inizio della pipeline OWIN quindi la porzione di file statici Nancy non calciare.

application.UseFileServer(new FileServerOptions
{
  FileSystem = new PhysicalFileSystem("static"),
  RequestPath = new PathString("/static")
});

Il parametro FileSystem dice file server dove cercare i file servire. Sto usando una cartella denominata statico. Il RequestPath specifica il prefisso del percorso per ascoltare le richieste per questo contenuto. In questo caso, ho scelto di rispecchiare il nome statico, ma queste non devono corrispondere. Utilizzare il seguente link nel layout per fare riferimento al foglio di stile bootstrap (naturalmente, inserire il foglio di stile Bootstrap in una cartella CSS all'interno della cartella statica):

<link rel="stylesheet" href="/static/css/bootstrap.min.css">

Una parola sul contenuto statico e visualizzazioni

Prima di passare, voglio parlarvi di un suggerimento che trovo utile durante lo sviluppo di un'applicazione Web indipendente. Normalmente, si dovrebbe impostare il contenuto statico e visualizzazioni MVC per essere copiato nella directory di output, quindi i componenti Web indipendenti li possono trovare relativo all'assembly in esecuzione corrente. Non solo questo è un onere e facile dimenticare, cambiando le viste e contenuto statico richiede ricompilare l'applicazione — che uccide assolutamente la produttività. Pertanto, vi consiglio di non copiare il contenuto statico e viste nella directory di output; invece, configurare middleware come Nancy e il FileServer per mappare le cartelle di sviluppo.

Per impostazione predefinita, la directory di output di debug di un'applicazione console è bin/Debug, quindi nello sviluppo dire il FileServer a guardare due directory sopra la directory corrente per trovare la cartella statica contenente il foglio di stile Bootstrap:

FileSystem = new PhysicalFileSystem(IsDevelopment() ? 
  "
../../static" : "static")

Quindi, per dire a Nancy dove cercare visualizzazioni, creerò un NancyPathProvider personalizzato:

public class NancyPathProvider : IRootPathProvider
{
  public string GetRootPath()
  {
    return WebPipeline.IsDevelopment()
      ?
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, 
      @"..
\..
\")
      : AppDomain.CurrentDomain.BaseDirectory;
  }
}

Ancora una volta, io uso lo stesso controllo a guardare due directory sopra la directory di base se sono in esecuzione in modalità di sviluppo in Visual Studio. Ho lasciato l'implementazione di IsDevelopment fino a te; potrebbe essere un'impostazione di configurazione semplice oppure è possibile scrivere codice per rilevare quando l'applicazione è stata lanciata da Visual Studio.

Per registrare questo provider di percorso personalizzato radice, creo un NancyBootstrapper personalizzato e l'override della proprietà RootPathProvider predefinito per creare un'istanza di NancyPathProvider:

public class NancyBootstrapper : DefaultNancyBootstrapper
{
  protected override IRootPathProvider RootPathProvider
  {
    get { return new NancyPathProvider(); }
  }
}

E quando aggiungo Nancy alla pipeline OWIN, passare un'istanza di NancyBootstrapper nelle opzioni:

application.UseNancy(options => 
  options.Bootstrapper = new NancyBootstrapper());

Invio di messaggi

Ricezione dei messaggi è il lavoro di mezzo, ma l'applicazione ha ancora bisogno di un processo di inviare loro. Questo è un processo che tradizionalmente vivrebbe in un servizio isolato. In questa soluzione unificata posso semplicemente aggiungere un SmsSender che lancia all'avvio dell'applicazione. Io aggiungo al metodo Start Management (in un'applicazione reale, si deve aggiungere la possibilità di interrompere e di smaltire questa risorsa):

public void Start()
{
  WebApplication = WebApp.Start<WebPipeline>("http://localhost:5000");
  new SmsSender().Start();
}

All'interno del metodo Start SmsSender, iniziare un'attività di lunga durata per inviare messaggi:

public class SmsSender
{
  public void Start()
  {
    Task.Factory.StartNew(SendMessages, 
      TaskCreationOptions.LongRunning);
  }
}

Quando l'azione di invio WebApi riceve un messaggio, aggiunge a una coda che è una raccolta di blocca. Creare il metodo SendMessages di bloccare fino a quando arrivano i messaggi. Questo è possibile grazie alle astrazioni dietro GetConsumingEnumerable. Quando arriva una serie di messaggi, inizia immediatamente inviarli:

private static void SendMessages()
{
  foreach (var message in MessageQueue.Messages.GetConsumingEnumerable())
  {
    Console.WriteLine("Sending: " + message.Text);
  }
}

Sarebbe banale a girare su più istanze di SmsSender per espandere la capacità di inviare messaggi. In un'applicazione reale, volete passare un CancellationToken al GetConsumingEnumerable per interrompere in modo sicuro l'enumerazione. Se volete saperne di più su raccolte di blocco, troverete ottime informazioni a bit.ly/QgiCM7 e bit.ly/1m6sqlI.

Facile, ventilato distribuisce

Lo sviluppo di un servizio combinato e l'applicazione Web è piuttosto semplice e lineare, grazie alla Katana e Topshelf. Uno dei vantaggi di questa potente combinazione impressionanti è un processo di distribuzione ridicolmente semplice. Io vado a mostrarvi una distribuzione semplice in due passaggi utilizzando psake (github.com/psake/psake). Questo non è destinato a essere uno script robusto per uso produzione effettiva; Voglio solo dimostrare come veramente semplice il processo è, indipendentemente da quale strumento che si utilizza.

Il primo passo è quello di costruire l'applicazione. Creare un'attività di compilazione che chiamerà msbuild con il percorso alla soluzione e creare una build di rilascio (l'output finirà in bin/Release):

properties {
  $solution_file = "Sms.sln"
}
task build {
  exec { msbuild $solution_file /t:Clean /t:Build /p:Configuration=Release /v:q }
}

Il secondo passo è quello di distribuire l'applicazione come un servizio. Creo un'attività di distribuzione che dipende l'attività di compilazione e dichiarare una directory di consegna per contenere un tracciato il percorso di installazione. Per semplicità solo a distribuire in una directory locale. Quindi, a creare un'eseguibile variabile per scegliere l'applicazione console eseguibile nella directory consegna:

task deploy -depends build {
  $delivery_directory = "C:\delivery"
  $executable = join-path $delivery_directory 'Sms.exe'

In primo luogo, il compito di distribuire verificherà se esiste la directory di consegna. Se ne trova una directory di consegna, presupporrà che il servizio è già distribuito. In questo caso, l'attività di distribuzione disinstallare il servizio e rimuovere la directory di consegna:

if (test-path $delivery_directory) {
  exec { & $executable uninstall }
  rd $delivery_directory -rec -force 
}

Successivamente, il compito di distribuire copie della compilazione di output nella directory consegna a distribuire il nuovo codice e poi copia le viste e cartelle statiche nella directory consegna:

copy-item 'Sms\bin\Release' $delivery_directory -force -recurse -verbose
copy-item 'Sms\views' $delivery_directory -force -recurse -verbose
copy-item 'Sms\static' $delivery_directory -force -recurse –verbose

Infine, il compito di distribuire sarà installare e avviare il servizio:

exec { & $executable install start }

Quando si distribuisce il servizio, assicurarsi che l'implementazione IsDevelopment restituisce falsa o si otterrà un'eccezione accesso negato se il file server non riesce a trovare la cartella statica. Inoltre, a volte reinstallando il servizio su ogni distribuzione può essere problematico. Un'altra tattica è quella di interrompere, aggiornare e quindi avviare il servizio, se è già installato.

Come potete vedere, la distribuzione è ridicolmente semplice. IIS e InstallUtil vengono completamente rimossi dall'equazione; c'è un processo di distribuzione invece di due; e non è necessario preoccuparsi di come il Web e il servizio strato volontà comunicare. Questa attività di distribuzione può essere eseguita ripetutamente mentre si crea il tuo Web unificato e applicazione di servizio!

Novità future

Il modo migliore per determinare se questo modello combinato lavorerà per voi è quello di trovare un progetto a basso rischio e provarlo. È così semplice sviluppare e distribuire un'applicazione con questa architettura. Ci sta per essere una curva di apprendimento occasionale (se si utilizza ad esempio per MVC, Nancy). Ma la cosa grande circa usando OWIN, anche se l'approccio combinato non funzionasse, è ancora possibile ospitare la pipeline OWIN all'interno di IIS utilizzando l'host ASP.NET (Microsoft.Owin.Host.SystemWeb). Lo si può eseguire e verificarne il risultato.

Wes McClure sfrutta la sua esperienza per aiutare i clienti rapidamente offrire software di alta qualità a esponenzialmente aumenta il valore per i clienti creano. Gode di parlare di tutto ciò che riguarda lo sviluppo del software, è un autore Pluralsight e scrive sulle sue esperienze presso devblog.wesmcclure.com. Contattarlo al wes.mcclure@gmail.com.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Howard Dierking (Microsoft), Damian Hickey, Chris Patterson (RelayHealth), Chris Ross (Microsoft) e Travis Smith
Howard Dierking è un program manager del team di Windows Azure quadri e strumenti dove la sua attenzione è su ASP.NET, NuGet e Web API. In precedenza, Dierking servito il caporedattore di MSDN Magazine e corse anche lo sviluppatore programma di certificazione per Microsoft Learning. Ha trascorso 10 anni prima di Microsoft come un architetto, sviluppatore e applicazione, con un focus su sistemi distribuiti.

Chris Ross è un software design engineer presso Microsoft, dove si concentra su tutte le cose, networking e OWIN.

Chris Patterson è un architetto per RelayHealth, il business di connettività di McKesson Corporation ed è responsabile per l'architettura e lo sviluppo di applicazioni e servizi che accelerano la consegna di cura collegando pazienti, fornitori, istituti finanziari e farmacie. Chris è un contributore primario di Topshelf e MassTransit e ha ricevuto il premio di Most Valuable Professional di Microsoft per i suoi contributi tecnici comunitari.

Damian Hickey è uno sviluppatore di software con un focus su DDD\CQRS\ES applicazioni basate su. Egli è un sostenitore del software open source di .NET e contribuisce a vari progetti come Nancy, NEventStore e altri. Egli parla occasionalmente quando la gente si è preso la briga di ascoltare e occasionalmente al Blog http://dhickey.ie. Contattarlo al dhickey@gmail.com / @randompunter

Travis Smith è un sostenitore di sviluppatore per Atlassian e il Marketplace di Atlassian. Travis aiuta a promuovere un web aperto, polyglotism e le tecnologie emergenti del web. Travis è un collaboratore di un certo numero di progetti open source tra cui Topshelf e MassTransit. Travis si trova a eventi per sviluppatori parlando appassionatamente fare impressionante software o su Internet a http://travisthetechie.com.