Reputation: 7777
I am using Postal to render MVC Razor views and send them via email. I have a custom CSS that I have defined specifically for the email views. Currently I am including them as follows:
@Styles.Render("~/Content/EmailStyles.css")
However, this only includes the relative link to the stylesheet, which will not work in an email:
<link href="/Content/EmailStyles.css" rel="stylesheet"/>
I want to include the stylesheet inline so that it functions properly in the email. What is the best way to render the contents of a file-based resource within an MVC view?
Upvotes: 26
Views: 32593
Reputation: 698
I realize this is an old question, but here is a modified version of dprothero's answer that will embed bundles. Create a static C# class and put this method in it:
public static IHtmlString EmbedCss(this HtmlHelper htmlHelper, string path)
{
try
{
// Get files from bundle
StyleBundle b = (StyleBundle)BundleTable.Bundles.GetBundleFor("~/Content/css");
BundleContext bc = new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, "~/Content/css");
List<BundleFile> files = b.EnumerateFiles(bc).ToList();
// Create string to return
string stylestring = "";
// Iterate files in bundle
foreach(BundleFile file in files)
{
// Get full path to file
string filepath = HttpContext.Current.Server.MapPath(file.IncludedVirtualPath);
// Read file text and append to style string
string filetext = File.ReadAllText(filepath);
stylestring += $"<!-- Style for {file.IncludedVirtualPath} -->\n<style>\n{filetext}\n</style>\n";
}
return htmlHelper.Raw(stylestring);
}
catch
{
// return nothing if we can't read the file for any reason
return null;
}
Then go to whichever view you want to use it in. Be sure to add a using statement so your view can see the CSS helper. I also use TempData to decide whether or not to render it inline:
<!-- Using statement -->
@using Namespace.Helpers;
<!-- Check tempdata flag for whether or not to render inline -->
@if (TempData["inlinecss"] != null)
{
<!-- Embed CSS with custom code -->
@Html.EmbedCss("~/Content/css")
}
else
{
<!-- Use links to reference CSS -->
@Styles.Render("~/Content/css")
}
Upvotes: 0
Reputation: 3229
I had the same question myself, and came across Premailer.Net. It looks like the library you need. Here's what you'd have to do:
Create an extension method to help you embed your CSS into your page; there's an answer on a question on how to embed HTML in a Razor view that should help you. I've modified it for embedding CSS:
public static class HtmlHelpers
{
public static MvcHtmlString EmbedCss(this HtmlHelper htmlHelper, string path)
{
// take a path that starts with "~" and map it to the filesystem.
var cssFilePath = HttpContext.Current.Server.MapPath(path);
// load the contents of that file
try
{
var cssText = System.IO.File.ReadAllText(cssFilePath);
var styleElement = new TagBuilder("style");
styleElement.InnerHtml = cssText;
return MvcHtmlString.Create(styleElement.ToString());
}
catch (Exception ex)
{
// return nothing if we can't read the file for any reason
return null;
}
}
}
Then in your Razor template, just go:
@Html.EmbedCss("~/Content/EmailStyles.css")
to embed your CSS text.
Install the Premailer.Net package in your project; you can get it through NuGet.
Render your Razor view into a string (I guess that's what Postal is for in your process? I believe RazorEngine can also do that).
Run the string through Premailer.Net:
PreMailer pm = new PreMailer();
string premailedOutput = pm.MoveCssInline(htmlSource, false);
Send as an e-mail!
I've been using this technique in production for a while now, and it seems to be working quite well.
Edit: Remember that styles on pseudo-elements can't be inlined because they don't exist in the markup. I've also noticed the odd little bug in Premailer.Net -- I think their specificity and cascade rules aren't perfectly conformant. Still, it's pretty good and it's one more piece of code I didn't have to write!
Upvotes: 38
Reputation: 2722
Upvoted Paul d'Aoust's answer, but I found this version of his helper method to work a little better for me (wouldn't try to encode things like quotes in the CSS):
public static class CssHelper
{
public static IHtmlString EmbedCss(this HtmlHelper htmlHelper, string path)
{
// take a path that starts with "~" and map it to the filesystem.
var cssFilePath = HttpContext.Current.Server.MapPath(path);
// load the contents of that file
try
{
var cssText = File.ReadAllText(cssFilePath);
return htmlHelper.Raw("<style>\n" + cssText + "\n</style>");
}
catch
{
// return nothing if we can't read the file for any reason
return null;
}
}
}
Upvotes: 14
Reputation: 3756
I guess you will need to have a custom helper for that. On top of my head, there is no such a method to render the css path including the absolute path of the website.
e.g. http:www.example.com/css/EmailStyles.css
Upvotes: 1