RazorEngine 3.7.7 - Error when compiling a cached template
Asked Answered
K

1

7

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".

Koffman answered 19/2, 2016 at 18:33 Comment(0)
K
7

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.

Koffman answered 23/2, 2016 at 15:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.