Giter Club home page Giter Club logo

razorblade's Introduction

RazorBlade Logo

Build NuGet package GitHub release License

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:

@inherits RazorBlade.HtmlTemplate

snippet source | anchor

A version with a model is also available for convenience. The following will add a Model property and a constructor with a ModelType parameter:

@inherits RazorBlade.HtmlTemplate<MyApplication.ModelType>

snippet source | anchor

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:

@inherits RazorBlade.HtmlTemplate

Hello, <i>@Name</i>!

@functions
{
    public string? Name { get; init; }
}

snippet source | anchor

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:

var template = new ExampleTemplate
{
    Name = "World"
};

var result = template.Render();

snippet source | anchor

With a model

A similar template with a model would be:

@using MyApplication
@inherits RazorBlade.HtmlTemplate<GreetingModel>

Hello, <i>@Model.Name</i>!

snippet source | anchor

Instantiating the generated class requires a model argument:

var model = new GreetingModel { Name = "World" };
var template = new TemplateWithModel(model);
var result = template.Render();

snippet source | anchor

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.

@using MyApplication
@inherits RazorBlade.HtmlTemplate

Hello, <i>@Model.Name</i>!

@functions
{
    public required GreetingModel Model { get; init; }
}

snippet source | anchor

Instantiating the generated class is done similarly to the previous example:

var model = new GreetingModel { Name = "World" };
var template = new TemplateWithManualModel { Model = model };
var result = template.Render();

snippet source | anchor

Documentation

Base template classes

For HTML templates, specify one of the following base classes with an @inherits directive:

  • RazorBlade.HtmlTemplate
  • RazorBlade.HtmlTemplate<TModel>
  • 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.PlainTextTemplate
  • RazorBlade.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.

Templates can be included in other templates by evaluating them, since they implement IEncodedContent. For instance, a Footer template can be included by writing @(new Footer()). Remember to always create a new instance of the template to include, even if it doesn't contain custom code, as templates are stateful and not thread-safe.

The namespace of the generated class can be customized with the @namespace directive. The default value is deduced from the file location.

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 through the Layout property 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:

@{
    Layout = new LayoutToUse();
}

snippet source | anchor

Layout pages can be nested, and can use sections. Unlike in ASP.NET, RazorBlade does not verify if the body and all sections have been used. Sections may also be executed multiple times.

Executing templates

The RazorTemplate base class provides Render and RenderAsync methods to execute the template.

Templates are stateful and not thread-safe, so it is advised to always create new instances of the templates to render.

Flushing partial output

By default, the output of a template is buffered while it is executing, then copied to the provided writer when finished. This is necessary for features such as layouts to be supported, but may not always be desired.

The RazorTemplate class provides a FlushAsync method which will copy the buffered output to the provided TextWriter and then flush the writer:

<div>Lightweight content goes here.</div>
@await FlushAsync()
<div>Slower to render content goes here.</div>

snippet source | anchor

Important

Flushing is not compatible with layout usage.

MSBuild

The source generator will process RazorBlade MSBuild items which have the .cshtml file extension.

By default, all .cshtml files are included, unless one of the EnableDefaultRazorBladeItems or EnableDefaultItems properties are set to false. You can also manually customize this set.

Removing the dependency on RazorBlade

RazorBlade makes it possible to remove the dependency on its runtime assembly. This could be useful for library projects which should be self-contained, with no dependencies on external packages.

This mode is enabled by default when the PackageReference of RazorBlade has the PrivateAssets="all" attribute. In order to avoid compilation warnings, the assembly reference also needs to be explicitly excluded with ExcludeAssets="compile;runtime".

<PackageReference Include="RazorBlade" Version="..." ExcludeAssets="compile;runtime" PrivateAssets="all" />

A source generator will then embed an internal version of the RazorBlade library in the target project. This behavior can also be controlled by setting the RazorBladeEmbeddedLibrary MSBuild property to true or false.

razorblade's People

Contributors

filzrev avatar ltrzesniewski avatar simoncropp avatar slang25 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

razorblade's Issues

Can't use template with a model when RazorBladeEmbeddedLibrary is true

Hey, this package was exactly what I was looking for to add some simple templating capabilities to a CLI I'm working on for personal project.

I was trying out the RazorBladeEmbeddedLibrary mode described here to see if it would shrink my exe output and noticed it would only generate a default constructor even though the template uses @inherits RazorBlade.HtmlTemplate<MyModel>;. Unclear to me if this was intentional or not, but it was a bit confusing as I was testing things out. My code works fine if I remove that, but seems unexpected and not obvious where the issue is.

Happy to help with exact reproduction if needed but was essentially (net7.0, MacOS):

  1. Install package
  2. Create template with a model
  3. Add ExcludeAssets="compile;runtime" PrivateAssets="all" to the package reference
  4. See constructor remove model parameter

Thanks!

DiagnosticId is not available in netstandard2.0

When the library is used with a netstandard2.0 project the generated code from the cshtml files cannot be built as the property DiagnosticId of the Obsolete attribute was introduced in net5.

The build log:

Build started...
1>------ Build started: Project: ***********.csproj, Configuration: Debug Any CPU ------
1>C:\****************\RazorBlade.Analyzers\RazorBlade.Analyzers.RazorBladeSourceGenerator\************.RazorBlade.g.cs(17,95,17,107): error CS0246: The type or namespace name 'DiagnosticId' could not be found (are you missing a using directive or an assembly reference?)
1>C:\****************\RazorBlade.Analyzers\RazorBlade.Analyzers.RazorBladeSourceGenerator\************.RazorBlade.g.cs(23,95,23,107): error CS0246: The type or namespace name 'DiagnosticId' could not be found (are you missing a using directive or an assembly reference?)
1>Done building project "***********.csproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
========== Elapsed 00:00,453 ==========

`CancellationToken` in templates

Hi!

It would be nice to have some sort of CancellationToken inside a template, that you can pass by calling Render() or RenderAsync().

The motivation is such that a template may invoke async methods that support cancellation, so it could be nice to propagate a token during rendering.

In MiniRazor, I just exposed a CancellationToken property:

https://github.com/Tyrrrz/MiniRazor/blob/c83bf8b9d4ed1d07dc4a377b30b308f6cecc44e2/MiniRazor.Runtime/TemplateBase.cs#L27-L28

Which is set when calling RenderAsync():

https://github.com/Tyrrrz/MiniRazor/blob/c83bf8b9d4ed1d07dc4a377b30b308f6cecc44e2/MiniRazor.CodeGen/TemplateClassGenerator.cs#L95-L103

Make the generator self-contained (no runtime dependency)

If I understand correctly, RazorBlade doesn't have any run-time dependencies, which means it should be marked as a development dependency. Installing it will automatically add PrivateAssets="all" to the package reference.

To do it, you need to add this property to the project file:

<PropertyGroup>
  <DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>

Streaming HTML

There was an interesting HN post recently on streaming HTML without JavaScript, and it got me thinking about how to flush partially rendered HTML in RazorBlade.

Basically, the idea is that the HTML rendered up to the "FLUSH" marker is sent immediately because the content after might take awhile to render:

<div>
  <template shadowrootmode="open">
    <header>Header</header>
    <main>
      <slot name="content"></slot>
    </main>
    <footer>Footer</footer>
  </template>

  <!-- FLUSH -->
  <div slot="content">
    This div will be rendered inside the slot above. Magic!
  </div>
</div>

If RazorBlade used the output stream directly I could invoke it with a TextWriter whose stream was directly attached to a network socket and then directly call Flush/FlushAsync:

<div>
  <template shadowrootmode="open">
    <header>Header</header>
    <main>
      <slot name="content"></slot>
    </main>
    <footer>Footer</footer>
  </template>

  @(await Output.FlushAsync())

  <div slot="content">
    This div will be rendered inside the slot above. Magic!
  </div>
</div>

But it looks like you first render to a string builder, then write that to the output. I think this is a bit outside of your original use case, but is there a compelling reason to not render directly to the given TextWriter?

Layout support

Hi, i was playing around with this package and cannot figure out how would i make a .cshtml with Layout support? So i want to have two .cshtml and one of them would be a wrapper for the other.

e.g. in classic razor pages we are able to do in layout (lets call it SimpleLayout.cshtml)

<header>This is header</header>
@RenderBody()
<footer>This is footer</footer>

and then in view:

@{
    Layout = "SimpleLayout"
}

My content!

so the actual result would be:

This is header
My content!
This is footer

Greetings!

Forward base class constructors by default

If you have a custom template base, such as:

internal abstract class MarkdownRazorTemplate<T> : PlainTextTemplate<T>
{
    protected MarkdownRazorTemplate(T model)
        : base(model)
    {
    }

    public new void WriteLiteral(string? literal)
    {
        // ...
    }
}

And a template that inherits from it:

@inherits MarkdownRazorTemplate<MyModel>

Hello @Model.Foo!

And try to instantiate the template like this:

var template = new MyTemplate(new MyModel("world"));

Then the compiler produces an error, saying:

  Program.cs(108, 39): [CS1729] 'MyTemplate' does not contain a constructor that takes 1 arguments

If I modify my template to @inherits RazorBlade.HtmlTemplate<MyModel>, then everything works correctly.

Available workarounds:

  • Inline MarkdownRazorTemplate members into MyTempalte. This is not viable for me because I have multiple templates inheriting from it.
  • Don't use the model approach and instead use properties. This is not viable for me because I have a lot of shared data between templates, and I'd prefer to pass it around as an object.
  • Create partial classes for all templates and add constructors manually. This is what I'm currently doing, although it's not ideal.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.