Settembre 2017

Volume 32 Numero 9

Il presente articolo è stato tradotto automaticamente.

ASP.NET Core - App ASP.NET MVC più semplici con le pagine Razor

Da Steve Smith

Pagine Razor sono una nuova funzionalità di base di ASP.NET 2.0. Forniscono un modo più semplice per organizzare il codice all'interno delle applicazioni ASP.NET Core, mantenendo la logica e visualizzare i modelli più vicino di implementazione per il codice di implementazione di visualizzazione. Offrono anche un modo più semplice per iniziare lo sviluppo di App ASP.NET Core, ma ciò non significa che è necessario chiudere se si è sviluppatori .NET esperti. È inoltre possibile utilizzare pagine Razor per migliorare l'organizzazione dei più grandi e complesse applicazioni ASP.NET Core.

Il modello Model-View-Controller (MVC) è un modello di interfaccia utente avanzato che Microsoft ha supportato per lo sviluppo di applicazioni ASP.NET perché 2009. Offre una serie di vantaggi che consente agli sviluppatori di applicazioni di ottenere una separazione delle problematiche, risultante in software più gestibile. Sfortunatamente, il modello come implementato nei modelli di progetto predefiniti spesso causa di una notevole quantità di file e cartelle, che è possono aggiungere le forze di attrito allo sviluppo, in particolar modo un'applicazione aumenta. Nell'articolo settembre 2016, ho scritto sull'utilizzo di sezioni di funzionalità come uno degli approcci disponibili per risolvere il problema (msdn.com/magazine/mt763233). Pagine Razor offrono un modo di nuovo e diverso per affrontare questo problema, in particolare per gli scenari sono concettualmente basata su pagina. Questo approccio è particolarmente utile quando è una vista quasi statica o un form semplice in cui è necessario impostare solo per eseguire un POST-Redirect-GET. Questi scenari sono l'ideale per pagine Razor, evitare la convenzione necessaria per le applicazioni MVC sono disponibili numerose.

Introduzione a pagine Razor

Per iniziare a utilizzare le pagine Razor, è possibile creare una nuova applicazione Web di ASP.NET Core in Visual Studio con ASP.NET 2.0 Core e selezionare il modello di pagine Razor, come mostrato nella figura 1.

componenti di base di ASP.NET 2.0 applicazione Web con Razor pagine modello

Figura 1 componenti di base di ASP.NET 2.0 applicazione Web con Razor pagine modello

È possibile ottenere la stessa operazione dall'interfaccia della riga di comando (CLI) dotnet utilizzando:

razor nuovo dotnet

È necessario assicurarsi che si esegue almeno la versione 2.0 di .NET Core SDK; Verificare con:

dotnet - versione

In entrambi i casi, se si esamina il progetto generato, si noterà include una nuova cartella, pagine, come illustrato nella figura 2.

pagine Razor organizzazione del modello di progetto

Figura 2 pagine Razor organizzazione del modello di progetto

In particolare assente da questo modello sono due cartelle che sono in genere associate ai progetti MVC: Controller e visualizzazioni. Pagine Razor utilizzano cartella delle pagine per contenere tutte le pagine dell'applicazione. È possibile utilizzare le cartelle all'interno della cartella radice di pagine per organizzare le pagine in modo significativo per l'applicazione. Pagine Razor consentono agli sviluppatori di combinare le funzionalità di qualità del codice del modello MVC i vantaggi di produttività di raggruppamento di elementi che tendono a cambiare insieme.

Si noti che le pagine fa parte di ASP.NET MVC di base nella versione 2. È possibile aggiungere il supporto per le pagine a qualsiasi app ASP.NET MVC Core semplicemente aggiungendo una cartella e aggiunta di file di pagine Razor in questa cartella.

Pagine Razor utilizzano la struttura di cartelle come una convenzione per il routing delle richieste. Mentre la pagina in una tipica applicazione MVC predefinita può essere trovato in "/," nonché "Home /" e "Home/Index," la pagina di indice predefinito in un'applicazione utilizzando le pagine Razor corrisponderanno "/" e "/ indice". Utilizzando le sottocartelle, è molto intuitivo per creare varie sezioni della tua app, con route corrispondenti di conseguenza. Ogni cartella può disporre di un file cshtml come relativa pagina radice.

Ricerca in una singola pagina, sono disponibili è presente una direttiva di pagina nuovi, @page, che è necessaria per pagine Razor. Questa direttiva deve essere visualizzato nella prima riga del file di paging, che deve utilizzare l'estensione. cshtml. Pagine Razor aspetto e comportano molto simile ai file di visualizzazione in base Razor e una semplice pagina può includere solo HTML:

@page
<h1>Hello World</h1>

In cui le pagine Razor caratterizzano è incapsulamento e raggruppare i dettagli dell'interfaccia utente. Pagine Razor supportano inline o modelli di pagina basate su classi separata, che possono rappresentare la pagina consente di visualizzare o modificare gli elementi di dati. Supportano inoltre i gestori che eliminano la necessità di separato controller e metodi di azione. Queste funzionalità riducono notevolmente il numero di cartelle e file necessari per l'utilizzo con una determinata pagina in un'app Web. Figura 3 confronta le cartelle e i file necessari per un approccio basato su MVC tipico con l'approccio pagine Razor.

MVC cartelle e file di Visual Studio. Pagine Razor

Figura 3 MVC cartelle e file di Visual Studio. Pagine Razor

Per dimostrare pagine Razor nel contesto di un'applicazione ASP.NET MVC di base, verrà di utilizzare un progetto di esempio semplice.

Un progetto di esempio

Per simulare un progetto con un minimo di complessità e alcune aree funzionali diverse, verrà restituito per l'esempio che è usato per l'articolo funzionalità sezioni. In questo esempio comporta la visualizzazione e gestione di un numero di tipi diversi di entità, inclusi ninjas e spade avanzato, nonché pirates, piante e zombie. Si supponga l'app è correlato di un gioco casuale e consente di gestire i costrutti di gioco. Utilizza l'approccio azienda tipico di MVC, sarebbe probabilmente necessario molte cartelle diverse, che contiene i controller, visualizzazioni, ViewModel e più elevato per ognuno di questi tipi di costrutti. Con pagine Razor, è possibile creare una gerarchia di cartelle semplice che esegue il mapping alla struttura dell'URL dell'applicazione.

In questo caso, l'applicazione dispone di una home page semplice e quattro sezioni, ognuna con una sottocartella in pagine. La struttura di cartelle è molto pulita, con solo la home page (cshtml) e alcuni file di supporto nella radice della cartella delle pagine e le altre sezioni nelle rispettive cartelle, come figura 4 Mostra.

organizzazione della cartella con pagine Razor

Figura 4 organizzazione della cartella con pagine Razor

Pagine semplici non spesso necessario separare i modelli di pagina. Ad esempio, l'elenco di spade avanzato nel /Ninjas/Swords/Index.cshtml semplicemente utilizza variabili inline, come figura 5 Mostra.

Figura 5 utilizzando variabili di Inline

@page
@{ 
  var swords = new List<string>()
  {
    "Katana",
    "Ninjago"
  };
}
<h2>Ninja Swords</h2>
<ul>
  @foreach (var item in swords)
  {
    <li>@item</li>
  }
</ul>
<a asp-page="/Ninjas/Index">Ninja List</a>

Le variabili dichiarate in blocchi Razor sono nell'ambito della pagina. verrà visualizzata come è possibile dichiarare funzioni e persino classi tramite blocchi @functions nella sezione successiva. Si noti l'utilizzo dell'helper nuovo tag pagina asp nel collegamento nella parte inferiore della pagina. Questi helper di tag pagine di riferimento per i cicli di lavorazione e supportano i percorsi assoluti e relativi. In questo esempio, "Ninjas/indice"potrebbe inoltre essere stato scritto come"... / Indice "o semplicemente"... "e indirizza alla stessa pagina Razor cshtml nella cartella Ninjas. È inoltre possibile utilizzare l'helper di tag della pagina asp per gli elementi < form > per specificare la destinazione del modulo. Poiché gli helper di tag della pagina asp compilare sopra il supporto del routing di ASP.NET Core potente, supportano molti scenari di generazione di URL oltre semplice URL relativi.

Modelli di pagina

Pagine Razor possono supportare i modelli di pagina fortemente tipizzata. Specificare il modello per una pagina Razor con la direttiva @model (come una visualizzazione fortemente tipizzata di MVC). È possibile definire il modello all'interno del file di pagina Razor, come illustrato nella figura 6.

Figura 6 la definizione del modello

@page
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@model IndexModel
@functions
{
  public class IndexModel : PageModel
  {
    private readonly IRepository<Zombie> _zombieRepository;
       
    public IndexModel(IRepository<Zombie> zombieRepository)
    {
      _zombieRepository = zombieRepository;
    }
    // additional code omitted
  }
}

È inoltre possibile definire il modello di pagina in un file il codebehind separato denominato Pagename.cshtml.cs. In Visual Studio, i file che seguono questa convenzione sono collegati i relativi file di pagina corrispondente, semplificando per spostarsi tra di esse. Lo stesso codice mostrato nel blocco di @functions nella figura 6 potrebbe trovarsi in un file separato.

Esistono vantaggi e svantaggi per entrambi gli approcci per l'archiviazione dei modelli di pagina. Modello di pagina logica all'interno della pagina Razor stesso comporta un minor numero di file e consente la flessibilità di compilazione di runtime, pertanto è possibile effettuare aggiornamenti per la logica della pagina senza la necessità di una distribuzione completa dell'app. D'altra parte, Impossibile individuare gli errori di compilazione nei modelli di pagina definiti all'interno di pagine Razor fino al runtime. Visual Studio visualizza gli errori nel file Razor aperti (senza doverli effettivamente). Esecuzione del comando di compilazione dotnet non compilazione delle pagine Razor o le informazioni sui potenziali errori di questi file.

Le classi di modello di pagina separate offrono leggermente migliore separazione delle problematiche, perché la pagina Razor possono concentrarsi esclusivamente sul modello per la visualizzazione dei dati, lasciando il modello di pagina separata per gestire la struttura di dati della pagina e i gestori corrispondenti. I modelli di pagina il codebehind separato anche trarre vantaggio dal controllo degli errori in fase di compilazione e sono più semplici lo unit test rispetto ai modelli di pagina inline. Infine, è possibile scegliere se utilizzare alcun modello, un modello inline o modelli di pagina separata in pagine Razor.

Routing, modello di associazione e i gestori eventi

Due funzionalità fondamentali di MVC che in genere si trovano all'interno di classi Controller esegue il routing e associazione del modello. La maggior parte delle applicazioni ASP.NET MVC di base utilizzano gli attributi per definire le route, i verbi HTTP e i parametri di route, utilizzando la sintassi seguente:

[HttpGet("{id}")]
public Task<IActionResult> GetById(int id)

Come indicato in precedenza, il percorso della route per pagine Razor è basato sulle convenzioni e corrisponde il percorso della pagina all'interno della gerarchia di cartelle del browser. Tuttavia, è possibile supportare i parametri di route aggiungendoli alla direttiva @page. Anziché specificare i verbi HTTP supportati utilizzando gli attributi, le pagine Razor utilizzare gestori che seguono una convenzione di denominazione di OnVerb, dove verbo è un verbo HTTP come Get, Post e così via. I gestori di pagina Razor si comportano in modo molto simile alle azioni del Controller MVC e usano l'associazione di modelli per popolare i parametri che definiscono. Figura 7 viene illustrato un esempio di pagina Razor che utilizza route parametri, l'inserimento di dipendenze e un gestore per visualizzare i dettagli di un record.

Figura 7 Details.cshtml—Displaying dettagli per un determinato Id di Record

public async Task OnGetAsync()
{
  Ninjas = _ninjaRepository.List()
    .Select(n => new NinjaViewModel { Id = n.Id, Name = n.Name }).ToList();
}

public async Task<IActionResult> OnPostAddAsync()
{
  var entity = new Ninja()
  {
    Name = "Random Ninja"
  };
_  ninjaRepository.Add(entity);

  return RedirectToPage();
}

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
  var entityToDelete = _ninjaRepository.GetById(id);
_ ninjaRepository.Delete(entityToDelete);

  return RedirectToPage();
}
@page "{id:int}"
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@inject IRepository<Ninja> _repository

