RazorEngine layouts
Asked Answered
S

4

46

I am using the Razor engine https://github.com/Antaris/RazorEngine to parse the body of my email templates. Is it possible to define a layout and include other .cshtml files? for example a common header and a footer.

Sibby answered 10/7, 2012 at 13:18 Comment(0)
S
55

I got common templates and a layout working, with the help of these two posts:

RazorEngine string layouts and sections?

http://blogs.msdn.com/b/hongyes/archive/2012/03/12/using-razor-template-engine-in-web-api-self-host-application.aspx

This is my solution:

Solution 1: Layout

Used by setting _Layout

@{
    _Layout = "Layout.cshtml";
    ViewBag.Title = Model.Title;
 }

Footer

@section Footer 
{
   @RenderPart("Footer.cshtml")
}

Layout.cshtml

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html>
    <head>
    </head>
    <body>
        <div id="content">
            @RenderBody()
        </div> 
        @if (IsSectionDefined("Footer"))
        { 
            <div id="footer">
                @RenderSection("Footer")
            </div>
        }
    </body> 
</html>

TemplateBaseExtensions

Extend TemplateBase with a RenderPart Method

public abstract class TemplateBaseExtensions<T> : TemplateBase<T>
{
    public string RenderPart(string templateName, object model = null)
    {
        string path = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Templates", templateName);
        return Razor.Parse(File.ReadAllText(path), model);
    }
}

Razor Config

Set BaseTemplateType to your TemplateBaseExtensions class

TemplateServiceConfiguration templateConfig = new TemplateServiceConfiguration
{
     BaseTemplateType = typeof(TemplateBaseExtensions<>)
};

Razor.SetTemplateService(new TemplateService(templateConfig));

Edit Solution 2:

If you are using a TemplateResolver. RenderPart isn't needed use the @Include instead

Footer

@section Footer 
{
   @Include("Footer.cshtml")
}

Resolver

public class TemplateResolver : ITemplateResolver
{
    public string Resolve(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }

        string path = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Templates", name);
        return File.ReadAllText(path, System.Text.Encoding.Default);
    }
}

Config

TemplateServiceConfiguration templateConfig = new TemplateServiceConfiguration
{
     Resolver = new TemplateResolver()
};
Razor.SetTemplateService(new TemplateService(templateConfig));

Update by The Muffin Man Specify a template and render a string

var templateResolver = Razor.Resolve("Registration.cshtml");
return templateResolver.Run(new ExecuteContext());

Also I, along with others at this link https://github.com/Antaris/RazorEngine/issues/61 had issues with using _Layout whereas Layout worked.

'_Layout' is the old syntax. It was updated to 'Layout' in a future release.

Sibby answered 3/8, 2012 at 15:39 Comment(10)
I tried to implement the solution in the MS blog above and I keep getting a stackoverflow exception. I've spent all day on this.Archery
I can send you a quick application ive thrown together so you get the ideaSibby
rapidshare.com/files/3962348204/… Its been thrown together but sure u'll get the idea. The applicaiton base is RazorEngineConsoleApplication\bin\Debug so ive copied the templates into there. To stick to the example I posted.Sibby
Instead of @RendarPart use @Include("Footer.cshtml", Model) and it will use the template resolver. The RendarPart extension isnt neededSibby
Thanks for this answer. I successfully integrated layout support into JuniorRoute, which uses RazorEngine v3 internally. I didn't know about the Resolver feature, which turned out to be quite useful.Ajay
Hi could you re-post the sample application on somewhere like github please. Unfortunately rapid share has being taken downCorvette
Hi Jeff I must of deleted the sample application. The post should contain everything you need. If you get stuck give me a shout.Sibby
@Sibby This code doesn't show you actually call a template and get back the result string.Archery
ITemplateResolver was the ticket. Couldn't get anything to go otherwise. Ended up writing a custom pathing handler to level out any quirks when searching for views.Bryson
RazorEngine has since marked TemplateResolver as obsolete, even though error messages still tell you to write one. Unfortunately it now requires TemplateManager. Don't know what that entails.Isobar
P
1

