Code Monkey home page Code Monkey logo

minirazor's Introduction

MiniRazor

Status Made in Ukraine Build Coverage Version Downloads Discord Fuck Russia

Development of this project is entirely funded by the community. Consider donating to support!

Note: As an alternative, consider using RazorBlade for build-time template compilation and RazorLight for run-time template compilation.

MiniRazor is a tiny abstraction over the Razor engine, designed to provide a simple interface to compile and render templates, both during build and at run-time.

Terms of use[?]

By using this project or its source code, for any purpose and in any shape or form, you grant your implicit agreement to all the following statements:

  • You condemn Russia and its military aggression against Ukraine
  • You recognize that Russia is an occupant that unlawfully invaded a sovereign state
  • You support Ukraine's territorial integrity, including its claims over temporarily occupied territories of Crimea and Donbas
  • You reject false narratives perpetuated by Russian state propaganda

To learn more about the war and how you can help, click here. Glory to Ukraine! πŸ‡ΊπŸ‡¦

Install

All-in-one meta package:

  • πŸ“¦ NuGet: dotnet add package MiniRazor

Specialized packages:

  • πŸ“¦ NuGet: dotnet add package MiniRazor.Compiler (run-time compilation only)
  • πŸ“¦ NuGet: dotnet add package MiniRazor.CodeGen (build-time compilation only)

⚠ If you're referencing MiniRazor.CodeGen, ensure that it's NOT marked as a development dependency! Although the source generator assembly itself is only used during build, this package also contains binaries which are required by the generated code at run-time.

Usage

Compiling templates at build-time

Warning: Compiling at build time requires MiniRazor or MiniRazor.CodeGen.

MiniRazor comes with a source generator that can parse Razor templates and transpile them into C# classes directly at build time. This workflow is suitable and highly recommended for scenarios where your templates are not expected to change.

To do that, first create a Razor template as shown here:

@inherits MiniRazor.TemplateBase<string>
@namespace MyNamespace.Templates

<html>

<head>
    <title>Hello @Model</title>
</head>

<body>
    <p>Hello @Model</p>
</body>

</html>

Note the usage of two important directives at the top of the file:

  • @inherits directive indicates that the base type of this template is MiniRazor.TemplateBase<TModel>, with the model of type string. If this directive is not included, the template will inherit from MiniRazor.TemplateBase<dynamic> instead β€” providing no type-safety when working with the model.
  • @namespace directive instructs the compiler to put the generated template class into the MyNamespace.Templates namespace. If this directive is not included, the default namespace of MiniRazor.GeneratedTemplates will be used instead.

In order to make the template accessible by MiniRazor's source generator, you need to add it to the project using the AdditionalFiles element and mark it with the IsRazorTemplate="true" attribute:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <!-- Include a single template -->
    <AdditionalFiles Include="Templates/TemplateFoo.cshtml" IsRazorTemplate="true" />

    <!-- Include multiple templates at once -->
    <AdditionalFiles Include="Templates/*.cshtml" IsRazorTemplate="true" />
  </ItemGroup>

  <!-- ... -->

</Project>

After that, you should be able to run dotnet build to trigger the source generator and generate the corresponding template classes. The template from the above example will then become available as the MyNamespace.Templates.TemplateFoo class. To render it, you can call its RenderAsync(...) static method:

// Reference the namespace where the template is located
using MyNamespace.Templates;

// Render the template to a string, with @Model set to "world"
var output = await TemplateFoo.RenderAsync("world");

// Or, alternatively, render it to the specified TextWriter
await TemplateFoo.RenderAsync(Console.Out, "world");

Note that the type of the model parameter in RenderAsync(...) is automatically inferred based on the @inherits directive specified in the template. Here, since the template is derived from MiniRazor.TemplateBase<string>, the method expects a parameter of type string.

Compiling templates at run-time

Warning: Compiling at run-time requires MiniRazor or MiniRazor.Compiler.

If the previous approach doesn't fit your usage scenario, you can also compile templates at run-time. To do that, call Razor.Compile(...) with the template's source code:

using MiniRazor;

