Dicembre 2016

Volume 31 Numero 13

Il presente articolo è stato tradotto automaticamente.

Roslyn - Generare JavaScript con Roslyn e i modelli T4

Da Nick Harrison | Dicembre 2016

L'altro giorno mia figlia ha riferito una battuta su una conversazione tra un dispositivo smartphone e un telefono cellulare. È simile al seguente: Cosa ha lo smartphone a telefono funzionalità? "Sono dal futuro, è possibile comprendere me?" Talvolta sembra simile quando imparare qualcosa di nuovo e all'avanguardia. Roslyn si trova in futuro e può essere difficile da comprendere inizialmente.

In questo articolo, mi occuperò Roslyn in modo che non vengano più lo stato attivo come merita. Mi concentrerò sull'uso di Roslyn come un'origine dei metadati per la generazione di JavaScript con T4. Questo utilizzerà l'API dell'area di lavoro, alcune delle API di sintassi, l'API di simboli e un modello della fase di esecuzione dal motore di T4. L'effettivo JavaScript generato è secondario per comprendere i processi utilizzati per raccogliere i metadati.

Poiché Roslyn fornisce inoltre alcune opzioni utili per la generazione di codice, si potrebbe pensare che le due tecnologie potrebbero essere in conflitto e non funzionino bene insieme. Tecnologie spesso conflitti quando le sandbox si sovrappongono, ma queste due tecnologie possono riprodurre insieme piuttosto correttamente.

Attendere che cos'è T4?

Se si conosce T4, un e-book 2015 dalla Syncfusion sinteticamente serie, "T4 sinteticamente" fornisce tutte le informazioni necessarie (bit.ly/2cOtWuN).

Per ora, la cosa principale da sapere è che T4 è toolkit di trasformazione di Microsoft basata sul modello di testo. Feed di metadati per il modello e il testo viene visualizzato il codice desiderato. In realtà, non è limitata al codice. È possibile generare qualsiasi tipo di testo, ma il codice sorgente è riportato l'output più comune. È possibile generare HTML, SQL, documentazione di testo, Visual Basic .NET, c# o qualsiasi output basato su testo.

Esaminare figura 1. Viene illustrato un semplice programma di applicazione Console. In Visual Studio, aggiunto un nuovo modello di testo di runtime denominato AngularResourceService.tt. Il codice del modello genera automaticamente codice c# che verrà implementato il modello in fase di esecuzione, è possibile visualizzare nella finestra della console.

Utilizzo di T4 per la generazione di codice in fase di progettazione
Figura 1 utilizzo T4 per la generazione di codice in fase di progettazione

In questo articolo, mostrerò come usare Roslyn per raccogliere i metadati da un progetto API Web al feed di T4 per generare una classe JavaScript e quindi usare Roslyn per aggiungere tale JavaScript nuovamente alla soluzione.

Concettualmente, il flusso del processo sarà simile a figura 2.

T4 Flusso del processo
Figura 2 flusso del processo T4

Roslyn feed T4

Generazione di codice è un processo intensivo di metadati. È necessario che i metadati descrivono il codice che si desidera generare. Reflection, modello di codice e il dizionario dei dati sono generalmente dovute a metadati disponibili. Roslyn può fornire tutti i metadati che potrebbe aver ricevuto dal Reflection o il modello di codice senza alcuni dei problemi di che questi altri approcci comportano.

In questo articolo, utilizzerò Roslyn per trovare le classi derivate da ApiController. Utilizzerò il modello T4 per creare una classe JavaScript per ogni Controller ed esporre un metodo per ogni azione e una proprietà per ogni proprietà nel ViewModel associati al Controller. Il risultato sarà simile al codice in figura 3.

Figura 3 risultato dell'esecuzione del codice

var app = angular.module("challenge", [ "ngResource"]);
  app.factory(ActivitiesResource , function ($resource) {
    return $resource(
      'http://localhost:53595//Activities',{Activities : '@Activities'},{
    Id : "",
    ActivityCode : "",
    ProjectId : "",
    StartDate : "",
    EndDate : "",
  , get: {
      method: "GET"
    }
  , put: {
      method: "PUT"
    }
  , post: {
      method: "POST"
    }
  , delete: {
      method: "DELETE"
    }
  });
});

La raccolta dei metadati

Iniziare la raccolta dei metadati tramite la creazione di un nuovo progetto applicazione console in Visual Studio 2015. In questo progetto verrà avere una classe dedicato per la raccolta dei metadati con Roslyn, nonché un modello T4. Si tratterà di un modello di runtime che consentono di generare un codice JavaScript in base ai metadati raccolti.

Una volta creato il progetto, vengono emessi i comandi seguenti dalla Console di gestione pacchetti:

Install-Package Microsoft.CodeAnalysis.CSharp.Workspaces

In questo modo il codice più recente di Roslyn per il compilatore CSharp e vengono utilizzati i servizi correlati.

È sufficiente posizionare il codice per i vari metodi in una nuova classe denominata RoslynDataProvider. Si farà riferimento a questa classe in tutto l'articolo e deve essere un utile riferimento ogni volta che si desidera raccogliere i metadati con Roslyn.

Utilizzare il MSBuildWorksspace per ottenere un'area di lavoro che fornirà tutte il contesto necessario per la compilazione. Una volta ottenuto la soluzione, è possibile facilmente esaminate i progetti per il progetto API Web:

private Project GetWebApiProject()
{
  var work = MSBuildWorkspace.Create();
  var solution = work.OpenSolutionAsync(PathToSolution).Result;
  var project = solution.Projects.FirstOrDefault(p =>
    p.Name.ToUpper().EndsWith("WEBAPI"));
  if (project == null)
    throw new ApplicationException(
      "WebApi project not found in solution " + PathToSolution);
  return project;
}

Se si segue una convenzione di denominazione diversa, è possibile incorporarlo facilmente nelle GetWebApiProject per trovare il progetto in cui si è interessati.

Ora che conosco il progetto in cui desidera lavorare, è necessario ottenere la compilazione per il progetto, nonché un riferimento al tipo da utilizzare per identificare i controller di interesse. La compilazione è necessario perché si utilizzerà il SemanticModel per determinare se una classe derivata da System.Web.Http.ApiController. Dal progetto, otterrò i documenti inclusi nel progetto. Ogni documento è un file separato, che potrebbe includere più di una dichiarazione di classe, sebbene sia consigliabile migliore esclusivamente per includere una singola classe in qualsiasi file e il nome del file corrisponde il nome della classe. ma non tutti gli utenti segue questo standard contemporaneamente.

Individuare i controller

Figura 4 viene illustrato come trovare tutte le dichiarazioni di classe in ogni documento e determinare se la classe è derivata da ApiController.

Figura 4 ricerca i controller in un progetto

public IEnumerable<ClassDeclarationSyntax> FindControllers(Project project)
{
  compilation = project.GetCompilationAsync().Result;
  var targetType = compilation.GetTypeByMetadataName(
    "System.Web.Http.ApiController");
  foreach (var document in project.Documents)
  {
    var tree = document.GetSyntaxTreeAsync().Result;
    var semanticModel = compilation.GetSemanticModel(tree);
    foreach (var type in tree.GetRoot().DescendantNodes().
      OfType<ClassDeclarationSyntax>()
      .Where(type => GetBaseClasses(semanticModel, type).Contains(targetType)))
    {
      yield return type;
    }
  }
}

Poiché la compilazione ha accesso a tutti i riferimenti necessari per compilare il progetto, non avrà alcun problema di risoluzione del tipo di destinazione. Quando si ottiene l'oggetto di compilazione, è iniziata la compilazione del progetto, ma sto interrotta le tramite una volta ottenuto i dettagli per ottenere i metadati necessari.

Figura 5 Mostra il metodo GetBaseClasses che esegue il lavoro sporco per determinare se la classe corrente deriva dalla classe di destinazione. Questa operazione un maggiore carico di elaborazione maggiore effettivamente necessario. Per determinare se una classe è derivata da ApiController, che non interessa veramente le interfacce implementate lungo il percorso, ma includendo i dettagli, questo diventa un metodo di utilità che può essere utilizzato in un'ampia gamma di posizioni.

Figura 5 individuazione di interfacce e classi Base

public static IEnumerable<INamedTypeSymbol> GetBaseClasses
  (SemanticModel model, BaseTypeDeclarationSyntax type)
{
  var classSymbol = model.GetDeclaredSymbol(type);
  var returnValue = new List<INamedTypeSymbol>();
  while (classSymbol.BaseType != null)
  {
    returnValue.Add(classSymbol.BaseType);
    if (classSymbol.Interfaces != null)
      returnValue.AddRange(classSymbol.Interfaces);
    classSymbol = classSymbol.BaseType;
  }
  return returnValue;
}

