Dynamically adding CSS cuts down page weight? - asp.net

Ok, this is bizaare and the only shop I've seen do this..granted we only have 2 developers in this shop. My boss thinks that adding css files individually to pages using a helper method makes our site more efficient because when adding the style references in the Masterpage, then every page gets those styles making the site load slower.
So we have a company.css which is our main styles for stuff that is global like the main buckets such as header, footer, main, etc.
Then he wants me to go into each code-behind of lets say a certain set of pages for one of our features and use a utility method we created to dynamically add the product-related style sheet to those pages. So for instance lets say we have a Reviews & Ratings feature. We put all our reviews & ratings related classes and ids in Reviews.css. Ok great.
So for instance in the .aspx.cs pages Init, we'd put this:
Util.AddStylesheet("Ratings.css");
Now he wants me to include that stylesheet in the codebehind of those pages where our reviews code touches in .aspx pages. But says do not put a reference to Reviews.css in the master page.
Am I the only one who questions this method and says what's the point of having a master page then? I mean really, if you put all your .css references in your master page does the argument of saying that now all pages have all these styles (some not used because they do not relate to that page) add weight to your load time for end users?
I mean common right? Maybe people do this but I've never seen it and it's a management nightmare cause you have references all over the fing place dynamically adding certain .css pages to subsets of .aspx pages.
To me, even though we are an e-commerce site that receives 1 million hits a month, please, I mean seriously adding stylesheets manually and bypassing just putting them all in a master page just boggles my mind. Am I the only one who thinks this is nuts and questions his assumption that doing that would make page weight excessive because they would be inheriting all stylesheets, some which would be wasted .css because it doesn't necessarily relate to that particular page? For me, adding them to the master page is a done deal...simple, maintainable, and who cares.
I can understand page weight with JS, but .css?

What you actually want to do is minimize the number of total http requests made, as well as the total bandwidth used. If you have your reviews.css file linked from the master page, you need an extra http request for every page on your site, whether not you ever use the styles. So in that sense, your boss is right. Don't put this on the master page if it won't be used from all or most of your pages.
That said, I wouldn't have anything in the code-behind for this either, but rather a user control you can put on the required pages to render a style section or link.

I actually do do the compromise fairly often.
Most pages in my sites use all the standard css files declared in the masterpage but there's the odd one page here and there that use a special stylesheet. A high resolution view, a print view or maybe a jquery lighbox/gallery type control.
My ad-hoc stylesheet loading code is as follows:
using System;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
namespace ExtensionMethods
{
public static class PageExtensions
{
public static void AddStyleSheet(this Page page, String Href)
{
PageExtensions.AddStyleSheet(page, Href, "all");
}
public static void AddStyleSheet(this Page page, String Href, String Media)
{
Href = VirtualPathUtility.ToAppRelative(Href);
if (!PageExtensions.StyleSheetAlreadyExists(page, Href))
{
HtmlLink htmlLink = new HtmlLink();
htmlLink.Href = Href;
htmlLink.Attributes.Add("rel", "stylesheet");
htmlLink.Attributes.Add("type", "text/css");
htmlLink.Attributes.Add("media", Media);
page.Header.Controls.Add(htmlLink);
}
}
static private bool StyleSheetAlreadyExists(Page page, String Href)
{
var preExisting = from Control c in page.Header.Controls
where c is HtmlLink && ((HtmlLink)c).Attributes["Href"] == Href
select c;
return preExisting.Any();
}
}
}

Remember, by default CSS files are being CACHED in the browser, so CSS will be loaded only ONCE anyway.
From my experience more seperate CSS files == more duplicated rules == bigger mess.
If you want to save up bit of bandwidth make sure you compress your CSS when writing it, so instead of:
#review
{
margin-top:10px;
margin-left:12px;
margin-bottom:12px;
margin-right:35px;
background-color:#ffffff;
background-image:url(images/blah.png);
background-repeat: repeat-x;
}
You write:
#review{margin:10px 35px 12px 12px;background:#fff url(images/blah.png) no-repeat;}

How about suggesting a compromise?
One master page with the most-commonly used elements defined (headers, links, navigation lists, etc) and then a location-specific stylesheet, ie catalogue.css, for such pages as need those styles. So the css is still able to be cached, but isn't forced on everyone that just visits the about-us page.
It's not ideal, but if the goal is to reduce the bandwidth of a single monolithic css file, it might get you some headway.
As it is, my personal approach is always to use the monolithic stylesheet; though having thought about it I'm tempted to try the suggested approach. It should work? Maybe... =/

Related

Blazor pages: how to use BuildRenderTree to add dynamic content

Background: We run dozens of sites for clients; all use an identical code base (ASP.NET WebForms) but completely different designs. Page structure is generated programmatically from SQL Server meta-data, using controls such as Panel, which are added to the ASP.NET page's Controls collection, and become DIVs in the rendered HTML.
Objective: We want to migrate eventually to ASP.NET CORE. However, there seems to be no equivalent to the page's controls collection. The closest thing I can find is the RenderTreeBuilder to add a Blazor component.
Question: Is it possible use BuildRenderTree to add a component which contains our home-baked HTML (for instance, to contain everything between <body> and </body>?
I've read articles such as:
https://chrissainty.com/building-components-via-rendertreebuilder/
https://learn.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#manual-rendertreebuilder-logic
... and experimented with adding HTML elements, but it's extremely cumbersome, and I'd like to programmatically generate the HTML for pretty much the whole page, and add it as one RenderFragment (is that the right term?).
Is this possible? Is there an alternative?
Edit:
#Henk's answer, using the MarkupString struct, and mine, using RenderTreeBuilder.AddMarkupContent seem similar in terms of effort and plumbing required.
Are there any pros and cons of the two approaches I should consider?
If you just want HTML (plain, dead) then you don't need a rendertree:
<h1>My Page</h1>
#MyHtmlComposer()
#code{
private MarkupString MyHtmlComposer()
{
string html = "<p>Hello</p>";
return new MarkupString(html);
}
}
I hadn't come across the MarkupString struct, so #Henk's answer is really helpful. I've now also come across the RenderTreeBuilder.AddMarkupContent method, so I'll offer this as an alternate answer:
My markup:
#page "/"
<PageBuilder></PageBuilder>
PageBuilder is a class that inherits from ComponentBase:
public class PageBuilder : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder b)
{
base.BuildRenderTree(b);
b.OpenElement(0, "div");
b.AddMarkupContent(1, TheContent());
b.CloseElement();
}
public string TheContent()
{
return "<div>This is the generated content</div>";
}
I'll edit my original question a little, as I'd like to know whether there's anything to choose between this approach and #Henk's.
You can also render a string as HTML like this:
#page "/test";
<h1>My Page</h1>
#((MarkupString)content)
#code{
//Get content from Database
string content = "<p>Hello</p>";
}
See section "Raw HTML" here

Kentico NodeName as a body class?

I'm trying to identify the home page, and then sub-pages on nodes. Ideally as a class on the Body so i can make style changes based on which section the user is in.
I have two .master pages, Global and Site. The body tag is in Global, and Site is a child master.
As this is an English/French site, I'm hoping using the Node Name would be the easiest approach for me.
If your home page and internal pages are of different Page Type then you can benefit from the macro below. We use something like this to give Page type specific id on the body tag and is usually helpful
<body class="{% CurrentDocument.NodeClass.ClassName.ToString() #%}">
However, it can be tweaked to suit your needs.
I'd recommend you to implement one generic stylesheet, shared across all the pages, and a couple of section specific stylesheets.
I'm not 100% clear about what you are describing, but you could use NodeLevel direct as / is NodeLevel = 0, and everything else will be Level 1+
Here's what i've come up in the .cs of my global .Master. So far it seem to be giving me enough to work with. From here i can use js and css to target what i need.
string aliasPath = CMS.DocumentEngine.DocumentContext.OriginalAliasPath.ToLower().TrimStart('/');
if (aliasPath == "")
{
this.BodyClass += " home";
}
else
{
this.BodyClass += " " + aliasPath.Replace("/","_");
}
The simplest approach, if you are willing to add either a data attribute or an id attribute to the body tag, is to add something like this to the master page, in the text box of the body tag:
id="{%nodealias%}"
Since the node alias will typically be unique unless you have multiple pages, at different paths, with the same name, ID should work fine. I prefer to use "nodealias" as opposed to the friendly name because it ensures there will not be any special characters, and will replace white space etc with a dash "-". The above macro will add the node alias as an id attribute to the body tag. Here's a screenshot:
Alternative approach explained here: https://devnet.kentico.com/forums/f49/t43559/body-class which involves setting the body class using a combination of macros and code behind, or via a custom web part. Basically you can modify it using CMS.CMSHelper.CMSContext.CurrentBodyClass (this was written in 2014 so the syntax will be slightly different)
-Edit:
Adding to some of the other answers, if you'd rather add a stylesheet on a template or page level, you can do so. To add a stylesheet reference on every instance of a template, edit the template header properties:
If you want to include the stylesheet on any particular page, this can be accomplished by editing the General tab on the page level, but this will remove the main stylesheet you are using (depending on how you are including it in the page):
In the body section in the Master page you can start with
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if(CurrentDocument.DocumentName== "Site")
CMS.DocumentEngine.DocumentContext.CurrentBodyClass += "body-site";
else
CMS.DocumentEngine.DocumentContext.CurrentBodyClass += "body-global";
}
</script>

