DotLiquid模板引擎簡介


DotLiquid是一個在.Net Framework上運行的模板引擎,采用Ruby的Liquid語法,這個語法廣泛的用在Ruby on rails和Django等網頁框架中。
DotLiquid相比於Mvc默認模板引擎Razor的好處有:

  • 因為不需要編譯到程序集再載入
    • 首次渲染速度很快
    • 不會導致內存泄漏
  • 可以在任何地方使用
    • 不需要先准備WebViewPage,ViewContext等復雜的上下文對象

DotLiquid的官網是http://dotliquidmarkup.org/,開源協議是非常寬松的MS-PL。

示例代碼

我創建一個使用了DotLiquid的示例Mvc項目,完整代碼可以查看這里
以下的示例將以Mvc中的Action為單位,都存放在HomeController下。

最基礎的使用

Template.Parse可以把字符串解析為模板對象,再使用Render把模板對象渲染為字符串。
打開頁面可以看見Hello, World!

public ActionResult HelloWorld()
{
	var template = Template.Parse("Hello, {{ name }}!");
	var result = template.Render(Hash.FromAnonymousObject(new { name = "World" }));
	return Content(result);
}

使用過濾器

|后面的就是過濾器,過濾器可以連鎖起來使用。
escape過濾器用於做html編碼,避免name中的"<"當成是html標簽描畫。
upcase過濾器把字符串中的字母全部轉換為大寫。
打開頁面可以看見Hello, <WORLD>!

public ActionResult HelloFilter()
{
	var template = Template.Parse("Hello, {{ name | escape | upcase }}!");
	var result = template.Render(Hash.FromAnonymousObject(new { name = "<World>" }));
	return Content(result);
}

定義過濾器

DotLiquid支持自定義過濾器,首先需要一個過濾器類型,其中的函數名稱就是過濾器名稱。
過濾器支持多個參數和默認參數。

public class DotliquidCustomFilter
{
	public static string Substr(string value, int startIndex, int length = -1)
	{
		if (length >= 0)
			return value.Substring(startIndex, length);
		return value.Substring(startIndex);
	}
}

在網站啟動的時候把這個過濾器注冊到DotLiquid

public class MvcApplication : System.Web.HttpApplication
{
	protected void Application_Start()
	{
		// 在原有的代碼下添加
		Template.RegisterFilter(typeof(DotliquidCustomFilter));
	}
}

這個例子會顯示Hello, orl!

public ActionResult CustomFilter()
{
	var template = Template.Parse("Hello, {{ name | substr: 1, 3 }}!");
	var result = template.Render(Hash.FromAnonymousObject(new { name = "World" }));
	return Content(result);
}

使用標簽

DotLiquid中有兩種標簽,一種是普通標簽(Block),一種是自閉合標簽(Tag)。
這里的assign是自閉合標簽,if是普通標簽,普通標簽需要用end+標簽名閉合。
顯示內容是Hello, World!

public ActionResult HelloTag()
{
	var template = Template.Parse(@"
		{% assign name = 'World' %}
		{% if visible %}
		Hello, {{ name }}!
		{% endif %}
	");
	var result = template.Render(Hash.FromAnonymousObject(new { visible = true }));
	return Content(result);
}

自定義標簽

這里我將定義一個自閉合標簽conditional,這個標簽有三個參數,如果第一個參數成立則描畫第二個否則描畫第三個參數。

public class ConditionalTag : Tag
{
	public string ConditionExpression { get; set; }
	public string TrueExpression { get; set; }
	public string FalseExpression { get; set; }

	public override void Initialize(string tagName, string markup, List<string> tokens)
	{
		base.Initialize(tagName, markup, tokens);
		var expressions = markup.Trim().Split(' ');
		ConditionExpression = expressions[0];
		TrueExpression = expressions[1];
		FalseExpression = expressions.Length >= 3 ? expressions[2] : "";
	}

	public override void Render(Context context, TextWriter result)
	{
		var condition = context[ConditionExpression];
		if (!(condition == null || condition.Equals(false) || condition.Equals("")))
			result.Write(context[TrueExpression]);
		else
			result.Write(context[FalseExpression]);
	}
}

在網站啟動時把這個標簽注冊到DotLiquid

public class MvcApplication : System.Web.HttpApplication
{
	protected void Application_Start()
	{
		// 在原有的代碼下添加
		Template.RegisterTag<ConditionalTag>("conditional");
	}
}

這個例子會顯示Bar

public ActionResult CustomTag()
{
	var template = Template.Parse("{% conditional cond foo bar %}");
	var result = template.Render(Hash.FromAnonymousObject(new { cond = false, foo = "Foo", bar = "Bar" }));
	return Content(result);
}

模板文件

DotLiquid也支持從文件讀取模板,需要先定義一個TemplateFileSystem

public class DotliquidTemplateFileSystem : IFileSystem
{
	public string ReadTemplateFile(Context context, string templateName)
	{
		var path = context[templateName] as string;
		if (string.IsNullOrEmpty(path))
			return path;
		var fullPath = HttpContext.Current.Server.MapPath(path);
		return File.ReadAllText(fullPath);
	}
}

設置DotLiquid使用自定義的文件系統

public class MvcApplication : System.Web.HttpApplication
{
	protected void Application_Start()
	{
		// 在原有的代碼下添加
		Template.FileSystem = new DotliquidTemplateFileSystem();
	}
}

再定義一個控制器基類

public abstract class DotliquidController : Controller
{
	public ContentResult DotliquidView(string path = null, object parameters = null)
	{
		// 路徑為空時根據當前的Action決定
		if (string.IsNullOrEmpty(path))
		{
			var controller = RouteData.Values["controller"];
			var action = RouteData.Values["action"];
			path = $"~/DotliquidViews/{controller}/{action}.html";
		}
		// 根據路徑讀取模板內容
		var templateStr = Template.FileSystem.ReadTemplateFile(new Context(), "'" + path + "'");
		// 解析模板,這里可以緩存Parse出來的對象,但是為了簡單這里就略去了
		var template = Template.Parse(templateStr);
		// 描畫模板
		Hash templateParameters;
		if (parameters is IDictionary<string, object>)
			templateParameters = Hash.FromDictionary((IDictionary<string, object>)parameters);
		else
			templateParameters = Hash.FromAnonymousObject(parameters ?? new { });
		var result = template.Render(templateParameters);
		// 返回描畫出來的內容
		return Content(result, "text/html");
	}
}

現在可以在控制器中使用基於DotLiquid的模板了

public ActionResult HelloTemplateFile()
{
	return DotliquidView();
}

上面會返回文件~/DotliquidViews/Home/HelloTemplateFile.html的內容

Hello, Template!

嵌入子模板

為了實現代碼的重用,DotLiquid的模板還可以嵌入其他子模板,嵌入需要使用include標簽。
以下例子會顯示Hello, Include!

public ActionResult HelloInclude()
{
	return DotliquidView();
}

文件~/DotliquidViews/Home/HelloInclude.html的內容

Hello, {% include "~/DotliquidViews/Home/HelloIncludeContents.html" %}!

文件~/DotliquidViews/Home/HelloIncludeContents.html的內容

Include

繼承父模板

除了嵌入子模版,還能實現布局(Layout)方式的繼承父模板,繼承需要使用extends和block標簽。
以下例子會返回Html<div class="layout"><h1>Here is title</h1><p>Here is body</p></div>

public ActionResult HelloExtends()
{
	return DotliquidView();
}

文件~/DotliquidViews/Home/HelloExtendsLayout.html的內容

<div class="layout">
	<h1>
		{% block title %}
		Default title
		{% endblock %}
	</h1>
	<p>
		{% block body %}
		Default body
		{% endblock %}
	</p>
</div>

文件~/DotliquidViews/Home/HelloExtends.html的內容

{% extends "~/DotliquidViews/Home/HelloExtendLayout.html" %}

{% block title %}
Here is title
{% endblock %}

{% block body %}
Here is body
{% endblock %}

描畫自定義對象

請先看以下的例子

public class ExampleViewModel
{
	public string Name { get; set; }
	public int Age { get; set; }
}
public ActionResult CustomObject()
{
	var template = Template.Parse("Name: {{ model.Name }}, Age: {{ model.Age }}");
	var model = new ExampleViewModel() { Name = "john", Age = 35 };
	var result = template.Render(Hash.FromAnonymousObject(new { model }));
	return Content(result);
}

你可能預料這個例子會顯示Name: john, Age: 35,但實際運行時會給出以下錯誤

Name: Liquid syntax error: Object 'Dotliquid.Example.Dotliquid.ExampleViewModel' is invalid because it is neither a built-in type nor implements ILiquidizable, Age: Liquid syntax error: Object 'Dotliquid.Example.Dotliquid.ExampleViewModel' is invalid because it is neither a built-in type nor implements ILiquidizable

這是因為DotLiquid為了安全性,默認不允許描畫未經注冊的對象,這樣即使模板由前端使用者提供也不會導致信息泄露。
為了解決上面的錯誤,需要把ExampleViewModel注冊為可描畫的對象。
除了使用RegisterSafeType注冊,你也可以讓ExampleViewModel繼承ILiquidizable,在部分場景下會更適合。

public class MvcApplication : System.Web.HttpApplication
{
	protected void Application_Start()
	{
		// 在原有的代碼下添加
		Template.RegisterSafeType(typeof(ExampleViewModel), Hash.FromAnonymousObject);
	}
}

寫在最后

DotLiquid是一個靈活性很高並且依賴很少的模板引擎,雖然沒有Razor流行,但大量的單元測試保證它可以經得起實際的使用。
目前使用了DotLiquid的項目有

目前DotLiquid准備升級2.0版本,作者正在召集PR,如果你有意向可以到DotLiquid的github看看。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM