CSS Sprite for ASP.NET

CSS sprites are becoming popular as a way to increase application performance by eliminating HTTP requests by the client to the web server. It also serves as a path for better cache management. I will need to go through a bit of background before we start (sorry... we'll get to the generation in a second!).

 

What is a CSS Sprite?

A CSS sprite is a fancy name for an image that is composed other images. In your CSS you have this one base image, but you pick the image inside of the image. It does what you expect, to just show that one single image. The following is an example of a CSS sprite:

 

sprite

 

In your CSS file, you would pick this base image as your background-image and then to pick a particular image, you could need to specify the height, width, and also the position of where the image starts... background-position.

Long story short, it cuts down on the number of images that need to be passed from your server to the client that is visiting the site. This adds up over time, especially is you have A LOT of images.

A lot of people go to a sprite generator online. Those are nice, but what happens when you need to change an image? You have to go back and re-upload all your images and recalculate your background-positions. That's not a good thing if you are following DRY (don't repeat yourself). Plus it's hard work NO ONE should have to do.

 

How I handle output caching

What I do is this: all my CSS files, JavaScript files, etc have a common ID which is basically what I call the "Distro ID" which is generated at the beginning of my ASP.NET applications so every time you do a publish, the number will change and your files will also change to their updated content. The heavy lifting is all done in the Global.asax file.

   1: //C#
   2: public class Global : System.Web.HttpApplication {
   3:     public static string DistributionNumber { get; set; }
   4:  
   5:     protected void Application_Start(object sender, EventArgs e){
   6:         SetDistro();
   7:         ..
   8:     }
   9:     private void SetDistro() {
  10:         DistributionNumber = DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString();
  11:     }
  12: }
   1: 'VB
   2: Inherits System.Web.HttpApplication
   3:  
   4: Private m_distronum as String = String.Empty
   5: Public Shared Property DistributionNumber() As String
   6:     Get
   7:         Return m_distronum
   8:     End Get
   9:     Set(ByVal value as String)
  10:         m_distronum = value
  11:     End Set
  12: End Property
  13:  
  14: Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
  15:     SetDistro()
  16:     ..
  17: End Sub
  18: Private Sub SetDistro()
  19:     DistributionNumber = DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString()
  20: End Sub

 

Now since I am mostly using ASP.NET MVC, I have a controller for the Distribution. So my URLs look like this: [BASE_URL]/20080303010101/CSS. I ago ahead and create separate Routes for this, but the default would also work. The ID that is passed to the CSS, JavaScript, Sprite is the distro number. To do the output caching, you need a separate ID so that the browser can differentiate one distribution's CSS, JS, etc from another. This way, we can client side cache with no worries.

If you were using Web Forms, I would suggest using separate Generic handlers (one for CSS, JS, and the Sprite). They would accomplish the same thing. Just remember that you need to take in an ID as a Query String for the Client-side cache to be safe for your users.

If the ID is not passed to the controller OR the generic handler (whichever you are using), don't client-side cache. But this is something you will want to offer as it, too, saves you in the performance department.

 

Back to the Sprite generator... get me some images!

You have 2 options when it comes to getting images. You have the option of enumerating through a folder(s) or putting the URLs in by hand. I suggest enumeration if you have the same file type. If you put in the file names by hand, that can get tricky... which I'm sure you can tell why. In my Sprite generator, I process the CSS and replace the images with the sprite reference... so we don't need the URL of the original images anyway's. All that we need to reference the images is the name (lowercase).

 

As I showed you in my last post about CSS Minification (which we will refer back to), I have a file enumerator method:

   1: //C#
   2: public static IList<System.IO.FileInfo> GetFiles(string serverPath, string extention)
   3: {
   4:   if (!serverPath.StartsWith("~/"))
   5:   {   
   6:     if (serverPath.StartsWith("/"))   
   7:       serverPath = "~" + serverPath;
   8:     else
   9:       serverPath = "~/" + serverPath;
  10:   }
  11:   string path = HttpContext.Current.Server.MapPath(serverPath);
  12:   
  13:   if (!path.EndsWith("/"))
  14:     path = path + "/";
  15:   
  16:   if (!Directory.Exists(path))
  17:     throw new System.IO.DirectoryNotFoundException();
  18:   
  19:   IList<FileInfo> files = new List<FileInfo>();
  20:   
  21:   string[] fileNames = Directory.GetFiles(path, "*." + extention, System.IO.SearchOption.AllDirectories);
  22:   
  23:   foreach (string name in fileNames)
  24:     files.Add(new FileInfo(name));
  25:   
  26:   return files;
  27: }
   1: 'VB
   2: Public Shared Function GetFiles(ByVal serverPath As String, ByVal extention As String) As IList(Of System.IO.FileInfo)
   3:   If Not serverPath.StartsWith("~/") Then
   4:     If serverPath.StartsWith("/") Then
   5:       serverPath = "~" + serverPath
   6:     Else
   7:       serverPath = "~/" + serverPath
   8:     End If
   9:   End If
  10:   Dim path As String = HttpContext.Current.Server.MapPath(serverPath)
  11:  
  12:   If Not path.EndsWith("/") Then
  13:     path = path + "/"
  14:   End If
  15:  
  16:   If Not Directory.Exists(path) Then
  17:     Throw New System.IO.DirectoryNotFoundException()
  18:   End If
  19:  
  20:   Dim files As IList(Of FileInfo) = New List(Of FileInfo)()
  21:  
  22:   Dim fileNames As String() = Directory.GetFiles(path, "*." + extention, System.IO.SearchOption.AllDirectories)
  23:  
  24:   For Each name As String In fileNames
  25:     files.Add(New FileInfo(name))
  26:   Next
  27:  
  28:   Return files
  29: End Function

 

If you were going the hand-coded image URLs, you would just need to have a method that return a List of FileInfo, just like the method from above.

 

Get the SPRITE already!

We need to specify a few data models so that they can contain the individual images and the sprites. They aren't anything special... just basically containers for the bitmap object (SpriteImage) and a Collection of the images for the Sprite generation (SpriteImageCollection). The collection is actually the generator of the sprite. The GetSprite() method is the method that will do the image concatenating for you.

   1: //C#
   2: public class SiteImage
   3: {
   4:   public SiteImage(FileInfo fileinfo)
   5:   {
   6:     Stream stream = new FileStream(fileinfo.FullName, FileMode.Open, FileAccess.Read);
   7:     Img = (Bitmap)Bitmap.FromStream(stream);
   8:     File = fileinfo;
   9:   }
  10:  
  11:   public int Width {
  12:     get { return Img.Width; }
  13:   }
  14:   public int Height {
  15:     get { return Img.Height; }
  16:   }
  17:   public int YValue { get; set; }
  18:   public Bitmap Img  { get; set; }
  19:   public FileInfo File { get; set; }
  20: }
  21:  
  22: public class SiteImageCollection : List<SiteImage>
  23: {
  24:     public int MaxWidth 
  25:     {
  26:         get 
  27:         {
  28:             int largest = 0;
  29:  
  30:             foreach (SiteImage s in this)
  31:                 if (s.Width > largest)
  32:                     largest = s.Width;
  33:  
  34:             return largest;
  35:         } 
  36:     }
  37:  
  38:     public int TotalHeight 
  39:     {
  40:         get 
  41:         {
  42:             int ttl = 0;
  43:             foreach (SiteImage s in this)
  44:                 ttl += s.Height;
  45:             return ttl;
  46:         }
  47:     }
  48:  
  49:     public Bitmap GetSprite() 
  50:     {
  51:         Bitmap spriteImg = new Bitmap(MaxWidth, TotalHeight);
  52:  
  53:         Graphics sprite = Graphics.FromImage(spriteImg);
  54:  
  55:         int curY = 0;
  56:         foreach (SiteImage si in this)
  57:         {
  58:             sprite.DrawImage(si.Img, 0, curY);
  59:             si.YValue = curY;
  60:             curY += si.Height;
  61:         }
  62:  
  63:         return spriteImg;
  64:     }
  65: }
   1: 'VB
   2: Public Class SiteImage
   3:     Public Sub New(ByVal fileinfo As FileInfo)
   4:         Dim stream As Stream = New FileStream(fileinfo.FullName, FileMode.Open, FileAccess.Read)
   5:         Img = DirectCast(Bitmap.FromStream(stream), Bitmap)
   6:         File = fileinfo
   7:     End Sub
   8:  
   9:     Public ReadOnly Property Width() As Integer
  10:         Get
  11:           Return Img.Width
  12:         End Get
  13:     End Property
  14:  
  15:     Public ReadOnly Property Height() As Integer
  16:     Get
  17:       Return Img.Height
  18:     End Get
  19:     End Property
  20:   
  21:     Private m_yval As Integer
  22:     Public Property YValue() As Integer
  23:     Get
  24:         Return m_yval
  25:     End Get
  26:     Set(ByVal value As Integer)
  27:         m_yval = value
  28:     End Set
  29:     End Property
  30:  
  31:     Private m_Img As Bitmap
  32:     Public Property Img() As Bitmap
  33:         Get
  34:             Return m_Img
  35:         End Get
  36:         Set(ByVal value As Bitmap)
  37:             m_Img = value
  38:         End Set
  39:     End Property
  40:   
  41:     Private m_file As FileInfo
  42:     Public Property File() As FileInfo
  43:         Get
  44:             Return m_file
  45:         End Get
  46:         Set(ByVal value As FileInfo)
  47:             m_file = value
  48:         End Set
  49:     End Property
  50: End Class
  51:  
  52: Public Class SiteImageCollection
  53:   Inherits List(Of SiteImage)
  54:   Public ReadOnly Property MaxWidth() As Integer
  55:     Get
  56:       Dim largest As Integer = 0
  57:  
  58:       For Each s As SiteImage In Me
  59:         If s.Width > largest Then
  60:           largest = s.Width
  61:         End If
  62:       Next
  63:  
  64:       Return largest
  65:     End Get
  66:   End Property
  67:  
  68:   Public ReadOnly Property TotalHeight() As Integer
  69:     Get
  70:       Dim ttl As Integer = 0
  71:       For Each s As SiteImage In Me
  72:         ttl += s.Height
  73:       Next
  74:       Return ttl
  75:     End Get
  76:   End Property
  77:  
  78:   Public Function GetSprite() As Bitmap
  79:     Dim spriteImg As New Bitmap(MaxWidth, TotalHeight)
  80:  
  81:     Dim sprite As Graphics = Graphics.FromImage(spriteImg)
  82:  
  83:     Dim curY As Integer = 0
  84:     For Each si As SiteImage In Me
  85:       sprite.DrawImage(si.Img, 0, curY)
  86:       si.YValue = curY
  87:       curY += si.Height
  88:     Next
  89:  
  90:     Return spriteImg
  91:   End Function
  92: End Class

 

