/// <summary>Initializes a new instance of the <see cref="RazorTemplater" /> class.</summary> /// <param name="templateAssemblyPath">The template assembly path. This is the path where the generated templates are stored/cached. /// If shadow copy is enabled this path will be ignored and the shadow copy path will be used.</param> /// <param name="renderTimeout">The render timeout. This is the time in ms a template is allowed to render itself.</param> /// <param name="templateNamespace">The template namespace.</param> /// <param name="allowedDirectories">The directories the templates are allowed to read from.</param> /// <param name="baseType">Type of the template base class. Defaults to <see cref="TemplateBase" />.</param> /// <param name="defaultNamespaces">The default namespaces. Defaults to "System", "System.Collections.Generic", "System.Linq" and "System.Text".</param> /// <param name="forbiddenTypes">The forbidden types (FQDN). Defaults to "Task", "Thread", "System.Activator" and "System.Reflection.Assembly".</param> /// <param name="language">The language. Defaults to C#.</param> /// <param name="sponsor">The sponsor to keep the object alive.</param> /// <param name="persistTemplates">If set to <c>true</c> the generated templates are persisted over multiple application runs. Otherwise they are deleted when disposing.</param> public RazorTemplater(string templateAssemblyPath, int renderTimeout = 5000, string templateNamespace = "IsolatedRazor.RazorTemplate", List <string> allowedDirectories = null, Type baseType = null, List <string> defaultNamespaces = null, List <string> forbiddenTypes = null, RazorCodeLanguage language = null, ClientSponsor sponsor = null, bool persistTemplates = false) { RenderTimeout = renderTimeout; this.templateNamespace = templateNamespace; this.persistTemplates = persistTemplates; DefaultNamespaces = defaultNamespaces ?? new List <string>() { "System", "System.Collections.Generic", "System.Net", "System.Linq", "System.Text", "IsolatedRazor" }; ForbiddenTypes = forbiddenTypes ?? new List <string>() { "System.Threading.Tasks.Task", "System.Threading.Tasks.Task`1", "System.Threading.Thread", "System.Activator", "System.Reflection.Assembly" }; clientSponsor = sponsor ?? new ClientSponsor(TimeSpan.FromMinutes(1)); defaultBaseClass = (baseType ?? typeof(TemplateBase)).FullName; var host = new RazorEngineHost(language ?? new CSharpRazorCodeLanguage()) { DefaultNamespace = templateNamespace }; DefaultNamespaces.ForEach(n => host.NamespaceImports.Add(n)); engine = new RazorTemplateEngine(host); provider = host.CodeLanguage.LanguageName == "vb" ? (CodeDomProvider) new VBCodeProvider() : new CSharpCodeProvider(); adSetup = new AppDomainSetup(); if (AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles == "true") { isShadowCopied = true; templatePath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.CachePath, AppDomain.CurrentDomain.SetupInformation.ApplicationName); var shadowCopyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (shadowCopyDir.Contains("assembly")) { shadowCopyDir = shadowCopyDir.Substring(0, shadowCopyDir.LastIndexOf("assembly")); } var privatePaths = new List <string>(); foreach (var assemblyLocation in AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic && a.Location.StartsWith(shadowCopyDir)).Select(a => a.Location)) { privatePaths.Add(Path.GetDirectoryName(assemblyLocation)); } adSetup.ApplicationBase = shadowCopyDir; adSetup.PrivateBinPath = String.Join(";", privatePaths); } else { isShadowCopied = false; templatePath = templateAssemblyPath; adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; adSetup.PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath; } var resolver = new DefaultAssemblyResolver(); resolver.AddSearchDirectory(Path.GetDirectoryName(adSetup.ApplicationBase)); readerParameters = new ReaderParameters() { AssemblyResolver = resolver }; if (templateCache == null) { var path = Path.Combine(templatePath, TEMPLATE_CACHE_FILE); if (persistTemplates && File.Exists(path)) { using (var filestream = File.Open(path, FileMode.Open)) { var formatter = new BinaryFormatter(); templateCache = (TemplateCache)formatter.Deserialize(filestream); } } else { templateCache = new TemplateCache(); } } Directory.CreateDirectory(templatePath); permissionSet = new PermissionSet(PermissionState.None); permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); // run the code permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.RemotingConfiguration)); // remoting lifetime (sponsor) permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, templatePath)); // read templates permissionSet.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess)); // support dynamic if (allowedDirectories != null) { allowedDirectories.ForEach(dir => permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, dir))); } RecycleAppDomain(); }
/// <summary>Compiles the specified templates and stores it in the cache with the given group name.</summary> /// <param name="groupName">The group name.</param> /// <param name="templates">The template group.</param> /// <param name="timestamp">The time-stamp the template (NOT the assembly) was created.</param> /// <param name="baseTypeName">Type of the template base class as its FQDN. Defaults to the default base class.</param> /// <param name="additionalNamespaces">The additional namespaces.</param> /// <param name="generatedCodePath">If not NULL the generated code will be saved to this path.</param> /// <returns>The path to the generated assembly.</returns> /// <exception cref="System.ObjectDisposedException">RazorTemplater already disposed.</exception> /// <exception cref="CompilerException">The supplied template is not valid.</exception> public Task <string> CompileAsync(string groupName, Dictionary <string, string> templates, DateTime timestamp, string baseTypeName = null, List <string> additionalNamespaces = null, string generatedCodePath = null) { if (appDomain == null) { throw new ObjectDisposedException("RazorTemplater"); } var cachedTemplate = templateCache.Get(groupName, templates, timestamp); if (!String.IsNullOrWhiteSpace(cachedTemplate)) { return(Task.FromResult(cachedTemplate)); } return(Task.Run(() => { engine.Host.DefaultBaseClass = baseTypeName ?? defaultBaseClass; if (additionalNamespaces != null) { additionalNamespaces.ForEach(n => engine.Host.NamespaceImports.Add(n)); } var razorTemplates = new List <GeneratorResults>(); foreach (var template in templates) { engine.Host.DefaultClassName = ClassName(groupName + "_" + template.Key); var razorTemplate = engine.GenerateCode(new StringReader(template.Value)); razorTemplates.Add(razorTemplate); if (!String.IsNullOrWhiteSpace(generatedCodePath)) { SaveGeneratedCode(razorTemplate, generatedCodePath + String.Format(".{0}.cs", engine.Host.DefaultClassName)); } else if (isShadowCopied) { SaveGeneratedCode(razorTemplate, Path.Combine(templatePath, engine.Host.DefaultClassName + ".cs")); } } var outputName = groupName; foreach (char c in System.IO.Path.GetInvalidFileNameChars()) { outputName = outputName.Replace(c, '_'); } var compilerParameters = new CompilerParameters() { GenerateExecutable = false, IncludeDebugInformation = false, OutputAssembly = Path.Combine(templatePath, outputName + "." + DateTime.Now.ToString("yyyyMMddHHmmss") + ".dll"), // date-time to avoid accessing locked files CompilerOptions = "/target:library /optimize /define:RAZORTEMPLATE" }; var assemblies = AppDomain.CurrentDomain.GetAssemblies() .Where(a => !a.IsDynamic && File.Exists(a.Location)) .GroupBy(a => a.GetName().Name).Select(g => g.First(y => y.GetName().Version == g.Max(x => x.GetName().Version))) // group assemblies on FullName to avoid loading duplicate assemblies .Select(a => a.Location); compilerParameters.ReferencedAssemblies.AddRange(assemblies.ToArray()); var compiledAssembly = provider.CompileAssemblyFromDom(compilerParameters, razorTemplates.Select(t => t.GeneratedCode).ToArray()); templater.RemoveFromAssemblyCache(compiledAssembly.PathToAssembly); if (additionalNamespaces != null) { engine.Host.NamespaceImports.Clear(); DefaultNamespaces.ForEach(n => engine.Host.NamespaceImports.Add(n)); } if (compiledAssembly.Errors.Count > 0) { var message = String.Empty; foreach (var error in compiledAssembly.Errors) { if (error is CompilerError) { var compilerError = error as CompilerError; message += compilerError.ErrorText + Environment.NewLine; } else { message += error.ToString() + Environment.NewLine; } } if (File.Exists(compiledAssembly.PathToAssembly)) { File.Delete(compiledAssembly.PathToAssembly); } throw new CompilerException(message, compiledAssembly.Errors); } ValidateAssembly(compiledAssembly); var assemblyPath = compiledAssembly.PathToAssembly; templateCache.Set(groupName, assemblyPath, templates, timestamp); return assemblyPath; })); }