Package Description
$ dotnet add package RazorSlicesLightweight Razor-based templates for ASP.NET Core without MVC, Razor Pages, or Blazor, optimized for high-performance, unbuffered rendering with low allocations. Compatible with trimming and native AOT. Great for returning dynamically rendered HTML from Minimal APIs, middleware, etc. Supports .NET 8+
Install the NuGet package into your ASP.NET Core project (.NET 8+):
> dotnet add package RazorSlices
Create a directory in your project called Slices and add a _ViewImports.cshtml file to it with the following content:
@inherits RazorSlice
@using System.Globalization;
@using Microsoft.AspNetCore.Razor;
@using Microsoft.AspNetCore.Http.HttpResults;
@using RazorSlices;
@tagHelperPrefix __disable_tagHelpers__:
@removeTagHelper *, Microsoft.AspNetCore.Mvc.Razor
In the same directory, add a Hello.cshtml file with the following content:
@inherits RazorSlice<DateTime>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello from Razor Slices!</title>
</head>
<body>
<p>
Hello from Razor Slices! The time is @Model
</p>
</body>
</html>
Each .cshtml file will have a proxy type generated for it by the Razor Slices source generator that you can use as the generic argument to the various APIs in Razor Slices for rendering slices. The source generator detects the model type from the @inherits directive and generates a strongly-typed Create method:
Create(ModelType model) (e.g., MyApp.Slices.Hello.Create(DateTime.Now))Create() (e.g., MyApp.Slices.Home.Create())This provides compile-time validation that you're passing the correct model type.
Add a minimal API to return the slice in your Program.cs:
app.MapGet("/hello", () => MyApp.Slices.Hello.Create(DateTime.Now));
Optional: By default, all .cshtml files in your project are treated as Razor Slices (excluding _ViewImports.cshtml and ViewStart.cshtml). You can change this by setting the EnableDefaultRazorSlices property to false and then including the desired RazorSlice items in your project file, e.g.:
<PropertyGroup>
<EnableDefaultRazorSlices>false</EnableDefaultRazorSlices>
</PropertyGroup>
<ItemGroup>
<!-- Only treat .cshtml files in Slices directory as Razor Slices -->
<RazorSlice Include="Slices\**\*.cshtml" Exclude="Slices\**\_Layout.cshtml;Slices\**\_ViewImports.cshtml" />
</ItemGroup>
This will configure the Razor Slices source generator to only generate proxy types for .cshtml files in the Slices directory in your project.
This package is currently available from nuget.org:
> dotnet add package RazorSlices
If you wish to use builds from this repo's main branch you can install them from this repo's package feed.
Create a personal access token for your GitHub account with the read:packages scope with your desired expiration length:
At the command line, navigate to your user profile directory and run the following command to add the package feed to your NuGet configuration, replacing the <GITHUB_USER_NAME> and <PERSONAL_ACCESS_TOKEN> placeholders with the relevant values:
~> dotnet nuget add source -n GitHub -u <GITHUB_USER_NAME> -p <PERSONAL_ACCESS_TOKEN> https://nuget.pkg.github.com/DamianEdwards/index.json
You should now be able to add a reference to the package specifying a version from the repository packages feed
See these instructions for further details about working with GitHub package feeds.
The library is still new and features are being actively added.
ASP.NET Core 8.0 and above
Strongly-typed models (via @inherits RazorSlice<MyModel>) with compile-time model validation — the source generator detects the model type from the @inherits directive (including _ViewImports.cshtml hierarchy) and generates strongly-typed Create(MyModel model) methods on the proxy class
Razor constructs:
Implicit expressions, e.g. @someVariable
Explicit expressions, e.g. @(someBool ? thisThing : thatThing)
Control structures, e.g. @if(), @switch(), etc.
Looping, e.g. @for, @foreach, @while, @do
Code blocks, e.g. @{ var someThing = someOtherThing; }
@functions {
private readonly string _someString = "A very important string";
private int DoAThing() => 123;
}
Templated Razor methods, e.g.
@inherits RazorSlice<Todo>
<h1>@Title(Model)</h1>
@functions {
private IHtmlContent Title(Todo todo)
{
<text>Todo @todo.Id: @todo.Title</text>
return HtmlString.Empty;
}
}
Templated Razor delegates, e.g.
@inherits RazorSlice<Todo>
@{
var tmpl = @<div>
This is a templated Razor delegate. The following value was passed in: @item
</div>;
}
@tmpl(DateTime.Now)
NOTE: Async templated Razor delegates are NOT supported and will throw an exception at runtime
DI-activated properties via @inject
Rendering slices from slices (aka partials) via @(await RenderPartialAsync<MyPartial>())
Using slices as layouts for other slices, including layouts with strongly-typed models:
For the layout slice, inherit from RazorLayoutSlice or RazorLayoutSlice<TModel> and use @await RenderBodyAsync() in the layout to render the body
@inherits RazorLayoutSlice<LayoutModel>
<!DOCTYPE html>
<html lang="en">
<head>
<title>@Model.Title</title>
@await RenderSectionAsync("head")
</head>
<body>
@await RenderBodyAsync()
<footer>
@await RenderSectionAsync("footer")
</footer>
</body>
</html>
For the slice using the layout, implement IUsesLayout<TLayout> or IUsesLayout<TLayout, TModel> to declare which layout to use. If using a layout with a model, ensure you implement the LayoutModel property in your @functions block, e.g
@inherits RazorSlice<SomeModel>
@implements IUsesLayout<LayoutSlice, LayoutModel>
<div>
@* Content here *@
</div>
@functions {
public LayoutModel LayoutModel => new() { Title = "My Layout" };
}
Layouts can render sections via @await RenderSectionAsync("SectionName") and slices can render content into sections by overriding ExecuteSectionAsync, e.g.:
protected override Task ExecuteSectionAsync(string name)
{
if (name == "lorem-header")
{
<p class="text-info">This page renders a custom <code>IHtmlContent</code> type that contains lorem ipsum content.</p>
}
return Task.CompletedTask;
}
NOTE: The @section directive is not supported as it's incompatible with the rendering approach of Razor Slices
Asynchronous rendering, i.e. the template can contain await statements, e.g. @await WriteTheThing()
Writing UTF8 byte[] values directly to the output
Rendering directly to PipeWriter, Stream, TextWriter, StringBuilder, and string outputs, including optimizations for not boxing struct values, zero-allocation rendering of primitives like numbers, etc. (rather than just calling ToString() on everything)
RazorSlice implements IResult so you can return a slice instance from a minimal APIs route handler directly, or via Results.Extensions.RazorSlice<Hello>() and in .NET 10+ Results.RazorSlice<Hello>(). When using slices with models use Results.Extensions.RazorSlice<Hello, MyModel>(model) and in .NET 10+ Results.RazorSlice<Hello, MyModel>(model).
Full support for trimming and native AOT when used in conjunction with ASP.NET Core Minimal APIs
Generated Razor Slice proxy types are public sealed partial class by default. To use record classes instead, making them public sealed partial record, set the RazorSliceProxiesAsRecords property to true in your project file, e.g.:
<PropertyGroup>
<RazorSliceProxiesAsRecords>true</RazorSliceProxiesAsRecords>
</PropertyGroup>
ExecuteAsync method as an async method unless the template contains await statements to save on the async state machine overheadReadOnlySpan<byte>) instead of string literals to save on the UTF16 to UTF8 conversion during rendering@addtaghelper and @model directives which rely on MVC@model directive (the Razor compiler does not support its use in conjunction with custom base-types via @inherits)@attribute [Authorize] (wrong layer of abstraction for minimal APIs, etc.)@section directive (the Razor compiler emits code that is incompatible with the rendering approach of Razor Slices)