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