Skip to content

@Include and @Layout

Alexander edited this page Jul 1, 2020 · 6 revisions

Maintainer strongly believes implementing @include and @Layout is unnecessary complexity for standalone RazorEngine.

  1. Since RazorEngine.Compile accept template as a string, one will need also to accept a Dictionary (key, template) to be able to resolve @Include and @Layout

  2. It is easily implementable outside of RazorEngine

Extension

Usage

We would like to have code like this: Supply primary template, parts, and model and see the result as a string

namespace ConsoleApp11
{
    class Program
    {
        static void Main(string[] args)
        {
            string template = @"
                @{
                    Layout = ""MyLayout"";
                 }

                <h1>@Model.Title</h1>

                @Include(""outer"", Model)
            ";

            IDictionary<string, string> parts = new Dictionary<string, string>()
            {
                    {"MyLayout", @"
                        LAYOUT HEADER
                            @RenderBody()
                        LAYOUT FOOTER
                    "},
                    {"outer", "This is Outer include, <@Model.Title>, @Include(\"inner\")"},
                    {"inner", "This is Inner include"}
            };

            IRazorEngine razorEngine = new RazorEngine();

            MyCompiledTemplate compiledTemplate = razorEngine.Compile(template, parts);

            string result = compiledTemplate.Run(new {Title = "Hello"});

            Console.WriteLine(result);
            Console.ReadKey();
        }
    }
}

Output

LAYOUT HEADER

<h1>Hello</h1>
This is Outer include, <Hello>, This is Inner include

LAYOUT FOOTER

Custom template base

Lets make custom template base with Include function. It will be available as @Include("footer") in template. We will inialize IncludeCallback just before running template

public class MyTemplateBase : RazorEngineTemplateBase
{
    public Func<string, object, string> IncludeCallback { get; set; }
    public Func<string> 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();
    }
}

RazorEngine extension

public static class RazorEngineCoreExtensions
{
    public static MyCompiledTemplate Compile(this RazorEngine razorEngine, string template, IDictionary<string, string> parts)
    {
        return new MyCompiledTemplate(
                razorEngine.Compile<MyTemplateBase>(template),
                parts.ToDictionary(
                        k => k.Key,
                        v => razorEngine.Compile<MyTemplateBase>(v.Value)));
    }
}
public class MyCompiledTemplate
{
    private readonly IRazorEngineCompiledTemplate<MyTemplateBase> compiledTemplate;
    private readonly Dictionary<string, IRazorEngineCompiledTemplate<MyTemplateBase>> compiledParts;

    public MyCompiledTemplate(IRazorEngineCompiledTemplate<MyTemplateBase> compiledTemplate, Dictionary<string, IRazorEngineCompiledTemplate<MyTemplateBase>> compiledParts)
    {
        this.compiledTemplate = compiledTemplate;
        this.compiledParts = compiledParts;
    }

    public string Run(object model)
    {
        return this.Run(this.compiledTemplate, model);
    }

    public string Run(IRazorEngineCompiledTemplate<MyTemplateBase> 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.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
    }
}