Décembre 2016

Volume 31, numéro 13

Cet article a fait l'objet d'une traduction automatique.

Roslyn - Générer du JavaScript avec des modèles T4 et Roslyn

Par Nick Harrison | Décembre 2016

L’autre jour ma fille m’a dit une plaisanterie sur une conversation entre un smartphone et un téléphone. Il pourrait ressembler à ceci : Ce que fait le smartphone que pour le téléphone ? « Je suis de l’avenir, vous comprendrez me ? » Parfois, il semble similaire lorsque apprendre quelque chose de nouveau et à la pointe. Roslyn provient de l’avenir et peut être difficile à comprendre dans un premier temps.

Dans cet article, j’aborderai Roslyn d’une manière qui vous n’obtiendrez pas forcément aussi grande attention qu’il mérite. Je me concentrerai sur l’utilisation de Roslyn en tant que source de métadonnées pour la génération de JavaScript avec T4. Cette opération utilise l’API de l’espace de travail, l’API de la syntaxe, de l’API de symbole ou un modèle d’exécution du moteur T4. Le JavaScript réel généré est secondaire pour comprendre les processus utilisés pour collecter les métadonnées.

Étant donné que Roslyn fournit également certaines options intéressantes pour la génération de code, vous pouvez considérer que les deux technologies seraient sont en conflit et ne fonctionnent pas bien ensemble. Technologies sont souvent en conflit lors de leur sandbox se chevauchent, mais ces deux technologies peuvent cohabiter ensemble au lieu de cela.

Attendez, ce qui est T4 ?

Si T4 est nouveau pour vous, un livre 2015 à la Syncfusion succinctement série, « T4 succinctement, » fournit toutes les informations contextuelles nécessaires (bit.ly/2cOtWuN).

Pour le moment, la principale chose à savoir est que T4 toolkit de transformation de Microsoft basée sur le modèle de texte. Flux de métadonnées pour le modèle et le texte devient le code que vous souhaitez. En fait, vous n’êtes pas limité au code. Vous pouvez générer tout type de texte, mais le code source est la sortie courante. Vous pouvez générer le HTML, SQL, documentation de texte, Visual Basic .NET, c# ou de sortie de texte.

Examinez Figure 1. Il montre un programme d’Application Console simple. Dans Visual Studio, j’ai ajouté un nouveau modèle de texte runtime nommé AngularResourceService.tt. Le code du modèle génère automatiquement du code c# que vous allez implémenter le modèle au moment de l’exécution, que vous pouvez voir dans la fenêtre de console.

À l’aide de T4 pour la génération de Code au moment du Design
Figure 1 Utilisation T4 pour la génération de Code au moment du Design

Dans cet article, je vais vous montrer comment Roslyn permet de rassembler des métadonnées à partir d’un projet d’API Web au flux T4 pour générer une classe JavaScript, puis utilisez Roslyn pour ajouter ce JavaScript sauvegarder à la solution.

Point de vue conceptuel, le flux de processus ressemblera à Figure 2.

T4 Flux de processus
Figure 2 processus de T4

Roslyn flux T4

Génération de code est un processus gourmand en métadonnées. Vous devez les métadonnées pour décrire le code que vous souhaitez générer. Réflexion, modèle de Code et le dictionnaire de données sont des sources courantes des métadonnées disponibles. Roslyn peut fournir toutes les métadonnées qu'est reçu à partir de la réflexion ou le modèle de Code, mais sans certains problèmes qu'entraînent ces autres approches.

Dans cet article, j’utiliserai Roslyn pour trouver les classes dérivées de ApiController. Je vais utiliser le modèle T4 pour créer une classe JavaScript pour chaque contrôleur et d’exposer une méthode pour chaque Action et une propriété pour chaque propriété dans le ViewModel associés au contrôleur. Le résultat doit ressembler à du code dans Figure 3.

Figure 3 les résultats de l’exécution du Code

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"
    }
  });
});

Collecte des métadonnées

Je commence la collecte des métadonnées en créant un nouveau projet d’application console dans Visual Studio 2015. Dans ce projet, je vais avoir une classe consacré à la collecte des métadonnées avec Roslyn, ainsi qu’un modèle T4. Il s’agit d’un modèle d’exécution qui génère du code JavaScript basé sur les métadonnées recueillies.