Questo tipo di analisi Ottiene complicato con la Reflection perché un approccio riflettente si basa su ricorsione e potenzialmente la necessità di dover caricare qualsiasi numero di assembly lungo il percorso per ottenere l'accesso a tutti i tipi coinvolti. Questo tipo di analisi non è possibile anche con il modello di codice, ma è relativamente semplice roslyn utilizzando l'elemento SemanticModel. Elemento SemanticModel è un vero e proprio Tesoro di metadati. rappresenta tutto che il compilatore conoscenza del codice dopo aver superato la necessità di associazione di alberi della sintassi per i simboli. Oltre a individuare i tipi di base, può essere utilizzato per rispondere a domande difficili come la risoluzione dell'overload/sostituzione o l'individuazione di tutti i riferimenti a un metodo o proprietà o un simbolo.

Trovare il modello associato

A questo punto, è possibile accedere a tutti i controller nel progetto. Nella classe JavaScript, è inoltre utile per esporre le proprietà presenti nei modelli restituiti dalle azioni del controller. Per comprendere il funzionamento, esaminiamo il codice seguente, che mostra l'output ottenuto eseguendo lo scaffolding per un'API Web:

public class Activity
  {
    public int Id { get; set; }
    public int ActivityCode { get; set; }
    public int ProjectId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
  }

In questo caso, è stato eseguito lo scaffolding sui modelli, come illustrato nella figura 6.

Figura 6 Controller API generata

