Archives

Archives / 2008 / March
  • High Interface diet

    As long as I'm on the coding conventions track (as many of users have commented on my last post), I'll talk about a small coding convention I am trying to do.

  • Coding Pet Peeves

    Any experienced coder has pet peeves when it comes to reading other people's code or writing code. It might be that you don't like regions or that every method should have comments. But here are my two biggest pet peeves when it comes to C#.

  • 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