RazorSlices by Damiam Edwards
Nuget / site data
Details
Info
Name: RazorSlices
Package Description
Author: Damiam Edwards
NuGet: https://www.nuget.org/packages/RazorSlices/
You can find more details at https://github.com/DamianEdwards/RazorSlices
Original Readme
Razor Slices
Lightweight 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+
Getting Started
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 RazorSliceHttpResult
@using System.Globalization;
@using Microsoft.AspNetCore.Razor;
@using Microsoft.AspNetCore.Http.HttpResults;
@tagHelperPrefix __disable_tagHelpers__:
@removeTagHelper *, Microsoft.AspNetCore.Mvc.RazorIn the same directory, add a Hello.cshtml file with the following content:
@inherits RazorSliceHttpResult<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.
Add a minimal API to return the slice in your Program.cs:
app.MapGet("/hello", () => Results.Extensions.RazorSlice<MyApp.Slices.Hello, DateTime>(DateTime.Now));
Installation
NuGet Releases
This package is currently available from nuget.org:
> dotnet add package RazorSlices
CI Builds
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.
Features
The library is still new and features are being actively added.
Currently supported
ASP.NET Core 8.0 and above
Strongly-typed models (via
@inherits RazorSlice<MyModel>
or@inherits RazorSliceHttpResult<MyModel>
)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, e.g.
@functions {
private readonly string _someString = "A very important string";
private int DoAThing() => 123;
}Templated Razor delegates, 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;
}
}
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
orRazorLayoutSlice<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>
orIUsesLayout<TLayout, TModel>
to declare which layout to use. If using a layout with a model, ensure you implement theLayoutModel
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 overridingExecuteSectionAsync
, 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 outputRendering directly to
PipeWriter
,Stream
,TextWriter
,StringBuilder
, andstring
outputs, including optimizations for not boxing struct values, zero-allocation rendering of primitives like numbers, etc. (rather than just callingToString()
on everything)Return a slice instance directly as an
IResult
in minimal APIs via@inherits RazorSliceHttpResult
andResults.Extensions.RazorSlice("/Slices/Hello.cshtml")
Full support for trimming and native AOT when used in conjunction with ASP.NET Core Minimal APIs
Interested in supporting but not sure yet
- Extensions to help support using HTMX with Razor Slices
- Getting small updates to the Razor compiler itself to get some usability and performance improvements, e.g.:
- Don't mark the template's
ExecuteAsync
method as anasync
method unless the template containsawait
statements to save on the async state machine overhead - Support compiling static template elements to UTF8 string literals (
ReadOnlySpan<byte>
) instead of string literals to save on the UTF16 to UTF8 conversion during rendering - Support disabling the default registered
@addtaghelper
and@model
directives which rely on MVC
- Don't mark the template's
No intention to support
- Tag Helpers and View Components (they're tied to MVC and are intrinsically "heavy")
@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)
About
Generating html from razor templates. Attention, generates IHttpResult, not html string.
How to use
Example ( source csproj, source files )
- CSharp Project
- Program.cs
- PersonHTML.cshtml
- Person.cs
This is the CSharp Project that references RazorSlices
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="RazorSlices" Version="0.8.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
This is the use of RazorSlices in Program.cs
using RazorDemoSlices;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/hello", (string firstName,string lastName)
=> Results.Extensions.RazorSlice<RazorDemoSlices.Slices.PersonHTML, Person>(
new Person { FirstName = firstName, LastName = lastName }
));
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
This is the use of RazorSlices in PersonHTML.cshtml
@inherits RazorSliceHttpResult<RazorDemoSlices.Person>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello from Razor Slices!</title>
</head>
<body>
<p>
My name is @Model.FirstName @Model.LastName
</p>
</body>
</html>
This is the use of RazorSlices in Person.cs
namespace RazorDemoSlices;
public class Person
{
public string FirstName { get; set; }=string.Empty;
public string LastName { get; set; }= string.Empty;
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- Slices_PersonHTML_cshtml.g.cs
- Slices__ViewImports_cshtml.g.cs
- RazorDemoSlices.RazorSliceProxies.g.cs
#pragma checksum "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\PersonHTML.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "6b3710e80836b438a5d8935ea469d238fc095e46298456ca847e09519393bb5e"
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Slices_PersonHTML), @"mvc.1.0.view", @"/Slices/PersonHTML.cshtml")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#nullable restore
#line (3,2)-(4,1) "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\_ViewImports.cshtml"
using System.Globalization;
#nullable disable
#nullable restore
#line (4,2)-(5,1) "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\_ViewImports.cshtml"
using Microsoft.AspNetCore.Razor;
#nullable disable
#nullable restore
#line (5,2)-(6,1) "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\_ViewImports.cshtml"
using Microsoft.AspNetCore.Http.HttpResults;
#line default
#line hidden
#nullable disable
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/Slices/PersonHTML.cshtml")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Slices_PersonHTML : RazorSliceHttpResult<RazorDemoSlices.Person>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral("<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"utf-8\">\r\n <title>Hello from Razor Slices!</title>\r\n</head>\r\n<body>\r\n <p>\r\n My name is ");
Write(
#nullable restore
#line (10,21)-(10,36) "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\PersonHTML.cshtml"
Model.FirstName
#line default
#line hidden
#nullable disable
);
WriteLiteral(" ");
Write(
#nullable restore
#line (10,38)-(10,52) "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\PersonHTML.cshtml"
Model.LastName
#line default
#line hidden
#nullable disable
);
WriteLiteral("\r\n </p>\r\n</body>\r\n</html>");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
#pragma checksum "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\_ViewImports.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "95493514af34e5705fffb1e5c7121f0e7abde13ee7a1cff8fbafa2085da18fff"
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Slices__ViewImports), @"mvc.1.0.view", @"/Slices/_ViewImports.cshtml")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#nullable restore
#line (3,2)-(4,1) "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\_ViewImports.cshtml"
using System.Globalization;
#nullable disable
#nullable restore
#line (4,2)-(5,1) "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\_ViewImports.cshtml"
using Microsoft.AspNetCore.Razor;
#nullable disable
#nullable restore
#line (5,2)-(6,1) "D:\gth\RSCG_Examples\v2\rscg_examples\RazorSlices\src\RazorDemoSlices\Slices\_ViewImports.cshtml"
using Microsoft.AspNetCore.Http.HttpResults;
#line default
#line hidden
#nullable disable
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/Slices/_ViewImports.cshtml")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Slices__ViewImports : RazorSliceHttpResult
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral("\r\n");
WriteLiteral("\r\n");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
// <auto-generated/>
using global::System.Diagnostics.CodeAnalysis;
using global::RazorSlices;
#nullable enable
namespace RazorDemoSlices
{
/// <summary>
/// All calls to create Razor Slices instances via the generated <see cref="global::RazorSlices.IRazorSliceProxy"/> classes
/// go through this factory to ensure that the generated types' Create methods are always invoked via the static abstract
/// methods defined in the <see cref="global::RazorSlices.IRazorSliceProxy"/> interface. This ensures that the interface
/// implementation is never trimmed from the generated types.
/// </summary>
/// <remarks>
/// Workaround for https://github.com/dotnet/runtime/issues/102796
/// </remarks>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] // Hide from IntelliSense.
internal static class RazorSlicesGenericFactory
{
public static RazorSlice CreateSlice<TProxy>() where TProxy : IRazorSliceProxy => TProxy.CreateSlice();
public static RazorSlice<TModel> CreateSlice<TProxy, TModel>(TModel model) where TProxy : IRazorSliceProxy => TProxy.CreateSlice(model);
}
}
namespace RazorDemoSlices.Slices
{
/// <summary>
/// Static proxy for the Razor Slice defined in <c>Slices\PersonHTML.cshtml</c>.
/// </summary>
public sealed class PersonHTML : global::RazorSlices.IRazorSliceProxy
{
[global::System.Diagnostics.CodeAnalysis.DynamicDependency(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All, TypeName, "RazorDemoSlices")]
private const string TypeName = "AspNetCoreGeneratedDocument.Slices_PersonHTML, RazorDemoSlices";
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)]
private static readonly global::System.Type _sliceType = global::System.Type.GetType(TypeName)
?? throw new global::System.InvalidOperationException($"Razor view type '{TypeName}' was not found. This is likely a bug in the RazorSlices source generator.");
private static readonly global::RazorSlices.SliceDefinition _sliceDefinition = new(_sliceType);
/// <summary>
/// Creates a new instance of the Razor Slice defined in <c>Slices\PersonHTML.cshtml</c> .
/// </summary>
public static global::RazorSlices.RazorSlice Create()
=> global::RazorDemoSlices.RazorSlicesGenericFactory.CreateSlice<global::RazorDemoSlices.Slices.PersonHTML>();
/// <summary>
/// Creates a new instance of the Razor Slice defined in <c>Slices\PersonHTML.cshtml</c> with the given model.
/// </summary>
public static global::RazorSlices.RazorSlice<TModel> Create<TModel>(TModel model)
=> global::RazorDemoSlices.RazorSlicesGenericFactory.CreateSlice<global::RazorDemoSlices.Slices.PersonHTML, TModel>(model);
// Explicit interface implementation
static global::RazorSlices.RazorSlice global::RazorSlices.IRazorSliceProxy.CreateSlice() => _sliceDefinition.CreateSlice();
// Explicit interface implementation
static global::RazorSlices.RazorSlice<TModel> global::RazorSlices.IRazorSliceProxy.CreateSlice<TModel>(TModel model) => _sliceDefinition.CreateSlice(model);
}
}
Usefull
Download Example (.NET C# )
Share RazorSlices
https://ignatandrei.github.io/RSCG_Examples/v2/docs/RazorSlices