日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

.NET 中安全高效跨平台的模板引擎 Fluid 使用文档

發布時間:2023/12/4 asp.net 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .NET 中安全高效跨平台的模板引擎 Fluid 使用文档 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Liquid 是一門開源的模板語言,由 Shopify 創造并用 Ruby 實現。它是 Shopify 主題的主要構成部分,并且被用于加載店鋪系統的動態內容。它是一種安全的模板語言,對于非程序員的受眾來說也非常容易理解。

Fluid 是一個基于 Liquid 模板語言的開源 .NET 模板引擎。由 Sébastien Ros 開發并發布在 GitHub 上,NuGet 上的引用地址是:https://www.nuget.org/packages/Fluid.Core 。

Liquid 模板語言

如果你對 Liquid 模板語言還不了解,可以先行查看筆者翻譯的 Liquid 模板語言中文文檔:https://www.coderbusy.com/archives/1219.html 。Liquid 模板的文件擴展名為 .liquid ,假如我們有以下 Liquid 模板:

<ul id="products">{% for product in products %}<li><h2>{{product.name}}</h2>Only {{product.price | price }}{{product.description | prettyprint | paragraph }}</li>{% endfor %} </ul>

該模板被渲染后將會產生以下輸出:

<ul id="products"><li><h2>Apple</h2>$329Flat-out fun.</li><li><h2>Orange</h2>$25Colorful.</li><li><h2>Banana</h2>$99Peel it.</li> </ul>

在項目中使用 Fluid

你可以直接在項目中引用 NuGet 包。

Hello World

C# 代碼:

var parser = new FluidParser();var model = new { Firstname = "Bill", Lastname = "Gates" }; var source = "Hello {{ Firstname }} {{ Lastname }}";if (parser.TryParse(source, out var template, out var error)) {var context = new TemplateContext(model);Console.WriteLine(template.Render(context)); } else {Console.WriteLine($"Error: {error}"); }

運行結果:

Hello Bill Gates

線程安全

FluidParser 類型是線程安全的,可以被整個應用程序共享。常規做法是將其定義為一個本地的靜態變量:

private static readonly FluidParser _parser = new FluidParser();

IFluidTemplate 類型也是線程安全的,其實例可以被緩存起來,并被多個線程并發使用。

TemplateContext?不是線程安全的,每次使用時都應該新建一個實例。

過濾器

過濾器改變 Liquid 對象的輸出,通過一個?|?符號分隔。

{{ "/my/fancy/url" | append: ".html" }} /my/fancy/url.html

多個過濾器可以共同作用于同一個輸出,并按照從左到右的順序執行。

{{ "adam!" | capitalize | prepend: "Hello " }} Hello Adam!

Fluid 實現了 Liquid 所有的標準過濾器,同時支持自定義過濾器。

自定義的過濾器可以是同步的,也可以是異步的。過濾器被定義為一個委托,該委托接收一個輸入,一個參數集合和當前的渲染上下文。以下是一個實現文字轉小寫過濾器的代碼:

public static ValueTask<FluidValue> Downcase(FluidValue input, FilterArguments arguments, TemplateContext context) {return new StringValue(input.ToStringValue().ToLower()); }

過濾器需要注冊在?TemplateOptions?對象上,該 Options 對象可以被重用。

var options = new TemplateOptions(); options.Filters.AddFilter('downcase', Downcase);var context = new TemplateContext(options);

成員屬性白名單

Liquid 是一種安全的模板語言,它只允許白名單中的成員屬性被訪問,并且成員屬性不能被改變。白名單成員需要被加入到 TemplateOptions.MemberAccessStrategy?中。

另外,MemberAccessStrategy 可以被設置為 UnsafeMemberAccessStrategy ,這將允許模板語言訪問所有成員屬性。

將特定類型加入白名單

下面的代碼會將 Person 類型加入白名單,這意味著該類型下所有公開的字段和屬性都可以被模板讀取:

var options = new TemplateOptions(); options.MemberAccessStrategy.Register<Person>();

注意:當用 new TemplateContext(model) 傳遞一個模型時,模型對象會被自動加入白名單。該行為可以通過調用 new TemplateContext(model, false) 來禁用。

將特定成員加入白名單

下面的代碼只允許模板讀取特定的成員:

var options = new TemplateOptions(); options.MemberAccessStrategy.Register<Person>("Firstname", "Lastname");

