Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RazorEngine 3.7.7 - Error when compiling a cached template

I'm trying to figure out an issue we've been having lately with RazorEngine 3.7.5 and higher (tried 3.7.7)

Exception:

System.ArgumentException: Please either set a template manager to templates or add the template 'MySolution.Billing.Templates.Layout.cshtml'!

It occurs when trying to cache the template with Engine.Razor.Compile method.

public void AddTemplate(string templateName, string source)
{
     Engine.Razor.AddTemplate(templateName, source);
}

public void CacheTemplate(string templateName, Type type)
{
     var templateKey = new NameOnlyTemplateKey(templateName, ResolveType.Layout, null);
     Engine.Razor.Compile(templateKey, type);
}

The PreloadTemplates method is called when the service which contains it is created using StructureMap for instanciation. Each templates is stored as an Embedded Resource and loaded into RazorEngine cache and immediatly after that compiled using RazorEngine to make sure that all templates load as quick as possible.

private void PreloadTemplates()
{
        var embeddedResources = Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(x => x.StartsWith("MySolution.Billing.Templates")).ToList();
        foreach (var invoiceResource in embeddedResources)
        {
            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(invoiceResource))
            {
                using (var reader = new StreamReader(stream))
                {
                    var template = reader.ReadToEnd();
                    this._templatingService.AddTemplate(invoiceResource, template);
                }
            }
        }

        this._templatingService.CacheTemplate("MySolution.Billing.Templates.Header.cshtml", typeof(HeaderModel));
        this._templatingService.CacheTemplate("MySolution.Billing.Templates.Layout.cshtml", typeof(LayoutModel));
        this._templatingService.CacheTemplate("MySolution.Billing.Templates.Footer.cshtml", null);
}

RazorEngine is configured as follow

var config = new TemplateServiceConfiguration();
config.CachingProvider = new DefaultCachingProvider(t => { });
config.DisableTempFileLocking = true;

How we are using RazorEngine, flow of the application

  1. WCF (InvoiceQueryFacade)
    • Global.asax.cs registers StructureMap registries
  2. IInvoiceService (Instanciated by StructureMap to provide an InvoiceService)
    • The service calls PreloadTemplates in it's constructor

Steps to reproduce

We can reproduce the error almost everytimes by stopping IIS and starting it back again and doing a a call to the WCF method. It seems to be a problem with recycling the app pool or stopping IIS because the error does not come back after WCF has "warmed up".

like image 420
Maxime Forest Avatar asked Feb 19 '16 18:02

Maxime Forest


People also ask

How does razorengine handle templated emails?

Currently, I am using RazorEngine v2. 1 as part of a background process that sends templated emails (thousands of them). To speed things up, the templates are compiled with their md5 sum as a name. This makes it so that when a template is changed, it is re-compiled and all emails using the template are able to use the same compiled template.

Where does razorengine store its templates?

It seems that RazorEngine stores cache for compiled templates inside TemplateService instance. So you can recreate new instances of TemplateService from time to time to drop all cached templates.

How do I free up memory in a razorengine application?

When you change and recompile templates you have a memory leak, because you cannot unload loaded assemblies (which RazorEngine compiles and loads for you in the background). The only way to really free the memory is to reload the AppDomain or restart the process.

Are cached templates stored in memory?

The problem: It has occurred to me that after a really long time and after lots of template modifications that all these cached compiled templates will probably still be in memory since it looks like they are being stored in a dynamic.


1 Answers

I was able to find an answer by myself afterall.

I modified my TemplatingService class as following

public class TemplatingService : ITemplatingService
{
    private readonly IRazorEngineService _razorEngineService;

    public TemplatingService(Assembly assembly, string templatesNamespace)
    {
        var config = new TemplateServiceConfiguration();
        config.TemplateManager = new EmbeddedResourceTemplateService(assembly, templatesNamespace);

#if DEBUG
        config.Debug = true;
#endif

        this._razorEngineService = RazorEngineService.Create(config);
    }

    public void CacheTemplate(string templateName, Type type)
    {
        var templateKey = new NameOnlyTemplateKey(templateName, ResolveType.Layout, null);
        this._razorEngineService.Compile(templateKey, type);
    }

    public string RunTemplate(string templateName, Type type, object model, IDictionary<string, object> dynamicViewBag = null)
    {
        var templateKey = new NameOnlyTemplateKey(templateName, ResolveType.Layout, null);
        return this._razorEngineService.RunCompile(templateKey, type, model, dynamicViewBag != null ? new DynamicViewBag(dynamicViewBag) : null);
    }
}

I started using the TemplatingManager from the official website: RazorEngine string layouts and sections? and it seemed to have done the trick.

this.For<ITemplatingService>()
            .Singleton()
            .Add<TemplatingService>()
            .Named("invoiceTemplates")
            .Ctor<Assembly>("assembly").Is(billingDocumentGeneratorAssembly)
            .Ctor<string>("templatesNamespace").Is("MyBillingNamespace.DocumentGenerator.Invoices.Templates");

And I can use the TemplatingService as follows

var footerHtml = this._templatingService.RunTemplate("Footer.cshtml", null, null);
var headerHtml = this._templatingService.RunTemplate("Header.cshtml", typeof(AccountStatementHeaderModel), accountStatementModel.Header);

I hope this help someone else.

like image 157
Maxime Forest Avatar answered Oct 14 '22 07:10

Maxime Forest