private static void AddIComponentRazorViewFactoryImplementation(ClassDeclarationIRNode classNode) { // The Activator.CreateInstance feature that I added to the DNA runtime is very basic and doesn't // actually invoke the default constructor of the type being created. It just allocates memory for // the instance and returns it, without having run any constructor. This could be confusing if you // put constructor logic (such as field initializers) in your Razor page, given that we instantiate // it using Activator.CreateInstance. // As a workaround (without actually adding constructor support to Activator.CreateInstance, which // would be nontrivial), the Razor views privately implement an interface IComponentRazorViewFactory // that can return new instances of their own type. We can then just call this with normal .NET code. // This means we allocate memory for two instances of the view even though we're only using one, // but it's not going to matter much as the first instance will just be released to GC immediately. if (classNode.Interfaces == null) { classNode.Interfaces = new List <string>(); } classNode.Interfaces.Add(typeof(IRazorComponentFactory).FullName); var methodStatement = new CSharpStatementIRNode { Parent = classNode, Source = null }; classNode.Children.Add(methodStatement); methodStatement.Children.Add(new RazorIRToken { Kind = RazorIRToken.TokenKind.CSharp, Parent = classNode, Content = $@" {typeof(RazorComponent).FullName} {typeof(IRazorComponentFactory).FullName}.{nameof(IRazorComponentFactory.Instantiate)}() {{ return new {classNode.Name}(); }}" }); }
public static string CompileToStream(bool enableLogging, string rootDir, string[] referenceAssemblies, string outputAssemblyName, Stream outputStream) { if (!rootDir.EndsWith(Path.DirectorySeparatorChar.ToString())) { rootDir += Path.DirectorySeparatorChar.ToString(); } EnableLogging = enableLogging; Log("Creating Razor engine..."); var engine = RazorEngine.Create(builder => { var defaultCSharpLoweringPhase = builder.Phases.OfType <IRazorCSharpLoweringPhase>().Single(); builder.Phases.Remove(defaultCSharpLoweringPhase); builder.Phases.Add(new VirtualDomCSharpLoweringPhase(defaultCSharpLoweringPhase)); builder.SetNamespace("Views"); builder.SetBaseType(typeof(RazorComponent).FullName); builder.ConfigureClass((codeDoc, classNode) => { classNode.Name = RazorComponent.GetViewClassName(rootDir, codeDoc.Source.FileName); if (!string.IsNullOrEmpty((string)codeDoc.Items["DetectedBaseClass"])) { classNode.BaseType = (string)codeDoc.Items["DetectedBaseClass"]; } AddIComponentRazorViewFactoryImplementation(classNode); var layoutProperty = new CSharpStatementIRNode { Parent = classNode, Source = null }; classNode.Children.Add(layoutProperty); layoutProperty.Children.Add(new RazorIRToken { Kind = RazorIRToken.TokenKind.CSharp, Parent = classNode, Content = $"protected override string Layout {{ get {{ return \"{ codeDoc.Items["DetectedLayout"] ?? string.Empty }\"; }} }}" }); }); }); Log("Compiling Razor files to C#..."); var filenames = GetFilesToCompile(rootDir); var syntaxTrees = GetSyntaxTrees(engine, rootDir, filenames); Log("Compiling C#..."); var modelAssemblyRefs = referenceAssemblies .Select(a => MetadataReference.CreateFromFile(Path.GetFullPath(a))) .Cast <MetadataReference>() .ToList(); CompileToFile(syntaxTrees, modelAssemblyRefs, outputAssemblyName, outputStream); return($"Compiled {syntaxTrees.Count} view(s) as {outputAssemblyName}"); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) { var parserOptions = irDocument.Options; var designTime = parserOptions.DesignTime; var walker = new DirectiveWalker(); walker.VisitDocument(irDocument); var classNode = walker.ClassNode; foreach (var node in walker.FunctionsDirectiveNodes) { node.Parent.Children.Remove(node); foreach (var child in node.Children.Except(node.Tokens)) { child.Parent = classNode; classNode.Children.Add(child); } } foreach (var node in walker.InheritsDirectiveNodes.Reverse()) { node.Parent.Children.Remove(node); var token = node.Tokens.FirstOrDefault(); if (token != null) { classNode.BaseType = token.Content; break; } } var sectionsMethodNode = new CSharpStatementIRNode(); RazorIRBuilder.Create(sectionsMethodNode) .Add(new RazorIRToken() { Kind = RazorIRToken.TokenKind.CSharp, Content = "public override void DefineSections() {" }); classNode.Children.Add(sectionsMethodNode); foreach (var node in walker.SectionDirectiveNodes) { //var sectionIndex = node.Parent.Children.IndexOf(node); node.Parent.Children.Remove(node); var lambdaContent = designTime ? "__razor_section_writer" : "builder"; var sectionName = node.Tokens.FirstOrDefault()?.Content; var defineSectionStartStatement = new CSharpStatementIRNode(); RazorIRBuilder.Create(defineSectionStartStatement) .Add(new RazorIRToken() { Kind = RazorIRToken.TokenKind.CSharp, Content = $"DefineSection(\"{sectionName}\", async ({lambdaContent}) => {{" }); classNode.Children.Add(defineSectionStartStatement); foreach (var child in node.Children.Except(node.Tokens)) { classNode.Children.Add(child); } var defineSectionEndStatement = new CSharpStatementIRNode(); RazorIRBuilder.Create(defineSectionEndStatement) .Add(new RazorIRToken() { Kind = RazorIRToken.TokenKind.CSharp, Content = "});" }); classNode.Children.Add(defineSectionEndStatement); } var sectionsMethodNodeEnd = new CSharpStatementIRNode(); RazorIRBuilder.Create(sectionsMethodNodeEnd) .Add(new RazorIRToken() { Kind = RazorIRToken.TokenKind.CSharp, Content = "}" }); classNode.Children.Add(sectionsMethodNodeEnd); }
public override void VisitCSharpStatement(CSharpStatementIRNode node) { Context.BasicWriter.WriteCSharpStatement(Context, node); }
private static void AddIComponentRazorViewFactoryImplementation(ClassDeclarationIRNode classNode, RazorCodeDocument codeDoc) { // The Activator.CreateInstance feature that I added to the DNA runtime is very basic and doesn't // actually invoke the default constructor of the type being created. It just allocates memory for // the instance and returns it, without having run any constructor. This could be confusing if you // put constructor logic (such as field initializers) in your Razor page, given that we instantiate // it using Activator.CreateInstance. // As a workaround (without actually adding constructor support to Activator.CreateInstance, which // would be nontrivial), the Razor views privately implement an interface IComponentRazorViewFactory // that can return new instances of their own type. We can then just call this with normal .NET code. // This means we allocate memory for two instances of the view even though we're only using one, // but it's not going to matter much as the first instance will just be released to GC immediately. if (classNode.Interfaces == null) { classNode.Interfaces = new List <string>(); } classNode.Interfaces.Add(typeof(IRazorComponentFactory).FullName); var methodStatement = new CSharpStatementIRNode { Parent = classNode, Source = null }; classNode.Children.Add(methodStatement); var model = codeDoc.Items["DetectedModel"]; string content = ""; string isPage = ""; string addItems = ""; if (model != null) { content = $@"Model = new {model}();"; } if ((bool)codeDoc.Items["DetectedPage"]) { isPage += "classNode.IsPage = true;"; } if ((codeDoc.Items["DetectedPageMatches"]) != null) { var list = (List <string>)codeDoc.Items["DetectedPageMatches"]; addItems += "List<string> list = new List<string>();"; foreach (var item in list) { addItems += $"\r\n list.Add(\"{item}\");"; } addItems += "\r\n Items = list;"; } var razorToken = new RazorIRToken { Kind = RazorIRToken.TokenKind.CSharp, Parent = classNode, Content = $@" {typeof(RazorComponent).FullName} {typeof(IRazorComponentFactory).FullName}.{nameof(IRazorComponentFactory.Instantiate)}() {{ {content} var classNode = new {classNode.Name}(); {isPage} {addItems} return classNode; }}" }; methodStatement.Children.Add(razorToken); }