訪問攔截

Fluid 提供了一種可以在運行時攔截屬性訪問的方式,通過該方式你可以允許訪問成員并返回自定義值,或者阻止訪問。

下面的代碼演示了如何攔截對 JObject 的調用并返回相應的屬性:

var options = new TemplateOptions(); options.MemberAccessStrategy.Register<JObject, object>((obj, name) => obj[name]);

繼承處理

當被注冊到白名單中的類型包含繼承關系時,情況將變得復雜:默認情況下被注冊類型的父類實例成員將不能被訪問,子類實例中的派生成員可以被訪問。

類型定義

public class Animal {public string Type { get; set; } } public class Human : Animal {public string Name { get; set; }public Int32 Age { get; set; } } public class Boy : Human {public string Toys { get; set; } }

測試代碼

var parser = new FluidParser();var model = new { }; var source = @"Animal=Type:{{Animal.Type}}Human=Type:{{Human.Type}},Name:{{Human.Name}},Age:{{Human.Age}}Boy=Type:{{Boy.Type}},Name:{{Boy.Name}},Age:{{Boy.Age}},Toys:{{Boy.Toys}}";var options = new Fluid.TemplateOptions { }; options.MemberAccessStrategy.Register(typeof(Human));if (parser.TryParse(source, out var template, out var error)) {var context = new TemplateContext(model, options);context.SetValue("Animal", new Animal { Type = "Human" });context.SetValue("Human", new Human { Type = "Human", Name = "碼農很忙", Age = 30 });context.SetValue("Boy", new Boy { Type = "Human", Name = "小明", Age = 10, Toys = "小汽車" });Console.WriteLine(template.Render(context)); } else {Console.WriteLine($"Error: {error}"); }

輸出結果

Animal=Type:Human=Type:Human,Name:碼農很忙,Age:30Boy=Type:Human,Name:小明,Age:10,Toys:

成員名稱風格

默認情況下,注冊對象的屬性是區分大小寫的,并按照其源代碼中的內容進行注冊。例如,屬性 FirstName 將使用?{{ p.FirstName }}?訪問。

同時,也可以配置使用不同的名稱風格。比如小駝峰(firstName)或者蛇形(first_name)風格。

以下代碼可以配置為使用小駝峰風格:

var options = new TemplateOptions(); options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;

執行限制

限制模板遞歸

當調用?{% include 'sub-template' %}?語句時,有些模板可能會產生無限的遞歸,從而阻塞服務器。為了防止這種情況,TemplateOptions 類定義了一個默認的 MaxRecursion = 100?,防止模板的深度超過100?。

限制模板執行

模板可能會不經意地創建無限循環,這可能會使服務器無限期地運行而堵塞。為了防止這種情況,TemplateOptions 類定義了一個默認的 MaxSteps。默認情況下,這個值沒有被設置。

轉換 CLR 類型

當一個對象在模板中被操作時,它會被轉換為一個特定的 FluidValue 實例。該機制與 JavaScript 中的動態類型系統有些類似。

在Liquid中,它們可以是數字、字符串、布爾值、數組或字典。Fluid會自動將CLR類型轉換為相應的Liquid類型,同時也提供專門的類型。

為了能夠定制這種轉換,你可以添加自定義的轉換器。

添加一個值轉換器

當轉換邏輯不能直接從一個對象的類型中推斷出來時,可以使用一個值轉換器。

值轉換器可以返回:

  • null 代表值不能被轉換。

  • 一個 FluidValue 實例,代表停止進一步的轉換,并使用這個值。

  • 其他對象實例,代表需要繼續使用自定義和內部類型映射進行轉換。

以下的代碼演示了如何將實現接口的任意實例轉換為自定義字符串值:

var options = new TemplateOptions(); options.ValueConverters.Add((value) => value is IUser user ? user.Name : null);

注意:類型映射的定義是全局的,對整個程序都生效。

在模型中使用 Json.NET 對象

Json.NET 中使用的類并不像類那樣有直接命名的屬性,這使得它們在 Liquid 模板中無法開箱使用。

為了彌補這一點,我們可以配置 Fluid,將名稱映射為 JObject 屬性,并將 JValue 對象轉換為 Fluid 所使用的對象。

var options = new TemplateOptions();// When a property of a JObject value is accessed, try to look into its properties options.MemberAccessStrategy.Register<JObject, object>((source, name) => source[name]);// Convert JToken to FluidValue options.ValueConverters.Add(x => x is JObject o ? new ObjectValue(o) : null); options.ValueConverters.Add(x => x is JValue v ? v.Value : null);var model = JObject.Parse("{\"Name\": \"Bill\"}");var parser = new FluidParser();parser.TryParse("His name is {{ Name }}", out var template); var context = new TemplateContext(model, options);Console.WriteLine(template.Render(context));

編碼

默認情況下,Fluid 不會對輸出進行編碼。在模板上調用 Render() 或 RenderAsync() 時可以指定編碼器。

HTML 編碼

可以使用 System.Text.Encodings.Web.HtmlEncoder.Default 實例來渲染 HTML 編碼的模板。

該編碼被 MVC View engine 作為默認編碼使用。

在上下文中禁用編碼

當一個編碼器被定義后,你可以使用一個特殊的 raw 過濾器或 {% raw %} … {% endraw %} 標簽來阻止一個值被編碼。例如,如果你知道這個內容是 HTML 并且是安全的:

代碼

{% assign html = '<em>This is some html</em>' %}Encoded: {{ html }} Not encoded: {{ html | raw }

結果

&lt;em%gt;This is some html&lt;/em%gt; <em>This is some html</em>

Capture 塊不會被二次編碼

當使用 capture 塊時,內部內容被標記為預編碼,如果在?{{ ?}}?標簽中使用,就不會被再次編碼。

代碼

{% capture breaktag %}<br />{% endcapture %}{{ breaktag }}

結果

<br />

本地化

默認情況下,模板使用不變的文化( Invariant culture ,對應 CultureInfo.InvariantCulture 。)進行渲染,這樣在不同的系統中可以得到一致的結果。這項設置在輸出日期、時間和數字時很重要。

即便如此,也可以使用 TemplateContext.CultureInfo 屬性來定義渲染模板時使用的文化信息(你也可以稱之為多語言信息)。

代碼

var options = new TemplateOptions(); options.CultureInfo = new CultureInfo("en-US"); var context = new TemplateContext(options); var result = template.Render(context);

模板

{{ 1234.56 }} {{ "now" | date: "%v" }}

結果

1234.56 Tuesday, August 1, 2017

時區

系統時區

TemplateOptions 和 TemplateContext 提供了一個定義默認時區的屬性,以便在解析日期和時間時使用。該屬性的默認值是當前系統的時區。當日期和時間被解析而沒有指定時區時,將會使用默認時區。設置一個自定義的時區可以防止在不同環境(數據中心)時產生不同的結果。

注意:date 過濾器符合 Ruby 的日期和時間格式:https://ruby-doc.org/core-3.0.0/Time.html#method-i-strftime 。要使用 .NET 標準的日期格式,請使用 format_date 過濾器。

代碼

var context = new TemplateContext { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time") } ; var result = template.Render(context);

模板

{{ '1970-01-01 00:00:00' | date: '%c' }}

結果

Wed Dec 31 19:00:00 -08:00 1969

時區轉換

日期和時間可以使用 time_zone 標簽轉換為特定的時區,格式為:time_zone:<iana>?。

代碼

var context = new TemplateContext(); context.SetValue("published", DateTime.UtcNow);

模板

{{ published | time_zone: 'America/New_York' | date: '%+' }}

結果

Tue Aug 1 17:04:36 -05:00 2017

自定義標簽和塊

Fluid 的語法可以被修改,以使其接受任何新的標記(tag)和帶有任何自定義參數的塊(block)。Fluid 使用了 Parlot 作為語法分析器,這使得 Fluid 完全可擴展。

與塊(block)不同,標記(tag)沒有結束元素(例如:循環,自增)。當把一個模板的某個部分作為一組語句來操作時,塊很有用。

Fluid 提供了用于注冊常見標簽和塊的幫助方法。所有的標簽和塊總是以他們的名稱作為標識符開始。

自定義標簽時需要提供一個委托(delegate),該委托會在標簽被匹配時執行。該委托可以使用使用以下三個屬性:

  • writer,TextWriter的實例,用于渲染文字。

  • encode,TextEncoder 的實例,例如 HtmlEncoder 或者 NullEncoder。由模板的調用者定義。

  • context,TemplateContext 的實例。

  • 注冊自定義標簽

    自定義標簽可以分為三種類型:

  • Empty:空白標簽,沒有任何參數,例如 {% renderbody %} 。

  • Identifier:標識符。將標識符作為標簽參數,例如{% increment my_variable %} 。

  • Expression:表達式。以表達式作為參數,例如 {% layout 'home' | append: '.liquid' %} 。

  • 代碼

    parser.RegisterIdentifierTag("hello", (identifier, writer, encoder, context) => {writer.Write("Hello ");writer.Write(identifier); });

    模板

    {% hello you %}

    結果

    Hello you

    注冊自定義塊

    塊的創建方式與標記相同,可以在委托中訪問塊內的語句列表。

    源碼

    parser.RegisterExpressionBlock("repeat", (value, statements, writer, encoder, context) => {for (var i = 0; i < value.ToNumber(); i++){await return statements.RenderStatementsAsync(writer, encoder, context);}return Completion.Normal; });

    模板

    {% repeat 1 | plus: 2 %}Hi! {% endrepeat %}

    結果

    Hi! Hi! Hi!

    自定義模板解析

    如果 identifier、 empty 和 expression 解析器不能滿足你的要求,RegisterParserBlock 和 RegisterParserTag 方法可以接受自定義的解析結構。這些結構可以是 FluidParser 中定義的標準解析器,例如 Primary或者其他任意組合。

    例如,RegisterParseTag(Primary.AndSkip(Comma).And(Primary), …)?將期望兩個 Primary 元素用逗號隔開。然后,該委托將被調用,使用 ValueTuple<Expression, Expression>?代表這兩個 Primary 表達式。

    注冊自定義運算符

    運算符是用來比較數值的,比如?>?或 contains 。如果需要提供特殊的比較,可以定義自定義運算符。

    自定義 xor 運算符

    下面的例子創建了一個自定義的?xor?運算符,如果左或右表達式被轉換為布爾時只有一個是真的,它將為真。

    using Fluid.Ast; using Fluid.Values; using System.Threading.Tasks;namespace Fluid.Tests.Extensibility {public class XorBinaryExpression : BinaryExpression{public XorBinaryExpression(Expression left, Expression right) : base(left, right){}public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context){var leftValue = await Left.EvaluateAsync(context);var rightValue = await Right.EvaluateAsync(context);return BooleanValue.Create(leftValue.ToBooleanValue() ^ rightValue.ToBooleanValue());}} }

    配置解析器

    parser.RegisteredOperators["xor"] = (a, b) => new XorBinaryExpression(a, b);

    模板

    {% if true xor false %}Hello{% endif %}

    結果

    Hello

    空白控制

    Liquid 在支持空白方面遵循嚴格的規則。默認情況下,所有的空格和新行都從模板中保留下來。Liquid 的語法和一些 Fluid 選項允許自定義這種行為。

    通過連字符控制空白輸出

    例如有以下模板:

    {% assign name = "Bill" %} {{ name }}

    在 assign 標簽之后的換行將被保留下來。輸出如下:

    Bill

    標簽和值可以使用連字符來剝離空白。

    {% assign name = "Bill" -%} {{ name }}

    這將輸出:

    Bill

    模板中的?-%}?將 assign 標簽右側的空白部分剝離。

    通過模板選項控制空白輸出

    Fluid 提供了 TemplateOptions.Triming 屬性,可以用預定義的偏好來設置何時應該自動剝離空白,即使標簽和輸出值中不存在連字符。

    貪婪模式

    當 TemplateOptions.Greedy 中的貪婪模式被禁用時,只有第一個新行之前的空格被剝離。貪婪模式默認啟用,這是 Liquid 語言的標準行為。

    自定義過濾器

    Fliud 默認提供了一些非標準過濾器。

    format_date

    使用標準的 .NET 日期和時間格式來格式化日期和時間。它使用系統當前的多語言信息。

    輸入

    "now" | format_date: "G"

    輸出

    6/15/2009 1:45:30 PM

    詳細的文檔可以看這里:https://docs.microsoft.com/zh-cn/dotnet/standard/base-types/standard-date-and-time-format-strings

    format_number

    使用 .NET 數字格式來格式化數字。

    輸入

    123 | format_number: "N"

    輸出

    123.00

    詳細的文檔可以看這里:https://docs.microsoft.com/zh-cn/dotnet/standard/base-types/standard-numeric-format-strings

    format_string

    格式化字符串

    輸入

    "hello {0} {1:C}" | format_string: "world" 123

    輸出

    hello world $123.00

    詳細的文檔可以看這里:https://docs.microsoft.com/zh-cn/dotnet/api/system.string.format?view=net-5.0

    性能

    緩存

    如果你在渲染之前對解析過的模板進行緩存,你的應用程序可以獲得一些性能提升。解析是內存安全的,因為它不會引起任何編譯(意味著如果你決定解析大量的模板,所有的內存都可以被收集),你可以通過存儲和重用 FluidTemplate 實例來跳過解析步驟。

    只要每次對 Render()?的調用使用一個獨立的 TemplateContext實例,這些對象就是線程安全的。

    基準測試

    Fluid 項目的源代碼中提供了一個基準測試應用程序,用于比較 Fluid、Scriban、DotLiquid 和 Liquid.NET 。在本地運行該項目,分析執行特定模板所需的時間。

    Fluid 比所有其他知名的 .NET Liquid 模板分析器更快,分配的內存更少。對于解析,Fluid 比 Scriban快30%,分配的內存少 3 倍。對于渲染,Fluid 比 Scriban 快 3 倍,分配的內存少 5 倍。與 DotLiquid 相比,Fluid 的渲染速度快 10 倍,分配的內存少 40 倍。

    BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores .NET Core SDK=5.0.201[Host] : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJITShortRun : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJITJob=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | |------------------- |--------------:|-------------:|------------:|-------:|--------:|----------:|---------:|--------:|------------:| | Fluid_Parse | 7.056 us | 1.081 us | 0.0592 us | 1.00 | 0.00 | 0.6714 | - | - | 2.77 KB | | Scriban_Parse | 9.209 us | 2.989 us | 0.1638 us | 1.31 | 0.03 | 1.8005 | - | - | 7.41 KB | | DotLiquid_Parse | 38.978 us | 13.704 us | 0.7512 us | 5.52 | 0.14 | 2.6855 | - | - | 11.17 KB | | LiquidNet_Parse | 73.198 us | 25.888 us | 1.4190 us | 10.37 | 0.29 | 15.1367 | 0.1221 | - | 62.08 KB | | | | | | | | | | | | | Fluid_ParseBig | 38.725 us | 11.771 us | 0.6452 us | 1.00 | 0.00 | 2.9907 | 0.1831 | - | 12.34 KB | | Scriban_ParseBig | 49.139 us | 8.313 us | 0.4557 us | 1.27 | 0.02 | 7.8125 | 1.0986 | - | 32.05 KB | | DotLiquid_ParseBig | 208.644 us | 45.839 us | 2.5126 us | 5.39 | 0.15 | 13.1836 | 0.2441 | - | 54.39 KB | | LiquidNet_ParseBig | 24,211.719 us | 3,862.113 us | 211.6955 us | 625.30 | 8.32 | 6843.7500 | 375.0000 | - | 28557.49 KB | | | | | | | | | | | | | Fluid_Render | 414.462 us | 12.612 us | 0.6913 us | 1.00 | 0.00 | 22.9492 | 5.3711 | - | 95.75 KB | | Scriban_Render | 1,141.302 us | 114.127 us | 6.2557 us | 2.75 | 0.02 | 99.6094 | 66.4063 | 66.4063 | 487.64 KB | | DotLiquid_Render | 5,753.263 us | 7,420.054 us | 406.7182 us | 13.88 | 0.96 | 867.1875 | 125.0000 | 23.4375 | 3879.18 KB | | LiquidNet_Render | 3,262.545 us | 1,245.387 us | 68.2639 us | 7.87 | 0.18 | 1000.0000 | 390.6250 | - | 5324.5 KB |

    以上結果的測試時間是 2021年3月26 日,使用的組件詳情如下:

    • Scriban 3.6.0

    • DotLiquid 2.1.405

    • Liquid.NET 0.10.0

    測試項目說明

    Parse:解析一個包含過濾器和屬性的簡單 HTML 模板。
    ParseBig:解析一個博客文章模板。
    Render:使用 500 個產品渲染一個包含過濾器和屬性的簡單 HTML 模板。

    總結

    以上是生活随笔為你收集整理的.NET 中安全高效跨平台的模板引擎 Fluid 使用文档的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。