[.NET] .NET 5 + Source Generator = Javascript
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Задача реализовать генерацию 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
Полный исходный код можно посмотреть тут
Источники
- 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
===========
Источник:
habr.com
===========
Похожие новости:
- [.NET, CRM-системы, Microsoft Azure, DevOps] CI/CD для Dynamics CRM на базе Azure DevOps
- [DIY или Сделай сам] Математика, красота, любовь — история одной валентинки
- [Разработка веб-сайтов, JavaScript, Программирование, .NET] Рабочий прототип секретного мессенджера
- [.NET, C#] Тестирование генератора исходного кода
- [.NET, C#, F#] Букварь по F# для любопытствующих C#-разработчиков (перевод)
- [Программирование, .NET, C#] Шпион под прикрытием: проверяем исходный код ILSpy с помощью PVS-Studio
- [Программирование, .NET, C#] A Spy Undercover: PVS-Studio to Check ILSpy Source Code
- [.NET, C++] Разбор протокола World of Tanks
- [C#, VueJS] Оптимизация страницы с использованием RxJS и Expression Tree
- [.NET, Разработка игр, C#] Бэк-офис для игр или «результат борьбы с пенсионной скукой»
Теги для поиска: #_.net, #_#net5, #_#sourcegenerator, #_#javascript, #_#js, #_#react, #_#vue, #_.net
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 11:43
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Задача реализовать генерацию 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 =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 11:43
Часовой пояс: UTC + 5