Скомпилированные шаблоны RazorEngine

В настоящее время я использую RazorEngine v2.1 как часть фонового процесса, который отправляет шаблонные письма (тысячи из них). Чтобы ускорить процесс, шаблоны скомпилированы с их суммой md5 как имя. Это делает так, что при изменении шаблона он повторно компилируется, и все письма с использованием шаблона могут использовать один и тот же скомпилированный шаблон. Я отслеживаю имена скомпилированных шаблонов в списке, чтобы я знал, когда снова вызывать компиляцию (и делать несколько других вещей).

Проблема. Мне пришло в голову, что после очень долгого времени и после многих модификаций шаблонов все эти кэшированные скомпилированные шаблоны, вероятно, все еще будут в памяти, поскольку похоже, что они хранятся в dynamic. Для этого конкретного процесса, который может работать в течение нескольких месяцев без перезагрузки, это может стать серьезной утечкой памяти, если все предыдущие версии шаблонов все еще висят вокруг.

Вопрос: есть ли способ откопать старые шаблоны, чтобы они больше не висели в dynamic?

Если, например, мне удалось сохранить скомпилированные объекты шаблонов и просто передать их в RazorEngine, когда я захочу их использовать, я бы мог решить, когда их выбросить, и это устранит утечку памяти. Однако, если у RazorEngine уже есть способ обойти это, то знать об этом было бы удобно, так как я не могу найти очень много ссылок на эту конкретную проблему в Интернете. Есть много вещей, почему скомпилированные шаблоны должны использоваться для сокращения использования памяти, но мне было трудно найти что-нибудь о тоннах и тоннах неиспользуемых скомпилированных шаблонов, накапливаемых в долгоживущем приложении.

EDIT: я только что немного прочитал о том, как работает кеширование, и что если одно и то же имя передается с другим шаблоном, оно повторно кэширует его и отбрасывает старый. Проблема здесь все еще сохраняется, поскольку со временем электронные письма будут добавлены и удалены, и со временем все старые удаленные письма все равно будут (хотя и не будут хранить копии каждой версии шаблона).

Ответ 1

Отвечая на это, потому что он по-прежнему кажется актуальным для некоторых людей. (https://github.com/Antaris/RazorEngine/issues/232#issuecomment-128802285)

Для этого конкретного процесса, который может работать в течение нескольких месяцев без перезагрузки, это может стать серьезной утечкой памяти, если все предыдущие версии шаблонов все еще висят.

Когда вы меняете и перекомпилируете шаблоны, у вас есть утечка памяти, потому что вы не можете выгружать загруженные сборки (которые RazorEngine компилирует и загружает для вас в фоновом режиме).

Единственный способ освободить память - перезагрузить AppDomain или перезапустить процесс.

Другие ответы, похоже, говорят о более новых версиях, которые предотвращают утечку памяти в конфигурации по умолчанию (чтобы вы знали об этой проблеме) и требуют, чтобы какая-то пользовательская конфигурация могла перекомпилировать ключ с другим кодом шаблона. Обратите внимание, что все остальные ответы фактически увеличивают потребление памяти!

matthid,  a RazorEngine contributor

Ответ 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();

Ответ 3

Кажется, что RazorEngine хранит кеш для скомпилированных шаблонов внутри экземпляра TemplateService. Таким образом, вы можете повторно создавать новые экземпляры TemplateService время от времени, чтобы удалить все кэшированные шаблоны.

Вы также можете использовать мою собственную библиотеку, которая основана на RazorEngine, и реализует механизм пользовательского кэширования с истечением срока действия: http://www.nuget.org/packages/Essential.Templating.Razor