Add CSS references to page's <head> from a partial view
Asked Answered
R

6

14

Is there a way to add CSS references to a page from a partial view, and have them render in the page's <head> (as required by the HTML 4.01 spec)?

Reiche answered 17/11, 2010 at 23:46 Comment(6)
Your question has been answered a few times already, #913255 & #886490. Whether we talk about css or js in this context doesn't really matter.Morula
@BurningIce - you're absolutely right, the type of resource doesn't matter. The questions you link to have the same accepted answer, which doesn't work for partial views because they can't use asp:Content controls (Parser Error Message: Content controls have to be top-level controls in a content page or a nested master page that references a master page.) Or am I missing something?Reiche
Hmmm interesting point. I guess the only real way to do this would be to separate your script/css into their own partial views. Then render partial in the asp:Content for the head section. That's really all I can think of.Outfitter
Good point, i did miss the fact you're using partial views.Morula
Does this answer your question? Injecting content into specific sections from a partial view ASP.NET MVC 3 with Razor View EngineEisenach
This question was here first but this one has more answers on the same subject (and perhaps "better answers" in some sense/cases https://mcmap.net/q/86917/-injecting-content-into-specific-sections-from-a-partial-view-asp-net-mvc-3-with-razor-view-engine/125981 so technically THAT one should be closed as a duplicate of this one.Eisenach
R
1

You can use a HttpModule to manipulate the response HTML and move any CSS/script references to the appropriate places. This isn't ideal, and I'm not sure of the performance implications, but it seems like the only way to resolve the issue without either (a) a javascript-based solution, or (b) working against MVC principles.

Reiche answered 18/11, 2010 at 23:48 Comment(1)
Agree with the "This isn't ideal" partEisenach
Z
6

If you're using MVC3 & Razor, the best way to add per-page items to your section is to: 1) Call RenderSection() from within your layout page 2) Declare a corresponding section within your child pages:

/Views/Shared/_Layout.cshtml:

<head>
    <!-- ... Rest of your head section here ... ->
    @RenderSection("HeadArea")
</head>

/Views/Entries/Index.cshtml:

@section HeadArea {
    <link rel="Stylesheet" type="text/css" href="/Entries/Entries.css" />
}

The resultant HTML page then includes a section that looks like this:

<head>
    <!-- ... Rest of your head section here ... ->
    <link rel="Stylesheet" type="text/css" href="/Entries/Entries.css" />
<head>
Zanthoxylum answered 13/2, 2011 at 22:21 Comment(4)
@Richard, what you describe here doesn't target the OP's issue, he was talking about a partial view, while _Layout.cshtml is a layout, rather than a partial view (that is for instances included within Index.cshtml, and wants to render a script right into _layout.cshtml's <head> tag.Ruddock
If not all view have the "HeadArea", those without one will throw an error. To avoid that, use @RenderSection("HeadArea", required:false) in the _Layout.cshtml instead.Metaphor
Perhaps alter the comment <!-- ... Rest of your head section here ... -> to @* ... Rest of your head section here ... *@ ref: https://mcmap.net/q/88829/-how-to-write-a-comment-in-a-razor-view/125981Eisenach
@ShimmyWeitzhandler Your statement seems untrue in that this is exactly what that does - render in the <head></head> where it saysEisenach
B
3

You could also use the Telerik open source controls for MVC and do something like :

<%= Html.Telerik().StyleSheetRegistrar()
                  .DefaultGroup(group => group
                     .Add("stylesheet.css"));

in the head section and

<%= Html.Telerik().ScriptRegistrar()
                  .DefaultGroup(group => group
                     .Add("script.js"));

in the script section at the botttom of your page.

And you can keep adding scripts on any view , or partial view and they should work.

If you don't want to use the component you can always inspire yourself from there and do something more custom.

Oh, with Telerik you also have options of combining and compressing the scripts.

Boob answered 18/11, 2010 at 13:11 Comment(2)
+1, I like this solution. Though I'm curious to know how Telerik does it's business. I might go have a look at it.Outfitter
@Chev They do business by getting you to use the "free" version then later make it a paid version for any updates/newer versions once they get you vested in their product - which might happen to any copyrighted product like this.Eisenach
O
1

You could have the partial view load in a javascript block that drops in the style to the head, but that would be silly considering that you probably want the javascript block in the head section for the same reason.

I recently discovered something pretty cool though. You can serialize a partial view into a string and send it back to the client as part of a JSON object. This enables you to pass other parameters as well, along with the view.

Returning a view as part of a JSON object

You could grab a JSON object with JQuery and ajax and have it loaded with the partial view, and then another JSON property could be your style block. JQuery could check if you returned a style block, if so then drop it into the head section.

Something like:

$.ajax(
{
     url: "your/action/method",
     data: { some: data },
     success: function(response)
     {
          $('#partialViewContainer).html(response.partialView);
          if (response.styleBlock != null)
               $('head').append(response.styleBlock);
     }
});
Outfitter answered 18/11, 2010 at 0:15 Comment(1)
Good thinking, this will work but it depends on the user having javascript enabled (which is fair enough if you're loading script resources, but not so much for CSS.)Reiche
R
1

You can use a HttpModule to manipulate the response HTML and move any CSS/script references to the appropriate places. This isn't ideal, and I'm not sure of the performance implications, but it seems like the only way to resolve the issue without either (a) a javascript-based solution, or (b) working against MVC principles.

Reiche answered 18/11, 2010 at 23:48 Comment(1)
Agree with the "This isn't ideal" partEisenach
A
0

Another approach, which defeats the principles of MVC is to use a ViewModel and respond to the Init-event of your page to set the desired css/javascript (ie myViewModel.Css.Add(".css") and in your head render the content of the css-collection on your viewmodel.

To do this you create a base viewmodel class that all your other models inherits from, ala

public class BaseViewModel
{
    public string Css { get; set; }
}

In your master-page you set it to use this viewmodel

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage<BaseViewModel>" %>

and your head-section you can write out the value of the Css property

<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />

    <%= Model.Css %>
</head>

Now, in your partial view you need to have this code, which is kinda ugly in MVC

<script runat="server">
    protected override void OnInit(EventArgs e)
    {
        Model.Css = "hej";

        base.OnInit(e);
    }
</script>
Ami answered 18/11, 2010 at 12:41 Comment(2)
altho the OP doesn't mention ajax requests for the partial, this would of course work, tho only as part of a standard page request. i guess the challenge is to make it work for partialviews whether called as part of a single request or as an ajax refresh (which i can't see how it would work unless it was injected into the head section via javascript)Tibold
Yikes! I don't like this at all. I'd much rather violate the protocol for keeping styles in the head section than throw away separation of concerns in my MVC app!Outfitter
T
0

The following would work only if javascript were enabled. it's a little helper that i use for exactly the scenario you mention:

// standard method - renders as defined in as(cp)x file
public static MvcHtmlString Css(this HtmlHelper html, string path)
{
    return html.Css(path, false);
}
// override - to allow javascript to put css in head
public static MvcHtmlString Css(this HtmlHelper html, 
                                string path, 
                                bool renderAsAjax)
{
    var filePath = VirtualPathUtility.ToAbsolute(path);

    HttpContextBase context = html.ViewContext.HttpContext;
    // don't add the file if it's already there
    if (context.Items.Contains(filePath))
        return null;

    // otherwise, add it to the context and put on page
    // this of course only works for items going in via the current
    // request and by this method
    context.Items.Add(filePath, filePath);

    // js and css function strings
    const string jsHead = "<script type='text/javascript'>";
    const string jsFoot = "</script>";
    const string jsFunctionStt = "$(function(){";
    const string jsFunctionEnd = "});";
    string linkText = string.Format("<link rel=\"stylesheet\" type=\"text/css\" href=\"{0}\"></link>", filePath);
    string jsBody = string.Format("$('head').prepend('{0}');", linkText);

    var sb = new StringBuilder();

    if (renderAsAjax)
    {
        // join it all up now
        sb.Append(jsHead);
        sb.AppendFormat("\r\n\t");
        sb.Append(jsFunctionStt);
        sb.AppendFormat("\r\n\t\t");
        sb.Append(jsBody);
        sb.AppendFormat("\r\n\t");
        sb.Append(jsFunctionEnd);
        sb.AppendFormat("\r\n");
        sb.Append(jsFoot);
    }
    else
    {
        sb.Append(linkText);
    }

    return MvcHtmlString.Create( sb.ToString());
}

usage:

<%=Html.Css("~/content/site.css", true) %>

works for me, tho as stated, only if javascript is enabled, thus limiting its usefulness a little.

Tibold answered 18/11, 2010 at 13:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.