IconHandler 2.0: file icons in your ASP.NET applications

Since its release, IconHandler has been a pretty popular module (on its own and with the custom DirectoryListingModule).  Today, I am releasing v2.0 of IconHandler, which contains some much-requested functionality and fixes a few issues that people have reported with the original version.

(I don’t RTFM, take me to download)

Here are the notable changes in v2.0:

1.       Icon pre-generation. The IconHandler now includes IconGen.exe, a tool you can use to export all of the icons registered on your workstation machine, and use those icons on a production server instead of retrieving them on demand from the OS. This way you get the real icons instead of the generic icon for all file types not registered on the server. for More on this later …

2.       Kernel caching. When using pre-generated icons, you can now enable kernel caching for maximum speed. If getting icons dynamically, you can use either response caching or icon caching.

3.       Fixes for several issues. This includes fixes for the “Win32 handle that was passed to Icon is not valid or is the wrong type” and “Object is currently in use elsewhere” exceptions.

I've also been asked for more information on how to deploy and use IconHandler. I’ll describe this below, as well as some general best practices and techniques you should consider when using it.

First, to deploy and use IconHandler:

1)      Place the assemblies in the provided sample application into your application’s /BIN directory. These include ShellIconHandler.dll, and ShellIcons.dll.

2)      Register the <iconHandler> configuration section in your application’s root web.config

3)      Add the handler entry for IIS 7.0 Integrated mode to the <handlers> section, and IIS 6.0/ IIS 7.0 Classic mode to the <httpHandlers> section.

4)      Specify the desired configuration settings for the handler using the <iconHandler> configuration section

Here is a sample web.config file in the root of your application that accomplishes steps 2-4:

<configuration>

  <!– ShellIconHandler configuration section declaration –>

  <configSections>

    <section name="iconHandler" type="Mvolo.ShellIcons.Web.ShellIconHandlerConfigurationSection" />

  </configSections>

  <system.webServer>

    <!– Add IconHandler for IIS 7.0 Integrated mode –>

    <handlers>

      <add name="iconhandler" path="geticon.axd" verb="GET" type="Mvolo.ShellIcons.Web.ShellIconHandler" />

    </handlers>

    <validation validateIntegratedModeConfiguration="false" />

  </system.webServer>

  <system.web>

    <!– Add IconHandler for IIS 6.0 / IIS 7.0 Classic mode –>

    <httpHandlers>

      <add path="geticon.axd" verb="GET" type="Mvolo.ShellIcons.Web.ShellIconHandler" />

    </httpHandlers>

  </system.web>

  <!–

  Icon Handler by Mike Volodarsky

  Retrieves the shell icon for the specified file name.

  –>

  <iconHandler enabled="true"

              alwaysUseExtension="true"

              enableClientCaching="true"

              enableServerCaching="true" />

</configuration>

This configuration works on IIS 7.0 for both Integrated and Classic mode apps, as well as on IIS 6.0 and IIS 5.1 (Remove the system.webServer stuff for ASP.NET 1.1).

One cool way to use the IconHandler is with the DirectoryListingModule. Or write your own control.

To test IconHandler:

1)      Make a request to geticon.axd?file=FILENAME

2)      Get back the icon as a PNG file that can be displayed in the browser

IconHandler showing a file icon 

The file parameter can contain a filename or an extension. If the filename is provided, the handler can extract the icon for that specific file if one exists and specifies a custom icon. Generally, it’s preferred to use an extension because it avoids a file system access, AND, makes server caching of icons more efficient. The handler also provides a alwaysUseExtension config setting which instructs it to always use the extension part of the specified path to facilitate this usage.

You can also specify the optional size querystring parameter to select between small and large icons, for example geticon.axd?file=.ppt&size=small will give you a small version of the icon above.

Tweak configuration:

IconHandler provides some configuration settings you can tweak for your app:

enabled

You guessed it. If set to false, requests to IconHandler will be rejected.

enableClientCaching

If set to true, enables caching in the browser for 30 minutes at a time.

enableServerCaching

If set to true, the icons returned from the OS will be cached and re-used for subsequent requests for the same extension. This only works when asking for icons for an extension (not a file path), or if alwaysUseExtension is true.

