问题 RazorEngine取消缓存已编译的模板


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

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

这个问题:有没有办法取消缓存旧模板,以便它们不再闲置在 dynamic

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

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


3229
2018-01-16 23:14


起源



答案:


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

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

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

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

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

matthid,  RazorEngine的贡献者


5
2017-08-07 19:36





我最近升级到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();

4
2018-03-03 01:57



我的场景有点不同,我想从控制器将模板(emailBody)返回给用户,以便在页面上显示。如何将其作为视图返回? - user2818430
@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.。我所做的就是从数据库中获取模板。 - dev
很好的答案。谢谢!! - John Livermore


答案:


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

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

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

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

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

matthid,  RazorEngine的贡献者


5
2017-08-07 19:36





我最近升级到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();

4
2018-03-03 01:57



我的场景有点不同,我想从控制器将模板(emailBody)返回给用户,以便在页面上显示。如何将其作为视图返回? - user2818430
@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.。我所做的就是从数据库中获取模板。 - dev
很好的答案。谢谢!! - John Livermore


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

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


2
2018-03-12 09:14