Une fois que le projet est créé, les commandes suivantes à partir de la Console du Gestionnaire de Package sont émis :

Install-Package Microsoft.CodeAnalysis.CSharp.Workspaces

Cela garantit que le dernier code de Roslyn pour le compilateur CSharp et services connexes sont utilisés.

Je place le code pour les différentes méthodes dans une classe appelée RoslynDataProvider. Je me référerai à cette classe tout au long de l’article et il s’agit d’une référence pratique lorsque je souhaite recueillir des métadonnées avec Roslyn.

J’utilise le MSBuildWorksspace pour obtenir un espace de travail qui fournira tout le contexte nécessaire pour la compilation. Une fois que j’ai la solution, je peux facilement via les projets de recherche pour le projet d’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;
}

Si vous suivez une convention d’affectation des noms différents, vous pouvez facilement l’incorporer dans le GetWebApiProject pour rechercher le projet qui vous intéresse.

Maintenant que je sais que je souhaite utiliser le projet dans lequel, je dois obtenir la compilation de ce projet, ainsi qu’une référence au type que je vais utiliser pour identifier les contrôleurs d’intérêt. J’ai besoin la compilation parce que je vais utiliser l’élément SemanticModel pour déterminer si une classe dérivée de System.Web.Http.ApiController. À partir du projet, je peux obtenir les documents inclus dans le projet. Chaque document est un fichier distinct, qui peut inclure plusieurs déclarations de classe, bien qu’il soit conseillé meilleures uniquement inclure une classe unique dans un fichier et que le nom de la correspondance de fichier le nom de la classe. mais pas tout le monde suit cette norme tout le temps.

Recherche les contrôleurs

Figure 4 montre comment rechercher toutes les déclarations de classe dans chaque document et déterminer si la classe est dérivée de ApiController.

Figure 4 recherche les contrôleurs dans un projet

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;
    }
  }
}

Étant donné que la compilation a accès à toutes les références nécessaires à la compilation du projet, il n’aura aucun problème de résolution du type cible. Quand j’obtiens l’objet de la compilation, j’avoir démarré la compilation du projet, mais suis partie interrompue via une fois que je les détails pour obtenir les métadonnées nécessaires.

Figure 5 illustre la méthode GetBaseClasses qui effectue le gros du travail pour déterminer si la classe actuelle est dérivée de la classe cible. Il fait un peu plus de traitement que celui qui est strictement nécessaire. Pour déterminer si une classe est dérivée de ApiController, je ne se soucient véritablement les interfaces implémentées au cours du processus, mais en incluant ces détails, cela devient une méthode utilitaire pratique qui peut être utilisée dans de nombreux endroits.

Figure 5 recherche d’Interfaces et Classes de 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;
}

Ce type d’analyse est compliqué avec la réflexion, car une approche réfléchissante reposera sur la récurrence et potentiellement avoir besoin d’avoir à charger n’importe quel nombre d’assemblys en cours de route pour accéder à tous les types concernés. Ce type d’analyse n’est pas encore possible avec le modèle de Code, mais il est relativement simple avec Roslyn à l’aide de l’élément SemanticModel. L’élément SemanticModel est une mine de métadonnées ; Il représente tout ce que le compilateur connaît le code après avoir effectué la peine des arborescences de syntaxe de liaison aux symboles. Outre le dépistage des types de base, il peut être utilisé pour répondre aux questions difficiles de résolution de surcharge/remplacement ou de recherche toutes les références à une méthode ou une propriété ou un symbole.

Recherche le modèle associé

À ce stade, j’ai accès à tous les contrôleurs dans le projet. Dans la classe JavaScript, il est également intéressante d’exposer les propriétés trouvées dans les modèles retournés par les Actions du contrôleur. Pour comprendre comment cela fonctionne, examinez le code suivant, qui montre la sortie de la structure en cours d’exécution pour une 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; }
  }

Dans ce cas, la structure a été exécutée sur des modèles, comme indiqué dans Figure 6.

Figure 6 contrôleur de l’API générée

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’attribut ResponseType ajouté aux actions lie le ViewModel au contrôleur. À l’aide de cet attribut, vous pouvez obtenir le nom du modèle associé à l’action. Tant que le contrôleur a été créé à l’aide de la structure, puis chaque action à associer avec le même modèle, mais contrôleurs créées manuellement ou modifiées après que la génération peut ne pas être conforme. Figure 7 montre comment comparer par rapport à toutes les actions pour obtenir une liste complète des modèles associés à un contrôleur au cas où plusieurs.

