This library provides a .NET Core 3.1 compatible MSBuild task that will automatically compile all .cshtml
files into .cs
files in-place and also provides you code you can call to then invoke those classes with a model and get the resulting output.
This library is inspired by RazorGenerator, which was a custom tool for Visual Studio that created a compiled .cs
file when saving a .cshtml
file in your IDE. This was handy for situations where you want to have static template files (e.g. email templates, library templates, etc.). RazorGenerator doesn't support .NET Core, hence creating this.
This library is implemented by providing a thin wrapper over the RazorLight library and breaking it up into two stages:
- Compilation of
.cshtml
files to.cs
files - this happens just before theCoreCompile
task of the project you install this library into via the included MSBuild task. - Rendering / invocation of the resultant
.cs
code
Because this library generates .cs
files you don't need to worry about compilation performance, runtime compilation errors, caching, etc. It also allows you to have the predictability of having real classes you can reference from your code rather than relying on magic strings to find your .cshtml
files and needing to either ship your .cshtml
files with your code or embed them into your dll. Using this solution those files are only used at compile time and can then be discarded.
- Include
SomeFile.cshtml
in your (netcoreapp3.1) project Install-Package MSBuildRazorCompiler
- Compile - you should now see
SomeFile.cshtml.cs
next to yourSomeFile.cshtml
, this new file will contain a classSomeFile
that extendsRazorLight.TemplatePage<TModel>
and will be in the namespace of your project at the folder level your file was in - Execute the following code to get a rendered result (note: you'll need to add a
using RazorRenderer;
):new SomeFile().Render(model, viewBag or null)
- there is also an async variant
By default your dll will use IlRepack to internalise RazorRenderer and RazorLight so your assembly won't have any transitive dependencies outside of Microsoft.AspNetCore.Mvc.Razor.Extensions
and Microsoft.AspNetCore.Razor.Language
.
If you want to configure the options for the compiler you can override any of these default variables in your .csproj
file:
<BuildPath Condition="'$(BuildPath)' == ''">$(MSBuildThisFileDirectory)</BuildPath>
<MSBuildRazorCompile Condition="'$(MSBuildRazorCompile)' == ''">true</MSBuildRazorCompile>
<MsBuildRazorCompilerILRepack Condition="'$(MsBuildRazorCompilerILRepack)' == ''">true</MsBuildRazorCompilerILRepack>
<MSBuildRazorCompilerExe Condition="'$(MSBuildRazorCompilerExe)' == ''">dotnet "$(BuildPath)netcoreapp3.1\MSBuildRazorCompiler.dll"</MSBuildRazorCompilerExe>
<MsBuildRazorCompilerPath Condition="'$(MsBuildRazorCompilerPath)' == ''">$(MSBuildProjectDirectory)</MsBuildRazorCompilerPath>
<MsBuildRazorCompilerRootNamespace Condition="'$(MsBuildRazorCompilerRootNamespace)' == ''">$(RootNamespace)</MsBuildRazorCompilerRootNamespace>
<MsBuildRazorCompilerClassModifier Condition="'$(MsBuildRazorCompilerClassModifier)' == ''">internal</MsBuildRazorCompilerClassModifier>
<CopyLocalLockFileAssemblies Condition="'$(MsBuildRazorCompilerILRepack)' == 'true' and '$(CopyLocalLockFileAssemblies)' == ''">true</CopyLocalLockFileAssemblies>
For intellisense to work it's recommended you add the following to the top of your file:
@inherits RazorLight.TemplatePage<MyModelType>
or:
@inherits RazorRenderer.BasePage<MyModelType>
The latter will opt you into functionality added to this library (see below).
The RazorLight Include
functionality has been disabled since it uses a magic string and this library aims to avoid runtime dynamic functionality, however, there is an include functionality that you can use. If you extend from RazorRenderer.BasePage<TModel>
from the parent page and the child page:
@inherits RazorRenderer.BasePage<MyModelType>
Then in your parent file you can include another page using:
@{ Include<SubPage, MyModelType>(new MyModelType()); }
Where SubPage
is the typename generated for the included page.
It's recommended that you add *.cshtml.cs
to your .gitignore
file so that you don't commit the generated files. That avoids superfluous diff/commit noise.
By default, your .cshtml.cs
files will be nested under their .cshtml
counterparts this using the File Nesting feature in Visual Studio if your project is classified as an ASP.NET Core project.
If you have a console app or class library you can trick it by ensuring your .csproj
file starts with <Project Sdk="Microsoft.NET.Sdk.Web">
. There are a couple of automatic behaviours you need to opt-out of though if you do that.
Either way you should add the following to the .csproj
:
<PropertyGroup>
<PreserveCompilationContext>true</PreserveCompilationContext>
<RazorLangVersion>3.0</RazorLangVersion>
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnPublish>false</RazorCompileOnPublish>
</PropertyGroup>
Note: if you have a console app that means you'll need to add a launchsettings.json
file in your Properties
folder with something like this so that F5 still launches the app rather than IIS Express:
{
"profiles": {
"Test": {
"commandName": "Project"
}
}
}
You should add the following to the .csproj
:
<PropertyGroup>
<OutputType>Library</OutputType>
</PropertyGroup>
And add a launchsettings.json
file in your Properties
folder with something like this so that F5 doesn't launch IIS Express (it will fail, but it's meant to - it's a class library):
{}