How to include CSS styles inline in Razor view?

31.3k Views Asked by At

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?

4

There are 4 best solutions below

4
On BEST ANSWER

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

0
On

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")
}
5
On

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;
    }
  }
}
6
On

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:

  1. 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;
            }
        }
    }
    
  2. Then in your Razor template, just go:

    @Html.EmbedCss("~/Content/EmailStyles.css")
    

    to embed your CSS text.

  3. Install the Premailer.Net package in your project; you can get it through NuGet.

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

  5. Run the string through Premailer.Net:

    PreMailer pm = new PreMailer();
    string premailedOutput = pm.MoveCssInline(htmlSource, false);
    
  6. 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!