enableResponseCaching

If set to true, will cache the response using ASP.NET output cache. Use this as an alternative to enableServerCaching to allow for greater performance or when not using extensions, especially when used together with the kernel cache.

alwaysUseExtension

If set to true, the handler always uses the extension part of the file parameter even if it contains a file name or a path. In v2.0, I set this to true by default, because it makes caching more effective and helps avoid the “Win32 handle that was passed to Icon is not valid or is the wrong type” issue.

useSavedIcons

If set to true, the handler will use the icons generated with IconGen.exe in the /App_Resources/Icons directory of the application instead of retrieving them from the OS. This mode can only be used with extensions, it does not support retrieving icons for specific files. Also, if the extension does not have a pre-generated icon, 404 is returned instead of a generic icon.

Pre-generating icons:

By default, IconHandler retrieves the icons on demand using the shell API SHGetFileInfo. This works well on a Windows Vista or Windows XP workstation that has all the right file types registered to installed applications, but on production servers (typically Windows Server 2003 or 2008) these applications are not installed so you would get generic icons for most file types.

To help with this, IconHandler provides the useSavedIcons config setting that instructs it to find the pre-generated icon for the requested extension in the /App_Resources/Icons directory.

How do these icons get there? Simple, you use the provided IconGen.exe tool to export the icons from your workstation to a directory, and then deploy them to the /App_Resources/Icons directory in your app. Here is an example:

> IconGen.exe c:Icons large

This will export all of the registered icons on your machine to the c:Icons directory as large icons. You can repeat the process using “small” to generate the small icons too.

To export only a specific set of icons that you want to use, provide the path to a text file containing the list of extensions, one per line. This file can look like this:

.txt
.exe
.docx

Here is a cheesy trick I baked into the tool to do this. First, run it for all extensions, and pipe the standard out to a text file:

>IconGen.exe c:icons large > extensions.txt

You’ll end up with extensions.txt containing all of the known extensions on your machine (836 for my Vista machine). Then, delete the lines you don’t want in the file, and re-run the tool with the file provided:

>rmdir c:icons /s/q
>IconGen.exe c:icons large extensions.txt

Voila.

Caching tips:

The IconHandler supports 3 types of caching: client caching (browser/proxies), server caching (caches the icons obtained from the OS), and response caching (caches the entire response containing the icon).

The recommended way to use this is as follows:

1)      enableClientCaching = true. Each browser will cache each icon it gets for 30 minutes. Why 30 mins, when icons are not likely to ever change? If you are still reading this far, you can ask me to change it J

2)      enableServerCaching = true if using extensions, and fetching icons dynamically, OR

3)      enableResponseCaching = true, if you are not using extensions, OR if you are using saved icons where #2 doesn’t apply. With this, you can also configure kernel caching for max performance.

To enable kernel caching, you need to add the following to your application’s root web.config:

  <system.web>

    <caching>

      <outputCache enableKernelCacheForVaryByStar="true" />

    </< span style="font-size: 10pt; color: #a31515; font-family: 'Courier New'">caching>

  </system.web>

This allows ASP.NET to enable kernel caching for all urls that vary by all parameters. Note that this may start kernel caching other pages that weren’t previously, so be aware of that when enabling. Also be aware of the standard cache bloat implications if you request a lot of unique urls.

Here is a obligatory “theoretical” perf comparison of 200 clients making sets of 7 requests for same 7 extensions (theoretical because there is a 100% hit ratio):

Performance test of IconHandler caching modes

1)      No caching: 350-ish RPS

2)      Server bitmap caching (enableServerCaching=true): 1100 RPS

3)      Response caching (useSavedIcons=true, enableServerCaching=false, enableResponseCaching=true): 4400 RPS

4)      Response caching + kernel cache: 9300 RPS

As always, kernel caching has superior performance to other forms of caching. NOTE that at the 9300 RPS, my CPU usage drops from the fully utilized 100% to about 35%. This means that the client is not able to drive the server fast enough, so we can project about 26000 RPS on fully utilized server.

This also effectively makes IconHandler performance the same as native IIS static file handler on cache hits, and since there is a finite number of extensions, real performance on a busy site should approach it as well.

