• 小白的源码阅读之旅_RazorEngine_起因


    前言

    为什么要花费时间记录下来呢?
    为什么想要看源码?
    为什么是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是怎么实现的,就成了不可跨过的问题了。


    (睡了!!明天基本就能把核心部分看完了把应该!)

  • 相关阅读:
    创建struct类型的数组
    simulate windows touch input
    Could not load file using Ranorex runtime : General Questions
    UIAutomator 编译
    w3cmark前端精彩博文周报 10.27-11.2
    w3cmark前端精彩博文周报 10.20-10.27
    w3cmark前端精彩博文周报 10.13-10.19
    sublime自定义snippet代码片段
    Iconfont在移动端遇到问题的探讨
    一个小效果引出的兼容性探讨
  • 原文地址:https://www.cnblogs.com/Thancoo/p/RazorEngineRead.html
Copyright © 2020-2023  润新知