// Compile the template into an in-memory assembly
var template = Razor.Compile("<p>Hello, @Model.Subject!</p>");

// Render the template to a string
var output = await template.RenderAsync(new MyModel { Subject = "World" });
// <p>Hello, World!</p>

Calling Razor.Compile(...) transforms the provided Razor template directly into IL code hosted in a dynamic in-memory assembly. This returns an instance of TemplateDescriptor, which you can then use to render output.

By default, MiniRazor uses the default assembly load context, which means that the compiled IL code will stay in memory forever. To avoid that, you can pass a custom instance of AssemblyLoadContext that lets you control the lifetime of the generated assemblies:

// Create an isolated assembly load context
var alc = new AssemblyLoadContext("MyALC", true);

// Compile the template
var template = Razor.Compile("<p>Hello, @Model.Subject!</p>", alc);

// Unload the ALC once it's no longer needed
alc.Unload();

Templating features

HTML encoding

Output rendered with Razor templates is HTML-encoded by default. If you want to print raw HTML content, for example if it's sourced from somewhere else, you can use the Raw(...) method:

@{
    string GetHtml() => "<p>Hello world!</p>";
}

@GetHtml() // &lt;p&gt;Hello world!&lt;/p&gt;
@Raw(GetHtml()) // <p>Hello world!</p>

minirazor's People

Contributors

kevingliewe avatar slang25 avatar thejaymann avatar tyrrrz 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  avatar  avatar

minirazor's Issues

Add overload for RenderAsync when model is a tuple type

If a template were to declare a tuple type model, such as when using @inherits MiniRazor.TemplateBase<(string name, int age)> a static RenderAsync method is generated.

/// <summary>Renders the template using the specified writer.</summary>
public static async global::System.Threading.Tasks.Task RenderAsync(global::System.IO.TextWriter output, (string name, int age) model)
{
    var template = new Template();
    template.Output = output;
    template.Model = model;

    await template.ExecuteAsync().ConfigureAwait(false);
}

/// <summary>Renders the template to a string.</summary>
public static async global::System.Threading.Tasks.Task<string> RenderAsync((string name, int age) model)
{
    using (var output = new global::System.IO.StringWriter())
    {
        await RenderAsync(output, model).ConfigureAwait(false);
        return output.ToString();
    }
}

In the case that the model is detected to be a tuple type (possibly only when each tuple item has a name), I would desire that two additional overloads of RenderAsync be generated.

public static global::System.Threading.Tasks.Task RenderAsync(global::System.IO.TextWriter output, string name, int age) => RenderAsync(output, (name, age));
public static global::System.Threading.Tasks.Task<string> RenderAsync(string name, int age) => RenderAsync((name, age));

Allow public access modifier for generated class

It appears that when a class is generated for a razor file, it is always generated as an internal class. While this makes sense as a sensible default, there are cases where a public class would be desirable, such as cases where the templates are to be shared among different projects or to allow use in projects where source generators either are not allowed or are difficult to use (such as older style projects for web applications based on System.Web). Perhaps this can be done in the <AdditionalItems> element, with an attribute such as 'AccessModifier="public". Currently I have made a static class which contains one static method per template, each of which simply wraps the call to the generated RenderAsync method, which eliminates most of the benefits of having generators to begin with.

Support compile to dll

Details

Can you support compiling to DLL files? I checked the source code and found that the contents of the DLL already exist, but you got the type from the DLL and returned it.

C# versioning error when using in .NET Standard 2.0 project

Version

ver 2.2.1

Details

I'm using MiniRazor within a .NET Standard 2.0 project (as this is the maximum version allowed to reference this project from a UWP project). Using compile-time template generation yields the following error:

Feature 'nullable reference types' is not available in C# 7.3. Please use language version 8.0 or greater.

The NuGet package listing seems to indicate this package is compatible with .NET Standard 2.0.

Steps to reproduce

  • Create a new .NET Standard 2.0 project.
  • Install the MiniRazor Nuget package.
  • Create a Razor template and set it be built at compile time as per documentation.
  • Observe error message on build.

Transitive assembly loading is too eager