public class ActivitiesController : ApiController
  {
    private ApplicationDbContext db = new ApplicationDbContext();
    // GET: api/Activities
    public IQueryable<Activity> GetActivities()
    {
      return db.Activities;
    }
    // GET: api/Activities/5
    [ResponseType(typeof(Activity))]
    public IHttpActionResult GetActivity(int id)
    {
      Activity activity = db.Activities.Find(id);
      if (activity == null)
      {
        return NotFound();
      }
      return Ok(activity);
    }
    // POST: api/Activities
    [ResponseType(typeof(Activity))]
    public IHttpActionResult PostActivity(Activity activity)
    {
      if (!ModelState.IsValid)
      {
        return BadRequest(ModelState);
      }
      db.Activities.Add(activity);
      db.SaveChanges();
      return CreatedAtRoute("DefaultApi", new { id = activity.Id }, activity);
    }
    // DELETE: api/Activities/5
    [ResponseType(typeof(Activity))]
    public IHttpActionResult DeleteActivity(int id)
    {
      Activity activity = db.Activities.Find(id);
      if (activity == null)
      {
        return NotFound();
      }
      db.Activities.Remove(activity);
      db.SaveChanges();
      return Ok(activity);
    }

L'attributo ResponseType aggiunto alle azioni consente di collegare l'elemento ViewModel il Controller. Utilizzo di questo attributo, è possibile ottenere il nome del modello associato all'azione. Fino a quando il Controller è stato creato usando lo scaffolding, quindi ogni azione sarà associata con lo stesso modello, ma i controller creati manualmente o modificate dopo potrebbe non essere coerente in modo da generare. Figura 7 viene illustrato come confrontare tutte le azioni per ottenere un elenco completo dei modelli di cui è associata a un Controller in caso di più.

Figura 7 ricerca i modelli associati a un Controller

public IEnumerable<TypeInfo> FindAssociatedModel
  (SemanticModel semanticModel, TypeDeclarationSyntax controller)
{
  var returnValue = new List<TypeInfo>();
  var attributes = controller.DescendantNodes().OfType<AttributeSyntax>()
    .Where(a => a.Name.ToString() == "ResponseType");
  var parameters = attributes.Select(a =>
    a.ArgumentList.Arguments.FirstOrDefault());
  var types = parameters.Select(p=>p.Expression).OfType<TypeOfExpressionSyntax>();
  foreach (var t in types)
  {
    var symbol = semanticModel.GetTypeInfo(t.Type);
    if (symbol.Type.SpecialType == SpecialType.System_Void) continue;
    returnValue.Add( symbol);
  }
  return returnValue.Distinct();
}

È interessa logica succedendo in questo metodo. alcune di esse 's piuttosto complesso. Tenere presente l'attributo ResponseType l'aspetto seguente:

[ResponseType(typeof(Activity))]

Si desidera accedere alle proprietà nel tipo di cui viene fatto riferimento nel tipo di espressione, ovvero il primo parametro all'attributo, ovvero in questo caso, l'attività. La variabile di attributi è un elenco degli attributi ResponseType trovato nel controller. La variabile di parametri è un elenco di parametri a questi attributi. Ciascuno di questi parametri sarà un TypeOfExpressionSyntax e ottenere il tipo associato tramite la proprietà del tipo degli oggetti TypeOfExpressionSyntax. Elemento SemanticModel ancora una volta, viene utilizzato per inserire il simbolo per quel tipo, che offrirà a tutti i dettagli che potrebbe desiderare.

Alla fine del metodo DISTINCT verrà assicurarsi che ogni modello restituito sia univoco. Nella maggior parte dei casi, si aspetta abbia duplicati poiché più azioni del controller da associare con lo stesso modello. È inoltre consigliabile verificare il ResponseType essere void. Non disponibili tutte le proprietà interessante.

Esaminare il modello associato

Il codice seguente viene illustrato come trovare le proprietà di tutti i modelli disponibili nel Controller:

public IEnumerable<ISymbol> GetProperties(IEnumerable<TypeInfo> models)
{
  return models.Select(typeInfo => typeInfo.Type.GetMembers()
    .Where(m => m.Kind == SymbolKind.Property))
    .SelectMany(properties => properties).Distinct();
}

Le azioni di individuazione

Oltre a illustrare le proprietà da modelli associati, si desidera includere i riferimenti ai metodi presenti nel Controller. I metodi del Controller sono azioni. Desidero solo i metodi pubblici, e poiché si tratta di azioni di API Web, questi devono tutti essere convertiti nel verbo HTTP appropriato.

Esistono un paio di convenzioni differenti per la gestione di questo mapping. Il seguito dallo scaffolding è per il nome del metodo iniziare con il nome del verbo. Il metodo put sarebbe PutActivity, il metodo post sarebbe PostActivity, il metodo delete sarebbe DeleteActivity e in genere sono che due metodi get: GetActivity e GetActivities. È possibile indicare la differenza tra i metodi get esaminando i tipi restituiti per questi metodi. Se il tipo restituito direttamente o indirettamente implementa l'interfaccia IEnumerable, il metodo get è ottenere tutti; in caso contrario, è il metodo singolo elemento get.

L'altro approccio è che aggiungere in modo esplicito gli attributi per specificare il verbo, quindi il metodo può avere qualsiasi nome. Figura 8 viene illustrato il codice per GetActions che identifica i metodi pubblici e quindi ne esegue il mapping ai verbi con entrambi i metodi.

Figura 8 ricerca le azioni in un Controller

public IEnumerable<string> GetActions(ClassDeclarationSyntax controller)
{
  var semanticModel = compilation.GetSemanticModel(controller.SyntaxTree);
  var actions = controller.Members.OfType<MethodDeclarationSyntax>();
  var returnValue = new List<string>();
  foreach (var action in actions.Where
        (a => a.Modifiers.Any(m => m.Kind() == SyntaxKind.PublicKeyword)))
  {
    var mapName = MapByMethodName(semanticModel, action);
    if (mapName != null)
      returnValue.Add(mapName);
    else
    {
      mapName = MapByAttribute(semanticModel, action);
      if (mapName != null)
        returnValue.Add(mapName);
    }
  }
  return returnValue.Distinct();
}

Il metodo GetActions tenta innanzitutto di eseguire il mapping in base al nome del metodo. Se il problema persiste, sarà provare a eseguire il mapping degli attributi. Se il metodo non può essere associato, non sarà incluso nell'elenco di azioni. Se si dispone di una convenzione diversa che si desidera verificare, è possibile incorporarlo facilmente nel metodo GetActions. Figura 9 illustrate le implementazioni per i metodi MapByMethodName e MapByAttribute.

Figura 9 MapByName e MapByAttribute

private static string MapByAttribute(SemanticModel semanticModel,
  MethodDeclarationSyntax action)
{
  var attributes = action.DescendantNodes().OfType<AttributeSyntax>().ToList();
  if ( attributes.Any(a=>a.Name.ToString() == "HttpGet"))
    return IdentifyIEnumerable(semanticModel, action) ? "query" : "get";
  var targetAttribute = attributes.FirstOrDefault(a =>
    a.Name.ToString().StartsWith("Http"));
  return targetAttribute?.Name.ToString().Replace("Http", "").ToLower();
}
private static string MapByMethodName(SemanticModel semanticModel,
  MethodDeclarationSyntax action)
{
  if (action.Identifier.Text.Contains("Get"))
    return IdentifyIEnumerable(semanticModel, action) ? "query" : "get";
  var regex = new Regex("\b(?'verb'post|put|delete)", RegexOptions.IgnoreCase);
  if (regex.IsMatch(action.Identifier.Text))
    return regex.Matches(action.Identifier.Text)[0]
      .Groups["verb"].Value.ToLower();
  return null;
}

Entrambi i metodi di avviano la ricerca per l'azione di ottenere in modo esplicito e determinare il tipo di "get" a cui fa riferimento il metodo.

Se l'azione non è uno dei "diventa", MapByAttribute controlla per vedere se l'azione ha un attributo che inizia con Http. Se viene individuato uno, il verbo può essere determinato semplicemente assumendo il nome di attributo e rimuovendo Http dal nome dell'attributo. Non è necessario per verificare in modo esplicito ogni attributo per determinare quale verbo da utilizzare.

MapByMethodName è strutturato in modo analogo. Dopo il primo controllo per la lettura, questo metodo utilizza un'espressione regolare per vedere se vi sono altri verbi corrispondono. Se viene trovata una corrispondenza, è possibile ottenere il nome del verbo dal gruppo di acquisizione.

Entrambi i metodi di mapping è necessario distinguere tra ottenere singolo e ottenere tutte le azioni e possono utilizzare il metodo IdentifyEnumerable illustrato nel codice seguente:

private static bool IdentifyIEnumerable(SemanticModel semanticModel,
  MethodDeclarationSyntax actiol2n)
{
  var symbol = semanticModel.GetSymbolInfo(action.ReturnType);
  var typeSymbol = symbol.Symbol as ITypeSymbol;
  if (typeSymbol == null) return false;
  return typeSymbol.AllInterfaces.Any(i => i.Name == "IEnumerable");
}

Nuovo elemento SemanticModel riveste un ruolo fondamentale. Distinguere i metodi get esaminando il tipo restituito del metodo. Elemento SemanticModel restituirà il simbolo associato al tipo restituito. Con questo simbolo, è possibile stabilire se il tipo restituito implementa l'interfaccia IEnumerable. Se il metodo restituisce un elenco<T> o un Enumerable<T> o qualsiasi tipo di raccolta, verrà implementata l'interfaccia IEnumerable.</T> </T>

Il modello T4

Ora che tutti i metadati raccolti, è necessario visitare il modello T4 limiterà insieme tutti questi elementi. Iniziare aggiungendo un modello di testo della fase di esecuzione per il progetto.

Per un modello di testo di Runtime, l'output dell'esecuzione del modello è una classe che consente di implementare il modello definito e non il codice che si desidera generare. Nella maggior parte, nulla è possibile eseguire in un modello di testo è possibile eseguire con un modello di testo della fase di esecuzione. La differenza è come si esegue il modello per generare codice. Con un modello di testo, Visual Studio verranno gestiti in esecuzione il modello e creazione dell'ambiente di hosting in cui verrà eseguito il modello. Con un modello di testo di Runtime, è responsabilità dell'utente per l'impostazione dell'ambiente di hosting e l'esecuzione del modello. Quantità di lavoro in questo modo è, ma offre inoltre molto maggiore controllo sulle modalità di esecuzione del modello e procedere con l'output. Rimuove inoltre le dipendenze da Visual Studio.

Iniziare modificando la AngularResource.tt e aggiungendo il codice in figura 10 al modello.

Figura 10 modello iniziale

<#@ template debug="false" hostspecific="false" language="C#" | #>
var app = angular.module("challenge", [ "ngResource"]);
  app.factory(<#=className #>Resource . function ($resource) {
    return $resource('<#=Url#>/<#=className#>',{<=className#> : '@<#=className#>'},{
    <#=property.Name#> : "",
  query : {
    method: "GET"
    , isArray : true
    }
  ' <#=action#>: {
    method: "<#= action.ToUpper()#>
    }
  });
});

