RazorEngine string layouts and sections?

前端 未结 3 935
[愿得一人]
[愿得一人] 2020-12-24 03:09

I use razor engine like this:

public class EmailService : IService
{
    private readonly ITemplateService templateService;

    public EmailService(ITemplat         


        
相关标签:
3条回答
  • 2020-12-24 03:38

    It looks like someone else solved it for you.

    https://github.com/aqueduct/Appia/blob/master/src/Aqueduct.Appia.Razor/RazorViewEngine.cs

    The code you want is in the second ExecuteView method. Though they're creating their own view engine, you can instead create your own custom templating solution and use something similar. Basically they are looking for the Layout property of the Template, and if it exists doing a search and replace for the content from the layout.

    Here is a link to RazorEngine's custom templating:

    http://razorengine.codeplex.com/wikipage?title=Building%20Custom%20Base%20Templates&referringTitle=Documentation

    Here is where I found your solution:

    .NET Razor engine - Implementing layouts

    0 讨论(0)
  • 2020-12-24 03:39

    Turns out after some digging that layouts are supported, we just have to declare them with _Layout instead of Layout

    As for the embedded resource issue, I implemented the following ITemplateResolver

    using System;
    using System.IO;
    using System.Reflection;
    using Bruttissimo.Common;
    using RazorEngine.Templating;
    
    namespace Website.Extensions.RazorEngine
    {
        /// <summary>
        /// Resolves templates embedded as resources in a target assembly.
        /// </summary>
        public class EmbeddedTemplateResolver : ITemplateResolver
        {
            private readonly Assembly assembly;
            private readonly Type type;
            private readonly string templateNamespace;
    
            /// <summary>
            /// Specify an assembly and the template namespace manually.
            /// </summary>
            /// <param name="assembly">The assembly where the templates are embedded.</param>
            /// <param name="templateNamespace"></param>
            public EmbeddedTemplateResolver(Assembly assembly, string templateNamespace)
            {
                if (assembly == null)
                {
                    throw new ArgumentNullException("assembly");
                }
                if (templateNamespace == null)
                {
                    throw new ArgumentNullException("templateNamespace");
                }
                this.assembly = assembly;
                this.templateNamespace = templateNamespace;
            }
    
            /// <summary>
            /// Uses a type reference to resolve the assembly and namespace where the template resources are embedded.
            /// </summary>
            /// <param name="type">The type whose namespace is used to scope the manifest resource name.</param>
            public EmbeddedTemplateResolver(Type type)
            {
                if (type == null)
                {
                    throw new ArgumentNullException("type");
                }
                this.assembly = Assembly.GetAssembly(type);
                this.type = type;
            }
    
            public string Resolve(string name)
            {
                if (name == null)
                {
                    throw new ArgumentNullException("name");
                }
                Stream stream;
                if (templateNamespace == null)
                {
                    stream = assembly.GetManifestResourceStream(type, "{0}.cshtml".FormatWith(name));
                }
                else
                {
                    stream = assembly.GetManifestResourceStream("{0}.{1}.cshtml".FormatWith(templateNamespace, name));
                }
                if (stream == null)
                {
                    throw new ArgumentException("EmbeddedResourceNotFound");
                }
                string template = stream.ReadFully();
                return template;
            }
        }
    }
    

    Then you just wire it like this:

        internal static ITemplateService InstanceTemplateService()
        {
            TemplateServiceConfiguration configuration = new TemplateServiceConfiguration
            {
                Resolver = new EmbeddedTemplateResolver(typeof(EmailTemplate))
            };
            ITemplateService service = new TemplateService(configuration);
            return service;
        }
    

    The type you pass is just for referencing the assembly and namespace where the resources are embedded.

    namespace Website.Domain.Logic.Email.Template
    {
        /// <summary>
        /// The purpose of this class is to expose the namespace of razor engine templates in order to
        /// avoid having to hard-code it when retrieving the templates embedded as resources.
        /// </summary>
        public sealed class EmailTemplate
        {
        }
    }
    

    One last thing, in order to have the templates resolved with our resolver we have to resolve them like this:

    ITemplate template = templateService.Resolve(templateName, model);
    string body = template.Run();
    return body;
    

    .Run is just a simple extension method since I can't find any use for a ViewBag.

    public static class ITemplateExtensions
    {
        public static string Run(this ITemplate template)
        {
            ExecuteContext context = new ExecuteContext();
            string result = template.Run(context);
            return result;
        }
    }
    

    UPDATE

    Here are the missing extensions

        public static string FormatWith(this string text, params object[] args)
        {
            return string.Format(text, args);
        }
    
        public static string ReadFully(this Stream stream)
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                return reader.ReadToEnd();
            }
        }
    
    0 讨论(0)
  • 2020-12-24 03:48

    I needed to supply my own layout as either a string or a file name. Here is how I solved this (based on this blog post)

    public static class RazorEngineConfigurator
    {
        public static void Configure()
        {
            var templateConfig = new TemplateServiceConfiguration
                {
                    Resolver = new DelegateTemplateResolver(name =>
                        {
                            //no caching cause RazorEngine handles that itself
                            var emailsTemplatesFolder = HttpContext.Current.Server.MapPath(Properties.Settings.Default.EmailTemplatesLocation);
                            var templatePath = Path.Combine(emailsTemplatesFolder, name);
                            using (var reader = new StreamReader(templatePath)) // let it throw if doesn't exist
                            {
                                return reader.ReadToEnd();
                            }
                        })
                };
            RazorEngine.Razor.SetTemplateService(new TemplateService(templateConfig));
        }
    }
    

    Then I call RazorEngineConfigurator.Configure() in Global.asax.cs and it's ready.

    The path to my templates is in Properties.Settings.Default.EmailTemplatesLocation

    In my view I have this:

    @{ Layout = "_layout.html";}
    

    _layout.html is in emailsTemplatesFolder

    It's a pretty standard HTML with a @RenderBody() call in the middle.

    As far as I understand, RazorEngine uses the template name ("_layout.html" in this case) as a key to its cache so the delegate in my configurator is called only once per template.

    I think it uses that resolver for every template name it doesn't know (yet).

    0 讨论(0)
提交回复
热议问题