RazorEngine string layouts and sections?

≯℡__Kan透↙ 提交于 2019-12-29 14:19:44

问题


I use razor engine like this:

public class EmailService : IService
{
    private readonly ITemplateService templateService;

    public EmailService(ITemplateService templateService)
    {
        if (templateService == null)
        {
            throw new ArgumentNullException("templateService");
        }
        this.templateService = templateService;
    }

    public string GetEmailTemplate(string templateName)
    {
        if (templateName == null)
        {
            throw new ArgumentNullException("templateName");
        }
        Assembly assembly = Assembly.GetAssembly(typeof(EmailTemplate));
        Stream stream = assembly.GetManifestResourceStream(typeof(EmailTemplate), "{0}.cshtml".FormatWith(templateName));
        string template = stream.ReadFully();
        return template;
    }

    public string GetEmailBody(string templateName, object model = null)
    {
        if (templateName == null)
        {
            throw new ArgumentNullException("templateName");
        }
        string template = GetEmailTemplate(templateName);
        string emailBody = templateService.Parse(template, model, null, null);
        return emailBody;
    }
}

The templating service I use is injected although it's just a default implementation:

    internal ITemplateService InstanceDefaultTemplateService()
    {
        ITemplateServiceConfiguration configuration = new TemplateServiceConfiguration();
        ITemplateService service = new TemplateService(configuration);
        return service;
    }

Since in this case in particular I will be building emails from these templates. I want to be able to use @sections for the email'a subject, and different sections of the email body, while using a layout where I specify the styles that are common to the whole email structure (which will look like one of MailChimp's probably).

The question is then twofold:

  • How can I specify layouts in RazorEngine?
  • How can I specify these layouts from strings (or a stream)? since as you can see, I use embedded resources to store the razor email templates.

Update

Maybe I wasn't clear, but I'm referring to the RazorEngine library.


回答1:


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();
        }
    }



回答2:


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).




回答3:


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



来源:https://stackoverflow.com/questions/10369519/razorengine-string-layouts-and-sections

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!