update CSS class dynamically for a whole website

I have a reference site for a series of books, and I'd like to make it so that readers can avoid spoilers. The idea I had was to make a setup webpage where they click on a checkbox for each book from which they want to see info. I could then store that (somehow) as a cookie for each time that they visit the site, plus have it work for each page in the site. So, one page might have this:
<li class="teotw">Rand killed a Trolloc</li>
and another page might have
<li class="teotw">Nynaeve tugged her braid</li>
and that information would not show up on the page unless they had checked the box for the "teotw" book. My initial thoughts are to do something like toggling the CSS class value like this:
document.styleSheets[0]['cssRules'][0].class['teotw'] = 'display:none';
document.styleSheets[0]['cssRules'][0].class['teotw'] = 'display:inherit';
but I'm not sure if this is the best method. Firstly, it would only apply to the current document only so I'd need a way to re-apply it to each page they visit. I'm using YUI as well, if it matters.
Any ideas on the best way of doing this?
There are many ways to implement it. You can use the YUI Stylesheet module (read its User Guide for how to use it) which will do what you're considering with cross-browser support and it's much easier to use than using the DOM directly.
Another way would be to add classes to the body of the page. You can define styles like this:
.teotw {
display: none;
}
.teotw-visible .teotw {
display: block;
}
With the following JS code:
if (someCondition) {
// show the hidden content
Y.one('body').addClass('teotw-visible');
}
For the persistance of the visible state you can use cookies with the YUI Cookie utilty or local storage with CacheOffline. Code using cookies would look something like this:
var body = Y.one('body');
if (Y.Cookie.get('teotwVisible')) {
body.addClass('teotw-visible');
}
// change the cookie
Y.one('#teotw-toggle').on('click', function (e) {
var checked = this.get('checked');
Y.Cookie.set('teotwVisible', checked);
body.toggleClass('teotw-visible', checked);
});
You should probably store the different sections in a JS object and avoid hard-coding class names in every JS line. Or maybe use a convention between checkboxes IDs and section class names.

Set background-url css property to a memory image in .net

Please read about my following situation. So we have a web application in .net. Because some customers wanted to pay for some custom layout, we first started to use themes. It was ok until there were more than 10. Because after that it was difficult to maintain and more of them wanted custom design, I had to design a solution so that the user could change only a few css attributes through a front-end page.
So when a user clicked a button a folder with its name and another css file was generated and he could change the attributes and save the new file. This file was dynamically loaded on the Page_Load on some basebage. This was ok, until more and more users payed for this and now we have tens of folders and expect hundreds. There is also the problem of rights, by default IIS doesn't have writing right to the application folder. Also the deployment process builds a new version by deleting the whole app folder and recreating one so we had to do a workaround and make a copy of this mega folder that holds the users css. And anyway, I don't like the idea of having hundreds of files not under source control.
And now, I have a new requierement. The users to be allowed to upload a custom image to replace one of our own. So i said it is time to move evrything to the database. For basic attributes I solved it.
I have a "mutant" aspx page:
<%# Page Language="C#" ContentType="text/css" %>
.labelText
{
color:<%= WebApplication1.UserProfile.LabelColor%>
}
So the UserProfile class gets it's data from a table.
And then in the page_load of the basepage
HtmlLink link = new HtmlLink();
link.Attributes.Add("type", "text/css");
link.Attributes.Add("rel", "stylesheet");
link.Attributes.Add("href", "~/Styles/Override.aspx");
this.Header.Controls.Add(link);
It works ok, the problem the images are used like this:
.divHeader
{
background: 15px 0 url("logo.gif")
}
That logo.gig will be uploaded by the user,but I would like to be saved in the database. However when reading it as a stream I can't do something like:
background: 15px 0 url(WebApplication1.UserProfile.Logo)
Is there any otehr way I can specify an object in a css class?If it isn't what is the best approach? I was thinking about keeping the image in the db and also as a file, something similar to what we have now.
I realize that if it were for an image control it would have been a lot easier, but this is the design now, and remaking it is a little bit troublesome
Thanks for reading this novel and for any oppinions on this.
I hope you're saving the mime-type value of every image that you save to database (mime type is reported by the request during upload). If you don't, you'll have to look up what a mime type would be based on image-file extension or its content format. You'll need this value when images are requested (from within CSS files).
Implement an HTTP handler (.ashx) specifically for these images (new file > generic handler > fill up ProcessRequest).
Then, replace url(logo.gif) in your css files with something like this:
url(/GetImage.ashx?user=1234&image=logo);
Obviously, you can have the filename and query to be whatever you like/need. Maybe you have user info in session, so you don't have to include user id there. Maybe you'll want to implement this handler only for logo images, and if you have user info in session, you can simply say (css):
url(GetLogo.ashx);
Then, within that ashx handler, write code to get the image from the database (or where ever it is), and stream it to the browser. Something like this:
byte[] imageData = ... // get this from db
MemoryStream ms = new MemoryStream(imageData); // System.IO namespace
Response.ContentType = "image/png"; // remember the mime-type?
ms.WriteTo(context.Response.OutputStream); // context is current HttpContext
Alternatively, you can inject the whole image into the CSS, by encoding it to base-64 format. Note that the size of css file will grow for the size of the image, and you're practically combining multiple downloads into 1, which may be a good or a bad thing.
Example below (google's logo); paste this in CSS (notice the size of that thing).
Besides CSS, the data in this example (data:image/png;base64,iVBORw0KGg ...) can be used as URL with IMG tags, or manipulated by JS.
.somediv
{
width:275px;
height:95px;
background-image: url(data:image/png;base64,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);
}
+1 if you like this :)

