Русский
Русский
English
Статистика
Реклама

.NET 5 Source Generator Javascript

Задача реализовать генерацию SPA (Vue/React) приложения на основе моделей и контроллеров C#.

В .NET 5 появился source generator. С его помощью это и сделаем. В данной статье будут рассмотрены основные проблемы, с которыми я столкнулся при использовании source generator и их решение. Сама генерация UI выходит за рамки этой статьи. Используется Visual Studio 2019.

Итак, что для этого потребуется:
1. Возможность генерации js / vue / jsx файлов
2. Доступ к каталогу основного проекта
3. Доступ к файлу настроек
4. Использование сторонних библиотек внутри генератора, например Newtonsoft.Json
5. Использование других моих сборок внутри генератора
6. Доступ к классам/типам контроллеров и моделей, расположенных в разных сборках
7. Отладка

Пара слов о T4


В .NET 4.x есть кодогенератор T4. Изначально я пробовал решить свою задачу с его помощью. Был ряд проблем, в основном связанных с подгрузкой системных библиотек, которые решались с переменным успехом. Но когда дело дошло до обработки сборки .NET 5 с контроллерами, которая ссылается на чуждую (для .NET 4.x рантайма) AspNetCore библиотеку тут мой мозг зашел в тупик. T4 ни в какую не хотел ее находить и грузить.

Структура проекта


Все новые технологии Microsoft начинаются с Hello World, в котором все круто работает. Но когда начинаешь использовать их в реальном проекте, то сталкиваешься с кучей проблем. Одной из таких как раз является структура проекта. В Hello World это одна сборка. А в реальном проекте их несколько.

Мой проект включает в себя четыре условные сборки:
1. NetGenerator5.Web основное запускаемое веб-приложение (net5.0), содержит контроллеры, к нему подключается сборка с моделями и сам генератор.
2. NetGenerator5.Model cборка с моделями (net5.0)
3. NetGenerator5.Generator cборка с генератором (netstandard2.0)
4. NetGenerator5.Generator.Dependency условная сборка, которая используется внутри генератора (netstandard2.0)

Генератор


Класс генератора реализует интерфейс ISourceGenerator с двумя методами Initialize и Execute. Метод Execute будет запускаться непосредственно во время компиляции проекта, к которому подключен генератор.

Сам проект генератора

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

Как его подключать? Необходимо в основном проекте (NetGenerator5.Web), прописать следующее:

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

Возможность генерации js / vue / jsx файлов


Изначально у генератора на выходе cs файлы с C# кодом. Для этого внутри метода Execute используется метод контекста GeneratorExecutionContext.AddSource. Поменять расширение у них, я так понял, нельзя и эти файлы так же компилируются. Поэтому поместить туда код на любом другом языке не представляется возможным. Visual Studio начинает выдавать ошибки компиляции.

Поэтому для сохранения js / vue / jsx файлов нам потребуется другой подход. Обычный System.IO.File.WriteAllText мне помог. Но для этого необходимо знать куда именно надо сохранить сгенерированные файлы, т.е. знать каталог основного проекта.

Доступ к каталогу основного проекта


Его можно получить следующим образом:

Прописать в основном NetGenerator5.Web проекте следующее:
<ItemGroup>  <CompilerVisibleProperty Include="MSBuildProjectDirectory" /></ItemGroup>

Этим мы сделаем видимой системную переменную для source generator.

А в самом генераторе получим к ней доступ в методе Execute следующим образом:
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.MSBuildProjectDirectory", out var projectDirectory)

Помимо этого нам надо знать куда именно складывать сгенерированные файлы внутри самого веб проекта (например в wwwroot/js). Мне пришло в голову передать это через файл с настройками generatorsettings.json, который располагался бы в основном проекте. Но теперь мне как-то необходимо рассказать о нем генератору.

Доступ к файлу настроек


В генераторе есть возможность обратиться к файлам через коллекцию контекста GeneratorExecutionContext.AdditionalFiles внутри метода Execute. Чтобы мой файл с настройками оказался там, необходимо проставить у него свойство Build Action=C# analyzer additional file, или так:
<ItemGroup>  <AdditionalFiles Include="generatorsettings.json" /></ItemGroup>

После этого содержимое файла можно считать следующим образом
var content = context.AdditionalFiles.First(e => e.Path.EndsWith("generatorsettings.json")).GetText(context.CancellationToken);

Далее возникает проблема это же json, а как мне, собственно, его распарсить?

Использование сторонних библиотек внутри генератора


Использовать внешнюю библиотеку. Например Newtonsoft.Json. Вот тут действительно что-то пошло не так. Я ее подключил через nuget, но генератор ни в какую не хотел видеть эту библиотеку.

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.

и хоть ты тресни.

В cookbook есть раздел, посвященный этому
Там даже немного больше информации как свой генератор оформить в виде nuget пакета. Мне это почему-то не помогло.

В итоге сначала решил странным способом. Я тупо добавил саму библиотеку напрямую в проект как файл и указал для нее Copy to Output Directory = Copy always / Copy if newer и все заработало. Но позже мне ответили на вопрос в разделе дискуссий, посвящённому roslyn. Совет мне помог. Нужно прописать в проекте генератора именно так:

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

Или, как альтернатива, использовать встроенный System.Text.Json.

Использование других моих сборок внутри генератора


Далее, было бы неплохо использовать внутри генератора другие мои сборки. Например, вспомогательные классы для Vue и React хорошо бы разбросать по двум разным сборкам и подключать их к генератору по необходимости.

Как ни странно, здесь у меня все прошло гладко. Я просто подключил NetGenerator5.Generator.Dependency через Dependencies Add Project Reference. Хотя у кого-то возникали проблемы.

Доступ к классам/типам контроллеров и моделей, расположенных в разных сборках


Теперь перейдем к самому интересному. Чтобы сгенерировать файлы мне нужен был доступ к классам/типам контроллеров и моделей. Microsoft рекомендует использовать SyntaxReceiver
Но он имеет доступ только к классам текущего компилируемого проекта (т.е. в моем случае NetGenerator5.Web), а классов NetGenerator5.Model там нет.

В том же разделе дискуссий roslyn было найдено решение. Внутри контекста GeneratorExecutionContext есть Compilation.GlobalNamespace. По нему можно пройтись рекурсивно и получить описания всех типов, в том числе и текущей компилируемой сборки и сборки с моделями.

Отладка


Для отладки достаточно прописать в классе генератора в методе Initialize

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

При запуске билда основного проекта открывается окно с предложением запустить отладчик. Если нажать OK то будет запущен еще один экземпляр Visual Studio и в нем будет режим отладки данного генератора. Можно заходить внутрь всех других классов и методов, даже в те, которые находятся в отдельной сборке NetGenerator5.Generator.Dependency

Итоги


После компиляции в NetGenerator5.Web / wwwroot/js появится файл generated.js, а в NetGenerator5.Web\obj\GeneratedFiles\NetGenerator5.Generator\NetGenerator5.Generator.SourceGenerator появится файл пустышка generated.cs

Полный исходный код можно посмотреть тут

Источники


Источник: habr.com
К списку статей
Опубликовано: 14.02.2021 02:14:08
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Net

Net5

Sourcegenerator

Javascript

Js

React

Vue

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru