.NET 中安全高效跨平台的模板引擎 Fluid 使用文档
Liquid 是一門開源的模板語言,由 Shopify 創(chuàng)造并用 Ruby 實(shí)現(xiàn)。它是 Shopify 主題的主要構(gòu)成部分,并且被用于加載店鋪系統(tǒng)的動(dòng)態(tài)內(nèi)容。它是一種安全的模板語言,對(duì)于非程序員的受眾來說也非常容易理解。
Fluid 是一個(gè)基于 Liquid 模板語言的開源 .NET 模板引擎。由 Sébastien Ros 開發(fā)并發(fā)布在 GitHub 上,NuGet 上的引用地址是:https://www.nuget.org/packages/Fluid.Core 。
Liquid 模板語言
如果你對(duì) Liquid 模板語言還不了解,可以先行查看筆者翻譯的 Liquid 模板語言中文文檔:https://www.coderbusy.com/archives/1219.html 。Liquid 模板的文件擴(kuò)展名為 .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>該模板被渲染后將會(huì)產(chǎn)生以下輸出:
在項(xiàng)目中使用 Fluid
你可以直接在項(xiàng)目中引用 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}"); }運(yùn)行結(jié)果:
線程安全
FluidParser 類型是線程安全的,可以被整個(gè)應(yīng)用程序共享。常規(guī)做法是將其定義為一個(gè)本地的靜態(tài)變量:
private static readonly FluidParser _parser = new FluidParser();IFluidTemplate 類型也是線程安全的,其實(shí)例可以被緩存起來,并被多個(gè)線程并發(fā)使用。
TemplateContext?不是線程安全的,每次使用時(shí)都應(yīng)該新建一個(gè)實(shí)例。
過濾器
過濾器改變 Liquid 對(duì)象的輸出,通過一個(gè)?|?符號(hào)分隔。
{{ "/my/fancy/url" | append: ".html" }} /my/fancy/url.html多個(gè)過濾器可以共同作用于同一個(gè)輸出,并按照從左到右的順序執(zhí)行。
Fluid 實(shí)現(xiàn)了 Liquid 所有的標(biāo)準(zhǔn)過濾器,同時(shí)支持自定義過濾器。
自定義的過濾器可以是同步的,也可以是異步的。過濾器被定義為一個(gè)委托,該委托接收一個(gè)輸入,一個(gè)參數(shù)集合和當(dāng)前的渲染上下文。以下是一個(gè)實(shí)現(xiàn)文字轉(zhuǎn)小寫過濾器的代碼:
public static ValueTask<FluidValue> Downcase(FluidValue input, FilterArguments arguments, TemplateContext context) {return new StringValue(input.ToStringValue().ToLower()); }過濾器需要注冊(cè)在?TemplateOptions?對(duì)象上,該 Options 對(duì)象可以被重用。
var options = new TemplateOptions(); options.Filters.AddFilter('downcase', Downcase);var context = new TemplateContext(options);成員屬性白名單
Liquid 是一種安全的模板語言,它只允許白名單中的成員屬性被訪問,并且成員屬性不能被改變。白名單成員需要被加入到 TemplateOptions.MemberAccessStrategy?中。
另外,MemberAccessStrategy 可以被設(shè)置為 UnsafeMemberAccessStrategy ,這將允許模板語言訪問所有成員屬性。
將特定類型加入白名單
下面的代碼會(huì)將 Person 類型加入白名單,這意味著該類型下所有公開的字段和屬性都可以被模板讀取:
var options = new TemplateOptions(); options.MemberAccessStrategy.Register<Person>();注意:當(dāng)用 new TemplateContext(model) 傳遞一個(gè)模型時(shí),模型對(duì)象會(huì)被自動(dòng)加入白名單。該行為可以通過調(diào)用 new TemplateContext(model, false) 來禁用。
將特定成員加入白名單
下面的代碼只允許模板讀取特定的成員:
var options = new TemplateOptions(); options.MemberAccessStrategy.Register<Person>("Firstname", "Lastname");訪問攔截
Fluid 提供了一種可以在運(yùn)行時(shí)攔截屬性訪問的方式,通過該方式你可以允許訪問成員并返回自定義值,或者阻止訪問。
下面的代碼演示了如何攔截對(duì) JObject 的調(diào)用并返回相應(yīng)的屬性:
var options = new TemplateOptions(); options.MemberAccessStrategy.Register<JObject, object>((obj, name) => obj[name]);繼承處理
當(dāng)被注冊(cè)到白名單中的類型包含繼承關(guān)系時(shí),情況將變得復(fù)雜:默認(rèn)情況下被注冊(cè)類型的父類實(shí)例成員將不能被訪問,子類實(shí)例中的派生成員可以被訪問。
類型定義
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; } }測(cè)試代碼
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 = "碼農(nóng)很忙", Age = 30 });context.SetValue("Boy", new Boy { Type = "Human", Name = "小明", Age = 10, Toys = "小汽車" });Console.WriteLine(template.Render(context));
}
else
{Console.WriteLine($"Error: {error}");
}
輸出結(jié)果
Animal=Type:Human=Type:Human,Name:碼農(nóng)很忙,Age:30Boy=Type:Human,Name:小明,Age:10,Toys:
成員名稱風(fēng)格
默認(rèn)情況下,注冊(cè)對(duì)象的屬性是區(qū)分大小寫的,并按照其源代碼中的內(nèi)容進(jìn)行注冊(cè)。例如,屬性 FirstName 將使用?{{ p.FirstName }}?訪問。
同時(shí),也可以配置使用不同的名稱風(fēng)格。比如小駝峰(firstName)或者蛇形(first_name)風(fēng)格。
以下代碼可以配置為使用小駝峰風(fēng)格:
var options = new TemplateOptions(); options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;執(zhí)行限制
限制模板遞歸
當(dāng)調(diào)用?{% include 'sub-template' %}?語句時(shí),有些模板可能會(huì)產(chǎn)生無限的遞歸,從而阻塞服務(wù)器。為了防止這種情況,TemplateOptions 類定義了一個(gè)默認(rèn)的 MaxRecursion = 100?,防止模板的深度超過100?。
限制模板執(zhí)行
模板可能會(huì)不經(jīng)意地創(chuàng)建無限循環(huán),這可能會(huì)使服務(wù)器無限期地運(yùn)行而堵塞。為了防止這種情況,TemplateOptions 類定義了一個(gè)默認(rèn)的 MaxSteps。默認(rèn)情況下,這個(gè)值沒有被設(shè)置。
轉(zhuǎn)換 CLR 類型
當(dāng)一個(gè)對(duì)象在模板中被操作時(shí),它會(huì)被轉(zhuǎn)換為一個(gè)特定的 FluidValue 實(shí)例。該機(jī)制與 JavaScript 中的動(dòng)態(tài)類型系統(tǒng)有些類似。
在Liquid中,它們可以是數(shù)字、字符串、布爾值、數(shù)組或字典。Fluid會(huì)自動(dòng)將CLR類型轉(zhuǎn)換為相應(yīng)的Liquid類型,同時(shí)也提供專門的類型。
為了能夠定制這種轉(zhuǎn)換,你可以添加自定義的轉(zhuǎn)換器。
添加一個(gè)值轉(zhuǎn)換器
當(dāng)轉(zhuǎn)換邏輯不能直接從一個(gè)對(duì)象的類型中推斷出來時(shí),可以使用一個(gè)值轉(zhuǎn)換器。
值轉(zhuǎn)換器可以返回:
null 代表值不能被轉(zhuǎn)換。
一個(gè) FluidValue 實(shí)例,代表停止進(jìn)一步的轉(zhuǎn)換,并使用這個(gè)值。
其他對(duì)象實(shí)例,代表需要繼續(xù)使用自定義和內(nèi)部類型映射進(jìn)行轉(zhuǎn)換。
以下的代碼演示了如何將實(shí)現(xiàn)接口的任意實(shí)例轉(zhuǎn)換為自定義字符串值:
var options = new TemplateOptions(); options.ValueConverters.Add((value) => value is IUser user ? user.Name : null);注意:類型映射的定義是全局的,對(duì)整個(gè)程序都生效。
在模型中使用 Json.NET 對(duì)象
Json.NET 中使用的類并不像類那樣有直接命名的屬性,這使得它們?cè)?Liquid 模板中無法開箱使用。
為了彌補(bǔ)這一點(diǎn),我們可以配置 Fluid,將名稱映射為 JObject 屬性,并將 JValue 對(duì)象轉(zhuǎn)換為 Fluid 所使用的對(duì)象。
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));編碼
默認(rèn)情況下,Fluid 不會(huì)對(duì)輸出進(jìn)行編碼。在模板上調(diào)用 Render() 或 RenderAsync() 時(shí)可以指定編碼器。
HTML 編碼
可以使用 System.Text.Encodings.Web.HtmlEncoder.Default 實(shí)例來渲染 HTML 編碼的模板。
該編碼被 MVC View engine 作為默認(rèn)編碼使用。
在上下文中禁用編碼
當(dāng)一個(gè)編碼器被定義后,你可以使用一個(gè)特殊的 raw 過濾器或 {% raw %} … {% endraw %} 標(biāo)簽來阻止一個(gè)值被編碼。例如,如果你知道這個(gè)內(nèi)容是 HTML 并且是安全的:
代碼
{% assign html = '<em>This is some html</em>' %}Encoded: {{ html }} Not encoded: {{ html | raw }結(jié)果
<em%gt;This is some html</em%gt;
<em>This is some html</em>
Capture 塊不會(huì)被二次編碼
當(dāng)使用 capture 塊時(shí),內(nèi)部?jī)?nèi)容被標(biāo)記為預(yù)編碼,如果在?{{ ?}}?標(biāo)簽中使用,就不會(huì)被再次編碼。
代碼
{% capture breaktag %}<br />{% endcapture %}{{ breaktag }}結(jié)果
<br />
本地化
默認(rèn)情況下,模板使用不變的文化( Invariant culture ,對(duì)應(yīng) CultureInfo.InvariantCulture 。)進(jìn)行渲染,這樣在不同的系統(tǒng)中可以得到一致的結(jié)果。這項(xiàng)設(shè)置在輸出日期、時(shí)間和數(shù)字時(shí)很重要。
即便如此,也可以使用 TemplateContext.CultureInfo 屬性來定義渲染模板時(shí)使用的文化信息(你也可以稱之為多語言信息)。
代碼
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" }}
結(jié)果
1234.56
Tuesday, August 1, 2017
時(shí)區(qū)
系統(tǒng)時(shí)區(qū)
TemplateOptions 和 TemplateContext 提供了一個(gè)定義默認(rèn)時(shí)區(qū)的屬性,以便在解析日期和時(shí)間時(shí)使用。該屬性的默認(rèn)值是當(dāng)前系統(tǒng)的時(shí)區(qū)。當(dāng)日期和時(shí)間被解析而沒有指定時(shí)區(qū)時(shí),將會(huì)使用默認(rèn)時(shí)區(qū)。設(shè)置一個(gè)自定義的時(shí)區(qū)可以防止在不同環(huán)境(數(shù)據(jù)中心)時(shí)產(chǎn)生不同的結(jié)果。
注意:date 過濾器符合 Ruby 的日期和時(shí)間格式:https://ruby-doc.org/core-3.0.0/Time.html#method-i-strftime 。要使用 .NET 標(biāo)準(zhǔn)的日期格式,請(qǐng)使用 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' }}
結(jié)果
Wed Dec 31 19:00:00 -08:00 1969
時(shí)區(qū)轉(zhuǎn)換
日期和時(shí)間可以使用 time_zone 標(biāo)簽轉(zhuǎn)換為特定的時(shí)區(qū),格式為:time_zone:<iana>?。
代碼
var context = new TemplateContext(); context.SetValue("published", DateTime.UtcNow);模板
{{ published | time_zone: 'America/New_York' | date: '%+' }}
結(jié)果
Tue Aug 1 17:04:36 -05:00 2017
自定義標(biāo)簽和塊
Fluid 的語法可以被修改,以使其接受任何新的標(biāo)記(tag)和帶有任何自定義參數(shù)的塊(block)。Fluid 使用了 Parlot 作為語法分析器,這使得 Fluid 完全可擴(kuò)展。
與塊(block)不同,標(biāo)記(tag)沒有結(jié)束元素(例如:循環(huán),自增)。當(dāng)把一個(gè)模板的某個(gè)部分作為一組語句來操作時(shí),塊很有用。
Fluid 提供了用于注冊(cè)常見標(biāo)簽和塊的幫助方法。所有的標(biāo)簽和塊總是以他們的名稱作為標(biāo)識(shí)符開始。
自定義標(biāo)簽時(shí)需要提供一個(gè)委托(delegate),該委托會(huì)在標(biāo)簽被匹配時(shí)執(zhí)行。該委托可以使用使用以下三個(gè)屬性:
writer,TextWriter的實(shí)例,用于渲染文字。
encode,TextEncoder 的實(shí)例,例如 HtmlEncoder 或者 NullEncoder。由模板的調(diào)用者定義。
context,TemplateContext 的實(shí)例。
注冊(cè)自定義標(biāo)簽
自定義標(biāo)簽可以分為三種類型:
Empty:空白標(biāo)簽,沒有任何參數(shù),例如 {% renderbody %} 。
Identifier:標(biāo)識(shí)符。將標(biāo)識(shí)符作為標(biāo)簽參數(shù),例如{% increment my_variable %} 。
Expression:表達(dá)式。以表達(dá)式作為參數(shù),例如 {% layout 'home' | append: '.liquid' %} 。
代碼
parser.RegisterIdentifierTag("hello", (identifier, writer, encoder, context) => {writer.Write("Hello ");writer.Write(identifier); });模板
{% hello you %}
結(jié)果
Hello you
注冊(cè)自定義塊
塊的創(chuàng)建方式與標(biāo)記相同,可以在委托中訪問塊內(nèi)的語句列表。
源碼
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 %}
結(jié)果
Hi! Hi! Hi!
自定義模板解析
如果 identifier、 empty 和 expression 解析器不能滿足你的要求,RegisterParserBlock 和 RegisterParserTag 方法可以接受自定義的解析結(jié)構(gòu)。這些結(jié)構(gòu)可以是 FluidParser 中定義的標(biāo)準(zhǔn)解析器,例如 Primary或者其他任意組合。
例如,RegisterParseTag(Primary.AndSkip(Comma).And(Primary), …)?將期望兩個(gè) Primary 元素用逗號(hào)隔開。然后,該委托將被調(diào)用,使用 ValueTuple<Expression, Expression>?代表這兩個(gè) Primary 表達(dá)式。
注冊(cè)自定義運(yùn)算符
運(yùn)算符是用來比較數(shù)值的,比如?>?或 contains 。如果需要提供特殊的比較,可以定義自定義運(yùn)算符。
自定義 xor 運(yùn)算符
下面的例子創(chuàng)建了一個(gè)自定義的?xor?運(yùn)算符,如果左或右表達(dá)式被轉(zhuǎn)換為布爾時(shí)只有一個(gè)是真的,它將為真。
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 %}
結(jié)果
Hello
空白控制
Liquid 在支持空白方面遵循嚴(yán)格的規(guī)則。默認(rèn)情況下,所有的空格和新行都從模板中保留下來。Liquid 的語法和一些 Fluid 選項(xiàng)允許自定義這種行為。
通過連字符控制空白輸出
例如有以下模板:
{% assign name = "Bill" %} {{ name }}在 assign 標(biāo)簽之后的換行將被保留下來。輸出如下:
Bill標(biāo)簽和值可以使用連字符來剝離空白。
這將輸出:
模板中的?-%}?將 assign 標(biāo)簽右側(cè)的空白部分剝離。
通過模板選項(xiàng)控制空白輸出
Fluid 提供了 TemplateOptions.Triming 屬性,可以用預(yù)定義的偏好來設(shè)置何時(shí)應(yīng)該自動(dòng)剝離空白,即使標(biāo)簽和輸出值中不存在連字符。
貪婪模式
當(dāng) TemplateOptions.Greedy 中的貪婪模式被禁用時(shí),只有第一個(gè)新行之前的空格被剝離。貪婪模式默認(rèn)啟用,這是 Liquid 語言的標(biāo)準(zhǔn)行為。
自定義過濾器
Fliud 默認(rèn)提供了一些非標(biāo)準(zhǔn)過濾器。
format_date
使用標(biāo)準(zhǔn)的 .NET 日期和時(shí)間格式來格式化日期和時(shí)間。它使用系統(tǒng)當(dāng)前的多語言信息。
輸入
"now" | format_date: "G"輸出
6/15/2009 1:45:30 PM
詳細(xì)的文檔可以看這里:https://docs.microsoft.com/zh-cn/dotnet/standard/base-types/standard-date-and-time-format-strings
format_number
使用 .NET 數(shù)字格式來格式化數(shù)字。
輸入
123 | format_number: "N"輸出
123.00
詳細(xì)的文檔可以看這里: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
詳細(xì)的文檔可以看這里:https://docs.microsoft.com/zh-cn/dotnet/api/system.string.format?view=net-5.0
性能
緩存
如果你在渲染之前對(duì)解析過的模板進(jìn)行緩存,你的應(yīng)用程序可以獲得一些性能提升。解析是內(nèi)存安全的,因?yàn)樗粫?huì)引起任何編譯(意味著如果你決定解析大量的模板,所有的內(nèi)存都可以被收集),你可以通過存儲(chǔ)和重用 FluidTemplate 實(shí)例來跳過解析步驟。
只要每次對(duì) Render()?的調(diào)用使用一個(gè)獨(dú)立的 TemplateContext實(shí)例,這些對(duì)象就是線程安全的。
基準(zhǔn)測(cè)試
Fluid 項(xiàng)目的源代碼中提供了一個(gè)基準(zhǔn)測(cè)試應(yīng)用程序,用于比較 Fluid、Scriban、DotLiquid 和 Liquid.NET 。在本地運(yùn)行該項(xiàng)目,分析執(zhí)行特定模板所需的時(shí)間。
Fluid 比所有其他知名的 .NET Liquid 模板分析器更快,分配的內(nèi)存更少。對(duì)于解析,Fluid 比 Scriban快30%,分配的內(nèi)存少 3 倍。對(duì)于渲染,Fluid 比 Scriban 快 3 倍,分配的內(nèi)存少 5 倍。與 DotLiquid 相比,Fluid 的渲染速度快 10 倍,分配的內(nèi)存少 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 |以上結(jié)果的測(cè)試時(shí)間是 2021年3月26 日,使用的組件詳情如下:
Scriban 3.6.0
DotLiquid 2.1.405
Liquid.NET 0.10.0
測(cè)試項(xiàng)目說明
Parse:解析一個(gè)包含過濾器和屬性的簡(jiǎn)單 HTML 模板。
ParseBig:解析一個(gè)博客文章模板。
Render:使用 500 個(gè)產(chǎn)品渲染一個(gè)包含過濾器和屬性的簡(jiǎn)單 HTML 模板。
總結(jié)
以上是生活随笔為你收集整理的.NET 中安全高效跨平台的模板引擎 Fluid 使用文档的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 将多个图片合并成TIFF文件的两种
- 下一篇: 揭秘全球开发最新趋势!JS开发者达138