Make ASP.NET bundling specify media=screen for CSS bundle
Asked Answered
S

8

44

I'm just trying out ASP.NET 4.5 bundling and minification, and ran into an issue.

I've got around 10 css files, of which 2 were originally referenced in the layout using the attribute media="screen".

Since the syntax for adding a css to the bundle does not let you specify that such attribute should be added (makes sense, since the attribute would apply for the whole bundle), I was hoping to see an overload of @Styles.Render that would allow me to specify html attributes, like in other Html helpers, but there is none.

There is an ugly solution, in which since I know the url of the bundle created, i could just craft the tag myself, but I'd lose the caching mechanism that is handled by ASP.NET by allowing it to render the tag itself.

Is there a way to do this, am I missing something? Or is this just an oversight of the design team?

Scrupulous answered 20/8, 2012 at 17:30 Comment(1)
Just use @Styles.RenderFormat (look at my answer for more detailed information)Enchilada
E
77

I've found a more elegant solution.

I'm using the Styles.RenderFormat(format, bundle).

I have a BundlesFormats class with a property called PRINT and I use it like so:

public class BundlesFormats
{
    public const string PRINT = @"<link href=""{0}"" rel=""stylesheet"" type=""text/css"" media=""print"" />";
}

And in the cshtml:

@Styles.RenderFormat(BundlesFormats.PRINT, "~/bundles/Content/print")
Enchilada answered 10/3, 2013 at 19:55 Comment(4)
Just a note: This solution currently requires the prerelease version of "Microsoft ASP.NET Web Optimization Framework". The stable version does not have the "RenderFormat"-method.Ichinomiya
This is now part of the 1.1.0 releaseModernistic
Took me a minute to realize it can be done on one line: @Styles.RenderFormat("<link href=""{0}"" rel=""stylesheet"" type=""text/css"" media=""print"" />", "~/bundles/Content/print")Unshapen
I think this is the most elegant solution, IMO.August
S
14

Well, it's an ugly hack, but hopefully the team will add a built-in way to do it in the next release.

This is how I solved it, maintaining the caching string and still being able to add the media attribute to the tag.

@{
    var cssMediaBundleUrl = BundleTable.Bundles.ResolveBundleUrl("~/stylesheets/mediacss", true);
}
<link href="@cssMediaBundleUrl" rel="stylesheet" type="text/css" media="screen" />

Guess I can turn this into an Html helper, will do that later and edit.

Scrupulous answered 20/8, 2012 at 20:19 Comment(4)
You can just do this: < link href="@Styles.Url("~/stylehseets/mediacss")" rel="stylesheet" type="text/css" media="screen" />Marra
@Scrupulous After upgrading to VS 2013 Express For Web this start to produce 404 error. I posted it in #20052778 How to fix ?Bridging
@Andrus, have you tried any of the alternatives that other people have posted? I haven't revisited this code yet, but will certainly do soon, hopefully.Scrupulous
No, I havent. Please confirm that your/Hao solution does not work in VS2013 then I start to look into other ways. In this case this should unmarked as answerBridging
P
6

Another option to solve this issue, without compromising the debug ability, could be:

public static IHtmlString Render(string path, IDictionary<string, object> htmlAttributes)
{
    var attributes = BuildHtmlStringFrom(htmlAttributes);

#if DEBUG
    var originalHtml = Styles.Render(path).ToHtmlString();
    string tagsWithAttributes = originalHtml.Replace("/>", attributes + "/>");
    return MvcHtmlString.Create(tagsWithAttributes);
#endif

    string tagWithAttribute = string.Format(
        "<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\"{1} />", 
        Styles.Url(path), attributes);

    return MvcHtmlString.Create(tagWithAttribute);
}

What I'm doing is just appending the given html attributes to the end of the tags (on debug mode) or to the end of the only link tag (when minification/bundling are enabled).

The usage in views:

@Bundles.Render("~/css/print", new { media = "print" })

The rest of the code:

public static IHtmlString Render(string path, object htmlAttributes)
{
    return Render(path, new RouteValueDictionary(htmlAttributes));
}

private static string BuildHtmlStringFrom(IEnumerable<KeyValuePair<string, object>> htmlAttributes)
{
    var builder = new StringBuilder();

    foreach (var attribute in htmlAttributes)
    {
        builder.AppendFormat(" {0}=\"{1}\"", attribute.Key, attribute.Value);
    }

    return builder.ToString();
}

I've wrote a blog post about this subject: http://danielcorreia.net/quick-start-to-mvc4-bundling/

Perfoliate answered 5/12, 2012 at 22:40 Comment(0)
M
3

