.NET 5 + gerador de código-fonte = Javascript

A tarefa é implementar a geração de aplicativos SPA (Vue / React) baseados em modelos e controladores C #.



.NET 5 apresenta um gerador de código-fonte. Faremos isso com a ajuda dele. Este artigo cobrirá os principais problemas que encontrei ao usar o gerador de código-fonte e suas soluções. A geração da própria IU está além do escopo deste artigo. Usado pelo Visual Studio 2019.



Então, o que é necessário para isso:

1. Capacidade de gerar arquivos js / vue / jsx

2. Acesso ao diretório principal do projeto

3. Acesso ao arquivo de configurações

4. Usando bibliotecas de terceiros dentro do gerador, por exemplo Newtonsoft.Json

5. Usando meus outros assemblies dentro do gerador

6. Acesso a classes / tipos de controladores e modelos localizados em diferentes assemblies

7. Depuração



Algumas palavras sobre T4



.NET 4.x tem um gerador de código T4. Inicialmente, tentei resolver meu problema com ele. Houve uma série de problemas, principalmente relacionados ao carregamento das bibliotecas do sistema, que foram resolvidos com sucesso variável. Mas quando se tratava de lidar com um assembly .NET 5 com controladores, que se refere a uma biblioteca AspNetCore alienígena (para o .NET 4.x runtime), meu cérebro estava em um beco sem saída. T4 não queria encontrar e carregar de forma alguma.



Estrutura do projeto



Todas as novas tecnologias da Microsoft começam com Hello World, onde tudo funciona bem. Mas quando você começa a usá-los em um projeto real, você se depara com muitos problemas. Um deles é a estrutura do projeto. Em Hello World, esta é uma montagem. E em um projeto real, existem vários deles.



Meu projeto inclui quatro assemblies condicionais:

1. NetGenerator5.Web - o principal aplicativo da web lançado (net5.0), contém controladores, um assembly com modelos e o próprio gerador são conectados a ele.

2. NetGenerator5.Model - montagem com modelos (net5.0)

3. NetGenerator5.Generator - montagem com gerador (netstandard2.0)

4. NetGenerator5.Generator.Dependency - conjunto condicional que é usado dentro do gerador (netstandard2.0)



Gerador



A classe do gerador implementa a interface ISourceGenerator com dois métodos, Initialize e Execute. O método Execute será executado diretamente durante a compilação do projeto ao qual o gerador está conectado.



O próprio projeto do gerador



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

      
      





Como conectar? É necessário no projeto principal (NetGenerator5.Web), registrar o seguinte:



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

      
      





A capacidade de gerar arquivos js / vue / jsx



Inicialmente, o gerador gera arquivos cs com código C #. Para isso, dentro do método Execute, é utilizado o método de contexto GeneratorExecutionContext.AddSource. Pelo que entendi, é impossível alterar sua extensão e esses arquivos também são compilados. Portanto, não é possível colocar código em qualquer outra linguagem lá. O Visual Studio começa a lançar erros de compilação.



Portanto, precisamos de uma abordagem diferente para salvar arquivos js / vue / jsx. O usual System.IO.File.WriteAllText me ajudou. Mas para isso você precisa saber exatamente onde deseja salvar os arquivos gerados, ou seja, conheça o diretório do projeto principal.



Acesso ao diretório principal do projeto



Ele pode ser obtido da seguinte forma:



No projeto NetGenerator5.Web principal, escreva o seguinte:

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

      
      





Isso tornará a variável do sistema para o gerador de origem visível.



E no próprio gerador, iremos acessá-lo no método Execute da seguinte maneira:

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

      
      





Além disso, precisamos saber exatamente onde colocar os arquivos gerados dentro do próprio projeto da web (por exemplo, em wwwroot / js). Ocorreu-me passar isso por meio do arquivo de configuração generatorsettings.json no projeto principal. Mas agora, de alguma forma, preciso contar ao gerador sobre isso.



Acessando o arquivo de configurações



O gerador tem a capacidade de acessar arquivos por meio da coleção de contexto GeneratorExecutionContext.AdditionalFiles dentro do método Execute. Para que meu arquivo de configuração esteja lá, você precisa definir a propriedade Build Action = C # analoger arquivo adicional nele, ou assim:

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

      
      





Depois disso, o conteúdo do arquivo pode ser lido da seguinte forma

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





Em seguida, surge um problema - é json, mas como posso realmente analisá-lo?



Usando bibliotecas de terceiros dentro do gerador



Use uma biblioteca externa. Por exemplo Newtonsoft.Json. É aqui que algo realmente deu errado. Liguei através do nuget, mas o gerador não queria ver esta biblioteca de forma alguma.



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.
      
      





e mesmo que você crack.



O livro de receitas tem uma seção dedicada a isso.

Há ainda mais algumas informações sobre como projetar seu gerador como um pacote nuget. Por algum motivo, isso não me ajudou.



Como resultado, no começo eu decidi de uma maneira estranha. Eu estupidamente adicionei a própria biblioteca diretamente ao projeto como um arquivo e especifiquei Copiar para diretório de saída = Copiar sempre / Copiar se mais recente para ele e tudo funcionou. Mais tarde, porém, recebi uma resposta a uma pergunta na seção de discussão de Roslyn. O conselho me ajudou. É necessário se cadastrar no projeto do gerador exatamente assim:



<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, como alternativa, use o System.Text.Json integrado.



Usando meus outros conjuntos dentro do gerador



Além disso, seria bom usar meus outros conjuntos dentro do gerador. Por exemplo, as classes auxiliares para Vue e React seriam interessantes espalhadas em dois conjuntos diferentes e conectadas ao gerador conforme necessário.



Curiosamente, tudo correu bem para mim aqui. Acabei de conectar NetGenerator5.Generator.Dependency via Dependencies - Add Project Reference. Embora alguns tenham problemas.



Acesso a classes / tipos de controladores e modelos localizados em diferentes montagens



Agora vamos para a parte divertida. Para gerar arquivos - eu precisava acessar classes / tipos de controladores e modelos. A Microsoft recomenda o uso de SyntaxReceiver,

mas ele só tem acesso às classes do projeto compilado atual (ou seja, no meu caso NetGenerator5.Web), e as classes NetGenerator5.Model não estão lá.



Na mesma seção de discussão de Roslyn, uma solução foi encontrada . No contexto de GeneratorExecutionContext, existe Compilation.GlobalNamespace. Você pode examiná-lo recursivamente e obter descrições de todos os tipos, incluindo a montagem compilada atual e a montagem com modelos.



Depurando



Para depuração, basta escrever na classe geradora no método Initialize



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

      
      





Ao iniciar a construção do projeto principal, uma janela é aberta com uma proposta para iniciar o depurador. Se você clicar em OK, outra instância do Visual Studio será iniciada e o modo de depuração deste gerador estará nela. Você pode entrar em todas as outras classes e métodos, mesmo aqueles que estão em um assembly separado NetGenerator5.Generator.Dependency



Resultado



Após a compilação, o arquivo NetGenerator5.Web / wwwroot / js irá generated.js, e o



código-fonte completo do arquivo NetGenerator5.Web \ obj \ GeneratedFiles \ NetGenerator5.Generator \ NetGenerator5.Generator.SourceGenerator será gerado por dummy.cs aqui.



Origens






All Articles