【问题标题】:RazorEngine un-cache compiled templatesRazorEngine 取消缓存已编译的模板
【发布时间】:2012-12-31 10:23:59
【问题描述】:

目前,我正在使用 RazorEngine v2.1 作为发送模板电子邮件(数千封)的后台进程的一部分。为了加快速度,模板以它们的 md5 总和作为名称进行编译。这使得当模板更改时,它会被重新编译,并且所有使用该模板的电子邮件都能够使用相同的编译模板。我在列表中跟踪已编译模板的名称,以便知道何时再次调用 compile(并做一些其他事情)。

问题:我突然想到,经过很长时间和大量模板修改后,所有这些缓存的编译模板可能仍然在内存中,因为看起来它们正在被存储在dynamic。对于这个可能一次运行数月而无需重新启动的特定进程,如果所有以前版本的模板仍然存在,这可能构成严重的内存泄漏。

问题:有没有办法取消缓存旧模板,使它们不再在dynamic 中闲逛?

例如,如果我能够自己保留已编译的模板对象,并在我想使用它们时将它们传递给 RazorEngine,我可以决定何时将它们扔掉,这样就可以消除内存泄漏。但是,如果 RazorEngine 已经有办法解决这个问题,那么了解这一点也会很方便,因为我在互联网上找不到很多关于这个特定问题的参考资料。关于为什么应该使用编译模板来减少内存使用的原因有很多,但我很难找到关于在长期应用程序中积累大量未使用的编译模板的任何信息。

编辑:我刚刚阅读了一些关于缓存如何工作的内容,如果使用不同的模板传入相同的名称,它将重新缓存它并丢弃旧的。然而,这里的问题仍然存在,因为随着时间的推移,电子邮件将被添加和删除,并且随着时间的推移,所有已删除的旧电子邮件仍然存在(即使它不会存储模板的每个版本的副本)。

【问题讨论】:

    标签: c# service razorengine


    【解决方案1】:

    回答这个问题是因为它似乎仍然与某些人相关。 (https://github.com/Antaris/RazorEngine/issues/232#issuecomment-128802285)

    对于这个可能一次运行数月而无需重新启动的特定进程,如果所有以前版本的模板仍然存在,这可能构成严重的内存泄漏。

    当您更改和重新编译模板时,您会发生内存泄漏,因为您无法卸载已加载的程序集(RazorEngine 在后台为您编译和加载)。

    真正释放内存的唯一方法是重新加载 AppDomain 或重新启动进程。

    其他答案似乎是在谈论较新的版本,这些版本可以防止默认配置中的内存泄漏(让您意识到问题),并且需要一些自定义配置才能使用另一个模板代码重新编译密钥。请注意,所有其他答案实际上会增加内存消耗!

    马蒂德, RazorEngine 贡献者

    【讨论】:

      【解决方案2】:

      我最近升级到 RazorEngine (3.6.1) 的最新稳定版本,由于所有更改,我清除缓存的策略不再有效。发生了很多变化,该项目的文档不仅过时,而且从作者的角度编写,导致用户体验不佳。

      这是我当前使用 3.6.1 清除所有缓存模板的代码。

      public static class TemplateManager
      {
          static IRazorEngineService Service { get; set; }
          static TemplateServiceConfiguration Configuration { get; set; }
      
          static TemplateManager()
          {
              Configuration = new TemplateServiceConfiguration()
              {
                  // setting up our custom template manager so we map files on demand
                  TemplateManager = new MyTemplateManager()
              };
              Service = RazorEngineService.Create(Configuration);
              Engine.Razor = Service;
          }
      
          /// <summary>
          /// Resets the cache.
          /// </summary>
          public static void ResetCache()
          {
              Configuration.CachingProvider = new RazorEngine.Templating.DefaultCachingProvider();
          }
      
          /// <summary>
          /// Compiles, caches and parses a template using RazorEngine.
          /// </summary>
          /// <param name="templateType">Type of the template.</param>
          /// <param name="anonymousType">Type of the anonymous object.</param>
          /// <param name="cachedEnabled">true to enabled caching; false otherwise</param>
          /// <returns></returns>
          public static string GetTemplate<T>(EmailTemplateType templateType, T anonymousType, bool cachedEnabled = true)
          {
              string templateName = templateType.ToString();
      
              if (cachedEnabled == false)
                  ResetCache();
      
              // pre-compile, cache & parse the template
              return Engine.Razor.RunCompile(templateName, null, anonymousType);
          }
      }
      
      public enum EmailTemplateType
      {
          ForgotPassword,
          EmailVerification
      }
      
      public class MyTemplateManager : ITemplateManager
      {
          public ITemplateSource Resolve(ITemplateKey key)
          {
              string file = HttpContext.Current.Server.MapPath(string.Format("~/EmailTemplates/{0}.cshtml", key.Name));
              return new LoadedTemplateSource(System.IO.File.ReadAllText(file), file);
          }
      
          public ITemplateKey GetKey(string name, ResolveType resolveType, ITemplateKey context)
          {
              return new NameOnlyTemplateKey(name, resolveType, context);
          }
      
          public void AddDynamic(ITemplateKey key, ITemplateSource source)
          {
              throw new NotImplementedException("dynamic templates are not supported!");
          }
      }
      

      这是 Asp.Net MVC 中代码的示例用法:

      var emailBody = TemplateManager.GetTemplate(EmailTemplateType.ForgotPassword, new
      {
          SiteUrl = Url.Action(MVC.Home.Index(), protocol: Request.Url.Scheme),
          SiteFriendlyName = SiteSettings.Instance.DomainName.FriendlyName,
          PasswordResetLink = Url.Action(MVC.Account.ActionNames.ResetPassword, MVC.Account.Name, new { userId = user.Id, code = code }, protocol: Request.Url.Scheme),
          NotRequestedUrl = Url.Action(MVC.Account.ActionNames.PasswordResetNotReqeuested, MVC.Account.Name, new { userId = user.Id, requesterIpAddress = WebUtils.GetClientIPAddress(), code = code }, protocol: Request.Url.Scheme)
      },
      /* this setting allows me to disable caching during development */
      !SiteSettings.Instance.EmailSettings.DebugEmailTemplates );
      
      // I could also have a button on an admin page that executed this code to manually reset the cache in production.
      TemplateManager.ResetCache();
      

      【讨论】:

      • 我的场景有点不同,我想将模板(emailBody)从控制器返回给用户,以便在页面上可见。如何将其作为视图返回?
      • @tugboatcaptain 我尝试了您的解决方案并收到错误An exception of type 'System.ArgumentException' occurred in RazorEngine.dll but was not handled in user code. Additional information: Invalid token for impersonation - it cannot be duplicated.。我所做的只是从数据库中获取模板。
      • 很好的答案。感谢您的帮助!
      【解决方案3】:

      RazorEngine 似乎在 TemplateService 实例中存储了已编译模板的缓存。因此,您可以不时重新创建 TemplateService 的新实例以删除所有缓存的模板。

      你也可以考虑使用我自己的库,它基于 RazorEngine 并实现了自定义的过期缓存机制:http://www.nuget.org/packages/Essential.Templating.Razor

      【讨论】:

        猜你喜欢
        • 2016-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-01
        • 2015-02-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多