How to inject a CSS file from an ASP.NET MVC partial view (server-side)?

I have a partial view (.ascx) that should include its own CSS file as it's used in multiple other views. How do I inject a stylesheet in the page server-side, i.e. without using JavaScript?
Dario - due to using this for partialviews, you're going to always have the problem that the <head> section of the document is already in place and therefore can't be modified. If you want to remain WC3 compliant, then you'll have to put any further css into the head section via javascript. This may or may not be desirable (if you've got to cater for downsteam browsers with javascript turned off).
the main problem that you may be aluding to is the fact that you can't put <asp:contentplaceholders> into your partials. this is a pain (tho understandable as the masterpage ref would tie the partial too closely to a particular master page).
To this end, I've created a little helper method that does the basic grunt work to put the css file into the head section automatically.
edit - (as per Omu's js suggestion) this is a nice little halfway house:
// standard method - renders as defined in as(cp)x file
public static string Css(this HtmlHelper html, string path)
{
return html.Css(path, false);
}
// override - to allow javascript to put css in head
public static string 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 "";
// 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 sb.ToString();
}
usage:
<%=Html.Css("~/Content/yourstyle.Css")%>
or:
<%=Html.Css("~/Content/yourstyle.Css", true)%> // or false if you want!!
worth a back-pocket approach if all else fails. it may also be possible to adapt the logic above to hit an actionfilter and add the css to the reponse headers etc.., rather than outputting the js string.
Is there a reason you cannot just put the stylesheet in the main view page? Unless it is extremely large, it should not cause your page to load significantly slower when not being used, especially after compression.
Furthermore, if the partial view is being retrieved by ajax, you will end up re-downloading the css file multiple times (depending on the browser's caching).
I'm not an ASP.NET or MVC expert, but I am currently working on a project with it. Would it be possible to include something like this in the head of your master page?
<asp:ContentPlaceHolder ID="HeadContent" runat="server"></asp:ContentPlaceHolder>
Then somehow get your CSS into the content placeholder?
<asp:Content ID="styleSheetHolder" ContentPlaceHolderID="HeadContent" runat="server">
<link rel='stylesheet' href='yourstyle.css' type='text/css' />
</asp:Content>
It looks like you'd have to do this from the calling page, which would introduce duplication of code. I'm not sure how to get around that. Maybe it could it be done with an HTML helper.
A place I worked had a site with a lot of conditional partial views, which could and would change from time to time as new things were added to the application. The best way we found was to use naming conventions - naming the partial view, stylesheet and script the same (except extension, of course).
We added one or more stylesheets, scripts and partial views to the page's view model. We then used a custom HTML helper to loop through the relevant array in each content section and insert the required content. The pain was having to manage three or more files for each component, but at least we were able to reduce the amount of maintenance in the hosting pages.
Never did resolve the problem of putting a partial view inside a partial view, though.
Why not put the link tag in your partial view:
<link rel="stylesheet" href="yourstyle.css" type="text/css" />

Resources