There you have it. A nice sprite generated from existing images. The cool thing is that if you are working with PNGs, your transparencies are preserved, which is what I really needed. The only real down side to this is that all your images are lined up vertically. I'm not quite sure that this matters, but most other generators have the ability to somehow line the images up vertically and horizontally.

But you're not exactly done...

 

You still have the CSS to deal with...

CSS sprites don't have CSS in the name for nothing. They are best used in a CSS files. Lucky for me, I already have a process for my CSS files to go through (which includes minification, cache, etc). All I need to do is augment it to replace the image reference to the actual URL of the sprite and to calculate the background-position.

 

Logic Class

I really like having Logic classes (which you have seen in my past blog posts) because they are a pivotal point between the server-side cache mechanisms that I put into place. Here's what my Logic controller looks like:

   1: //C#
   2: public class CssSprite
   3: {
   4:     public static SiteImageCollection GetImagesForSprite() 
   5:     {
   6:         SiteImageCollection col = Helpers.UrlMapping.UrlHandler.Images.AllImages();
   7:         //we need to grab the sprite so that the Y-values are usable
   8:         Bitmap sprite = col.GetSprite();
   9:         return col;
  10:     }
  11:  
  12:     public static Bitmap GetCachedSprite() 
  13:     { 
  14:         ICache<SiteImageCollection> cache = new Helpers.Cache.CssSpritCache();
  15:  
  16:         return cache.Get().GetSprite();
  17:     }
  18:  
  19:     public static string CleanCSS(string currentCSS) 
  20:     {
  21:         string newCSS = currentCSS;
  22:         foreach(SiteImage si in CssSprite.GetImagesForSprite())
  23:         {
  24:  
  25:             string orig = "background-image: url(" + si.File.Name.ToLower() + ");";
  26:  
  27:             newCSS = newCSS.Replace(orig + "/**/", ReplacementCss(si, false));
  28:             newCSS = newCSS.Replace(orig, ReplacementCss(si, true));
  29:         }
  30:         return newCSS;
  31:     }
  32:  
  33:     private static string ReplacementCss(SiteImage si, bool widthheight) 
  34:     {
  35:         string rep = "background-image: url(" + Helpers.UrlMapping.UrlHandler.Images.Sprite().ToAbsoluteURL() + ");";
  36:         rep += "background-position: 0px -" + si.YValue + "px;";
  37:         if (widthheight)
  38:         {
  39:             rep += "width: " + si.Width + ";";
  40:             rep += "height: " + si.Height + ";";
  41:         }
  42:         return rep;
  43:     }
  44: }
   1: 'VB
   2: Public Class CssSprite
   3:   Public Shared Function GetImagesForSprite() As SiteImageCollection
   4:     Dim col As SiteImageCollection = Helpers.UrlMapping.UrlHandler.Images.AllImages()
   5:     'we need to grab the sprite so that the Y-values are usable
   6:     Dim sprite As Bitmap = col.GetSprite()
   7:     Return col
   8:   End Function
   9:  
  10:   Public Shared Function GetCachedSprite() As Bitmap
  11:     Dim cache As ICache(Of SiteImageCollection) = New Helpers.Cache.CssSpritCache()
  12:  
  13:     Return cache.[Get]().GetSprite()
  14:   End Function
  15:  
  16:   Public Shared Function CleanCSS(ByVal currentCSS As String) As String
  17:     Dim newCSS As String = currentCSS
  18:     For Each si As SiteImage In CssSprite.GetImagesForSprite()
  19:  
  20:       Dim orig As String = "background-image: url(" + si.File.Name.ToLower() + ");"
  21:  
  22:       newCSS = newCSS.Replace(orig + "/**/", ReplacementCss(si, False))
  23:       newCSS = newCSS.Replace(orig, ReplacementCss(si, True))
  24:     Next
  25:     Return newCSS
  26:   End Function
  27:  
  28:   Private Shared Function ReplacementCss(ByVal si As SiteImage, ByVal widthheight As Boolean) As String
  29:     Dim rep As String = "background-image: url(" + Helpers.UrlMapping.UrlHandler.Images.Sprite().ToAbsoluteURL() + ");"
  30:     rep &= "background-position: 0px -" + si.YValue + "px;"
  31:     If widthheight Then
  32:       rep &= "width: " + si.Width + ";"
  33:       rep &= "height: " + si.Height + ";"
  34:     End If
  35:     Return rep
  36:   End Function
  37: End Class

So what this does is replace the values where the background-image is equal to an image in the sprite's name. It then appends the style definition to add the background-position (which we found during the Sprite generation) and the height and width and changes the background-image URL. If you add a /**/ right before the background-image definition, the height and width are not added in.

 

The only thing that I didn't really show you was the AllImages() method (which returns a SiteImageCollection on the images I need sprited) and the Sprite() method in my UrlHandler. That's not that important... I'm sure you can figure out what those do :)

 

Cache mechanism

When we last talked about my CacheBase class, it was sort of all over the place. I have revamped it to use delegates so that we don't get any weird methods that don't make sense. It's still a little messy since I needed to write 2 classes, one that took 1 generic and another that took 2. Here it is:

 

   1: //C#
   2: public abstract class CacheBase<T> : ICache<T>
   3: {
   4:     public abstract Func<T> Method { get; }
   5:     public abstract string CacheKey { get; }
   6:     public abstract CacheItemPriority Priority { get; }
   7:     public abstract TimeSpan CacheDuration { get; }
   8:  
   9:     public T Get() { 
  10:         T CurValue = ((T)HttpContext.Current.Cache[CacheKey]);
  11:  
  12:         if (CurValue == null)
  13:             CurValue = Invoke();
  14:  
  15:         return CurValue;
  16:     }
  17:  
  18:     /// <summary>
  19:     /// Removes the Cache Object from the 
  20:     /// current cache.
  21:     /// </summary>
  22:     public void Delete(){
  23:         HttpContext.Current.Cache.Remove(CacheKey);
  24:     }
  25:  
  26:     private T Invoke(){
  27:         return Method.Invoke();
  28:     }
  29:  
  30:     /// <summary>
  31:     /// Adds the value into the Cache
  32:     /// </summary>
  33:     /// <param name="Value">Value of T</param>
  34:     internal T Insert(T Value)
  35:     {
  36:         HttpContext.Current.Cache.Add(CacheKey, Value, null, DateTime.Now.Add(CacheDuration), TimeSpan.Zero, Priority, null);
  37:         return Value;
  38:     }
  39: }
  40:  
  41: public abstract class CacheBase<T, P1> : ICache<T>
  42: {
  43:     public abstract Func<P1, T> Method { get; }
  44:     public abstract P1 ObjectDescripter { get; }
  45:     public abstract string CacheKey { get; }
  46:     public abstract CacheItemPriority Priority { get; }
  47:     public abstract TimeSpan CacheDuration { get; }
  48:  
  49:     public T Get(){
  50:         T CurValue = ((T)HttpContext.Current.Cache[CacheKey]);
  51:  
  52:         if (CurValue == null)
  53:             CurValue = Invoke();
  54:  
  55:         return CurValue;
  56:     }
  57:  
  58:     /// <summary>
  59:     /// Removes the Cache Object from the 
  60:     /// current cache.
  61:     /// </summary>
  62:     public void Delete(){
  63:         HttpContext.Current.Cache.Remove(CacheKey);
  64:     }
  65:  
  66:     private T Invoke(){
  67:         return Method.Invoke(ObjectDescripter);
  68:     }
  69:  
  70:     /// <summary>
  71:     /// Adds the value into the Cache
  72:     /// </summary>
  73:     /// <param name="Value">Value of T</param>
  74:     internal T Insert(T Value){
  75:         HttpContext.Current.Cache.Add(CacheKey, Value, null, DateTime.Now.Add(CacheDuration), TimeSpan.Zero, Priority, null);
  76:         return Value;
  77:     }
  78:  
  79: }
   1: 'VB
   2: Public MustInherit Class CacheBase(Of T)
   3:   Implements ICache(Of T)
   4:   Public MustOverride ReadOnly Property Method() As Func(Of T)
   5:   Public MustOverride ReadOnly Property CacheKey() As String
   6:   Public MustOverride ReadOnly Property Priority() As CacheItemPriority
   7:   Public MustOverride ReadOnly Property CacheDuration() As TimeSpan
   8:  
   9:   Public Function [Get]() As T
  10:     Dim CurValue As T = (DirectCast(HttpContext.Current.Cache(CacheKey), T))
  11:  
  12:     If CurValue Is Nothing Then
  13:       CurValue = Invoke()
  14:     End If
  15:  
  16:     Return CurValue
  17:   End Function
  18:  
  19:   ''' <summary>
  20:   ''' Removes the Cache Object from the 
  21:   ''' current cache.
  22:   ''' </summary>
  23:   Public Sub Delete()
  24:     HttpContext.Current.Cache.Remove(CacheKey)
  25:   End Sub
  26:  
  27:   Private Function Invoke() As T
  28:     Return Method.Invoke()
  29:   End Function
  30:  
  31:   ''' <summary>
  32:   ''' Adds the value into the Cache
  33:   ''' </summary>
  34:   ''' <param name="Value">Value of T</param>
  35:   Friend Function Insert(ByVal Value As T) As T
  36:     HttpContext.Current.Cache.Add(CacheKey, Value, Nothing, DateTime.Now.Add(CacheDuration), TimeSpan.Zero, Priority, _
  37:       Nothing)
  38:     Return Value
  39:   End Function
  40:  
  41: End Class
  42:  
  43: Public MustInherit Class CacheBase(Of T, P1)
  44:   Implements ICache(Of T)
  45:   Public MustOverride ReadOnly Property Method() As Func(Of P1, T)
  46:   Public MustOverride ReadOnly Property ObjectDescripter() As P1
  47:   Public MustOverride ReadOnly Property CacheKey() As String
  48:   Public MustOverride ReadOnly Property Priority() As CacheItemPriority
  49:   Public MustOverride ReadOnly Property CacheDuration() As TimeSpan
  50:  
  51:   Public Function [Get]() As T
  52:     Dim CurValue As T = (DirectCast(HttpContext.Current.Cache(CacheKey), T))
  53:  
  54:     If CurValue Is Nothing Then
  55:       CurValue = Invoke()
  56:     End If
  57:  
  58:     Return CurValue
  59:   End Function
  60:  
  61:   ''' <summary>
  62:   ''' Removes the Cache Object from the 
  63:   ''' current cache.
  64:   ''' </summary>
  65:   Public Sub Delete()
  66:     HttpContext.Current.Cache.Remove(CacheKey)
  67:   End Sub
  68:  
  69:   Private Function Invoke() As T
  70:     Return Method.Invoke(ObjectDescripter)
  71:   End Function
  72:  
  73:   ''' <summary>
  74:   ''' Adds the value into the Cache
  75:   ''' </summary>
  76:   ''' <param name="Value">Value of T</param>
  77:   Friend Function Insert(ByVal Value As T) As T
  78:     HttpContext.Current.Cache.Add(CacheKey, Value, Nothing, DateTime.Now.Add(CacheDuration), TimeSpan.Zero, Priority, _
  79:       Nothing)
  80:     Return Value
  81:   End Function
  82:  
  83: End Class

 

So the Sprite cache looks like this:

   1: //C#
   2: public class CssSpritCache : CacheBase<SiteImageCollection>
   3: {
   4:     public CssSpritCache() 
   5:     {
   6:     }
   7:  
   8:     public override Func<SiteImageCollection> Method
   9:     {
  10:         get { return new Func<SiteImageCollection>(Logic.CssSprite.GetImagesForSprite); }
  11:     }
  12:  
  13:     public override string CacheKey
  14:     {
  15:         get { return "CssSprite_" + Global.DistributionNumber; }
  16:     }
  17:  
  18:     public override System.Web.Caching.CacheItemPriority Priority
  19:     {
  20:         get { return System.Web.Caching.CacheItemPriority.Default; }
  21:     }
  22:  
  23:     public override TimeSpan CacheDuration
  24:     {
  25:         get { return new TimeSpan(1, 0, 0, 0); }
  26:     }
  27: }
   1: 'VB
   2: Public Class CssSpritCache
   3:   Inherits CacheBase(Of SiteImageCollection)
   4:   Public Sub New()
   5:   End Sub
   6:  
   7:   Public Overloads Overrides ReadOnly Property Method() As Func(Of SiteImageCollection)
   8:     Get
   9:       Return New Func(Of SiteImageCollection)(Logic.CssSprite.GetImagesForSprite)
  10:     End Get
  11:   End Property
  12:  
  13:   Public Overloads Overrides ReadOnly Property CacheKey() As String
  14:     Get
  15:       Return "CssSprite_" + [Global].DistributionNumber
  16:     End Get
  17:   End Property
  18:  
  19:   Public Overloads Overrides ReadOnly Property Priority() As System.Web.Caching.CacheItemPriority
  20:     Get
  21:       Return System.Web.Caching.CacheItemPriority.[Default]
  22:     End Get
  23:   End Property
  24:  
  25:   Public Overloads Overrides ReadOnly Property CacheDuration() As TimeSpan
  26:     Get
  27:       Return New TimeSpan(1, 0, 0, 0)
  28:     End Get
  29:   End Property
  30: End Class

 

Back in the CSS logic handler, you need to change the CombineCSS method to also do the replacement for the CSS Sprite.

   1: //C#
   2: public static string CombineCSS()
   3: {
   4:     string allCSS = string.Empty;
   5:  
   6:     foreach (FileInfo fi in Logic.Files.GetFiles("~/Content/CSS/", "css"))
   7:     {
   8:         using (StreamReader sr = new StreamReader(fi.FullName))
   9:             allCSS += sr.ReadToEnd();
  10:     }
  11:  
  12:     allCSS = CssSprite.CleanCSS(allCSS);
  13:  
  14:     allCSS = Compress(allCSS);
  15:  
  16:     return allCSS;
  17: }
   1: 'VB
   2: Public Shared Function CombineCSS() As String
   3:   Dim allCSS As String = String.Empty
   4:  
   5:   For Each fi As FileInfo In Logic.Files.GetFiles("~/Content/CSS/", "css")
   6:     Using sr As New StreamReader(fi.FullName)
   7:       allCSS += sr.ReadToEnd()
   8:     End Using
   9:   Next
  10:  
  11:   allCSS = CssSprite.CleanCSS(allCSS)
  12:  
  13:   allCSS = Compress(allCSS)
  14:  
  15:   Return allCSS
  16: End Function

 

Are you done?

You're sort of done. I've only given you the loose bits of the solution. You will have to display the CSS Sprite onto and image. I didn't show you my code for the actual CSS Sprite MVC Controller Action/Generic Handler. This is needed for you to use the CSS Sprite, after all.

   1: //C#
   2:  
   3: //--------MVC--------
   4: [ControllerAction]
   5: public void Sprite(string id)
   6: {
   7:     Response.ContentType = "image/png";
   8:  
   9:     Bitmap bmp = Logic.CssSprite.GetCachedSprite();
  10:  
  11:     MemoryStream stream = new MemoryStream();
  12:     bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
  13:  
  14:     Response.BinaryWrite(stream.ToArray());
  15:  
  16:     if(id != null)
  17:         Response.Cache.SetExpires(DateTime.Now.AddYears(3));
  18: }
  19:  
  20: //-------WebForms-----
  21: public void ProcessRequest(HttpContext context)
  22: {
  23:     context.Response.ContentType = "image/png";
  24:  
  25:     Bitmap bmp = Logic.CssSprite.GetCachedSprite();
  26:  
  27:     MemoryStream stream = new MemoryStream();
  28:     bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
  29:  
  30:     context.Response.BinaryWrite(stream.ToArray());
  31:  
  32:     if (context.Request.QueryString["id"] != null)
  33:         context.Response.Cache.SetExpires(DateTime.Now.AddYears(3));
  34: }
   1: 'VB
   2: '--------MVC--------
   3: <ControllerAction()> _
   4: Public Sub Sprite(ByVal id As String)
   5:   Response.ContentType = "image/png"
   6:  
   7:   Dim bmp As Bitmap = Logic.CssSprite.GetCachedSprite()
   8:  
   9:   Dim stream As New MemoryStream()
  10:   bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png)
  11:  
  12:   Response.BinaryWrite(stream.ToArray())
  13:  
  14:   If Not id Is Nothing Then
  15:     Response.Cache.SetExpires(DateTime.Now.AddYears(3))
  16:   End If
  17: End Sub
  18:  
  19: '-------WebForms-----
  20: Public Sub ProcessRequest(ByVal context As HttpContext)
  21:   context.Response.ContentType = "image/png"
  22:  
  23:   Dim bmp As Bitmap = Logic.CssSprite.GetCachedSprite()
  24:  
  25:   Dim stream As New MemoryStream()
  26:   bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png)
  27:  
  28:   context.Response.BinaryWrite(stream.ToArray())
  29:  
  30:   If Not context.Request.QueryString("id") Is Nothing Then
  31:     context.Response.Cache.SetExpires(DateTime.Now.AddYears(3))
  32:   End If
  33: End Sub

 

OK, NOW you're done

There you have it. A nice, clean CSS Sprite generated for you. This should save you some time in the long run and will certainly help your users avoid waiting too long for your website to load. If, say, you were to put 10 images into the sprite, for instance, you would save your users 9 requests from the server. That is HUGE for performance. And if your user has a primed cache, it doesn't need to have any request to the server until your distro number changes.

So in the end, we all win with CSS sprites. You get them generated for you at runtime, your users don't have to wait, and your website will be leaner and meaner!



kick it on DotNetKicks.com

3 Comments

  • Since your SetDistro is in Application_Start, won't it change every time your application fires up, not just when you publish?

    As far as I know the application pool will timeout (by default) 20 minutes after the last request's session ends. This could mean that your (or someones) number changes multiple times a day. Right?

  • @bdill

    Yes, that's true. But that doesn't really quite matter. You need it to refresh everytime you do a publish. The point is at one visit/page through your site, the stuff isn't resent from the server.

  • Iam fresher to .net,iam using css sprite generator online tool,i want the complete source code of this

Comments have been disabled for this content.