@functions {
  public Ninja Ninja { get; set; }

  public IActionResult OnGet(int id)
  {
    Ninja = _repository.GetById(id);

    // A void handler (no return) is equivalent to return Page()
    return Page();
  }
}
<h2>Ninja: @Ninja.Name</h2>
<div>
    Id: @Ninja.Id
</div>
<div>
    <a asp-page="..">Ninja List</a>
</div>

Pagine possono supportare più gestori, pertanto è possibile definire OnGet, OnPost e così via. Pagine Razor anche introducono un nuovo modello di associazione attributo [BindProperty], che risulta particolarmente utile nei form. È possibile applicare questo attributo a una proprietà in una pagina Razor (con o senza un PageModel esplicita) per acconsentire esplicitamente l'associazione dati per le richieste GET non alla pagina. In questo modo gli helper di tag come asp-per la convalida per asp per funzionare con la proprietà è specificato e consente ai gestori eventi lavorare con le proprietà associate senza doverli specificare come parametri di metodo. L'attributo [BindProperty] funziona anche sui controller.

Figura 8 viene mostrata una pagina Razor che consente agli utenti di aggiungere nuovi record all'applicazione.

Figura 8 New.cshtml—Adds un impianto di nuovo

@page
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@inject IRepository<Plant> _repository

@functions {
  [BindProperty]
  public Plant Plant { get; set; }

  public IActionResult OnPost()
  {
    if(!ModelState.IsValid) return Page();

    _repository.Add(Plant);

    return RedirectToPage("./Index");
  }
}
<h1>New Plant</h1>
<form method="post" class="form-horizontal">
  <div asp-validation-summary="All" class="text-danger"></div>
  <div class="form-group">
    <label asp-for="Plant.Name" class="col-md-2 control-label"></label>
    <div class="col-md-10">
      <input asp-for="Plant.Name" class="form-control" />
      <span asp-validation-for="Plant.Name" class="text-danger"></span>
    </div>
  </div>
  <div class="form-group">
    <div class="col-md-offset-2 col-md-10">
      <button type="submit" class="btn btn-primary">Save</button>
    </div>
  </div>
</form>
<div>
  <a asp-page="./Index">Plant List</a>
</div>

È piuttosto comune disporre di una pagina che supporta più di un'operazione utilizzando lo stesso verbo HTTP. Ad esempio, la pagina principale dell'esempio supporta elencare le entità (comportamento predefinito GET), nonché la possibilità di eliminare una voce o aggiungere una nuova voce (entrambi come le richieste POST). Pagine Razor supportano questo scenario di utilizzo dei gestori denominati, illustrati nella figura 9, che includono il nome dopo il verbo (ma prima del suffisso "Async", se presente). Il tipo di base PageModel è simile alla base Controller tipo in quanto fornisce un numero di metodi di supporto, è possibile utilizzare per la restituzione di risultati dell'azione. Quando si eseguono aggiornamenti, ad esempio l'aggiunta di un nuovo record, è spesso necessario reindirizzare l'utente immediatamente dopo l'operazione, se ha esito positivo. In questo modo si evita il problema dei duplicati chiamate al server di attivazione, di conseguenza i record duplicati (o peggiorando), gli aggiornamenti di browser. È possibile utilizzare RedirectToPage senza argomenti per il reindirizzamento al gestore GET predefinito della pagina Razor corrente.

Figura 9 denominato gestori

public async Task OnGetAsync()
{
  Ninjas = _ninjaRepository.List()
    .Select(n => new NinjaViewModel { Id = n.Id, Name = n.Name }).ToList();
}

public async Task<IActionResult> OnPostAddAsync()
{
  var entity = new Ninja()
  {
    Name = "Random Ninja"
  };
_  ninjaRepository.Add(entity);

  return RedirectToPage();
}

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
  var entityToDelete = _ninjaRepository.GetById(id);
_ ninjaRepository.Delete(entityToDelete);

  return RedirectToPage();
}

È possibile specificare un gestore denominato utilizzando l'helper di tag di gestore di pagina asp, applicato a un form, un collegamento o un pulsante:

<a asp-page-handler="Handler">Link Text</a>
<button type="submit" asp-page-handler="delete" asp-route-id="@id">Delete</button>

Il tag di gestore di pagina asp utilizza il routing per compilare l'URL. Per impostazione predefinita, il nome del gestore e degli attributi di parametro di route asp vengono applicati come valori di stringa di query. Pulsante Elimina nel codice precedente genera un URL simile alla seguente:

Ninjas? gestore = delete & id = 1

Se invece si desidera utilizzare il gestore come parte dell'URL, è possibile specificare questo comportamento con la direttiva @page:

@page "{gestore?} / {id}? "

Con questa route specificato, il collegamento generato per il pulsante Elimina è:

Ninjas/Delete/1

Filtri

I filtri sono un'altra potente funzionalità di ASP.NET MVC di base (e l'altra trattate nel numero di agosto 2016: msdn.microsoft.com/mt767699). Se si utilizza un modello di pagina in un file separato, è possibile utilizzare i filtri basati sull'attributo con pagine Razor, tra cui inserire gli attributi di filtro della classe di modello di pagina. In caso contrario, è possibile specificare filtri globali ancora quando si configura MVC per l'app. Uno degli usi più comuni dei filtri consiste nello specificare criteri di autorizzazione all'interno dell'app. È possibile configurare criteri di autorizzazione basata su pagina e cartelle a livello globale:

services.AddMvc()
  .AddRazorPagesOptions(options =>
  {
    options.Conventions.AuthorizeFolder("/Account/Manage");
    options.Conventions.AuthorizePage("/Account/Logout");
    options.Conventions.AllowAnonymousToPage("/Account/Login");
  });

È possibile utilizzare tutti i tipi di filtri esistenti con pagine Razor, ad eccezione di filtri azione, che si applicano solo ai metodi di azione all'interno del controller. Pagine Razor introducono anche il nuovo filtro di pagina, rappresentato da IPageFilter (o IAsyncPageFilter). Questo filtro consente di aggiungere il codice che verrà eseguito dopo che è stato selezionato un gestore di pagina specifica oppure prima o dopo un gestore di esecuzione del metodo. Il primo metodo può essere utilizzato per modificare il gestore viene utilizzato per gestire una richiesta, ad esempio:

public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
  context.HandlerMethod = 
    context.ActionDescriptor.HandlerMethods.First(m => m.Name == "Add");
}

Dopo aver selezionato un gestore, si verifica l'associazione del modello. Dopo l'associazione del modello, viene chiamato il metodo OnPageHandlerExecuting eventuali filtri di pagina. Questo metodo può accedere e modificare tutti i dati disponibili per il gestore di associazione a modello e possono corto circuito, la chiamata al gestore. Il metodo OnPageHandlerExecuted quindi viene chiamato dopo l'esecuzione del gestore, ma prima dell'azione di esecuzione del risultato.

Concettualmente, i filtri di pagina sono molto simili ai filtri azione, che vengono eseguiti prima e dopo le azioni eseguite.

Si noti che un filtro, ValidateAntiforgeryToken, non è affatto necessario per pagine Razor. Questo filtro viene utilizzato per proteggersi da attacchi di Cross-Site Request Forgery (CSRF o XSRF), ma questa protezione viene creata automaticamente in pagine Razor.

Modello architetturale

Pagine Razor fanno parte di ASP.NET MVC di base e usufruire di molte funzionalità incorporate di ASP.NET MVC di base quali routing, l'associazione di modelli e i filtri. Condividono alcune denominazione somiglianza con la funzionalità di pagine Web che Microsoft fornito con WebMatrix 2010. Tuttavia, mentre le pagine Web principalmente destinata agli sviluppatori Web richiedente e sono stati di poco interesse per gli sviluppatori più esperti, pagine Razor combinare architettura sicuro con approachability.

L'architettura pagine Razor non seguono il modello Model-View-Controller (MVC), in quanto dispongono di controller. Piuttosto, pagine Razor seguire più di un modello Model-View-ViewModel (MVVM) che è necessario conoscere per molti sviluppatori di app nativa. È anche possibile considerare pagine Razor per un esempio di modello Page Controller, che descrive Martin Fowler "un oggetto che gestisce una richiesta per una pagina specifica o un'azione su un sito Web. Tale [object] sia la pagina stessa, o potrebbe essere un oggetto separato che corrisponde a tale pagina." Naturalmente, il criterio di Page Controller deve anche essere noto a chiunque ha collaborato con Web Form ASP.NET, poiché si tratta di funzionamento nelle pagine ASP.NET originale nonché.

A differenza di Web Form ASP.NET, pagine Razor sono compilate su ASP.NET Core e il supporto di associazione separati, separazione delle problematiche e principi a tinta unita. Pagine Razor sono facilmente unit testata (se si utilizzano classi separate di PageModel) e può fornire una base per le applicazioni aziendali pulita e gestibile. Non registrare pagine Razor come solo una funzionalità di "training wheels" destinata a programmatori dilettanti. Assegnare un aspetto serio di pagine Razor e considerare se pagine Razor (singolarmente o in combinazione con le pagine di Controller e visualizzazione tradizionale) può migliorare la progettazione dell'applicazione ASP.NET di base, riducendo il numero di cartelle è necessario passare da quando si utilizza un particolare funzionalità.

Migrazione

Sebbene le pagine Razor non seguono il modello MVC, ma sono così strettamente compatibile con il controller di MVC ASP.NET Core e viste che il passaggio tra uno e l'altro è in genere molto semplice esistenti. Per eseguire la migrazione delle pagine esistenti basate sul Controller/visualizzazione per utilizzare le pagine Razor, seguire questi passaggi:

  1. Copiare il file di visualizzazione Razor nella posizione desiderata nella cartella browser.
  2. Aggiungere la direttiva @page alla visualizzazione. Se si tratta di una visualizzazione sola operazione è terminata.
  3. Aggiungere un file di PageModel denominato viewname.cshtml.cs e lo inserisce nella cartella con la pagina Razor.
  4. Se la vista dispone di un elemento ViewModel, copiarlo in un file PageModel.
  5. Copiare eventuali azioni associate alla visualizzazione da un Controller per la classe PageModel.
  6. Rinominare le azioni che utilizzino la sintassi di gestore pagine Razor (ad esempio, "OnGet").
  7. Sostituire i riferimenti ai metodi di supporto di visualizzazione con i metodi di pagina.
  8. Copiare il PageModel qualsiasi codice di attacco intrusivo nel codice del costruttore dipendenza dal Controller.
  9. Sostituire il modello di codice passando a viste con una proprietà di PageModel [BindProperty].
  10. Sostituire gli oggetti del modello di visualizzazione con una proprietà [BindProperty], nonché di accettare parametri di metodo di azione.

Un'applicazione MVC corretto factoring è spesso contenuti file diversi per le visualizzazioni, controller, ViewModel e i modelli di associazione, in genere ogni in cartelle separate nel progetto. Pagine Razor consentono di consolidare questi concetti in un paio di file collegati, in un'unica cartella, consentendo al codice di seguire la separazione logica delle problematiche.

Dovrebbe essere possibile invertire questa procedura per spostarsi da un'implementazione Razor pagine un approccio basate sul Controller/visualizzazione, nella maggior parte dei casi. La procedura seguente dovrebbe funzionare per la maggior parte delle azioni basate su MVC semplice e viste. Applicazioni più complesse possono richiedere passaggi aggiuntivi e risoluzione dei problemi.

Passaggi successivi

L'esempio include quattro versioni dell'applicazione libreria NinjaPiratePlantZombie, con supporto per l'aggiunta e la visualizzazione di ogni tipo di dati. Nell'esempio viene illustrato come organizzare un'app con diverse aree funzionali distinte con MVC tradizionale, MVC con aree, MVC porzioni di funzionalità e le pagine Razor. Questi diversi approcci di esplorare e visualizzare quelle che funzionano meglio nelle proprie applicazioni ASP.NET Core. Il codice di origine aggiornati per questo esempio è disponibile all'indirizzo bit.ly/2eJ01cS.


Steve Smith è indipendente dal sistema di training, mentor e consulente. Ha un destinatario di premio 14 ora MVP di Microsoft e collabora a stretto contatto con più team di prodotto Microsoft. È possibile contattarlo ardalis.com o su Twitter: @ardalis se il team si sta occupando un eventuale passaggio a ASP.NET di base o se si sta cercando di adottare migliori procedure consigliate.

Grazie per il seguente esperto tecnico di Microsoft per la revisione dell'articolo: Ryan Nowak


Viene illustrato in questo articolo nel forum di MSDN Magazine