.NET 5 + Source Generator = Javascript

Die Aufgabe besteht darin, die Generierung von SPA-Anwendungen (Vue / React) basierend auf C # -Modellen und Controllern zu implementieren.



In .NET 5 wird ein Quellgenerator eingeführt. Wir werden dies mit seiner Hilfe tun. Dieser Artikel behandelt die Hauptprobleme, die bei der Verwendung des Quellgenerators aufgetreten sind, und deren Lösungen. Das Generieren der Benutzeroberfläche selbst würde den Rahmen dieses Artikels sprengen. Wird von Visual Studio 2019 verwendet.



Was hierfür erforderlich ist:

1. Möglichkeit zum Generieren von js / vue / jsx-Dateien

2. Zugriff auf das Hauptprojektverzeichnis

3. Zugriff auf die Einstellungsdatei

4. Verwenden von Bibliotheken von Drittanbietern im Generator, z. B. Newtonsoft.Json

5. Verwenden meiner anderen Assemblys im Generator

6. Zugriff auf Klassen / Typen von Controllern und Modellen in verschiedenen Assemblys

7. Debuggen



Ein paar Worte zu T4



.NET 4.x verfügt über einen T4-Codegenerator. Anfangs habe ich versucht, mein Problem damit zu lösen. Es gab eine Reihe von Problemen, die hauptsächlich mit dem Laden von Systembibliotheken zusammenhängen und mit unterschiedlichem Erfolg gelöst wurden. Aber als es darum ging, eine .NET 5-Assembly mit Controllern zu handhaben, die sich auf eine fremde AspNetCore-Bibliothek (für .NET 4.x-Laufzeit) bezieht, befand sich mein Gehirn in einer Sackgasse. T4 wollte es in keiner Weise finden und laden.



Projektstruktur



Alle neuen Microsoft-Technologien beginnen mit Hello World, wo alles cool funktioniert. Wenn Sie sie jedoch in einem realen Projekt verwenden, treten viele Probleme auf. Eine davon ist die Projektstruktur. In Hello World ist dies eine Assembly. Und in einem realen Projekt gibt es mehrere davon.



Mein Projekt umfasst vier bedingte Assemblys:

1. NetGenerator5.Web - Die wichtigste gestartete Webanwendung (net5.0) enthält Controller, eine Assembly mit Modellen und der Generator selbst sind damit verbunden.

2. NetGenerator5.Model - Baugruppe mit Modellen (net5.0)

3. NetGenerator5.Generator - Baugruppe mit Generator (netstandard2.0)

4. NetGenerator5.Generator.Dependency - bedingte Assembly, die im Generator verwendet wird (netstandard2.0)



Generator



Die Generatorklasse implementiert die ISourceGenerator-Schnittstelle mit zwei Methoden: Initialisieren und Ausführen. Die Execute-Methode wird direkt während der Kompilierung des Projekts ausgeführt, an das der Generator angeschlossen ist.



Das Generatorprojekt selbst



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

      
      





Wie verbinde ich es? Registrieren Sie im Hauptprojekt (NetGenerator5.Web) Folgendes:



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

      
      





Die Fähigkeit, js / vue / jsx-Dateien zu generieren



Zunächst gibt der Generator cs-Dateien mit C # -Code aus. Zu diesem Zweck wird in der Execute-Methode die GeneratorExecutionContext.AddSource-Kontextmethode verwendet. Soweit ich weiß, ist es unmöglich, ihre Erweiterung zu ändern, und diese Dateien werden auch kompiliert. Daher ist es dort nicht möglich, Code in einer anderen Sprache einzufügen. Visual Studio löst Kompilierungsfehler aus.



Daher benötigen wir einen anderen Ansatz, um js / vue / jsx-Dateien zu speichern. Das übliche System.IO.File.WriteAllText hat mir geholfen. Dazu müssen Sie jedoch genau wissen, wo Sie die generierten Dateien speichern müssen, d. H. kennen das Verzeichnis des Hauptprojekts.



Zugriff auf das Hauptprojektverzeichnis



Es kann wie folgt abgerufen werden:



Schreiben Sie im Hauptprojekt NetGenerator5.Web Folgendes:

<ItemGroup>
  <CompilerVisibleProperty Include="MSBuildProjectDirectory" />
</ItemGroup>

      
      





Dadurch wird die Systemvariable für den Quellgenerator sichtbar.



Und im Generator selbst werden wir in der Execute-Methode wie folgt darauf zugreifen:

context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.MSBuildProjectDirectory", out var projectDirectory)

      
      





Außerdem müssen wir genau wissen, wo die generierten Dateien im Webprojekt selbst abgelegt werden sollen (z. B. in wwwroot / js). Mir ist der Gedanke gekommen, dies durch die Konfigurationsdatei generatorsettings.json im Hauptprojekt zu leiten. Aber jetzt muss ich dem Generator irgendwie davon erzählen.



