Antaris / RazorEngine

Open source templating engine based on Microsoft's Razor parsing engine
http://antaris.github.io/RazorEngine
Other
2.13k stars 576 forks source link

RazorEngine.Templating.TemplateCompilationException #528

Open vicinting opened 6 years ago

vicinting commented 6 years ago

UPDATE: We are using RazorEngine v3.8.2

I want to start off by saying I only encounter this issue on our test environment (deployed in azure app service) but not locally. Possibly due to debug configuration?

I am trying to compile e-mail templates. Originally, this was done in a worker role (cloud service) without issues. Now, I have enabled a preview functionality within our web application, and RazorEngine now runs in our web app to compile and display the e-mail template preview.

However, I run into issues once deployed to azure app service (not locally).

This is the error message details:

Type: RazorEngine.Templating.TemplateCompilationException 
Message: Errors while compiling a Template.
Please try the following to solve the situation:
* If the problem is about missing/invalid references or multiple defines either try to load 
the missing references manually (in the compiling appdomain!) or
Specify your references manually by providing your own IReferenceResolver implementation.
See https://antaris.github.io/RazorEngine/ReferenceResolver.html for details.
Currently all references have to be available as files!
* If you get 'class' does not contain a definition for 'member': 
try another modelType (for example 'null' to make the model dynamic).
NOTE: You CANNOT use typeof(dynamic) to make the model dynamic!
Or try to use static instead of anonymous/dynamic types.
More details about the error:
- error: (0, 0) An expression is too long or complex to compile
Temporary files of the compilation can be found in (please delete the folder): D:\local\Temp\RazorEngine_ckxscmhx.1ih

This is the template (redacted some parts) it tried to compile:

@{ Layout = Model.BrandingType + "_MasterLayout"; ViewBag.IncludeFooter = false;}@section Overview{ @Include("HeaderGreeting")

Welcome to the online portal for managing your needs. }@if (Model.Order != null){ 

You have purchased
@Include("PurchaseDetails") 
}

What happens next?

To activate your account, please log in to online portal using the details below.

Email: @Model.Email

Password: @Model.Password

Log in now 

You will be prompted to change your password when you log in for the first time.

For information about the portal, please visit our website.

The interesting part is that the list of loaded assemblies is crazy long. These are the first 10 lines:

List of loaded Assemblies:
D:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_32\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Web.ApplicationServices\v4.0_4.0.0.0__31bf3856ad364e35\System.Web.ApplicationServices.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Caching\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Caching.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.Build.Utilities.v4.0\v4.0_4.0.0.0__b03f5f7f11d50a3a\Microsoft.Build.Utilities.v4.0.dll
Loaded Assembly: D:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Web.RegularExpressions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.RegularExpressions.dll

However, it goes on for another 10,000+ more lines, 10,153 to be exact.

The stack trace points to the Compile method:

Source: RazorEngine 
Target Site: System.Tuple`2[System.Type,RazorEngine.Compilation.CompilationData] CompileTypeImpl(RazorEngine.Compilation.TypeContext) 
Stack Trace: 
at RazorEngine.Compilation.DirectCompilerServiceBase.CompileTypeImpl(TypeContext context) 
at RazorEngine.Compilation.DirectCompilerServiceBase.CompileType_Windows(TypeContext context) 
at RazorEngine.Compilation.DirectCompilerServiceBase.CompileType(TypeContext context) 
at RazorEngine.Templating.RazorEngineCore.CreateTemplateType(ITemplateSource razorTemplate, Type modelType) 
at RazorEngine.Templating.RazorEngineCore.Compile(ITemplateKey key, Type modelType) 
at RazorEngine.Templating.RazorEngineService.CompileAndCacheInternal(ITemplateKey key, Type modelType) 
at RazorEngine.Templating.RazorEngineService.Compile(ITemplateKey key, Type modelType) 
at RazorEngine.Templating.DynamicWrapperService.Compile(ITemplateKey key, Type modelType) 
at RazorEngine.Templating.RazorEngineServiceExtensions.Compile(IRazorEngineService service, String name, Type modelType)

Any ideas? Thanks!

csimone86 commented 6 years ago

Good luck.... We are moving to azure function too... Honestly I think I will change engine... It's about a week I am trying to make it work and there is no support. Here people answer rarely, you have been lucky because I am here to doing a new question... So, to solve your problem you must write a custom ReferenceResolver... This WAS mine:

    internal class ExternalAssemblyReferenceResolver : IReferenceResolver
    {
        private string[] _assembliesToLoad;
        public ExternalAssemblyReferenceResolver(params string[] assembliesToLoad)
        {
            _assembliesToLoad = assembliesToLoad;            
        }

        public IEnumerable<CompilerReference> GetReferences(TypeContext context, IEnumerable<CompilerReference> includeAssemblies = null)
        {
            IEnumerable<string> loadedAssemblies = CompilerServicesUtility
                .GetLoadedAssemblies()
                .Where(a => !a.IsDynamic && !a.FullName.Contains("Version=0.0.0.0") && File.Exists(a.Location) && !a.Location.Contains("CompiledRazorTemplates.Dynamic"))
                .GroupBy(a => a.GetName().Name).Select(grp => grp.First(y => y.GetName().Version == grp.Max(x => x.GetName().Version))) // only select distinct assemblies based on FullName to avoid loading duplicate assemblies
                .Select(a => CompilerReference.From(a))
                .Concat(includeAssemblies ?? Enumerable.Empty<CompilerReference>())
                .Select(r => r.GetFile())
                .ToArray();

            yield return CompilerReference.From(FindLoaded(loadedAssemblies, "mscorlib.dll"));
            yield return CompilerReference.From(FindLoaded(loadedAssemblies, "netstandard.dll"));
            yield return CompilerReference.From(FindLoaded(loadedAssemblies, "System.dll"));
            yield return CompilerReference.From(FindLoaded(loadedAssemblies, "System.Core.dll"));
            yield return CompilerReference.From(FindLoaded(loadedAssemblies, "RazorEngine.dll"));

            yield return CompilerReference.From(typeof(ExternalAssemblyReferenceResolver).Assembly); // Assembly

            foreach (var assembly in this._assembliesToLoad)
            {
                yield return CompilerReference.From(FindLoaded(loadedAssemblies, assembly));
            }
        }

        private string FindLoaded(IEnumerable<string> refs, string find)
        {
            return refs.First(r => r.EndsWith(System.IO.Path.DirectorySeparatorChar + find));
        }
    }

What I understood is that you must load all references that are necessary to compile the cshtml... so you must add system.dll, mscorlib.dll ... and so on (perhaps you do not need netstandard.dll, it depends on the type of project where your templates are saved)... than you must add also your dll where your models are saved... you cannot see it in my code because I passed the name of my dll inside the variable _assembliesToLoad... I think you can use my code and pass in input your dll name...

Finally, in your configuration you have to use your new ReferenceResolver:

TemplateServiceConfiguration configuration = new TemplateServiceConfiguration();
            configuration.ReferenceResolver = new ExternalAssemblyReferenceResolver(assembliesReferencePaths);

It should start to work but you should receive an advice that it is better that you run razorEngine in a new app domain, otherwise temp file are not deleted... infact if you check temp file will not delete.... It is about 3 days I am trying to resolve that problem.... but nothing...

If it still does not work let me know... I do not remember anymore all the passes I did to let it works (I am not sure you have to change also the template manager)... I have done a lot of reverse engineering to solve all my problems.. and I have not finished yet...

Hope this help

PS.: here one question I have done here (sadly the answer is mine too :( ) I dont know if it can help you in future: https://github.com/Antaris/RazorEngine/issues/526