Figure 7 recherche des modèles associés à un contrôleur

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();
}

Il est intéressante logique dans cette méthode ; certaines de ces 's plutôt subtile. N’oubliez pas que l’attribut ResponseType ressemble à :

[ResponseType(typeof(Activity))]

Je souhaite accéder aux propriétés dans le type référencé dans le type d’expression, qui est le premier paramètre de l’attribut, dans ce cas, l’activité. Les attributs de la variable sont une liste des attributs ResponseType trouvée dans le contrôleur. La variable de paramètres est une liste des paramètres pour ces attributs. Chacun de ces paramètres est un TypeOfExpressionSyntax, et je peux obtenir le type associé à la propriété type des objets TypeOfExpressionSyntax. Une fois encore, l’élément SemanticModel est utilisé pour extraire le symbole de ce type, afin de vous donner tous les détails que vous pourriez avoir besoin.

Distinct à la fin de la méthode garantit que chaque modèle retourné est unique. Dans la plupart des cas, vous vous attendez à avoir des doublons, car plusieurs actions du contrôleur sera associées au même modèle. Il est également judicieux de vérifier par rapport à la ResponseType est void. Vous ne trouverez pas les propriétés intéressantes.

Examen du modèle associé

Le code suivant montre comment rechercher les propriétés de tous les modèles trouvés dans le contrôleur :

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

Recherche les Actions

En plus d’afficher les propriétés à partir des modèles associés, je souhaite inclure des références aux méthodes qui sont dans le contrôleur. Les méthodes du contrôleur sont des Actions. Je suis uniquement intéressé par les méthodes publiques, et parce que ce sont des Actions de l’API Web, ils doivent tous être traduits dans le verbe HTTP approprié.

Il existe deux différentes conventions suivies pour gérer ce mappage. Le suivi de la structure est le nom de méthode démarrer avec le nom du verbe. La méthode put serait PutActivity, la méthode post est PostActivity, la méthode delete serait DeleteActivity et il y aura généralement le que deux méthodes get : GetActivity et GetActivities. Vous pouvez faire la différence entre les méthodes get en examinant les types de retour pour ces méthodes. Si le type de retour, directement ou indirectement implémente l’interface IEnumerable, la méthode get est prise tout ; dans le cas contraire, il est la seul élément get (méthode).

L’autre approche est que vous ajoutez explicitement des attributs pour spécifier le verbe, la méthode peut avoir n’importe quel nom. Figure 8 affiche le code de GetActions qui identifie les méthodes publiques et mappe ensuite les verbes à l’aide de ces deux méthodes.

Figure 8 recherche les Actions sur un contrôleur

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();
}

La méthode GetActions tente d’abord mapper basé sur le nom de la méthode. Si cela ne fonctionne pas, il essaierez ensuite mapper les attributs. Si la méthode ne peut pas être mappée, il ne sont pas inclus dans la liste des actions. Si vous avez une autre convention que vous souhaitez vérifier, vous pouvez facilement l’intégrer dans la méthode GetActions. Figure 9 illustre l’implémentation des méthodes MapByMethodName et MapByAttribute.

MapByAttribute et MapByName de la figure 9

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;
}

Les deux méthodes démarrer en recherchant l’Action prise explicitement et en déterminant le type de « get » à laquelle fait référence la méthode.

Si l’action n’est pas un des « devient », MapByAttribute vérifie si l’action possède un attribut qui commence par Http. S’il existe, le verbe peut être déterminé en simplement prendre le nom d’attribut et en supprimant Http à partir du nom d’attribut. Il n’est pas nécessaire de vérifier explicitement chaque attribut pour déterminer le verbe à utiliser.

MapByMethodName est structurée de la même façon. Après la première recherche cette action, cette méthode utilise une expression régulière pour voir si une des autres verbes correspond. Si une correspondance est trouvée, vous pouvez obtenir le nom du verbe à partir du groupe de capture nommé.

