.NET 5 memperkenalkan generator sumber. Kami akan melakukan ini dengan bantuannya. Artikel ini akan membahas masalah utama yang saya temui saat menggunakan generator sumber dan solusinya. Membuat UI itu sendiri berada di luar cakupan artikel ini. Digunakan oleh Visual Studio 2019.
Jadi, yang diperlukan untuk ini:
1. Kemampuan untuk menghasilkan file js / vue / jsx
2. Akses ke direktori proyek utama
3. Akses ke file pengaturan
4. Menggunakan pustaka pihak ketiga di dalam generator, misalnya Newtonsoft.Json
5. Menggunakan rakitan saya yang lain di dalam generator
6. Akses ke kelas / jenis pengontrol dan model yang terletak di rakitan berbeda
7. Debugging
Beberapa kata tentang T4
.NET 4.x memiliki generator kode T4. Awalnya, saya mencoba menyelesaikan masalah saya dengannya. Ada sejumlah masalah, terutama yang berkaitan dengan pemuatan perpustakaan sistem, yang diselesaikan dengan berbagai keberhasilan. Tetapi ketika harus menangani rakitan .NET 5 dengan pengontrol, yang mengacu pada pustaka AspNetCore alien (untuk .NET 4.x runtime), otak saya berada di jalan buntu. T4 tidak ingin mencari dan memuatnya dengan cara apa pun.
Struktur proyek
Semua teknologi baru Microsoft dimulai dengan Hello World, di mana semuanya bekerja dengan baik. Tetapi ketika Anda mulai menggunakannya dalam proyek nyata, Anda mengalami banyak masalah. Salah satunya adalah struktur proyek. Di Hello World, ini adalah satu perakitan. Dan dalam proyek nyata ada beberapa di antaranya.
Proyek saya mencakup empat rakitan bersyarat:
1. NetGenerator5.Web - aplikasi web utama yang diluncurkan (net5.0), berisi pengontrol, rakitan dengan model dan generator itu sendiri terhubung dengannya.
2. NetGenerator5.Model - perakitan dengan model (net5.0)
3. NetGenerator5.Generator - perakitan dengan generator (netstandard2.0)
4. NetGenerator5.Generator.Dependency - perakitan bersyarat yang digunakan di dalam generator (netstandard2.0)
Generator
Kelas generator mengimplementasikan antarmuka ISourceGenerator dengan dua metode, Initialize dan Execute. Metode Execute akan berjalan langsung selama kompilasi proyek yang dihubungkan dengan generator.
Proyek generator itu sendiri
<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>
Bagaimana cara menghubungkannya? Ini diperlukan dalam proyek utama (NetGenerator5.Web), daftarkan yang berikut ini:
<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>
Kemampuan untuk menghasilkan file js / vue / jsx
Awalnya, generator mengeluarkan file cs dengan kode C #. Untuk melakukannya, di dalam metode Execute, metode konteks GeneratorExecutionContext.AddSource digunakan. Sejauh yang saya mengerti, tidak mungkin untuk mengubah ekstensi mereka dan file-file ini juga dikompilasi. Oleh karena itu, tidak mungkin untuk meletakkan kode dalam bahasa lain di sana. Visual Studio mulai membuat kesalahan kompilasi.
Oleh karena itu, kita membutuhkan pendekatan berbeda untuk menyimpan file js / vue / jsx. System.IO.File.WriteAllText yang biasa membantu saya. Tetapi untuk ini, Anda perlu tahu persis di mana Anda perlu menyimpan file yang dihasilkan, mis. ketahui direktori proyek utama.
Akses ke direktori proyek utama
Ini dapat diperoleh sebagai berikut:
Dalam proyek NetGenerator5.Web utama, tulis yang berikut ini:
<ItemGroup>
<CompilerVisibleProperty Include="MSBuildProjectDirectory" />
</ItemGroup>
Ini akan membuat variabel sistem untuk generator sumber terlihat.
Dan di generator itu sendiri, kita akan mengaksesnya dengan metode Execute sebagai berikut:
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.MSBuildProjectDirectory", out var projectDirectory)
Selain itu, kita perlu tahu persis di mana harus meletakkan file yang dihasilkan di dalam proyek web itu sendiri (misalnya, di wwwroot / js). Terpikir oleh saya untuk melewatkan ini melalui file konfigurasi generatorsettings.json di proyek utama. Tapi sekarang entah bagaimana saya perlu memberi tahu generator tentang hal itu.
Mengakses file pengaturan
Generator memiliki kemampuan untuk mengakses file melalui kumpulan konteks GeneratorExecutionContext.AdditionalFiles di dalam metode Execute. Agar file konfigurasi saya ada di sana, Anda perlu mengatur properti file tambahan Build Action = C # analyzer di atasnya, atau seperti ini:
<ItemGroup>
<AdditionalFiles Include="generatorsettings.json" />
</ItemGroup>
Setelah itu, isi file tersebut dapat dibaca sebagai berikut
var content = context.AdditionalFiles.First(e => e.Path.EndsWith("generatorsettings.json")).GetText(context.CancellationToken);
Selanjutnya, masalah muncul - ini json, tapi bagaimana saya bisa menguraikannya?
Menggunakan pustaka pihak ketiga di dalam generator
Gunakan perpustakaan eksternal. Misalnya Newtonsoft.Json. Di sinilah ada sesuatu yang salah. Saya menghubungkannya melalui nuget, tetapi generator tidak ingin melihat perpustakaan ini dengan cara apa pun.
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.
dan meskipun Anda retak.
Buku masak memiliki bagian yang didedikasikan untuk
ini.Ada lebih banyak informasi tentang bagaimana merancang generator Anda sebagai paket nuget. Untuk beberapa alasan itu tidak membantu saya.
Akibatnya, pada awalnya saya memutuskan dengan cara yang aneh. Saya dengan bodoh menambahkan pustaka itu sendiri langsung ke proyek sebagai file dan menentukan Salin ke Direktori Output = Salin selalu / Salin jika lebih baru untuk itu dan semuanya berfungsi. Tetapi kemudian saya mendapat jawaban untuk sebuah pertanyaan di bagian diskusi roslyn. Nasihat itu membantu saya. Diperlukan untuk mendaftar di proyek generator persis seperti ini:
<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>
Atau sebagai alternatif, gunakan System.Text.Json bawaan.
Menggunakan rakitan saya yang lain di dalam generator
Selanjutnya, alangkah baiknya menggunakan rakitan saya yang lain di dalam generator. Misalnya, class helper untuk Vue dan React akan bagus untuk tersebar di dua rakitan berbeda dan dihubungkan ke generator sesuai kebutuhan.
Anehnya, semuanya berjalan lancar bagiku di sini. Saya baru saja menghubungkan NetGenerator5.Generator.Dependency melalui Dependencies - Tambahkan Referensi Proyek. Meskipun beberapa mengalami masalah.
Akses ke kelas / tipe pengontrol dan model yang terletak di rakitan berbeda
Sekarang mari kita ke bagian yang menyenangkan. Untuk menghasilkan file - Saya membutuhkan akses ke kelas / jenis pengontrol dan model. Microsoft merekomendasikan menggunakan SyntaxReceiver
Tetapi itu hanya memiliki akses ke kelas proyek yang dikompilasi saat ini (yaitu dalam kasus saya NetGenerator5.Web), dan kelas NetGenerator5.Model tidak ada.
Di bagian diskusi roslyn yang sama, solusi ditemukan . Dalam konteks GeneratorExecutionContext, ada Compilation.GlobalNamespace. Anda dapat membahasnya secara rekursif dan mendapatkan deskripsi dari semua jenis, termasuk perakitan terkompilasi saat ini dan perakitan dengan model.
Debugging
Untuk debugging, cukup menulis di kelas generator dalam metode Initialize
#if DEBUG
if (!Debugger.IsAttached)
{
Debugger.Launch();
}
#endif
Saat memulai pembuatan proyek utama, sebuah jendela terbuka dengan proposal untuk memulai debugger. Jika Anda mengklik OK, maka contoh lain dari Visual Studio akan diluncurkan dan mode debug generator ini akan ada di dalamnya. Anda dapat masuk ke dalam semua kelas dan metode lain, bahkan yang berada dalam rakitan terpisah NetGenerator5.Generator.Dependency
Hasil
Setelah kompilasi file NetGenerator5.Web / wwwroot / js akan dibuat.js, dan NetGenerator5.Web \ obj \ GeneratedFiles \ NetGenerator5.Generator \ NetGenerator5.Generator.SourceGenerator akan membuat file Dummy generated.cs
kode sumber lengkap dapat dilihat di sini
Sumber
- 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