You can easily do a lot of stuff with Razor; however, that particular project seems to abstract away a lot of the Razor engine stuff you could do (which is both good and bad). In your situation, it sounds like you would be much better off implementing your own Razor solution (it's actually not that bad) and then you can have your templates throw exceptions or pull in other content pretty easily.

For example; rolling your own solution allows you to make a base class for your razor templates which can expose the capacity to pull in "partial views" by invoking other templates. In addition, you can do model checking and throw exceptions if certain properties are null.

Peregrinate answered 10/7, 2012 at 13:59 Comment(0)
B
1

The easiest way to implement a layout with RazorEngine is by replacing what your template return in the @RenderBody() of the layout:

 var finalHtml = layout.Replace(@"@RenderBody()", templateHtml);

E.g.:

Your _Layout.cshtml with the typical @RenderBody()

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html>
    <head>
    </head>
    <body>
        <div>
            @RenderBody()
        </div> 
    </body> 
</html>

Your RazorEngine template MyTemplate.cshtml

@using RazorEngine.Templating
@inherits TemplateBase<myviewmodel>

<h1>Hello People</h1>
<p>@Model</p>

And wherever you call the RazorEngine template:

var TemplateFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates");
var template = File.ReadAllText(Path.Combine(TemplateFolderPath,"MyTemplate.cshtml"));
var layout = File.ReadAllText(Path.Combine(TemplateFolderPath, "_Layout.cshtml"));
var templateService = new TemplateService();
var templateHtml = templateService.Parse(template, myModel, null, null);
var finalHtml = layout.Replace(@"@RenderBody()", templateHtml);
Beatitude answered 2/10, 2016 at 18:24 Comment(0)
D
0

Fully custom solution of implementing Mail functionality.

Add nuget packet of RazorEngine

Create _Layout Template(.cshtml) :

<html>
<body style="margin: 0; padding: 0;">
    <div style="width:100%; display:block; float:left; height:100%;">
        <table cellpadding="0" cellspacing="0" border="0" align="center" width="100%">
            <tr>
                <td width="37" style="background-color: #ffffff;"></td>
                <td width="200" style="background-color: #ffffff">
                    <a href="@Url("")">Send Mail Logo</a>                    
                </td>
                <td style="background-color: #ffffff;">
                    &nbsp;

                </td>
                <td width="126" style="background-color: #ffffff;">
                    <img src="@Url("Images/mail/social-media.png")" alt="" width="126" height="73" border="0" usemap="#Map" />
                </td>
            </tr>
        </table>
        <table cellpadding="0" cellspacing="0" border="0" align="center" width="100%">
            <tr height="7">
                <td style="background-color: #ffffff;" colspan="3"></td>
            </tr>
            <tr height="54">
                <td colspan="3"></td>
            </tr>
            <tr>
                <td width="37">&nbsp;</td>
                <td style="font-family: Myriad, 'Helvetica Neue',Arial,Helvetica,sans-serif; font-size: 11pt; color: #6b6c6f; line-height: 24px;">
                    {{BODY}}
                </td>
                <td width="37">&nbsp;</td>
            </tr>

            <tr height="55">
                <td style="line-height: 0;" colspan="3">&nbsp;</td>
            </tr>
            <tr height="11">
                <td background="@Url("/Images/mail/dotted-line.png")" colspan="3" style="line-height: 0;">&nbsp;</td>
            </tr>
        </table>
    </div>
    <map name="Map" id="Map">
        <area shape="rect" coords="28,29,51,51" href="#" alt="Twitter" />
        <area shape="rect" coords="56,28,78,52" href="#" alt="Google+" />
        <area shape="rect" coords="84,28,104,51" href="#" alt="LinkedIn" />
    </map>
</body>
</html>

Create ConfirmEmail Template (.cshtml) :

@using yourProjectnamespace.LanguageResources.Mail
@model ConfirmEmail

@MailTemplateResource.YouHaveLoggedIn

<a href="@Url(string.Format("/User/Confirmemail?EmailId={0}", Model.UserId))">@MailTemplateResource.ClickHere</a> 

Create CustomTemplateBase class :

public class CustomTemplateBase<T> : TemplateBase<T>
    {
        public string Url(string url)
        {
            return MailConfiguration.BaseUrl + url.TrimStart('/');
        }          
    }

Create EmbeddedTemplateManager class :

internal class EmbeddedTemplateManager : ITemplateManager { private readonly string ns;

public EmbeddedTemplateManager(string @namespace)
{
    ns = @namespace;
}

public ITemplateSource Resolve(ITemplateKey key)
{
    var resourceName = $"{ns}.{key.Name}.cshtml";
    string content;

    using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
    using (var streamReader = new StreamReader(stream))
    {
        content = streamReader.ReadToEnd();
    }

    return new LoadedTemplateSource(content);
}

public ITemplateKey GetKey(string name, ResolveType resolveType, ITemplateKey context)
{
    return new NameOnlyTemplateKey(name, resolveType, context);
}

public void AddDynamic(ITemplateKey key, ITemplateSource source)
{
    throw new NotImplementedException("");
}

}

Create Mail Class :

public class Mail
    {
        private static readonly IRazorEngineService RazorEngine;

        static Mail()
        {
            var config = new TemplateServiceConfiguration
            {
                BaseTemplateType = typeof(CustomTemplateBase<>),
                TemplateManager = new EmbeddedTemplateManager(typeof(Mail).Namespace + ".Templates"),
                Namespaces = { "Add CurrentProjectName", "Add CurrentProjectName .Models" },
                CachingProvider = new DefaultCachingProvider()
            };
            RazorEngine = RazorEngineService.Create(config);
        }

        public Mail(string templateName)
        {
            TemplateName = templateName;
            ViewBag = new DynamicViewBag();
        }

        public string TemplateName { get; set; }

        public object Model { get; set; }

        public DynamicViewBag ViewBag { get; set; }

        public string GenerateBody()
        {
            var layout = RazorEngine.RunCompile("_Layout", model: null);
            var body = RazorEngine.RunCompile(TemplateName, Model.GetType(), Model);
            return layout.Replace("{{BODY}}", body);
        }

        public MailMessage Send(Guid key, string to, string subject, string cc = null)
        {
            var email = new MailMessage()
            {
                From = MailConfiguration.From,
                Body = GenerateBody(),
                IsBodyHtml = true,
                Subject = subject,
                BodyEncoding = Encoding.UTF8
            };

            email.Headers.Add("X-MC-Metadata", "{ \"key\": \"" + key.ToString("N") + "\" }");         

            foreach (var sendTo in to.Split(' ', ',', ';'))
            {
                email.To.Add(sendTo);
            }

            if (cc != null)
            {
                foreach (var sendCC in cc.Split(' ', ',', ';'))
                {
                    email.CC.Add(sendCC);
                }
            }

            var smtp = new MailClient().SmtpClient;
            smtp.EnableSsl = true;
            smtp.Send(email);
            return email;
        }
    }

    public class Mail<TModel> : Mail where TModel : class
    {
        public Mail(string templateName, TModel mailModel) : base(templateName)
        {
            Model = mailModel;
        }
    }

Create MailClient Class :

public class MailClient
    {
        public MailClient()
        {
            SmtpClient = new SmtpClient(MailConfiguration.Host)
            {
                Port = MailConfiguration.Port,
                Credentials = new NetworkCredential
                {
                    UserName = MailConfiguration.UserName,
                    Password = MailConfiguration.Password
                }
            };
        }

        public SmtpClient SmtpClient { get; }
    }

Create MailConfiguration Class :

public class MailConfiguration
    {
        private static string GetAppSetting(string key)
        {
            var element = ConfigurationManager.AppSettings["Mail:" + key];
            return element ?? string.Empty;
        }

        public static string BaseUrl => GetAppSetting("BaseUrl");     

        public static string Host => GetAppSetting("Host");

        public static int Port => Int32.Parse(GetAppSetting("Port"));

        public static string UserName => GetAppSetting("Username");

        public static string Password => GetAppSetting("Password");

        public static MailAddress From => new MailAddress(GetAppSetting("From"));
    }

MailSender Class :

Implement your method in MailSerder class and call MailSerder method in your repository or Controller.

Create public class MailSender : IMailSender
    {
        public MailSender()
        {

        }

        public void SendConfirmEmail(string emailId, Guid userId)
        {
            var confirmEmail = new ConfirmEmail
            {
                UserId = userId
            };
            ConfirmEmail(emailId, MailResource.YourRegistration, confirmEmail);
        }

        private void ConfirmEmail(string recipient,string subject,ConfirmEmail model)
        {
            var key = Guid.NewGuid();
            var mail = new Mail<ConfirmEmail>("ConfirmEmail", model);
            mail.ViewBag.AddValue("Recipient", recipient);           
            var sentMail = mail.Send(key, recipient, subject);          
        }
    }
Dearly answered 22/1, 2019 at 8:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.