Implementing a custom CSS stylesheet manager and taking advantage of caching

Hi,

this post will provide brief overview why and how I have implemented "basic" custom stylesheet manager. Note that even post will be related to solving Telerik related issue it's not binded to it, this approach could be used in your solutions that don't use Telerik at all. Sources are attached.

Background:

I'm developing a ASP.NET 4.0 WebForms application which is fully ajaxified and uses Telerik controls, one of issues I ran into at first was that when you load certain Telerik control dynamically it might have missing styles or images. Digging Telerik forums hinted that I should preload CSS for controls I load dynamically, one of approaches was direct call of GetWebResourceUrl method, well, I also wanted to opmitize things, so I utilized Telerik's RadStyleSheetManager which basically can combine and compress all your CSS in one file which is a good thing. That was what I wanted: I globally (in web.config) disabled loading of skins and base stylesheets for Telerik controls, because I was preloading those myself in RadStyleSheetManager. Everything was charming unless I wanted to use LinkManager, which is an additional built-in dialog for managing URLs for RadEditor: when loaded it was totally unstyled. Reason that dialog wasn't loading style was that I turned off loading of skins and css for all Telerik controls. Though RadEditor has property called DialogsCssFile I wasn't happy with that approach. What it does is you specify custom css file into that property and that css file would have set of css @import directives to preload required css files for that dialog. Basically that was saying: buddy, you wanted to optimize preloading of styles using RadStyleSheetManager, but in that case you will have to load styles "old way" (separate <link/> to every file), more over that means that I will need to copy those css and used images (for Telerik controls utilized inside LinkManager) from Telerik's installation folder to my WebApp. That was a last drop.

I have to mention here that I also wanted to customize LinkManager, so I followed example from their SDK on how to customize built-in dialogs.

I verified that indeed setting DialogsCssFile for RadEditor object results into rendering single <link/> tag in header of iframe container, so I thought that I could benefit from that.

Note: you can't use RadStyleSheetManager inside LinkManager.ascx, it will throw an exception saying that RadStyleManager is already defined (in my case in my master page)

Solution: 

I decided to get rid of RadStyleSheetManager. My use case was simple: I needed to preload css for all controls I use + my own css for WebApp itself, also I wanted to use preloaded set of css for external dialogs. I've checked web and found SquishIt library, which was able to compress everything and render as a single file with hash in it's name, also it could utilize embedded resources. I gave it a try and soon figured out that it's not what I wanted: I had to give explicit permissions for Network Service account to be able to write into my css folder (because SquishIt wants to create a file) and currently it doesnt support WebResource attribute's PerformSubstitution property, meaning that resource references defined inside Telerik styles won't work.

So I've implemented custom css manager. It's very draft, simple, but suits my needs. What it does is when initialized it caches all the referenced css files (e.g.: in css folder and embedded), performs proper processing of PerformSubstitution property and compresses result using AjaxMin. Next in WebApp you call  a static method which generates a link to custom handler with calculated hash. This handler serves css content and get cached on client and server. Hash changes when you modify css content.

Concept behind are groups of css references. You can create a group called telerikDialogs and reference there only those css that are used by Telerik dialogs, same way you can create a group named telerikDialogs and reference there all styles for all controls that are used in your WebApp, same for main styles. You can also copy styles from one group to another. Main point is: do that once, even though it won't let you do it twice :) All initialization should happen once, yes, might not be handy in certain scenarios, but I think currently it fits perfectly most of use cases (of course feel free to adjust).

Example of how you reference:

CssReferenceManager.SetActiveGroupName("default");

CssReferenceManager.AddReferencesFromGivenGroupToActive("telerikDialogs");
CssReferenceManager.AddEmbeddedReference("Telerik.Web.UI.RadToolBar, Telerik.Web.UI", "Telerik.Web.UI.Skins.ToolBar.css");
CssReferenceManager.AddEmbeddedReference("Telerik.Web.UI.RadToolBar, Telerik.Web.UI", "Telerik.Web.UI.Skins.Default.ToolBar.Default.css");
CssReferenceManager.AddEmbeddedReference("Telerik.Web.UI.RadButton, Telerik.Web.UI", "Telerik.Web.UI.Skins.Button.css");

......

..........

first you set a name for a group, then you add references, if you need another group (provide different name and add references. To perform actual processing call:

CssReferenceManager.PerformOneTimeProcessOfReferences();
that would do that job and cache. Next in your master page or where you reference to css files put this:

<%= CssReferenceManager.GetHandlerUrlWithLinkTag("default") %>

this line will result in <link/> tag with reference to handler (which should be registered in web.config).

If you will get exception saying that controls collection can't be modified due <% %> tags wrap this call into RadScriptBlock

<telerik:RadScriptBlock ID="rsb1" runat="server">
        <%= CssReferenceManager.GetHandlerUrlWithLinkTag("default") %>
</telerik:RadScriptBlock>

Please refer attached project with sources of StaticStyleSheetManager and test WebApp.

Download StaticStyleSheetManager.zip

No Comments