Using the API:

IconHandler comes with ShellIcons.dll, which provides a function that you can use to retrieve icons from the OS by p/invoking into SHGetFileInfo.

You can reference this DLL in your projects and call into the API to get icons (full trust required due to p/invoke). Here is a snippet:

using Mvolo.ShellIcons;


using (IconContainer icon = ShellIcons.GetIconForFile(".ppt", true, true))

{

    Bitmap b = icon.Icon.ToBitmap();

    // Do stuff with the icon …

}

The ShellIcons.GetIconForFile method allows you to get icons for a specific file or extension, optionally getting either a large or small icon.

The method returns the icon wrapped in the IconContainer class, which implements CriticalHandle. That means that if you hang on to the icon and forget to dispose it, the CLR will do it even under extreme conditions like thread abort and appdomain unload. But, you can only have so many of the icon handles open before the OS will fail to create any more, so it’s a good idea to dispose of the handle immediately like I show in the snippet above. You can than hang on to the Bitmap after the icon is disposed.

Possible issues to be aware of:

1)      My icons all show up as the generic icon! That means you don’t have file associations for the icons – try pre-generating on a machine that does, and then use saved icons.

2)      IconHandler requires full trust when fetching icons on demand. If you use saved icons, it should run fine under Medium trust.

3)      If  you are fetching icons on demand, and don’t use caching, or are making requests with file paths and not extensions, under heavy load you may cause the ShGetFileInfo API to exceed the number of allowed handles and start returning bogus handles. In this case, IconHandler will throw an exception indicating “You have exceeded the maximum number of open GDI handles. Close some of the existing icon handles to avoid this condition”.

4)      If you are fetching icons on demand, ShGetFileInfo API may end up loading all kinds of DLLs into your process to get icons for various file extensions.  If this is not acceptable, use saved icons instead.

 That’s it. Oh, and here is the download J:

Download: IconHandler 2.0 (DLLs and sample application)

Download: IconGen.exe (tool to pre-generate icons)

Enjoy! And let me know if you hit any issues …

Thanks,

Mike

 

23 Comments

  1. Anonymous

    Since its release, IconHandler has been a pretty popular module (on its own and with the custom DirectoryListingModule

  2. Anonymous

    I was wondering if there was a way to get the top directory links at the top to work with https or a non standard port? If you read this and know of a way can you email me at [email protected]?

  3. Anonymous

    I have been using the DirectoryListing for quite some time. When I updated to IconHandler 2.0 I lost the folder icons. If I disable useSavedIcons the folder icons work correctly. Any ideas?

  4. Mike Volodarsky

    Caleb,

    You may have found a bug where IconGen.exe doesnt export folder icons. Let me look into it …

    Thanks,

    Mike

  5. Anonymous

    Hey Mike, I think I found a bug in the dll. When using the saved icons it looks like it is serving up the BMP directly as text/html. Firefox wasn’t too happy about that.

    Thanks, Mike!

  6. Anonymous

    I have one error (in spanish)

    Mensaje de error del analizador: Atributo ‘useSavedIcons’ no reconocido. Tenga en cuenta que en los nombres de atributo se distinguen mayúsculas y minúsculas.

    Means: Attribute “useSavedIcons” not recognized

  7. Anonymous

    I’ve been working on the Directory Listing Module for a few hours now. I got the Directory Listing part to work fine in very little time. However, all the icons are showing up as broken JPEGs. When I go to “http://localhost/geticon.axd?file=.ppt” or any variation, I get 404’d. Right now I’m just using the Sample site that you provided. The page formatting is fine, just no icons. Any ideas where I went wrong? Thanks!

    -Patton

  8. Anonymous

    To those who noted that the folder icons do not appear when using DirectoryListing and IconHandler 2.0; if you pregen the icons, you need to rename the Folder_{size}.bmp to simply _{size}.bmp. This work around works quite nicely. If you look at the generated URL, the query parameter for "file" is empty, so I'm assuming this puts an icon with an empty file type. @Mike – Do you ever plan on opening this up on CodePlex or anything? Thanks! -dl

  9. Anonymous

    I have the same problem as General Patton04.

    In firefox it just says the text “icon” without the quotes next to the folder or file instead of showing an actual image.

    In IE, it looks like a broken image, there’s a box with a red X in it and it says icon, then the file or folder name. If I try going to /geticon.axd?file=.pdf or any other file type it says the page cannot be found.

  10. Anonymous

    Hey Mike, very good stuff you have created! Were you able to resolve any of the issues in this stream? I have the directorylist working great, but I like the other post have broken image icons for my icons, When I run GETICON.AXD I get teh correct Icon, I have also used the IconGen and put the large icons in the app_resouce folder. Running on IIS6 Server 2003. Thanks again

  11. Anonymous

    HttpRuntime.AppDomainAppVirtualPath is not returning the site header. ie http:///geticon.axd?file=

    the application is returning “/geticon.axd?file=” no host name.
    is there a specific item I need to enable or allow permissions to read this?

  12. Anonymous

    Hi Mike, from a fellow MVP. I’m just now trying your code, and am seeing the same problem that marc described on 3/18/09. Just curious if you have a resolution for this.

    Thank you…

  13. Anonymous

    I’ve got an odd issue. On the same server I have a functioning ‘useSavedIcons’ and a non-functioning one. Like others above, the directory listing works on both, however, one the icons fail to load.

    I’ve tried overwriting all the core files between the two, permissions, etc. Obviously there has to be something different but I can’t determine what.

    If I try the direct geticon.axd the faulty site returns 404. On the dir listing page source, I noticed that the img tag path is broken. It shows:
    src=”/filesgeticon.axd?file=”

    … rather than: src=”/files/geticon.axd?file=”
    The a href is set OK. I modified the dirlisting.aspx and added the ‘/’ so now the page source shows correctly, but still no icon.

  14. Anonymous

    I have an odd problem with icongen.exe and corel draw .cdr extensions. It doesn’t read the icon, I just get the generic icon. It creates icons for other formats; i.e. adobe illustrator (.ai),.jpg,.pdf, etc. as expected.

    I have Corel Draw X5 installed with Windows XP (32 bit). You can download a 30 day demo at corel.com to test.

    I checked the HKEY_CLASSES_ROOT.cdr and it has no defaulticon key (either does .ai) but the (default) value is “CorelDraw.Graphic.15”

    The (default) value for .ai is “Adobe.Illustrator.14”

    Both of those values have a DefaultIcon key with a path to the executable and an index of 1, i.e. “c:Program FilesCorelCorelDRAW Graphics Suite X5ProgramsCORELDRW.EXE,1”.

    Changing the case doesn’t matter and I can intentionally add an “X” at the start of the AI entry to make it generate a generic icon intentionally so I seem to be looking in the right place.

    I can manually extract icons from the corel executable.

    There are several file types associated with installed apps I still get the generic icons for.

    Any ideas?

    Thanks

  15. Anonymous

    Hey Mike –

    I’ve been trying to integrate your code with “FileUpload” method code:
    http://www.codeproject.com/KB/aspnet/FileHandlerPackage.aspx
    http://www.wrox.com/WileyCDA/Section/ASP-NET-2-0-FileUpload-Server-Control.id-292158.html

    …so that people can not only “see” the content of folders, but also upload within the folder CURRENTLY DISPLAYED by the DirectoryListing module.

    The problem I am having is with this code:

    FileUpload1.SaveAs(“C:WhatEverPath” +

    Is there a way to modify this line so that the path becomes variable to the SPECIFIC location currently being viewed by the DirectoryListing module?

    Thanks!

  16. Anonymous

    I can’t seem to get hidden files to not show up…
    I know it’s something easy..I’m a noob

    CODE:
    c:windowssystem32inetsrv> “C:WindowsMicrosoft.NETFramework64v2.0.50727csc.exe” /t:library /utf8output /R:”C:WindowsassemblyGAC_64System.EnterpriseServices2.0.0.0__b03f5f7f11d50a3aSystem.EnterpriseServices.dll” /R:”C:WindowsassemblyGAC_MSILSystem.Configuration2.0.0.0__b03f5f7f11d50a3aSystem.Configuration.dll” /R:”C:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Filesroota32885f99524be62assemblydl3ea4dee3002ce95c_bb9dc901ShellIcons.DLL” /R:”C:WindowsassemblyGAC_MSILSystem.ServiceModel.Web3.5.0.0__31bf3856ad364e35System.ServiceModel.Web.dll” /R:”C:WindowsassemblyGAC_MSILSystem2.0.0.0__b77a5c561934e089System.dll” /R:”C:WindowsMicrosoft.NETFramework64v2.0.50727mscorlib.dll” /R:”C:WindowsassemblyGAC_MSILSystem.Xml2.0.0.0__b77a5c561934e089System.Xml.dll” /R:”C:WindowsassemblyGAC_MSILSystem.Drawing2.0.0.0__b03f5f7f11d50a3aSystem.Drawing.dll” /R:”C:WindowsassemblyGAC_MSILSystem.Web.Services2.0.0.0__b03f5f7f11d50a3aSystem.Web.Services.dll” /R:”C:WindowsassemblyGAC_MSILSystem.ServiceModel3.0.0.0__b77a5c561934e089System.ServiceModel.dll” /R:”C:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Filesroota32885f99524be62assemblydl321b3e62a02ce95c_bb9dc901DirectoryListing.DLL” /R:”C:WindowsassemblyGAC_64System.Web2.0.0.0__b03f5f7f11d50a3aSystem.Web.dll” /R:”C:WindowsassemblyGAC_MSILSystem.Web.Mobile2.0.0.0__b03f5f7f11d50a3aSystem.Web.Mobile.dll” /R:”C:WindowsassemblyGAC_MSILSystem.WorkflowServices3.5.0.0__31bf3856ad364e35System.WorkflowServices.dll” /R:”C:WindowsassemblyGAC_MSILSystem.Runtime.Serialization3.0.0.0__b77a5c561934e089System.Runtime.Serialization.dll” /R:”C:WindowsassemblyGAC_64System.Data2.0.0.0__b77a5c561934e089System.Data.dll” /R:”C:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Filesroota32885f99524be62assemblydl3e9bd0f1202ce95c_bb9dc901ShellIconHandler.DLL” /R:”C:WindowsassemblyGAC_MSILSystem.IdentityModel3.0.0.0__b77a5c561934e089System.IdentityModel.dll” /out:”C:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Filesroota32885f99524be62App_Web_dirlisting.aspx.cdcab7d2.nvyzs8e8.dll” /debug- /optimize+ /win32res:”C:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Filesroota32885f99524be62lhpdyoat.res” /w:4 /nowarn:1659;1699;1701 “C:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Filesroota32885f99524be62App_Web_dirlisting.aspx.cdcab7d2.nvyzs8e8.0.cs” “C:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Filesroota32885f99524be62App_Web_dirlisting.aspx.cdcab7d2.nvyzs8e8.1.cs”

    Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.4927
    for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
    Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

    ERROR:
    c:Boarddirlisting.aspx(23,5): error CS0103: The name ‘Listhidden’ does not exist in the current context
    c:Boarddirlisting.aspx(23,22): error CS0305: Using the generic type ‘System.Collections.Generic.List‘ requires ‘1’ type arguments
    c:WindowsMicrosoft.NETFramework64v2.0.50727mscorlib.dll: (Location of symbol related to previous error)
    c:Boarddirlisting.aspx(28,8): error CS0103: The name ‘hidden’ does not exist in the current context
    c:Boarddirlisting.aspx(31,51): error CS0103: The name ‘hidden’ does not exist in the current context

    CODE:
    < %@ Page Language="c#" %>
    < %@ Import Namespace="Mvolo.DirectoryListing" %>
    < %@ Import Namespace="System.Collections.Generic" %>
    < %@ Import Namespace="System.IO" %>



    Directory contents of < %= Context.Request.Path %>



    < %= Context.Request.Path %>




    icon“>< %# ((DirectoryListingEntry)Container.DataItem).Filename %>
    &nbsp< %# GetFileSizeString(((DirectoryListingEntry)Container.DataItem).FileSystemInfo) %>



Leave a Reply

Your email address will not be published. Required fields are marked *