Zugriff auf die Einstellungsdatei



Der Generator kann über die GeneratorExecutionContext.AdditionalFiles-Kontextsammlung innerhalb der Execute-Methode auf Dateien zugreifen. Damit meine Konfigurationsdatei vorhanden ist, müssen Sie die zusätzliche Dateieigenschaft Build Action = C # -Analysator festlegen oder wie folgt:

<ItemGroup>
  <AdditionalFiles Include="generatorsettings.json" />
</ItemGroup>

      
      





Danach kann der Inhalt der Datei wie folgt gelesen werden

var content = context.AdditionalFiles.First(e => e.Path.EndsWith("generatorsettings.json")).GetText(context.CancellationToken);
      
      





Als nächstes tritt ein Problem auf - es ist json, aber wie kann ich es tatsächlich analysieren?



Verwenden von Bibliotheken von Drittanbietern im Generator



Verwenden Sie eine externe Bibliothek. Zum Beispiel Newtonsoft.Json. Hier ist wirklich etwas schief gelaufen. Ich habe es über Nuget verbunden, aber der Generator wollte diese Bibliothek in keiner Weise sehen.



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.
      
      





und obwohl du knackst.



Das Kochbuch enthält einen entsprechenden Abschnitt.

Es gibt sogar ein wenig mehr Informationen darüber, wie Sie Ihren Generator als Nuget-Paket gestalten können. Aus irgendeinem Grund hat es mir nicht geholfen.



Infolgedessen habe ich mich zunächst auf seltsame Weise entschieden. Ich habe dummerweise die Bibliothek selbst direkt als Datei zum Projekt hinzugefügt und "In Ausgabeverzeichnis kopieren = Immer kopieren / Kopieren, wenn es neuer ist und alles funktioniert hat" angegeben. Aber später bekam ich eine Antwort auf eine Frage im Roslyn-Diskussionsabschnitt. Der Rat hat mir geholfen. Es ist notwendig, sich genau so im Generatorprojekt zu registrieren:



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

      
      





Oder verwenden Sie alternativ das integrierte System.Text.Json.



Verwenden meiner anderen Baugruppen im Generator



Außerdem wäre es schön, meine anderen Baugruppen im Generator zu verwenden. Zum Beispiel wären Hilfsklassen für Vue und React schön, wenn sie in zwei verschiedene Baugruppen verteilt und nach Bedarf an den Generator angeschlossen würden.



Seltsamerweise lief hier für mich alles reibungslos. Ich habe gerade NetGenerator5.Generator.Dependency über Abhängigkeiten verbunden - Projektreferenz hinzufügen. Obwohl einige Probleme hatten.



Zugriff auf Klassen / Typen von Controllern und Modellen in verschiedenen Baugruppen



Kommen wir nun zum lustigen Teil. Um Dateien zu generieren, brauchte ich Zugriff auf Klassen / Typen von Controllern und Modellen. Microsoft empfiehlt die Verwendung von SyntaxReceiver.

Es hat jedoch nur Zugriff auf die Klassen des aktuell kompilierten Projekts (d. H. In meinem Fall NetGenerator5.Web), und die NetGenerator5.Model-Klassen sind nicht vorhanden.



Im selben Roslyn-Diskussionsabschnitt wurde eine Lösung gefunden . Im Kontext des GeneratorExecutionContext gibt es Compilation.GlobalNamespace. Sie können es rekursiv durchgehen und Beschreibungen aller Typen erhalten, einschließlich der aktuell kompilierten Baugruppe und Baugruppe mit Modellen.



Debuggen



Zum Debuggen reicht es aus, in der Generator-Klasse in der Initialize-Methode zu schreiben



#if DEBUG
  if (!Debugger.IsAttached)
  {
    Debugger.Launch();
  }
#endif

      
      





Beim Starten des Builds des Hauptprojekts wird ein Fenster mit einem Vorschlag zum Starten des Debuggers geöffnet. Wenn Sie auf OK klicken, wird eine weitere Instanz von Visual Studio gestartet und der Debug-Modus dieses Generators wird darin angezeigt. Sie können in alle anderen Klassen und Methoden gehen, auch in diejenigen, die sich in einer separaten Assembly NetGenerator5.Generator.Dependency befinden



Ergebnis



Nach der Kompilierung wird die Datei NetGenerator5.Web / wwwroot / js generiert.js und NetGenerator5.Web \ obj \ GeneratedFiles \ NetGenerator5.Generator \ NetGenerator5.Generator.SourceGenerator wird hier



angezeigt



Quellen






All Articles