Unfortunately there isn't a great way to hook into how the tags are rendered currently, we thought about adding a hook so you could add your own method to render each script/style tag. It sounds like we do need to do that. Should be pretty simple to add, I'll create a work item to enable this scenario...

As a temporary workaround, if you are willing to lose the debug/release functionality that Styles.Render gives you, you can render a reference to the bundle using Styles.Url which would give you just the bundle url, you can embed that inside your own tag.

Marra answered 20/8, 2012 at 19:4 Comment(3)
Hao, I'm guessing you're a member of the ASP.NET team? It's weird that other Html Helpers do have overloads to allow the developer to set html attributes and the Bundle renderer methods dont. I'm thinking of hacking the generated tag (which would include the caching string) and just add the media attribute myself, that way i'd still have the caching and i can add it..i just thought it's really weird that this escaped the team.Scrupulous
found a way to do it through the framework, although it's not nice. I'll post it in a bitScrupulous
Yes, Hao is the lead dev at MSFT for the optimization framework.Ammonium
T
2

Why not just use @media print? Check out http://www.phpied.com/5-years-later-print-css-still-sucks/

Tasha answered 13/6, 2013 at 8:36 Comment(2)
unless the blocking issue he refers to has been cleared up, this is a relevant pointDelora
I like this method because I can include the print-only file in my main styles bundle.Unshapen
H
0

Web Forms Solution

In BundleConfig.cs:

//Print css must be a separate bundle since we are going to render it with a media=print
Bundles.Add(new StyleBundle("~/bundles/printCSS").Include("~/Content/Print.css"));

Master Page:

<asp:Literal runat="server" ID="litCssPrint" />

Master Page Code File:

litCssPrint.Text = Styles.RenderFormat(@"<link href=""{0}"" rel=""stylesheet"" type=""text/css"" media=""print"" />", "~/bundles/printCSS").ToHtmlString();
Henleyonthames answered 23/3, 2016 at 23:45 Comment(0)
C
0

I took Adam Tal's suggestion a little further.

I'm probably over-coding it, but for readability, I created a static class to kind of mimic the Styles.Render format.

public static class StyleExtensions
{
    public enum Format
    {
        Async,
        Preload,
    }

    public static IHtmlString Render(string contentPath, Format format)
    {
        switch (format)
        {
            case Format.Async:
            return contentPath.ToAsyncFormat();
            case Format.Preload:
            return contentPath.ToPreloadFormat();
            default:
            return new HtmlString(string.Empty);
        }
    }

    public static IHtmlString RenderAsync(string contentPath)
    {
        return contentPath.ToAsyncFormat();
    }

    public static IHtmlString RenderPreload(string contentPath)
    {
        return contentPath.ToPreloadFormat();
    }

    public static IHtmlString ToAsyncFormat(this string contentPath)
    {
        return Styles.RenderFormat("<link rel=\"stylesheet\" type=\"text/css\" href=\"{0}\" media=\"print\" onload=\"this.media='all';this.onload=null;\">", contentPath);
    }

    public static IHtmlString ToPreloadFormat(this string contentPath)
    {
        return Styles.RenderFormat("<link rel=\"preload\" href=\"{0}\" as=\"style\" onload=\"this.rel='stylesheet';this.onload=null;\">", contentPath);
    }
}

I would probably erase either the direct constructor or the enum constructor, and you could really put the string extension inside the method too, depending on whatever makes more sense to you, but you'd call it either of these ways accordingly:

@StyleExtensions.Render("~/Content/bundle-bootstrap", StyleExtensions.Format.Preload)
@StyleExtensions.Render("https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;700&display=swap", StyleExtensions.Format.Preload)
@StyleExtensions.Render(Url.Content("~/Content/Styles/Primary.min.css"), StyleExtensions.Format.Async)

or

@StyleExtensions.RenderPreload("~/Content/bundle-bootstrap")
@StyleExtensions.RenderPreload("https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;700&display=swap")
@StyleExtensions.RenderAsync(Url.Content("~/Content/Styles/Primary.min.css"))
Cymry answered 13/7, 2020 at 17:30 Comment(0)
N
-1

So complicated, why not to use:

bundles.Add<StylesheetBundle>("~/Css/site.css", b => b.Media = "screen");

?

Nevlin answered 11/10, 2013 at 10:13 Comment(2)
What's the package version used here? Microsoft.AspNet.Web.Optimization.1.1.3 does not contain a generic Add methodHushaby
Where does those thing come from? Add<T>() and StylesheetBundleBlaise

© 2022 - 2024 — McMap. All rights reserved.