A seconda di come si ha dimestichezza con JavaScript, questa potrebbe essere nuova per l'utente, in questo caso, non preoccuparti.

La prima riga è una direttiva del modello e T4 indica che saranno scritti codice del modello nel linguaggio c#. gli altri due attributi vengono ignorati per i modelli di Runtime, ma per maggiore chiarezza, sono esplicitamente che non si prevede dall'ambiente di hosting e non prevede che i file intermedi da mantenere per il debug.

Il modello T4 è analoga a una pagina ASP. Il <# and="" #="">delimitano i tag tra unità del modello di codice e il testo da trasformare dal modello.</#> Il <#= #="">tag delimitano una sostituzione delle variabili che deve essere valutato e inserito nel codice generato.</#=>

Esaminando questo modello, si noterà che i metadati deve fornire un className, URL, un elenco di proprietà e un elenco di azioni. Poiché si tratta di un modello di Runtime sono disponibili un paio di aspetti che è possibile fare per semplificare le cose, ma innanzitutto esaminare il codice che viene creato durante l'esecuzione di questo modello, che viene eseguita salvando il file con estensione tt o pulsante destro del mouse sul file in Esplora soluzioni e scegliendo Esegui strumento personalizzato.

L'output ottenuto eseguendo il modello è una nuova classe, che corrisponde al modello. Più importante, che se scorrere verso il basso, noterà che il modello generato anche la classe di base. Questo è importante perché se si sposta la classe di base in un nuovo file e indicare in modo esplicito la classe di base nella direttiva del modello, è non necessario non è più generato e sono libero di modificare questa classe di base alle esigenze.

