Usually when you upgrade a framework, there is a chance you use a default configuration provided by the developers of the framework. Copy and paste. Forgetting the fact that default configuration might not be compatible with your code. I agree that testing the application with the upgraded framework -along with the default configuration that you have copied and pasted from the documentation- can reveal most of the possible issues but sometimes small mistakes can lead to horrible flaws in your application.
In the rest of this post I will bring you an example of how copy and pasted configuration suggested by a new version of a framework caused problems showing wrong images on the website. I am explaining a simplified version of the code to show the problem and the cause.
On one of our websites we serve some images from a different location and in order to make it easy for the editors who maintain the content of the website we have some code to rewrite a specific path to an image handler which then downloads the data from a file service and renders it to the response. In another word, we are rewriting the path /Images/* to /ImageHandler.ashx and pass the rest of the path as a query string parameter called virtual path. Look at the code below :
public class ImageRewriteModule : IHttpModule
{
public void Dispose()
{
//clean-up code here.
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(BeginRequest);
}
public void BeginRequest(object sender, EventArgs e)
{
var path = HttpContext.Current.Request.Path;
if (path.ToLower().StartsWith("/Images/"))
{
HttpContext.Current.RewritePath(path.Replace("/Images/", "/ImageHandler.ashx?virtualPath="));
}
}
}
And what we do in ImageHandler.ashx is something like below:
public class ImageHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/jpg";
var bytes = new byte[0];
try
{
var client = new WebClient();
bytes = client.DownloadData("http://ourfileservice/" + context.Request.QueryString["virtualPath"]);
}
catch (Exception)
{
//Log the error maybe?
}
context.Response.BinaryWrite(bytes);
}
public bool IsReusable
{
get
{
return false;
}
}
}
What we are doing in the above code is simply getting the virtual path from the query string and trying to download the image from the file service. The code in our system was not exactly like the code I have demonstrated above but the whole purpose of the code was getting the content of the image from another location and writing in the response. Also, I understand that the developers had made it a bit difficult for themselves and this problem has much simpler solutions.
The solution above was working fine. The content editors could simply set the image source e.g. /Images/Nature/1.jpg and it would serve the image from http://ourfileservice/Nature/1.jpg , until we had an upgrade and the below piece of configuration slipped into our system.
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<!-- This is to intercept request and rewrite the URL. This was also handled a bit differently in the real system but the behaviour is the same. -->
<add name="ImageRewriteModule" type="TestCaching.ImageRewriteModule,TestCaching" />
</modules>
<caching >
<profiles >
<add extension=".png" kernelCachePolicy="CacheUntilChange" policy="CacheUntilChange" location="Client" />
<add extension=".jpg" kernelCachePolicy="CacheUntilChange" policy="CacheUntilChange" location="Client" />
<!--Other extension-->
</profiles>
</caching>
</system.webServer>
The above configuration tells IIS (web server) to cache requests for jpg and png images. This is a fine configuration but due to the way we were handling those requests for images (rewriting and passing the path as a query string parameter) caused a serious bug in the website which led to images being displayed incorrectly. After a couple of requests to /Images/* the rest of the images would be displayed as the last image downloaded from the file server. The web server supposes that image URL is /ImageHandler.ashx and totally ignores the name of the image which is passed through a query string called virtualPath.
There are a few solution for the above issue :
- Changing the way of handling the images.
- Removing the caching config.
- Also we can use a config setting to tell the web server that it should consider a query string parameter. This is done like below
<add extension=".jpg" kernelCachePolicy="CacheUntilChange" policy="CacheUntilChange" location="Client" varyByQueryString="virtualPath" />
The above problem is very tricky and and as the caching does not happen on the first request, it doesn’t show itself without a thorough testing.
There is a lesson to be learnt and it is not to include any default configuration in an application that is serving a business and its many users.