前言
為什么要花費時間記錄下來呢?
為什么想要看源碼?
為什么是RazorEngine?
為什么要記錄下來?
因為第一次看源碼對我實在是個考驗,並且這樣不學無術的自己,無論以后我會變得很厲害還是很菜,都是一個激勵。這一點點一步步,都將引導自己去面對問題,分析問題,分解問題最后解決問題的動力。雖然不保證這些記錄能有什么決定性的作用。
而且我有一個很大的缺點,就是任性。只要是自己在開發中遇到一些讓自己不得不放棄自己覺得最好的想法,而通過其他的方式解決問題時,就會有尊嚴被踐踏的錯覺——這實質是一種自私與無知。而讓自己徹底認識到自己愚昧的方法,就是讓被強烈自尊武裝的愚蠢可笑的自己,徹底暴露在自己,與別人的視野里!
所以我想把我低效的學習方法,分析思路寫下來。一在讓自己能敏感的抓住一切好的思路、二在讓別人也能從我的經歷里看到自己的不足。
為什么想到看源碼?
原因之一是我熟悉工作環境,因為要考慮所有人的知識水平,所以並不適合也不應該出現晦澀的代碼。因為我也很難從工作中學習到新穎的程序設計思路。
原因之二是源碼框架所具有的健壯和可擴展性,要求源碼必須具有優秀的設計。所以提升設計能力的首選。
再有就是我的危機感越來越強烈,一旦脫離工作,我就覺得自己什么都不懂。而造成這種危機感的原因,就是太久沒給大腦換水了。
為什么是RazorEngine?
因為在程序的設計中,想要把數據與工廠方法分開,而分開的約束,是很難表達的,如果要用關系型數據庫進行設計,難免會增加設計本身的復雜度。所以怎么樣扁平的解決問題,才是我的最終目的。靈感是從最簡單的面對對象的程序設計中來的——讓Json框架可繼承,可重寫。而重寫時難免遇到計算列。比如一個簡單的Post請求數據,和一個只能接受請求頭、請求體兩個參數的簡單工廠方法。Post的基礎數據包含着要提交的表單數據,顯然提供的參數多語工廠方法需要的參數,這時如果能將Post的請求數據繼承工廠方法的兩個參數,並且通過其他參數來拼湊出工廠方法需要的兩個參數的話,呢么任何工廠方法都可以通過這個方法,將數據源和工廠方法分開。
這時就會遇到另一個問題:計算列如何實線將其他字段拼湊成需要的結果呢?如果要自己寫特定的規則,並且自己解析,並計算的話,無非會多出更多出錯的可能性,和維護的復雜性——最根本是時間成本。所以我就想到了模板引擎,而C#的開源引擎,我只知道RazorEngine一個,所以就選擇了RazorEngine。
什么是RazorEngine?
RazorEngine是一款模板引擎,主要功能就是將字符串中的特殊標記替換成動態的內容。
最基礎的用法就是
using RazorEngine; using RazorEngine.Templating; // For extension methods. string template = "Hello @Model.Name, welcome to RazorEngine!"; var result =Engine.Razor.RunCompile(template, "templateKey", null, new { Name = "World" }); //Output //Hello Word,welcome to RazorEngine!
當然還可以在模板中使用函數,和邏輯判斷、循環等語句。
如果有興趣可查看github:https://github.com/Antaris/RazorEngine。
只需要下載git工具,一句
git clone https://github.com/Antaris/RazorEngine.git
就能把代碼拷貝到本地,當然如果有自己的Github賬號,也可以將源碼上傳到自己的Github,可以通過一下兩個語句:
//將Repository拷貝到本地 git clone --bare https://github.com/exampleuser/old-repository.git //轉到Repositor根目錄,將Repository提交到遠程倉庫地址 git push --mirror https://github.com/exampleuser/new-repository.git
github:github是我接觸到的最牛逼的版本工具,最牛逼的代碼開源環境。不管能不能給自己帶來顯而易見的好處,都請嘗試接觸Gethub,相信一定會有驚喜。
開始切入
剛開始看這個源碼時,我腦子里最直觀的涌現的問題是
1. 如何處理編譯符號?
2. 怎么控制代碼的執行的上下文?
3. 怎么注入用於模板解釋的動態參數?
開始看源碼,我的閱讀思路是,接口解耦的最通用的入口,所以第一步,先搞清楚有哪些接口,這些接口是干什么的,派生類有哪些。
從從面的例子觸發,首先是Engine類
public static class Engine
{
private static object _syncLock = new object();
private static IRazorEngineService _service;
private static IRazorEngineService _isolatedService;
public static IRazorEngineService Razor
{
get
{
if (_service == null)
{
lock (_syncLock)
{
if (_service == null)
{
_service = RazorEngineService.Create();
}
}
}
return _service;
}
set
{
_service = value;
}
}
}
一看是以Service結尾的方法,下意識的覺得,這可能就是整個系統的入口了。
順着IRazorEngineService接口可以找到如下三個實現類:
DynamicWrapperService
IsolatedRazorEngineService
RazorEngineService
因為這三個實現類處於同樣的層面,所以我先去看RazorEngineService的實現。
IRazorEngineService接口具有以下定義:
//從ITemplateManager實現獲取給定的Key。
ITemplateKey GetKey(string name, ResolveType resolveType = ResolveType.Global, ITemplateKey context = null);
//檢查給定的模板是否已被緩存。
bool IsTemplateCached(ITemplateKey key, Type modelType);
//將一個給定的模板添加到模板管理器作為動態模板。
void AddTemplate(ITemplateKey key, ITemplateSource templateSource);
//編譯並緩存指定模板
void Compile(ITemplateKey key, Type modelType = null);
//運行指定緩存模板,如果該模板的緩存不存在,則會先編譯並緩存。
void RunCompile(ITemplateKey key, TextWriter writer, Type modelType = null, object model = null, DynamicViewBag viewBag = null);
//運行指定緩存的模板。
void Run(ITemplateKey key, TextWriter writer, Type modelType = null, object model = null, DynamicViewBag viewBag = null);
從這里看,RazorEngine還是挺簡單的,畢竟只提到了少量的關鍵詞:緩存,模板,Key,模板管理器,編譯,運行。
猜想緩存的實現一應該取決於編譯,模板就不用解釋了,模板管理器應該就是管理所有模板的地方吧。
哪么重點無疑就落到了【編譯】一詞上,如果搞不清楚編譯時什么,呢么緩存,編譯,運行都將無從說起。所以下面將從編譯着手。
注意例子中的RunCompile方法的簽名在IRazorEngineService中並不能找到,所以可能是擴展方法。果然在Templating命名空間下找到了TemplateCompilationException擴展方法類。
其中RunCompile方法是這么實現的:
service.AddTemplate(key, templateSource);
service.RunCompile(key, writer, modelType, model, viewBag);
先添加一個模板,然后再編譯緩存並運行模板。好像看編譯之前,無法越過添加模板這個坎了。哪么先簡單看一下IRazorEngineService的AddTemplate是怎么實現的。
馬上找到了RazorEngineService的AddTemplate方法,其代碼也很簡單,就一句:
Configuration.TemplateManager.AddDynamic(key, templateSource);
這里的Configuration是什么鬼呢?我感覺簡單的背后肯定有大文章,果然Configuration才是IRazorEngineService的頂梁柱。不得已要看Configuration是什么鬼了。
internal ITemplateServiceConfiguration Configuration { get { return _config; } }
又爆出了一個新的接口:ITemplateServiceConfiguration,一看嚇一跳,里面全是各種Provider,Services,Utility下面先列出來:
//獲取啟動器。
IActivator Activator { get; }
//獲取或設置是否允許在動態模型上缺失屬性。
bool AllowMissingPropertiesOnDynamic { get; }
//獲取基礎模板類型。
Type BaseTemplateType { get; }
//獲取代碼檢查者
IEnumerable<ICodeInspector> CodeInspectors { get; }
//獲取引用解析器。
IReferenceResolver ReferenceResolver { get; }
//獲取緩存提供程序。
ICachingProvider CachingProvider { get; }
//獲取編譯服務工廠
ICompilerServiceFactory CompilerServiceFactory { get; }
//獲取模板服務是否在調試模式下運行。
bool Debug { get; }
/*
使用Assembly.Load(byte [])加載所有動態程序集。這樣可以防止臨時文件被鎖定(這使得RazorEngine無法將其刪除)。
同時這完全關閉任何沙盒/安全。
僅當您使用有限數量的靜態模板(不對rumtime進行任何修改)時,才能使用此功能)
你完全信任,當一個單獨的AppDomain是沒有解決方案給你!
這個選項也會傷害調試。
*/
bool DisableTempFileLocking { get; }
//獲取編碼的字符串工廠。
IEncodedStringFactory EncodedStringFactory { get; }
//獲得語言配置
Language Language { get; }
//獲取命名空間。
ISet<string> Namespaces { get; }
//獲取模板解析器
ITemplateResolver Resolver { get; }
//獲取模板管理器。
ITemplateManager TemplateManager { get; }
再看在RazorEngineService中使用的ITemplateServiceConfiguration的實現TemplateServiceConfiguration,其中ITemplateManager實現是:
public ITemplateResolver Resolver {
get {
return resolver;
}
set {
resolver = value;
if (value != null)
{
TemplateManager = new WrapperTemplateManager(value);
}
}
}
public ITemplateManager TemplateManager { get; set; }
這真是個牛逼的寫法!不過以此可以看出,TemplateManager實際上是對ITemplateResolver的封裝。
果然在WrapperTemplateManager代碼中可以看出,AddDynamic方法其實只是將模板與Key值保存在字典里:
private readonly System.Collections.Concurrent.ConcurrentDictionary<ITemplateKey, ITemplateSource> _dynamicTemplates = new System.Collections.Concurrent.ConcurrentDictionary<ITemplateKey, ITemplateSource>();
public void AddDynamic(ITemplateKey key, ITemplateSource source)
{
_dynamicTemplates.AddOrUpdate(key, source, (k, oldSource) =>
{
if (oldSource.Template != source.Template)
{
throw new InvalidOperationException("The same key was used for another template!");
}
return source;
});
}
回到RazorEngineServiceExtensions類中的RunCompile方法中,看service.Compile的實現。
public void Compile(ITemplateKey key, Type modelType = null)
{
CompileAndCacheInternal(key, modelType);
}
這里只是調用了內部的CompileAndCacheInternal方法:
internal ICompiledTemplate CompileAndCacheInternal(ITemplateKey key, Type modelType)
{
var compiled = _core_with_cache.Compile(key, modelType);
_config.CachingProvider.CacheTemplate(compiled, key);
return compiled;
}
方法里分為了簡單的兩步,編譯,緩存。
不過這個_core_with_cache是什么鬼呢?一個F12,我找到了他的實例代碼:
private readonly RazorEngineCore _core_with_cache;
_core_with_cache = new RazorEngineCoreWithCache(new ReadOnlyTemplateServiceConfiguration(config), this);
如上,_core_with_cache在實例化的時候傳入了被ReadOnlyTemplateServiceConfiguration包裝起來的config。
直接看Compile方法是怎么實現的吧:
public ICompiledTemplate Compile(ITemplateKey key, Type modelType)
{
Contract.Requires(key != null);
var source = Resolve(key);
var result = CreateTemplateType(source, modelType);
return new CompiledTemplate(result.Item2, key, source, result.Item1, modelType);
}
可以看到調用了內部方法Resolve方法,獲取的參數是ITemplateSource。
看看實現,如果沒有猜錯的話調用的應該是RazorEngineServiceConfiguration的ITemplateManager的實現類WrapperTemplateManager的Resolve方法來實現的吧:
internal ITemplateSource Resolve(ITemplateKey key)
{
return Configuration.TemplateManager.Resolve(key);
}
可以看到代碼中的確是這么做的,這么一來,Resolve是怎么實現的,就成了不可跨過的問題了。
(睡了!!明天基本就能把核心部分看完了把應該!)