Les deux méthodes de mappage doivent faire la distinction entre les obtenir unique et obtenir toutes les Actions et elles utilisent la méthode IdentifyEnumerable indiquée dans le code suivant :

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");
}

Là encore, l’élément SemanticModel joue un rôle essentiel. Je peux faire la différence entre les méthodes get en examinant le type de retour de la méthode. L’élément SemanticModel retournera le symbole lié au type de retour. Lorsque ce symbole, je peux dire, si le type de retour implémente l’interface IEnumerable. Si la méthode retourne une liste<T> ou un Enumerable<T> ou tout type de collection, allez implémente l’interface IEnumerable.</T> </T>

Le modèle T4

Maintenant que toutes les métadonnées sont collectées, il est temps de visiter le modèle T4 réduira tous ces éléments ensemble. Je commence par l’ajout d’un modèle de texte Runtime au projet.

Pour un modèle de texte Runtime, la sortie de l’exécution du modèle sera une classe qui implémente le modèle qui est défini et non le code que je souhaite créer. La plupart du temps, tout ce que vous pouvez faire dans un modèle de texte que vous pouvez faire avec un modèle de texte Runtime. La différence est la façon dont vous exécutez le modèle pour générer du code. Avec un modèle de texte, Visual Studio gère le modèle en cours d’exécution et la création de l’environnement d’hébergement dans lequel le modèle sera exécuté. Avec un modèle de texte Runtime, vous êtes responsable de la configuration de l’environnement d’hébergement et le modèle en cours d’exécution. Cela met plus de travail sur vous, mais elle vous donne également beaucoup plus de contrôle sur le mode d’exécution du modèle et que pouvez-vous faire avec la sortie. Elle supprime également toutes les dépendances de Visual Studio.

Démarrage en modifiant le AngularResource.tt et en ajoutant le code dans Figure 10 pour le modèle.

Figure 10 initiales du modèle

<#@ 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()#>
    }
  });
});

Selon la façon dont vous vous familiariserez avec JavaScript, cela peut être nouveau pour vous, dans ce cas, ne vous inquiétez pas.

La première ligne est la directive de modèle et y T4 que je vais le code du modèle en c# ; les deux autres attributs sont ignorés pour les modèles d’exécution, mais par souci de clarté, je vais déclarer explicitement que je n’ai aucune attente à partir de l’environnement d’hébergement et que vous ne pensez pas que les fichiers intermédiaires doivent être conservés pour le débogage.

Le modèle T4 est un peu comme une page ASP. Le <# and="" #="">délimitent des balises entre le code pour le modèle de lecteur et le texte doit être transformée par le modèle.</#> Le <#= #="">balises délimitent une substitution de variable est évaluée et inséré dans le code généré.</#=>

En examinant ce modèle, vous pouvez voir que les métadonnées sont censée fournir un nom de classe, URL, une liste de propriétés et une liste d’actions. Il s’agit d’un modèle d’exécution quelques aspects que je peux faire pour simplifier les choses, mais tout d’abord un coup de œil au code qui est créé lors de l’exécution de ce modèle, qui s’effectue en enregistrant le fichier .TT ou en cliquant sur le fichier dans l’Explorateur de solutions et en sélectionnant exécuter un outil personnalisé.

La sortie de l’exécution du modèle est une nouvelle classe, ce qui correspond au modèle. Plus important encore, que si je fais défiler vers le bas, je découvre que le modèle généré également la classe de base. Ceci est important, car si transférer la classe de base vers un nouveau fichier et déclarer explicitement la classe de base dans la directive de modèle, n’allez plus être généré et je suis libre de modifier cette classe de base en fonction des besoins.

Ensuite, je vais modifier la directive de modèle à ceci :

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

Je pourrai ensuite le AngularResourceServiveBase dans son propre fichier. Lorsque j’exécute à nouveau le modèle, je verrai que la classe générée dérive toujours de la même classe de base, mais elle a été générée n’est plus. Je suis maintenant libre d’apporter des modifications nécessaires à la classe de base.

Ensuite, je vais ajouter quelques nouvelles méthodes et deux propriétés à la classe de base pour faciliter leur fournir les métadonnées pour le modèle.

Pour prendre en compte les nouvelles méthodes et propriétés, je devrai également quelques nouveaux à l’aide d’instructions :

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

Je vais ajouter les propriétés de l’URL et pour le RoslynDataProvider que j’ai créé au début de l’article :

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

Avec ces éléments en place, me faudra également quelques méthodes qui interagissent avec le MetadataProvider, comme indiqué dans Figure 11.

Méthodes d’assistance de la figure 11 ajoutés à 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);
}

Maintenant que ces méthodes ajoutées à la classe de base, je suis prêt à étendre le modèle pour les utiliser. Observez comment le modèle est modifié dans Figure 12.

Figure 12 de Version finale du modèle

<#@ 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"

Le modèle en cours d’exécution

Comme il s’agit d’un modèle d’exécution, je suis responsable de la configuration de l’environnement d’exécution du modèle. Figure 13 montre le code nécessaire pour exécuter le modèle.

Figure 13 un modèle de texte du Runtime en cours d’exécution

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();
}

La classe créée lorsque j’enregistre le modèle ou que vous exécutez l’outil personnalisé peut être directement instanciée et je peux définir ou accéder à toutes les propriétés publiques ou appeler toutes les méthodes publiques de la classe de base. Voici comment les valeurs des propriétés sont définies. Appel de la méthode TransformText pour exécuter le modèle et retourner le code généré sous forme de chaîne. La variable de résultats contiendra le code généré. Le reste du code traite de l’ajout d’un nouveau document au projet avec le code qui a été généré.

Il existe un problème avec ce code, cependant. L’appel à AddDocuments correctement crée un document et le place dans le dossier scripts. Lorsque j’appelle le TryApplyChanges, elle retourne réussie. Un problème se pose lorsque vous affichez la solution : Il existe un fichier de fabriques dans le dossier scripts. Le problème est qu’au lieu de factories.js, il s’agit de factories.cs. La méthode AddDocument n’est pas configurée pour accepter une extension. Indépendamment de l’extension, le document sera ajouté en fonction du type de projet auquel il est ajouté. Ceci est normal.

Par conséquent, une fois que le programme s’exécute et génère les classes JavaScript, le fichier sera dans le dossier scripts. J’ai à faire est Remplacez l’extension .cs .js.

Pour résumer

Plupart du travail effectué ici centrée sur l’obtention des métadonnées avec Roslyn. Quelle que soit la façon dont vous prévoyez d’utiliser ces métadonnées, ces mêmes pratiques arriveront utiles. Comme pour le code T4, il continuer à être pertinente dans différents emplacements. Si vous souhaitez générer le code pour n’importe quel langage non pris en charge par Roslyn, T4 est un très bon choix et facile à intégrer dans votre processus. Cela est approprié, car vous pouvez utiliser Roslyn pour générer le code pour c# et Visual Basic .NET et T4 vous permet de générer tout type de texte, qui peut être SQL, JavaScript, HTML, CSS ou même brut l’ancien texte.

Il est agréable de générer du code à ces classes JavaScript parce qu’ils sont fastidieuse et sujette à erreurs. Il est également facilement conforme à un modèle. Autant que possible, vous souhaitez suivre ce modèle aussi cohérente que possible. La plus importante, comme vous le souhaitez le code semble généré est susceptible de changer au fil du temps, en particulier pour quelque chose de nouveau, comme les meilleures pratiques sont formées. Si tout vous avez à faire est mise à jour d’un modèle T4 pour modifier la nouveau « meilleure façon de procéder », vous êtes susceptible de se conformer aux meilleures pratiques émergeantes ; mais si vous devez modifier les grandes quantités de code monotones, fastidieux généré manuellement, vous avez probablement plusieurs implémentation que chaque itération de la meilleure pratique était en vogue.


Nick Harrisonest un consultant logiciel vivant dans Columbia, S.C., avec son épouse tendres Tracy et fille.  Il a développé à l’aide de .NET pour créer des solutions d’entreprise depuis 2002 complète de la pile. Contactez-le sur Twitter : @Neh123us, où il annonce également ses blogs, articles publiés et des engagements rémunérés.

Merci à l'expert technique Microsoft suivant d'avoir relu cet article : James McCaffrey
Dr. James McCaffrey travaille pour Microsoft Research à Redmond, Wash. Il a travaillé sur plusieurs produits Microsoft, y compris Internet Explorer et Bing. Dr. James McCaffrey peut être atteint à jammc@microsoft.com.