diff --git a/ExampleAppCore/LayoutAndSections.cs b/ExampleAppCore/LayoutAndSections.cs new file mode 100644 index 0000000..545125f --- /dev/null +++ b/ExampleAppCore/LayoutAndSections.cs @@ -0,0 +1,180 @@ +using Microsoft.AspNetCore.Razor.Language.Extensions; +using RazorEngineCore; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + + +namespace ExampleApp +{ + + public static class LayoutAndSections + { + /// + /// Provides an example of using @section and @RenderSection, based on the layout implementation + /// at https://github.com/adoconnection/RazorEngineCore/wiki/@Include-and-@Layout + /// + public static void Test() + { + + string template = @" + @{ + Layout = ""MyLayout""; + } + +

@Model.Title

+ + @Include(""outer"", Model) + + @section mysection + { + This is in a section + } + + "; + + IDictionary parts = new Dictionary() + { + {"MyLayout", @" + LAYOUT HEADER + @RenderBody() + Before section + @RenderSection(""mysection"") + After section + LAYOUT FOOTER + "}, + {"outer", "This is Outer include, <@Model.Title>, @Include(\"inner\")"}, + {"inner", "This is Inner include"} + }; + + var razorEngine = new RazorEngine(); + + MyCompiledTemplate compiledTemplate = razorEngine.Compile(template, parts, (builder) => + { + builder.Options.ProjectEngineBuilderAction = (x) => SectionDirective.Register(x); + }); + + string result = compiledTemplate.Run(new { Title = "Hello" }); + + Console.WriteLine(result); + Console.ReadKey(); + } + } + + + public class MyTemplateBase : RazorEngineTemplateBase + { + public Func IncludeCallback { get; set; } + public Func RenderBodyCallback { get; set; } + public string Layout { get; set; } + + public string Include(string key, object model = null) + { + return this.IncludeCallback(key, model); + } + + public string RenderBody() + { + return this.RenderBodyCallback(); + } + + public Dictionary Sections = new Dictionary(); + + public void DefineSection(string name, Action a) + { + Debug.WriteLine("DefineSection " + name); + BeginCapture(); + a(); + string content = EndCapture(); + Sections.Add(name, content); + Debug.WriteLine("Section content:"); + Debug.WriteLine(content); + } + + public string RenderSection(string name, bool required = false) + { + if (Sections.TryGetValue(name, out var content)) + return content; + else if (required) + throw new Exception("Required section not found: " + name); + return string.Empty; + } + } + + + public class MyCompiledTemplate + { + private readonly IRazorEngineCompiledTemplate compiledTemplate; + private readonly Dictionary> compiledParts; + + public MyCompiledTemplate(IRazorEngineCompiledTemplate compiledTemplate, Dictionary> compiledParts) + { + this.compiledTemplate = compiledTemplate; + this.compiledParts = compiledParts; + } + + public string Run(object model) + { + return this.Run(this.compiledTemplate, model); + } + + public string Run(IRazorEngineCompiledTemplate template, object model) + { + MyTemplateBase templateReference = null; + + string result = template.Run(instance => + { + if (!(model is AnonymousTypeWrapper)) + { + model = new AnonymousTypeWrapper(model); + } + + instance.Model = model; + instance.IncludeCallback = (key, includeModel) => this.Run(this.compiledParts[key], includeModel); + + templateReference = instance; + }); + + if (templateReference.Layout == null) + { + return result; + } + + return this.compiledParts[templateReference.Layout].Run(instance => + { + if (!(model is AnonymousTypeWrapper)) + { + model = new AnonymousTypeWrapper(model); + } + instance.Sections = templateReference.Sections; + instance.Model = model; + instance.IncludeCallback = (key, includeModel) => this.Run(this.compiledParts[key], includeModel); + instance.RenderBodyCallback = () => result; + }); + } + + public void Save() + { + /* + TODO + + this.compiledTemplate.SaveToFile(); + this.compiledTemplate.SaveToStream(); + + foreach (var compiledPart in this.compiledParts) + { + compiledPart.Value.SaveToFile(); + compiledPart.Value.SaveToStream(); + } + */ + } + + public void Load() + { + // TODO + } + } + +} diff --git a/ExampleAppCore/RazorEngineCoreExtensions.cs b/ExampleAppCore/RazorEngineCoreExtensions.cs new file mode 100644 index 0000000..cf5a06c --- /dev/null +++ b/ExampleAppCore/RazorEngineCoreExtensions.cs @@ -0,0 +1,23 @@ +using RazorEngineCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ExampleApp +{ + public static class RazorEngineCoreExtensions + { + public static MyCompiledTemplate Compile(this RazorEngine razorEngine, string template, IDictionary parts, + Action builderAction = null) + { + return new MyCompiledTemplate( + razorEngine.Compile(template, builderAction), + parts.ToDictionary( + k => k.Key, + v => razorEngine.Compile(v.Value))); + } + + } + +} diff --git a/RazorEngineCore.Tests/RazorEngineCore.Tests.csproj b/RazorEngineCore.Tests/RazorEngineCore.Tests.csproj index 636858a..1ca2f7b 100644 --- a/RazorEngineCore.Tests/RazorEngineCore.Tests.csproj +++ b/RazorEngineCore.Tests/RazorEngineCore.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;net472;net5.0;net6.0 + net472;net6.0 false diff --git a/RazorEngineCore/RazorEngine.cs b/RazorEngineCore/RazorEngine.cs index ef31494..2a69456 100644 --- a/RazorEngineCore/RazorEngine.cs +++ b/RazorEngineCore/RazorEngine.cs @@ -60,6 +60,7 @@ protected virtual MemoryStream CreateAndCompileToStream(string templateSource, R (builder) => { builder.SetNamespace(options.TemplateNamespace); + options.ProjectEngineBuilderAction?.Invoke(builder); }); string fileName = string.IsNullOrWhiteSpace(options.TemplateFilename) ? Path.GetRandomFileName() : options.TemplateFilename; diff --git a/RazorEngineCore/RazorEngineCompilationOptions.cs b/RazorEngineCore/RazorEngineCompilationOptions.cs index 3aab592..98e628a 100644 --- a/RazorEngineCore/RazorEngineCompilationOptions.cs +++ b/RazorEngineCore/RazorEngineCompilationOptions.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using System; +using Microsoft.AspNetCore.Razor.Language; namespace RazorEngineCore { @@ -22,6 +23,8 @@ public class RazorEngineCompilationOptions "System.Collections.Generic" }; + public Action ProjectEngineBuilderAction { get; set; } + public RazorEngineCompilationOptions() { bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); diff --git a/RazorEngineCore/RazorEngineCore.csproj b/RazorEngineCore/RazorEngineCore.csproj index b145bdc..a7722cd 100644 --- a/RazorEngineCore/RazorEngineCore.csproj +++ b/RazorEngineCore/RazorEngineCore.csproj @@ -1,6 +1,6 @@  - net6.0;net5.0;netstandard2.0 + net7.0;netstandard2.0 true 2022.8.1 Alexander Selishchev, Simon Mourier, William David Cossey, Benjamin Smith, Dag H. Baardsen, krmr, jddj007-hydra @@ -20,7 +20,7 @@ true - - + + diff --git a/RazorEngineCore/RazorEngineTemplateBase.cs b/RazorEngineCore/RazorEngineTemplateBase.cs index b54e570..dd432cb 100644 --- a/RazorEngineCore/RazorEngineTemplateBase.cs +++ b/RazorEngineCore/RazorEngineTemplateBase.cs @@ -1,11 +1,29 @@ -using System.Text; +using System; +using System.Text; using System.Threading.Tasks; namespace RazorEngineCore { public abstract class RazorEngineTemplateBase : IRazorEngineTemplate { - private readonly StringBuilder stringBuilder = new StringBuilder(); + private readonly StringBuilder _stringBuilder = new StringBuilder(); + private StringBuilder _captureStringBuilder; + private StringBuilder stringBuilder => _captureStringBuilder ?? _stringBuilder; + + protected void BeginCapture() + { + if (_captureStringBuilder != null) + throw new InvalidOperationException("Already capturing"); + _captureStringBuilder = new StringBuilder(); + } + protected string EndCapture() + { + if (_captureStringBuilder == null) + throw new InvalidOperationException("Not capturing"); + string result = _captureStringBuilder.ToString(); + _captureStringBuilder = null; + return result; + } private string attributeSuffix = null;