Awesome
RazorGen
A C# source generator that renders Razor templates.
Warning: The source generator requires .NET 7 runtime installed and
dotnet
command available. See Source generation section for more information about the source generation process.
Info: If the project targets
.NET Standard 2.0
, then the project's references can be used in Razor templates.
Installation
dotnet add package Dartk.RazorGen
To avoid propagating dependency on the package set the option PrivateAssets="all"
in the project
file:
<ItemGroup>
<PackageReference Include="Dartk.RazorGen" Version="0.2.2" PrivateAssets="All" />
</ItemGroup>
Include razor template files with .razor extension to the project as AdditionalFiles
. For
example, to render all razor templates in the project add this to the project file:
<ItemGroup>
<AdditionalFiles Include="**/*.razor" />
</ItemGroup>
A complete example is presented below.
Source generation
Razor engine does not render a template directly, instead it generates a C# class that has a method that returns a rendered output. In order to render a template, an intermediate library with Razor generated classes needs to be compiled.
The source generator passes all found .razor
(included to the project as AdditionalFiles
)
templates to the Razor engine, which generates C# classes that render templates. Those classes are
compiled into a temporary intermediate library.
Source generators target .NET Standard 2.0
which does not support assembly unloading. In order to
prevent memory leak by repeated assembly loading, the intermediate library is called in
a separate process by an external .NET 6 executable.
If the project that uses the source generator targets .NET Standard 2.0
, then the project's
references will be referenced by the intermediate library.
Saving generated files
To save the generated source files set properties EmitCompilerGeneratedFiles
and CompilerGeneratedFilesOutputPath
in the project file:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<!--Generated files will be saved to 'obj\GeneratedFiles' folder-->
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
Example
Create a new console C# project:
dotnet new Example.Netstandard2_0
Install the package Dartk.RazorGen
and set the property PrivateAssets="All"
by
editing the project file Example.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<!--PrivateAssets="All" prevents propagation of dependency to other projects-->
<PackageReference Include="Dartk.RazorGen" Version="0.2.2"
PrivateAssets="All" />
<!--Scriban can be used within Razor templates, since the target platform is netstandard2.0-->
<PackageReference Include="Scriban" Version="5.7.0" />
</ItemGroup>
<!--Includes all .razor files as AdditionalFiles-->
<ItemGroup>
<AdditionalFiles Include="**\*.razor" />
</ItemGroup>
</Project>
Create a file RazorScribanMadness.razor
:
namespace Example.Netstandard2_0;
// 'partial' is used for JetBrains Rider, it's linter thinks that a '.razor' file declares
// a class, and will treat the generated code as second declaration highlighting errors
// in places where the class is being used.
public static partial class RazorScribanMadness
{
@using global::Scriban
@{
@:public static string Why() => "@(RenderScriban())";
string RenderScriban()
{
var template = Template.Parse("Because {{ reason }}!");
return template.Render(new { reason = "you can" });
}
}
}
The template above will generate following code:
namespace Example.Netstandard2_0;
// 'partial' is used for JetBrains Rider, it's linter thinks that a '.razor' file declares
// a class, and will treat the generated code as second declaration highlighting errors
// in places where the class is being used.
public static partial class RazorScribanMadness
{
public static string Why() => "Because you can!";
}