I ran into an issue with a .Net 5 project using MiniRazor that also references ZXing.Net.

I am not really sure where the fault lies, but while loading transitive dependencies, MiniRazor is attempting to load a reference to "PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" that I found out is referenced by ZXing.Net.

Our .Net 5 project is referencing the .Net Standard 2.0 version of ZXing.Net which doesn't seems to have any "active" references to PresentationCore, or at least not for the functionality we are using. GetReferencedAssemblies as part of Compile in MiniRazor returns the PresentationCore reference though, and MiniRazor attempts to load it.

Does it make sense to include an overload or configuration option in MiniRazor that would allow a consumer to specify a list of references to ignore/exclude, to be able to work around situation like this? I could perhaps provide a PR if we can agree on a suitable solution.

Option to remove insignificant whitespace

To make the output file smaller, it would be nice to be able to optionally remove insignificant whitespace. This would only work with HTML but theoretically Razor could be used for any type of output, so maybe consider some more generic solution?

MiniRazor.CodeGen as NuGet package?

Is there a reason why MiniRazor.CodeGen is not published as a NuGet package?
Right now i am referencing it as a git submodule like a caveman. πŸ˜„

Add support for templated razor delegates

I have recently had a desire to use Templated Razor Delegates in some of my razor templates recently, and found that the body ends up erased in the generated code. After looking into the diagnostics returned by the razor engine and further research, I believe this feature can be easily enabled. The feature requires adding an ITemplateTargetExtension as a target extension, and razor provides an implementation in TemplateTargetExtension. This requires that the template has access to a PushWriter(TextWriter) method and a PopWriter() method to temporarily swap the current TextWriter being used by the template, and access to a type which will wrap the TextWriter, has a constructor which will accept a delegate compatible with Func<TextWriter, Task>, and can be passed to the template's Write() method in order to render the contents of the wrapped TextWriter.

From what I can tell, the implementation of the wrapper type can be fairly simple and still work.

public class TemplateResult {
    private readonly Func<TextWriter, Task> _TemplateDelegate;
    public TemplateResult(Func<TextWriter, Task> templateDelegate) => _TemplateDelegate = templateDelegate;
    public override ToString() {
        using var output = new StringWriter();
        // Proper async code could probably be generated by implementing a GetAwaiter() method and requiring the result of 
        // the invocation of the razor delegate be awaited.  However, all implementations I've seen, including the implementation
        // built in to MVC use this method to convert the async delegate into a sync call.
        _TemplateDelegate(output).GetAwaiter().GetResult();
        return output.ToString();
    }
}

Pass `CancellationToken` through `RenderAsync(...)` and expose it in the template

Details

Add a new property called CancellationToken to TemplateBase:

https://github.com/Tyrrrz/MiniRazor/blob/67201b654fb5e67563b98b2c6686b3d4aa07fe35/MiniRazor.Runtime/TemplateBase.cs

Add CancellationToken parameter to TemplateDescriptor.RenderAsync(...):

https://github.com/Tyrrrz/MiniRazor/blob/master/MiniRazor.Compiler/TemplateDescriptor.cs#L25-L50

Add CancellationToken parameter to codegened RenderAsync(...):

/// <summary>Renders the template using the specified writer.</summary>
public static async global::System.Threading.Tasks.Task RenderAsync(global::System.IO.TextWriter output, {modelTypeName} model)
{{
var template = new {className}();
template.Output = output;
template.Model = model;
await template.ExecuteAsync().ConfigureAwait(false);
}}
/// <summary>Renders the template to a string.</summary>
public static async global::System.Threading.Tasks.Task<string> RenderAsync({modelTypeName} model)
{{
using (var output = new global::System.IO.StringWriter())
{{
await RenderAsync(output, model).ConfigureAwait(false);
return output.ToString();
}}
}}
");

Render another template from inside a template

First and foremost, great work here! nice compact library, with a pleasant API surface area!

As I start to dig in, the first thing that presents itself is the need to support more complex layout, and most project contributors will obviously be familiar with the asp.net mvc's method for doing so.

These pieces are missing from the basic examples given so far ... is there another method built-in for templating in MiniRazor?

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.