Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/DamianEdwards/RazorSlices

Lightweight Razor-based templates for ASP.NET Core without MVC, Razor Pages, or Blazor.
https://github.com/DamianEdwards/RazorSlices

Last synced: about 2 months ago
JSON representation

Lightweight Razor-based templates for ASP.NET Core without MVC, Razor Pages, or Blazor.

Awesome Lists containing this project

README

        

# Razor Slices

[![CI (main)](https://github.com/DamianEdwards/RazorSlices/actions/workflows/ci.yml/badge.svg)](https://github.com/DamianEdwards/RazorSlices/actions/workflows/ci.yml)
[![Nuget](https://img.shields.io/nuget/v/RazorSlices)](https://www.nuget.org/packages/RazorSlices/)

*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](#getting-started)
- [Installation](#installation)
- [Features](#features)

## Getting Started

1. [Install the NuGet package](#installation) into your ASP.NET Core project (.NET 8+):

``` shell
> dotnet add package RazorSlices
```

1. Create a directory in your project called *Slices* and add a *_ViewImports.cshtml* file to it with the following content:

``` cshtml
@inherits RazorSliceHttpResult

@using System.Globalization;
@using Microsoft.AspNetCore.Razor;
@using Microsoft.AspNetCore.Http.HttpResults;

@tagHelperPrefix __disable_tagHelpers__:
@removeTagHelper *, Microsoft.AspNetCore.Mvc.Razor
```

1. In the same directory, add a *Hello.cshtml* file with the following content:

``` cshtml
@inherits RazorSliceHttpResult




Hello from Razor Slices!



Hello from Razor Slices! The time is @Model




```

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.

1. Add a minimal API to return the slice in your *Program.cs*:

``` c#
app.MapGet("/hello", () => Results.Extensions.RazorSlice(DateTime.Now));
```

## Installation

### NuGet Releases

[![Nuget](https://img.shields.io/nuget/v/RazorSlices)](https://www.nuget.org/packages/RazorSlices/)

This package is currently available from [nuget.org](https://www.nuget.org/packages/RazorSlices/):

``` console
> 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](https://github.com/DamianEdwards/RazorSlices/pkgs/nuget/RazorSlices).

1. [Create a personal access token](https://github.com/settings/tokens/new) for your GitHub account with the `read:packages` scope with your desired expiration length:

[image](https://github.com/settings/tokens/new)

1. 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 `` and `` placeholders with the relevant values:

``` shell
~> dotnet nuget add source -n GitHub -u -p https://nuget.pkg.github.com/DamianEdwards/index.json
```

1. You should now be able to add a reference to the package specifying a version from the [repository packages feed](https://github.com/DamianEdwards/RazorSlices/pkgs/nuget/RazorSlices)

1. See [these instructions](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-nuget-registry) 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` or `@inherits RazorSliceHttpResult`)
- Razor constructs:
- [Implicit expressions](https://learn.microsoft.com/aspnet/core/mvc/views/razor#implicit-razor-expressions), e.g. `@someVariable`
- [Explicit expressions](https://learn.microsoft.com/aspnet/core/mvc/views/razor#implicit-razor-expressions), e.g. `@(someBool ? thisThing : thatThing)`
- [Control structures](https://learn.microsoft.com/aspnet/core/mvc/views/razor#control-structures), e.g. `@if()`, `@switch()`, etc.
- [Looping](https://learn.microsoft.com/aspnet/core/mvc/views/razor#looping-for-foreach-while-and-do-while), e.g. `@for`, `@foreach`, `@while`, `@do`
- [Code blocks](https://learn.microsoft.com/aspnet/core/mvc/views/razor#razor-code-blocks), e.g. `@{ var someThing = someOtherThing; }`
- [Conditional attribute rendering](https://learn.microsoft.com/aspnet/core/mvc/views/razor#conditional-attribute-rendering)
- Functions, e.g.

```cshtml
@functions {
private readonly string _someString = "A very important string";
private int DoAThing() => 123;
}
```

- [Templated Razor delegates](https://learn.microsoft.com/aspnet/core/mvc/views/razor#templated-razor-delegates), e.g.

```cshtml
@inherits RazorSlice

@Title(Model)

@functions {
private IHtmlContent Title(Todo todo)
{
Todo @todo.Id: @todo.Title
return HtmlString.Empty;
}
}
```

- DI-activated properties via `@inject`
- Rendering slices from slices (aka partials) via `@(await RenderPartialAsync())`
- Using slices as layouts for other slices, including layouts with strongly-typed models:
- For the layout slice, inherit from `RazorLayoutSlice` or `RazorLayoutSlice` and use `@await RenderBodyAsync()` in the layout to render the body

```cshtml
@inherits RazorLayoutSlice




@Model.Title
@await RenderSectionAsync("head")


@await RenderBodyAsync()


@await RenderSectionAsync("footer")



```

- For the slice using the layout, implement `IUsesLayout` or `IUsesLayout` 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

```cshtml
@inherits RazorSlice
@implements IUsesLayout


@* Content here *@

@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.:

```cshtml
protected override Task ExecuteSectionAsync(string name)
{
if (name == "lorem-header")
{

This page renders a custom IHtmlContent type that contains lorem ipsum content.


}

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)
- Return a slice instance directly as an `IResult` in minimal APIs via `@inherits RazorSliceHttpResult` and `Results.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 an `async` method unless the template contains `await` statements to save on the async state machine overhead
- Support compiling static template elements to UTF8 string literals (`ReadOnlySpan`) 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

### 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)