.NET 5 introduit un générateur de source. Nous ferons cela avec son aide. Cet article couvrira les principaux problèmes que j'ai rencontrés lors de l'utilisation du générateur de source et leurs solutions. La génération de l'interface utilisateur elle-même dépasse le cadre de cet article. Utilisé par Visual Studio 2019.
Donc, ce qui est nécessaire pour cela:
1. Possibilité de générer des fichiers js / vue / jsx
2. Accès au répertoire principal du projet
3. Accès au fichier de paramètres
4. Utilisation de bibliothèques tierces à l'intérieur du générateur, par exemple Newtonsoft.Json
5. Utilisation de mes autres assemblys à l'intérieur du générateur
6. Accès aux classes / types de contrôleurs et modèles situés dans différents assemblys
7. Débogage
Quelques mots sur T4
.NET 4.x dispose d'un générateur de code T4. Au départ, j'ai essayé de résoudre mon problème avec. Il y avait un certain nombre de problèmes, principalement liés au chargement des bibliothèques système, qui ont été résolus avec un succès variable. Mais quand il s'agissait de gérer un assemblage .NET 5 avec des contrôleurs, qui fait référence à une bibliothèque AspNetCore extraterrestre (pour le runtime .NET 4.x), mon cerveau était dans une impasse. T4 ne voulait en aucun cas le trouver et le charger.
Structure du projet
Toutes les nouvelles technologies Microsoft commencent par Hello World, où tout fonctionne bien. Mais lorsque vous commencez à les utiliser dans un vrai projet, vous rencontrez de nombreux problèmes. L'un de ceux-ci est la structure du projet. Dans Hello World, il s'agit d'un assemblage. Et dans un vrai projet, il y en a plusieurs.
Mon projet comprend quatre assemblys conditionnels:
1. NetGenerator5.Web - l'application Web principale lancée (net5.0), contient des contrôleurs, un assemblage avec des modèles et le générateur lui-même y sont connectés.
2. NetGenerator5.Model - assemblage avec les modèles (net5.0)
3. NetGenerator5.Generator - assemblage avec générateur (netstandard2.0)
4. NetGenerator5.Generator.Dependency - assemblage conditionnel utilisé à l'intérieur du générateur (netstandard2.0)
Générateur
La classe de générateur implémente l'interface ISourceGenerator avec deux méthodes, Initialize et Execute. La méthode Execute s'exécutera directement lors de la compilation du projet auquel le générateur est connecté.
Le projet générateur lui-même
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
</ItemGroup>
</Project>
Comment le connecter? Il est nécessaire dans le projet principal (NetGenerator5.Web), enregistrez les éléments suivants:
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NetGenerator5.Generator\NetGenerator5.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
La possibilité de générer des fichiers js / vue / jsx
Initialement, le générateur génère des fichiers cs avec du code C #. Pour ce faire, dans la méthode Execute, la méthode de contexte GeneratorExecutionContext.AddSource est utilisée. Pour autant que je sache, il est impossible de changer leur extension et ces fichiers sont également compilés. Par conséquent, il n'est pas possible d'y mettre du code dans une autre langue. Visual Studio commence à générer des erreurs de compilation.
Par conséquent, nous avons besoin d'une approche différente pour enregistrer les fichiers js / vue / jsx. Le System.IO.File.WriteAllText habituel m'a aidé. Mais pour cela, vous devez savoir exactement où vous devez enregistrer les fichiers générés, c'est-à-dire connaître le répertoire du projet principal.
Accès au répertoire principal du projet
Il peut être obtenu comme suit:
Dans le projet principal NetGenerator5.Web, écrivez ce qui suit:
<ItemGroup>
<CompilerVisibleProperty Include="MSBuildProjectDirectory" />
</ItemGroup>
Cela rendra visible la variable système du générateur source.
Et dans le générateur lui-même, nous y accéderons dans la méthode Execute comme suit:
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.MSBuildProjectDirectory", out var projectDirectory)
De plus, nous devons savoir exactement où placer les fichiers générés dans le projet Web lui-même (par exemple, dans wwwroot / js). Il m'est venu à l'esprit de passer cela à travers le fichier de configuration generatorsettings.json dans le projet principal. Mais maintenant, j'ai besoin d'en parler au générateur.
Accéder au fichier de paramètres
Le générateur a la capacité d'accéder aux fichiers via la collection de contexte GeneratorExecutionContext.AdditionalFiles dans la méthode Execute. Pour que mon fichier de configuration soit là, vous devez définir la propriété de fichier supplémentaire Build Action = C # analyzer, ou comme ceci:
<ItemGroup>
<AdditionalFiles Include="generatorsettings.json" />
</ItemGroup>
Après cela, le contenu du fichier peut être lu comme suit
var content = context.AdditionalFiles.First(e => e.Path.EndsWith("generatorsettings.json")).GetText(context.CancellationToken);
Ensuite, un problème se pose - c'est json, mais comment puis-je l'analyser?
Utilisation de bibliothèques tierces à l'intérieur du générateur
Utilisez une bibliothèque externe. Par exemple Newtonsoft.Json. C'est là que quelque chose a vraiment mal tourné. Je l'ai connecté via nuget, mais le générateur ne voulait en aucun cas voir cette bibliothèque.
Exception was of type 'FileNotFoundException' with message 'Could not load file or assembly 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies.
et même si vous craquez.
Le livre de cuisine a une section dédiée à cela,
il y a même un peu plus d'informations sur la façon de concevoir votre générateur sous forme de paquet nuget. Pour une raison quelconque, cela ne m'a pas aidé.
En conséquence, au début, j'ai décidé d'une manière étrange. J'ai stupidement ajouté la bibliothèque elle-même directement au projet en tant que fichier et spécifié Copier dans le répertoire de sortie = Copier toujours / Copier si plus récent pour cela et tout a fonctionné. Mais plus tard, j'ai eu une réponse à une question dans la section discussion de Roslyn. Les conseils m'ont aidé. Il est nécessaire de s'inscrire dans le projet générateur exactement comme ceci:
<ItemGroup>
<!-- Generator dependencies -->
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NetGenerator5.Generator.Dependency\NetGenerator5.Generator.Dependency.csproj" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
Ou utilisez le System.Text.Json intégré.
Utiliser mes autres assemblages à l'intérieur du générateur
De plus, ce serait bien d'utiliser mes autres assemblages à l'intérieur du générateur. Par exemple, les classes d'assistance pour Vue et React seraient bien dispersées dans deux assemblages différents et connectées au générateur si nécessaire.
Curieusement, tout s'est bien passé pour moi ici. Je viens de connecter NetGenerator5.Generator.Dependency via Dependencies - Add Project Reference. Bien que certains aient eu des problèmes.
Accès aux classes / types de contrôleurs et modèles situés dans différents assemblages
Passons maintenant à la partie amusante. Pour générer des fichiers - j'avais besoin d'accéder aux classes / types de contrôleurs et de modèles. Microsoft recommande d' utiliser SyntaxReceiver
Mais il n'a accès qu'aux classes du projet compilé actuel (c'est-à-dire dans mon cas NetGenerator5.Web), et les classes NetGenerator5.Model ne sont pas là.
Dans la même section de discussion de Roslyn, une solution a été trouvée . Dans le contexte de GeneratorExecutionContext, il existe Compilation.GlobalNamespace. Vous pouvez le parcourir de manière récursive et obtenir des descriptions de tous les types, y compris l'assembly compilé actuel et l'assembly avec des modèles.
Débogage
Pour le débogage, il suffit d'écrire dans la classe du générateur dans la méthode Initialize
#if DEBUG
if (!Debugger.IsAttached)
{
Debugger.Launch();
}
#endif
Lors du démarrage de la construction du projet principal, une fenêtre s'ouvre avec une proposition pour démarrer le débogueur. Si vous cliquez sur OK, une autre instance de Visual Studio sera lancée et le mode de débogage de ce générateur y figurera. Vous pouvez accéder à toutes les autres classes et méthodes, même celles qui sont dans un assembly distinct NetGenerator5.Generator.Dependency
Résultat
Après compilation, le fichier NetGenerator5.Web / wwwroot / js sera généré.js, et le
code source complet du fichier NetGenerator5.Web \ obj \ GeneratedFiles \ NetGenerator5.Generator \ NetGenerator5.Generator.SourceGenerator sera le fichier Dummy generated.cs, le code source complet peut être consulté ici
Sources
- github.com/amis92/csharp-source-generators
- github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md
- github.com/dotnet/roslyn/blob/master/docs/features/source-generators.cookbook.md
- mihailromanov.wordpress.com/2021/01/31/net-code-generation-part-6-c-source-generators
- dominikjeske.github.io/source-generators
- github.com/dotnet/roslyn/discussions
- habr.com/ru/post/530454
- habr.com/ru/post/533128