Successivamente, modificherà direttiva del modello a questo:

<#@ template debug="false" hostspecific="false" language="C#"
  inherits="AngularResourceServiceBase" #>

Si passerà il AngularResourceServiveBase nel relativo file. Quando si esegue nuovamente il modello, noterà che la classe generata ancora deriva dalla stessa classe di base, ma non è stato generato. È adesso possibile apportare le modifiche necessarie per la classe di base.

Successivamente, aggiungerò un paio di proprietà e alcuni nuovi metodi alla classe di base per renderlo più semplice fornire i metadati per il modello.

Per supportare i nuovi metodi e le proprietà, è inoltre necessario impostare alcune nuove istruzioni using:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

Aggiungerò proprietà per l'URL e RoslynDataProvider creata all'inizio dell'articolo:

public string Url { get; set; }
public RoslynDataProvider MetadataProvider { get; set; }

Con sul posto, è inoltre necessario impostare una coppia di metodi che interagiscono con MetadataProvider, come illustrato nella figura 11.

Figura 11 Helper metodi aggiunti alla AngularResourceServiceBase

public IList<ClassDeclarationSyntax> GetControllers()
{
  var project = MetadataProvider.GetWebApiProject();
  return MetadataProvider.FindControllers(project).ToList();
}
protected IEnumerable<string> GetActions(ClassDeclarationSyntax controller)
{
  return MetadataProvider.GetActions(controller);
}
protected IEnumerable<TypeInfo> GetModels(ClassDeclarationSyntax controller)
{
  return MetadataProvider.GetModels(controller);
}
protected IEnumerable<ISymbol> GetProperties(IEnumerable<TypeInfo> models)
{
  return MetadataProvider.GetProperties(models);
}

Ora che ho questi metodi aggiunti alla classe di base, sono pronto a estendere il modello per utilizzarli. Osservare come il modello Cambia figura 12.

Figura 12 la versione finale del modello

<#@ template debug="false" hostspecific="false" language="C#" inherits="AngularResourceServiceBase" #>
var app = angular.module("challenge", [ "ngResource"]);
<#
  var controllers = GetControllers();
  foreach(var controller in controllers)
  {
    var className = controller.Identifier.Text.Replace("Controller", "");
#>    app.facctory(<#=className #>Resource , function ($resource) {
      return $resource('<#=Url#>/<#=className#>',{<#=className#> : '@<#=className#>'},{
<#
    var models= GetModels(controller);
    var properties = GetProperties(models);
    foreach (var property in properties)
    {
#>
      <#=property.Name#> : "",
<#
    }
    var actions = GetActions(controller);
    foreach (var action in actions)
    {
#>
<#
      if (action == "query")
      {
#>      query : {
      method: "GET"

Il modello di esecuzione

Poiché si tratta di un modello di Runtime, sono responsabile dell'impostazione dell'ambiente per l'esecuzione del modello. Figura 13 Mostra il codice necessario per eseguire il modello.

Figura 13 in esecuzione di un modello di testo di Runtime

private static void Main()
{
  var work = MSBuildWorkspace.Create();
  var solution = work.OpenSolutionAsync(Path to the Solution File).Result;
  var metadata = new RoslynDataProvider() {Workspace = work};
  var template = new AngularResourceService
  {
    MetadataProvider = metadata,
    Url = @"http://localhost:53595/"
  };
  var results = template.TransformText();
  var project = metadata.GetWebApiProject();
  var folders = new List<string>() { "Scripts" };
  var document = project.AddDocument("factories.js", results, folders)
    .WithSourceCodeKind(SourceCodeKind.Script)
    ;
  if (!work.TryApplyChanges(document.Project.Solution))
    Console.WriteLine("Failed to add the generated code to the project");
  Console.WriteLine(results);
  Console.ReadLine();
}

È possibile creare direttamente istanze della classe creata quando si salva il modello o eseguire lo strumento personalizzato e si è possibile impostare o accedere a tutte le proprietà pubbliche o chiamare metodi pubblici della classe base. Ecco come i valori per le proprietà vengono impostati. La chiamata al metodo TransformText verrà eseguito il modello e restituire il codice generato come stringa. La variabile di risultati conterrà il codice generato. Il resto del codice riguarda l'aggiunta di un nuovo documento per il progetto con il codice generato.

Esiste tuttavia un problema con questo codice. La chiamata a AddDocuments correttamente viene creato un documento e lo colloca nella cartella script. Quando viene chiamata la TryApplyChanges, verrà restituito esito positivo. Il problema sorge quando si visualizzano nella soluzione: È un file Factory nella cartella script. Il problema è che anziché factories.js, factories.cs. Il metodo AddDocument non è configurato per accettare un'estensione. Indipendentemente dall'estensione, il documento verrà aggiunto in base al tipo di progetto a cui viene aggiunto. Si tratta in base alla progettazione.

Di conseguenza, dopo che il programma eseguito e genera le classi JavaScript, il file sarà nella cartella script. È sufficiente è modificare l'estensione da. cs in. js.

Conclusioni

Maggior parte del lavoro svolto qui centrata sul recupero di metadati con Roslyn. Indipendentemente dalla modalità si prevede di utilizzare i metadati, queste stesse consigliate verranno rivelarsi utile. Per il codice T4, continuerà a essere importanti in diverse posizioni. Se si desidera generare il codice per qualsiasi lingua non supportata da Roslyn, T4 è un'eccellente scelta e di facile incorporare i processi. Questo è valido perché è possibile utilizzare Roslyn per generare codice solo per c# e Visual Basic .NET, mentre T4 consente di generare qualsiasi tipo di testo, che può essere SQL, JavaScript, HTML, CSS o testo anche plain-old.

È utile generare il codice come queste classi JavaScript perché sono noiosa e soggetta a errori. Sono anche facilmente conforme a un modello. Quanto possibile, si desidera seguire tale modello nel modo più coerente possibile. Più importante, il modo in cui si desidera che il codice per la ricerca generato è probabile che cambiano nel tempo, soprattutto per qualcosa di nuovo, secondo le procedure consigliate sono formate. Se sarà sufficiente aggiornare un modello T4 per modificare il nuovo "modo migliore per farlo", più probabile di rispettare le procedure consigliate emergenti; ma se è necessario modificare grandi quantità di codice monotono lungo e noioso generato manualmente, sarà necessario probabilmente più implementazione come ogni iterazione delle migliori pratiche di moda.


Nick Harrisonè un consulente software vita Columbia, sottocutanea, con sua moglie loving Tracy e secondaria.  Egli ha sviluppato dello stack completa con .NET per creare soluzioni di business dal 2002. È possibile contattarlo su Twitter: @Neh123us, dove ha inoltre annuncia il post di blog, articoli pubblicati e interventi di pronuncia.

Grazie al seguente esperto tecnico Microsoft per la revisione dell'articolo: James McCaffrey
Dr. James McCaffrey lavora per Microsoft Research a Redmond, Washington. Ha lavorato di diversi prodotti Microsoft, inclusi Internet Explorer e Bing. Dr. È possibile contattarlo McCaffrey jammc@microsoft.com.