RazorBlade
Compile Razor templates at build-time without a dependency on ASP.NET.
Install / Use
/learn @ltrzesniewski/RazorBladeREADME
RazorBlade <picture><source media="(prefers-color-scheme: dark)" srcset="icon-dark.png"><img src="icon.png" align="right" alt="Logo"></picture>
Compile Razor templates at build-time without a dependency on ASP.NET.
RazorBlade is meant to be lightweight and self-contained: cshtml files are compiled into C# classes at build-time with a Roslyn source generator. No reference to ASP.NET is required.
A simple base class library is provided by default, but it can also be embedded into the target project, or even replaced by your own implementation.
Usage
This package will generate a template class for every .cshtml file in your project.
The generated classes will inherit from RazorBlade.HtmlTemplate by default, though it is advised to specify the base class explicitly to get the best IDE experience:
<a id='snippet-EmptyTemplate.cshtml'></a>
@inherits RazorBlade.HtmlTemplate
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/EmptyTemplate.cshtml#L1-L1' title='Snippet source file'>snippet source</a> | <a href='#snippet-EmptyTemplate.cshtml' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->A version with a model is also available for convenience. The following will add a Model property and a constructor with a ModelType parameter:
<a id='snippet-EmptyTemplateWithModel.cshtml'></a>
@inherits RazorBlade.HtmlTemplate<MyApplication.ModelType>
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/EmptyTemplateWithModel.cshtml#L1-L1' title='Snippet source file'>snippet source</a> | <a href='#snippet-EmptyTemplateWithModel.cshtml' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Please note that this will cause a constructor with a ModelType parameter to be added to the generated class, which may cause false errors to be shown in some IDEs.
Further documentation is provided below.
Example
The following template, in the ExampleTemplate.cshtml file:
<a id='snippet-ExampleTemplate.cshtml'></a>
@inherits RazorBlade.HtmlTemplate
Hello, <i>@Name</i>!
@functions
{
public string? Name { get; init; }
}
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/ExampleTemplate.cshtml#L1-L8' title='Snippet source file'>snippet source</a> | <a href='#snippet-ExampleTemplate.cshtml' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Will generate the following class in your project:
internal partial class ExampleTemplate : RazorBlade.HtmlTemplate
{
// ...
public string? Name { get; init; }
// ...
}
That you can use like the following:
<!-- snippet: ExampleTemplate.Usage --><a id='snippet-ExampleTemplate.Usage'></a>
var template = new ExampleTemplate
{
Name = "World"
};
var result = template.Render();
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/Examples.cs#L13-L22' title='Snippet source file'>snippet source</a> | <a href='#snippet-ExampleTemplate.Usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->With a model
A similar template with a model would be:
<!-- snippet: TemplateWithModel.cshtml --><a id='snippet-TemplateWithModel.cshtml'></a>
@using MyApplication
@inherits RazorBlade.HtmlTemplate<GreetingModel>
Hello, <i>@Model.Name</i>!
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/TemplateWithModel.cshtml#L1-L4' title='Snippet source file'>snippet source</a> | <a href='#snippet-TemplateWithModel.cshtml' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Instantiating the generated class requires a model argument:
<!-- snippet: TemplateWithModel.Usage --><a id='snippet-TemplateWithModel.Usage'></a>
var model = new GreetingModel { Name = "World" };
var template = new TemplateWithModel(model);
var result = template.Render();
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/Examples.cs#L27-L33' title='Snippet source file'>snippet source</a> | <a href='#snippet-TemplateWithModel.Usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Since this generates a constructor with a GreetingModel parameter in the TemplateWithModel class, it may cause false errors to be shown in some IDEs, as they don't recognize this constructor signature.
With a manual model property
Another way of implementing a template with a model is to add a Model property in the template and mark it as required. This will work around false errors which can be shown in some IDEs.
<a id='snippet-TemplateWithManualModel.cshtml'></a>
@using MyApplication
@inherits RazorBlade.HtmlTemplate
Hello, <i>@Model.Name</i>!
@functions
{
public required GreetingModel Model { get; init; }
}
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/TemplateWithManualModel.cshtml#L1-L9' title='Snippet source file'>snippet source</a> | <a href='#snippet-TemplateWithManualModel.cshtml' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Instantiating the generated class is done similarly to the previous example:
<!-- snippet: TemplateWithManualModel.Usage --><a id='snippet-TemplateWithManualModel.Usage'></a>
var model = new GreetingModel { Name = "World" };
var template = new TemplateWithManualModel { Model = model };
var result = template.Render();
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/Examples.cs#L38-L44' title='Snippet source file'>snippet source</a> | <a href='#snippet-TemplateWithManualModel.Usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Documentation
Base template classes
For HTML templates, specify one of the following base classes with an @inherits directive:
RazorBlade.HtmlTemplateRazorBlade.HtmlTemplate<TModel>RazorBlade.HtmlTemplateWithLayout<TLayout>(automatically applies the given layout)RazorBlade.HtmlLayout(for layouts only)
If you'd like to write a plain text template (which never escapes HTML), the following classes are available:
RazorBlade.PlainTextTemplateRazorBlade.PlainTextTemplate<TModel>
They all derive from RazorBlade.RazorTemplate, which provides the base functionality.
You can also write your own base classes. Marking a constructor with [TemplateConstructor] will forward it to the generated template class.
Writing templates
HTML escaping can be avoided by using the @Html.Raw(value) method, just like in ASP.NET. The IEncodedContent interface represents content which does not need to be escaped. The HtmlString class is a simple implementation of this interface.
The namespace of the generated class can be customized with the @namespace directive. The default value is deduced from the file location.
Partials
Templates can be included in other templates by evaluating them, as they implement IEncodedContent. For instance, a Footer template can be included by writing @(new Footer()).
Since templates are stateful and not thread-safe, the simplest way to avoid race conditions is to always create a new instance of a partial to render:
<!-- snippet: TemplateWithPartials.Simple --><a id='snippet-TemplateWithPartials.Simple'></a>
<ul>
@foreach (var item in ListItems)
{
@(new MyPartial { Value = item })
}
</ul>
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/TemplateWithPartials.cshtml#L4-L11' title='Snippet source file'>snippet source</a> | <a href='#snippet-TemplateWithPartials.Simple' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->But you may also reuse a single partial instance as long as it is only accessible to the template instance which uses it:
<!-- snippet: TemplateWithPartials.SingleInstance --><a id='snippet-TemplateWithPartials.SingleInstance'></a>
@{ var partialInstance = new MyPartial(); }
<ul>
@foreach (var item in ListItems)
{
partialInstance.Value = item;
@partialInstance
}
</ul>
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/TemplateWithPartials.cshtml#L13-L22' title='Snippet source file'>snippet source</a> | <a href='#snippet-TemplateWithPartials.SingleInstance' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Layouts
Layout templates may be written by inheriting from the RazorBlade.HtmlLayout class, which provides the relevant methods such as RenderBody and RenderSection. It inherits from RazorBlade.HtmlTemplate.
The layout to use can be specified by overriding the CreateLayout method of RazorBlade.HtmlTemplate. Given that all Razor templates are stateful and not thread-safe, always create a new instance of the layout page to use:
<a id='snippet-TemplateWithLayout.Usage'></a>
@functions
{
protected override HtmlLayout? CreateLayout()
=> new LayoutToUse();
}
<sup><a href='/src/RazorBlade.IntegrationTest/Examples/TemplateWithLayout.cshtml#L2-L8' title='Snippet source file'>snippet source</a> | <a href='#snippet-TemplateWithLayout.Usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->This can be simplified by using the HtmlTemplateWithLayout<TLayout> class, which can be useful in _ViewImports.cshtml files:
