Earlier today, I posted an article about using virtual application paths inside a css file. While that approach is indeed a step in the right direction, I wasn't quite happy w/ the way I had implemented it - specifically the fact that a new file would be spawned alongside the real css file, with a "_resolved.css" appendage.
This approach only works if you give ASP.NET full access to write, and even then, it's easy for a file to get locked, resulting in a fatty exception.
Jon Gilkison had a great idea to use HttpHandlers to intercept the request & intelligently cache the resolved css in memory. I took his code and slightly modified it to what you see below. This is by far the best approach. Props to Jon for writing the bulk of HttpHandler code.
Once employed, this is what your css links in markup will look like:

<link rel="stylesheet" href="~/resources/ui/all.css.ashx" />
<link rel="stylesheet" href="~/resources/ui/msie.css.ashx" />
Notice that the only change is the additional ".ashx" applied to the end of the file name. The ".ashx" exists to distinguish this resource from other ".js" files that should not get resolved. No controls to use or register - very clean & mad stupid dope.
This is the FileResolver class - the HttpHandler responsible for intercepting any calls requesting (in this case) .css.ashx files (could be any file - .myExtension.ashx).
Warning: Lots of code below. Copy, build, rock on.

namespace Oxford.Web
{
public class FileResolver : IHttpHandler
{
internal class FileCacheItem
{
internal string Content;
internal DateTime DateEntered = DateTime.Now;
internal FileCacheItem(string content)
{
this.Content = content;
}
}
private FileCacheItem UpdateFileCache(HttpContext context, string filePath)
{
string content;
using(FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
using(StreamReader sr = new StreamReader(fs))
{
content = sr.ReadToEnd();
sr.Close();
}
fs.Close();
}
string relAppPath = HttpRuntime.AppDomainAppVirtualPath;
if(!relAppPath.EndsWith("/"))
relAppPath += "/";
content = content.Replace("~/", relAppPath);
FileCacheItem ci = new FileCacheItem(content);
CacheDependency cd = new CacheDependency(filePath);
context.Cache.Insert(filePath, ci, cd);
return ci;
}
public void ProcessRequest(HttpContext context)
{
string absFilePath = context.Request.PhysicalPath.Replace(".ashx", "");
if(absFilePath.IndexOf("~\\") > -1)
absFilePath = absFilePath.Replace("~", "").Replace("\\\\", "\\");
if(!File.Exists(absFilePath))
{
context.Response.StatusCode = 404;
return;
}
FileCacheItem ci = (FileCacheItem)context.Cache[absFilePath];
if(ci != null)
{
if(context.Request.Headers["If-Modified-Since"] != null)
{
try
{
DateTime date = DateTime.Parse(context.Request.Headers["If-Modified-Since"]);
if(ci.DateEntered.ToString() == date.ToString())
{
context.Response.StatusCode = 304;
context.Response.StatusDescription = "Not Modified";
context.Response.End();
return;
}
}
catch(Exception){}
}
else
{
context.Response.AddHeader("If-Modified-Since", ci.DateEntered.ToString());
}
}
else
{
ci = UpdateFileCache(context, absFilePath);
}
context.Response.Cache.SetLastModified(ci.DateEntered);
context.Response.ContentType = "text/" + GetContentType(Path.GetExtension(absFilePath));
context.Response.Write(ci.Content);
context.Response.End();
}
private string GetContentType(string ext)
{
switch(ext.ToLower())
{
case ".css":
return "css";
break;
case ".xml":
return "xml";
break;
case ".js":
return "javascript";
break;
default:
return "plain";
break;
}
}
#region IHttpHandler Members
public bool IsReusable
{
get
{
return true;
}
}
#endregion
}
}
Since we are using HttpHandlers, you will need to add one entry to your web.config file.

...
<httpHandlers>
<add verb="GET" path="*.css.ashx" type="Oxford.Web.FileResolver,Oxford.Web" />
</httpHandlers>
...
Another benefit to this approach is that you can use it for any file type, not just css. Simply add additional line entries to your web.config for other types, like .js. I tested with a javascript file and it worked like a charm.

...
<httpHandlers>
<add verb="GET" path="*.css.ashx" type="Oxford.Web.FileResolver,Oxford.Web" />
<add verb="GET" path="*.js.ashx" type="Oxford.Web.FileResolver,Oxford.Web" />
</httpHandlers>
...
Likewise, to add the script tag to your markup would look like this:

<script lang="javascript" src="~/resources/script/script.js.ashx"> </script>
"You take yourself out of the game, you start talking about puppy dogs and ice cream and of course it's going to end up on the friendship tip." - DoubleDown, Swingers