<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Nathanael Jones &#187; ASP.NET</title>
	<atom:link href="http://nathanaeljones.com/category/asp-net/feed/" rel="self" type="application/rss+xml" />
	<link>http://nathanaeljones.com</link>
	<description>Ramblings of a computer linguist</description>
	<lastBuildDate>Mon, 04 Jan 2010 21:12:43 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Version 2.1b released!</title>
		<link>http://nathanaeljones.com/438/version-2-1b-released/</link>
		<comments>http://nathanaeljones.com/438/version-2-1b-released/#comments</comments>
		<pubDate>Fri, 13 Nov 2009 23:36:06 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Image Resizing]]></category>

		<guid isPermaLink="false">http://nathanaeljones.com/?p=438</guid>
		<description><![CDATA[Better performance, better installation, better samples, more features! Installation has been simplified to copying and pasting. ]]></description>
			<content:encoded><![CDATA[<p><strong>Better performance, better installation, better samples, more features!</strong></p>
<h2 style="font-size: 1.8em; margin-top: 0px; margin-right: 0px; margin-bottom: 0.8em; margin-left: 0px; color: #262625; letter-spacing: -1px; padding: 0px;">Fixed</h2>
<ul>
<li>I finally tracked down a simple, yet elusive performance bug  that was particularly problematic for users with many thousands of images. Performance should be much better for everyone now, so upgrading is highly suggested.  This bug was introduced with version 2.0., and caused a filesystem listing hit for each request.</li>
<li>All requests are now forced to pass through the UrlAuthorizationModule now. Previously, any URL rewriting (like customfolders.cs) caused URL auth rules to be circumvented. This was documented behavior, but a secure solution has now been found.</li>
</ul>
<h2 style="font-size: 1.8em; margin-top: 0px; margin-right: 0px; margin-bottom: 0.8em; margin-left: 0px; color: #262625; letter-spacing: -1px; padding: 0px;">Added</h2>
<ul>
<li>New users will find it much easier to get things working &#8211; <strong>Messing with IIS is now completely optional! <img src='http://nathanaeljones.com/wp-includes/images/smilies/icon_biggrin.gif' alt=':D' class='wp-smiley' />  <img src='http://nathanaeljones.com/wp-includes/images/smilies/icon_biggrin.gif' alt=':D' class='wp-smiley' /> </strong><br />
Instead of writing  <em>&#8220;image.jpg?width=500&#8243;</em> as you normally would, type <em>&#8220;image.jpg</em><strong><em>.axd</em></strong><em>?width=500&#8243;</em>. If you use the new syntax, you won&#8217;t be forced to configure wildcard mapping on IIS6/IIS7 classic.<br />
<strong>Both syntaxes will be supported in the future.<br />
<span style="font-weight: normal;">The suggested extension is .axd, but that can be changed or disabled using the ResizeExtension application setting.</span><br />
</strong></li>
<li>Dithering support added! You can dither an 8-bit image using the <strong>?dither=</strong> command. You can specify a dither percent, &#8220;true&#8221;, or &#8220;4pass&#8221;.</li>
<li>UploadSample  project (in VB). Resizing images as they are uploaded is now trivally easy. ImageManager.BuildImage now accepts an HttpPostedFile instance for resizing.</li>
<li>DisableCustomQuantization setting to allow GIFs to be generated on servers where the Marshal class is prohibited.</li>
<li>PerfTests project to run benchmarks on the image resizing and encoding code.</li>
</ul>
<h2 style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.8em; margin-left: 0px; color: #262625; font-size: 1.8em; letter-spacing: -1px; padding: 0px; border: 0px initial initial;">Upgrade proccess</h2>
<ol style="padding-top: 0px; padding-right: 0px; padding-bottom: 1.2em; padding-left: 2em; margin: 0px; border: 0px initial initial;">
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 20px; list-style-type: decimal; list-style-position: initial; list-style-image: initial; padding: 0px; border: 0px initial initial;">Replace the old files with the new files from the ImageResizer folder. If you are using the .DLL, replace the DLL in the bin folder. Keep your old CustomFolders.cs and WatermarkSettings.cs files if you have modified them.</li>
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 20px; list-style-type: decimal; list-style-position: initial; list-style-image: initial; padding: 0px; border: 0px initial initial;">Delete the /imagecache directory.</li>
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 20px; list-style-type: decimal; list-style-position: initial; list-style-image: initial; padding: 0px; border: 0px initial initial;">Insert/change the following application settings in web.config if you want to use the new syntax.</li>
</ol>
<blockquote>
<pre>&lt;add key="ResizeExtension" value=".axd"/&gt;</pre>
<pre>&lt;add key="AllowURLRewriting" value="true"/&gt;</pre>
</blockquote>
<p>If you are upgrading from 1.2, <a href="http://nathanaeljones.com/159/image-resizer-2-0-upgrade-notes/">follow the 1.2-&gt;2.0 upgrade instructions first.</a></p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/438/version-2-1b-released/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Design of an Image Resizing Module</title>
		<link>http://nathanaeljones.com/196/design-of-an-image-resizing-module/</link>
		<comments>http://nathanaeljones.com/196/design-of-an-image-resizing-module/#comments</comments>
		<pubDate>Tue, 26 May 2009 16:00:28 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[Image Resizing]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=196</guid>
		<description><![CDATA[In this sequel to 20 Image Resizing Pitfalls, I'm outlining the architecture of my image resizing module, and including the source code for the HttpModule (but not the supporting classes).]]></description>
			<content:encoded><![CDATA[<p>In this sequel to  20 Image Resizing Pitfalls, I&#8217;m outlining the architecture of my image resizing module, and including the source code for the HttpModule (but not the supporting classes).</p>
<p>Read <a href="/11191_20_Image_Resizing_Pitfalls">20 Image Resizing Pitfalls</a> first (part 1).  It explains why you need an HttpModule instead of an HttpHandler, why disk caching is a requirement, and why you always want to let IIS serve static files.</p>
<p>This article gives an architectural overview of my image resizer. The design has evolved several times as my needs grew, but I think the design has now matured to be very simple, modular, and extensible.</p>
<h3>Request flow</h3>
<ol class="normallist">
<li>Only intercept image requests that include certain querystring commands. Leave other requests intact. </li>
<li>Check to see if the resized image for the request URL is already cached. If cached and still up-to-date with the source file, goto step 4.</li>
<li>If not already cached, process the image and cache it. </li>
<li>Rewrite the request (not redirect) to the cached file. This will allow IIS to serve the file with full HTTP support (range requests, caching, not-modified support, etc).</li>
</ol>
<h3>A few other goals</h3>
<ol class="normallist">
<li>Allow custom syntaxes like /resize(50,50)/image.jpg or ?theme=smallthumb to be easily added without bloating the core module. </li>
<li>Permit or disable filename rewriting from Web.config. /resize(50,50)/ would be path rewriting, wheras ?theme=smallthumb would be just rewriting the querystring. This configurability is important, since path rewriting could make subfolder-based web.config authorization settings on image files have no effect.</li>
<li>Allow client caching time to be configurable. This will keep requests down, since clients won&#8217;t check for updates until the specified amount of time expires.</li>
<li>Make both server-side memory and disk  caching invalidate when the source file changes.</li>
<li>Allow a easily extensible and large set of commands, affecting the cropping/sizing, visual effects, color choices/border/padding, and image output format/compression.</li>
<li>Prevent DOS attacks by limiting the disk cache to a configurable number of images.</li>
<li>Allow easy watermarking or post-processing of resized images</li>
</ol>
<h2>Overview of classes</h2>
<p>With these goals in mind, I found it optimal to segment the logic into the following classes. While you may question my decision to split querystring parsing into 4 classes, it resulted in much cleaner code, and a much more usable interface for image manipulation from other classes. </p>
<ul class="normallist">
<li><strong>InterceptModule</strong><br />
IHttpModule that checks for relevant requests during PostAuthorizeRequest, caches the ImageManger-proccessed images using DiskCache, and rewrites the request to the cached file. It also sets HTTP caching headers later in PreSendRequestHeaders. The disk-cached filename is an SHA256, base16 encoded hash of the request URL. Although the image is coming from a different file, the user never sees the URL of the cached file &#8211; the original filename is maintained.</li>
<li><strong>DiskCache</strong>
<p>Completely abstracts away disk caching, cleanup, and the associated threading issues. InterceptModule calls method <em>UpdateCachedVersionIfNeeded(sourceFile,cachedFile, delegate updateMethod, int fileLockTimeout)</em> with   <em>ImageManager.getBestInstance().BuildImage(sourcePath, cachedPath, queryString);</em> as the contents of the delegate. DiskCache performs the modified date checks, cache cleanup, and thread locking to prevent the same image from being resizied by two different threads at the same time (it happens frequently!). The delegate is only executed if the cached file needs to be created or updated.</li>
<li><strong>CustomFolders</strong><br />
Exposes a single method,  static string processPath(string filePath, NameValueCollection q). This allows both URL rewriting and modifications to the querystring, such as /resize(w,h)/ syntax. InterceptModule simply calls this once prior to checking the querystring for valid commands.</li>
<li><strong>ImageManager</strong>
<p> exposes several overloads of a method called BuildImage() which allow both querystring and settings class configuration, and both bitmap and filename source and output. While BuildImage performs all the actual GDI calls, the querystring parsing, mathematics, and image writing are handled by other classes below.</li>
<li><strong>ResizeSettings</strong>(querystring)<br />
 Accepts a single NameValueCollection (Querystring) as the constructor argument. Stretch, crop, scale, flip, sourceFlip, width, height, maxwidth, and maxheight are parsed into member variables. Exposes a  <em>ImageSizingData CalculateSizingData(SizeF originalImageSize, SizeF maxBounds)</em> method that handles all mathematics. ImageSizingData includes the source rectangle on the image to copy from, the destination polygon for the image data, and a polygon that includes any whitespace.</li>
<li><strong>ImageOutputSettings</strong>(querystring) 
<p>Parses the format, colors, and quality commands. It also exposes a SaveImage(Stream s, Image i) method that handles the writing of the various image types based on the querystring arguments. Static methods for saving images are also available.</li>
<li><strong>ImageSettings</strong>(querystring) <br />
Parses the bgcolor, paddingWidth, paddingColor, borderWidth, and borderColor commands. BuildImage() uses this data when drawing the image.</li>
<li><strong>WatermarkSettings</strong>(querystring)<br /> allows for custom drawing on images. It exposes a  <em>ModifySettings(ResizeSettings rs, ImageSettings opts, ImageFilter adjustments, ImageOutputSettings ios)</em> method for adjusting settings prior to resizing, and a <em>Process(Bitmap b, Graphics g)</em> method for post-processing of resized images.</li>
</ul>
<h2>InterceptModule code</h2>
<p>I hope this overview is clear enough &#8211; if not, please leave a question below. Understanding the design, the source code for InterceptModule should now make sense. Thankfully, this is one of the shortest classes, but it should help you write your own HttpModule. I&#8217;ve removed the class declaration, imports, and profiling code for clarity. The original source code comments explain everything pretty clearly.</p>
<pre name="code" class="brush:c-sharp">
/// &lt;summary&gt;
/// This is where we filter requests and intercet those that want resizing performed.
/// We first check for image extensions...
/// If it is one, then we run it through the CustomFolders method to see if if there is custom resizing for it..
/// If there still aren't any querystring params after that, then we ignore the request.
/// If the file doesn't exist, we also ignore the request. They're going to cause a 404 anyway.
///
/// &lt;/summary&gt;

/// &lt;param name=&quot;sender&quot;&gt;&lt;/param&gt;
/// &lt;param name=&quot;e&quot;&gt;&lt;/param&gt;
protected virtual void CheckRequest_PostAuthorizeRequest(object sender, EventArgs e)
{
	//Get the http context, and only intercept requests where the Request object is actually populated
	HttpApplication app = sender as HttpApplication;
	if (app != null &amp;&amp; app.Context != null &amp;&amp; app.Context.Request != null)
	{

		//Is this an image request? Checks the file extension for .jpg, .png, .tiff, etc.
		if (ImageOutputSettings.IsAcceptedImageType(app.Context.Request.FilePath))
		{
			//Init the caching settings. These only take effect if the image is actually resized
			//CustomFolders.cs can override these during processPath
			app.Context.Items[&quot;ContentExpires&quot;] = DateTime.Now.AddHours(24); //Default to 24 hours
			string cacheSetting = ConfigurationManager.AppSettings[&quot;ImageResizerClientCacheMinutes&quot;];
			if (!string.IsNullOrEmpty(cacheSetting)){
				double f;
				if (double.TryParse(cacheSetting,out f)){
					if (f &gt;= 0)
						app.Context.Items[&quot;ContentExpires&quot;] = DateTime.Now.AddMinutes(f);
					else
						app.Context.Items[&quot;ContentExpires&quot;] = null;
				}
			}

			//Copy the querystring
			NameValueCollection q = new NameValueCollection(app.Context.Request.QueryString);

			//Call CustomFolders.cs to do resize(w,h,f)/ parsing and any other custom syntax.
			//The real virtual path should be returned (with the resize() stuff removed)
			//And q should be populated with the querystring values
			string basePath = CustomFolders.processPath(app.Context.Request.Path, q);

			//If the path has changed, this will circumvent the URL auth system.
			//Make sure the user has explicity allowed it through web.config
			if (!basePath.Equals(app.Context.Request.Path))
			{
				//Make sure the resize() notation is allowed.
				string allow = ConfigurationManager.AppSettings[&quot;AllowURLRewriting&quot;];
				if (string.IsNullOrEmpty(allow)) allow =ConfigurationManager.AppSettings[&quot;AllowFolderResizeNotation&quot;];
				if (string.IsNullOrEmpty(allow) || allow.Equals(&quot;false&quot;, StringComparison.OrdinalIgnoreCase)){
					return; //Skip the request
				}
				//Prevent access to the /imagecache/ directory (URL auth won't be protecting it now)
				if (new yrl(basePath).Local.StartsWith(DiskCache.GetCacheDir(), StringComparison.OrdinalIgnoreCase))
				{
					throw new HttpException(403, &quot;Access denied to image cache folder.&quot;);
				}
			}
			//See if resizing is wanted (i.e. one of the querystring commands is present).
			//Called after processPath so processPath can add them if needed.
			//Checks for thumnail, format, width, height, maxwidth, maxheight and a lot more
			if (ImageManager.getBestInstance().HasResizingDirective(q))
			{
				//Build a URL using the new basePath and the new Querystring q
				yrl current = new yrl(basePath);
				current.QueryString = q;

				//If the file exists, resize it
				if (current.FileExists)
					ResizeRequest(app.Context,current);

			}
		}
	}
}

/// &lt;summary&gt;

/// Builds the physical path for the cached version, using the hashcode of the normalized URL.
/// &lt;/summary&gt;
/// &lt;param name=&quot;request&quot;&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
protected string getCachedVersionFilename(yrl request)
{
	string dir = DiskCache.GetCacheDir();
	if (dir == null) return null;
	//Build the physical path of the cached version, using the hashcode of the normalized URL.
        //We don't use String.GetHashCode(), since it returns a 32-bit integer. Chances of a hash collision are low, but possible.
        // So, we use a 256-bit hash instead.
	SHA256 h = System.Security.Cryptography.SHA256.Create();
	byte[] hash = h.ComputeHash(new System.Text.UTF8Encoding().GetBytes(request.ToString().ToLower()));
	//Can't use base64 hash... filesystem has case-insensitive lookup.
	//Would use base32, but too much code to bloat the resizer. Simple base16 encoding is fine
	return dir.TrimEnd('/', '\\') + &quot;\\&quot; + Base16Encode(hash) + &quot;.&quot; + new ImageOutputSettings(request).GetFinalExtension();
}
/// &lt;summary&gt;

/// Returns a lowercase hexadecimal encoding of the specified binary data
/// &lt;/summary&gt;
protected string Base16Encode(byte[] bytes)
{
	StringBuilder sb = new StringBuilder(bytes.Length * 2);
	foreach (byte b in bytes)
		sb.Append(b.ToString(&quot;x&quot;).PadLeft(2, '0'));
	return sb.ToString();
}

/// &lt;summary&gt;
/// Generates the resized image to disk (if needed), then rewrites the request to that location.
/// Perform 404 checking before calling this method. Assumes file exists.
/// Called during PostAuthorizeRequest
/// &lt;/summary&gt;
/// &lt;param name=&quot;r&quot;&gt;&lt;/param&gt;
/// &lt;param name=&quot;extension&quot;&gt;&lt;/param&gt;

protected virtual void ResizeRequest(HttpContext context, yrl current)
{
	//This is where the cached version goes
	string cachedFile = getCachedVersionFilename(current);

	//Disk caching is good for images because they change much less often than the application restarts.

	//Make sure the resized image is in the disk cache.
	bool succeeded = DiskCache.UpdateCachedVersionIfNeeded(current.Local, cachedFile,
		delegate(){

			//This runs if the update is needed. This delegate is preventing from running in more
			//than one thread at a time for the specified source file (current.Local)
			ImageManager.getBestInstance().BuildImage(current.Local, cachedFile, current.QueryString);
		},30000);

	//If a co-occurring resize has the file locked for more than 30 seconds, quit with an error.
	if (!succeeded)
		throw new ApplicationException(&quot;Failed to acquire a lock on file \&quot;&quot; + current.Virtual +
                                              &quot;\&quot; within 30 seconds. Image resizing failed.&quot;);

	//Get domain-relative path of cached file.
	string virtualPath = yrl.GetAppFolderName().TrimEnd(new char[] { '/' }) + &quot;/&quot; + yrl.FromPhysicalString(cachedFile).ToString();

	//Add content-type headers (they're not added correctly when the source URL extension is wrong)
	//Determine content-type string;
	string contentType = new ImageOutputSettings(current).GetContentType();

	context.Items[&quot;FinalContentType&quot;] = contentType;
	context.Items[&quot;FinalCachedFile&quot;] = cachedFile;

	//Rewrite to cached, resized image.
	context.RewritePath(virtualPath, false);
}
/// &lt;summary&gt;

/// We don't actually send the data - but we still want to control the headers on the data.
/// PreSendRequestHeaders allows us to change the content-type and cache headers at excatly the last
/// second. We populate the headers from context.Items[&quot;FinalContentType&quot;],
/// context.Items[&quot;ContentExpires&quot;], and context.Items[&quot;FinalCachedFile&quot;].
/// This also indirectly enables server-side mem caching. (HttpCacheability.Public does it)
/// We set the file dependency to FinalCachedFile so changes are update quickly server-side
/// - however, clients will not check for updates until ContentExpires occurs.
///
/// &lt;/summary&gt;
/// &lt;param name=&quot;sender&quot;&gt;&lt;/param&gt;
/// &lt;param name=&quot;e&quot;&gt;&lt;/param&gt;

protected void context_PreSendRequestHeaders(object sender, EventArgs e)
{
	HttpApplication app = sender as HttpApplication;
	HttpContext context = (app != null) ? app.Context : null;
	//Check to ensure the context and Response is in good shape (it's needed)
	if (context != null &amp;&amp; context.Items != null &amp;&amp; context.Items[&quot;FinalContentType&quot;] != null
                          &amp;&amp; context.Items[&quot;FinalCachedFile&quot;] != null)
	{
		//Clear previous output
		//context.Response.Clear();
		context.Response.ContentType = context.Items[&quot;FinalContentType&quot;].ToString();
		//Add caching headers
		context.Response.AddFileDependency(context.Items[&quot;FinalCachedFile&quot;].ToString());

		//It's not UTC - server time zone.
		if (context.Items[&quot;ContentExpires&quot;] != null)
			context.Response.Cache.SetExpires((DateTime)context.Items[&quot;ContentExpires&quot;]);

		//Enables in-memory caching
		context.Response.Cache.SetCacheability(HttpCacheability.Public);
		context.Response.Cache.SetLastModifiedFromFileDependencies();
		context.Response.Cache.SetValidUntilExpires(false);
	}

}
</pre>
<p>I&#8217;m sure a lot of people will think I&#8217;m nuts for posting the source code to the main class of my image resizer,<br />
but I know that this will help a lot of people.  </p>
<p>Based on the response I get to this article, I may continue with additional parts. I would like to cover the math behind scaling and cropping, the GDI bugs, and maybe even image output/compression. </p>
<p>Hope this helps!<br />Nathanael</p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/196/design-of-an-image-resizing-module/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Demo Version of Image Resizing Module</title>
		<link>http://nathanaeljones.com/199/demo-version-of-image-resizing-module/</link>
		<comments>http://nathanaeljones.com/199/demo-version-of-image-resizing-module/#comments</comments>
		<pubDate>Thu, 21 May 2009 16:00:35 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[Image Resizing]]></category>
		<category><![CDATA[image resizer]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=199</guid>
		<description><![CDATA[I've received many requests for a 'trial' version of the image resizer. This demo version is fully-functional, but watermarks 50% of the generated images with "Unlicensed".]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve received many requests for a &#8216;trial&#8217; of the <a href="/products/asp-net-image-resizer/">image resizer</a>. This demo version is fully-functional, but watermarks 50% of the generated images with &#8220;Unlicensed&#8221;. Another note: the trial version includes supports resizing animated GIFs. That plug-in isn&#8217;t included in the default version; contact me for details.</p>
<p>Installation is the <a href="/11141_Image_Resizer_Installation">same as for the full version</a>.</p>
<p><a onclick="javascript:pageTracker._trackPageview('/download/resizertrial');" href="/wp-content/uploads/2009/11/ImageResizer2.1b-Nov-3-09-Trial-Version.zip">Download Trial version and sample project (4MB)</a></p>
<p>When you upgrade to the full version, remember to delete the <em>imagecache</em> directory, or you will continue to see the word &#8220;Unlicensed&#8221; on old pictures.</p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/199/demo-version-of-image-resizing-module/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>20 Image Resizing Pitfalls</title>
		<link>http://nathanaeljones.com/163/20-image-resizing-pitfalls/</link>
		<comments>http://nathanaeljones.com/163/20-image-resizing-pitfalls/#comments</comments>
		<pubDate>Tue, 19 May 2009 15:18:49 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[Image Resizing]]></category>
		<category><![CDATA[image resizer]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=163</guid>
		<description><![CDATA[Dozens of articles on server-side image resizing have been written. If we count other tongues, maybe hundreds. These contributions to the community have been invaluable to me, and I truly appreciate the time each author spent to share his or her knowledge.]]></description>
			<content:encoded><![CDATA[<p>
        Dozens of articles on server-side image resizing have been written. If we count other tongues, maybe hundreds. These contributions to the community<br />
        have been invaluable to me, and I truly appreciate the time each author spent to share his or her knowledge.</p>
<p>
        So why am I writing another? </p>
<p>
        Because each article I have read includes one of the errors below, leading readers to write either<br />
        slow, insecure, or incorrectly functioning code.  I have discovered many of these pitfalls the hard way.<br />
         I hope others won&#8217;t have to.
         </p>
<p>Instead of giving step-by-step instructions, this article will simply list pitfalls and the alternatives. I hope to publish my approach in a Part 2.</p>
<h2>
        Security and Performance Pitfalls</h2>
<ol class="normallist">
<li>Not using using(){}. You *must* wrap your Graphics, Bitmap, and MemoryStream objects<br />
            in a using(){} clause, or else they will not get cleaned out of memory for a while.<br />
            Under load this can cause *serious* issues. Read<br />
            <a href="http://blogs.msdn.com/tess/archive/2009/02/03/net-memory-leak-to-dispose-or-not-to-dispose-that-s-the-1-gb-question.aspx"><br />
                <em>to dispose, or not to dispose, that&#8217;s the 1GB question</em></a> if you have<br />
            any doubts regarding the severity of this error.</p>
<p>If you find yourself nesting a lot of using(){} statements, you can also use equivalent try{}finally{} code.</p>
<pre name="code" class="brush:c-sharp">
//Using method. object must implement IDisposable for this to work
using (object a = new object())
{
    using (object b = new object())
    {
        using (object c = new object())
        {

        }
    }
}

//Try finally method
object a = null;
object b = null;
object c = null;
try
{
    a = new object();
    b = new object();
    c = new object();
}
finally
{
    if (a != null) a.Dispose(); //If one of the Dispose methods throws an error, the others will not execute.
    if (b != null) b.Dispose(); //So there is an advantage to using nested using(){} clauses
    if (c != null) c.Dispose(); //You could nest try{} finally{ try{} finally{ try{} finally{}}} to solve that...
    //Two different techniques - take your pick
}
</pre>
</li>
<li>Using on-the-fly image resizing <em>without</em> disk caching! The ASP.NET<br />
            memory cache won&#8217;t cut it here folks &#8211; it gets cleaned out every application reboot,<br />
            and besides, you probably have more images than RAM. Resizing an image is fast,<br />
            but it will still flood the CPU if a single user browses a single page with 20 or more<br />
            resized images on it. This is a do-it-yourself <a href="http://en.wikipedia.org/wiki/Denial-of-service_attack"><br />
                DOS</a> attack. On-the-fly resizing is fine if you have disk caching. </li>
<li><em>Not</em> using on-the-fly resizing. This one bites also. If you decide to convert all<br />
            your images up-front, please realize how difficult it will be to track down the<br />
            originals and resizing them again next time you make a resolution jump. I&#8217;ve been<br />
            through this enough, and it&#8217;s painful &#8211; that&#8217;s why I wrote a dynamic image resizer!
        </li>
<li>Disk-caching without checking for updated (or reverted!) source files. Debugging<br />
            a resized image that won&#8217;t update can eat up lots of time. Make sure you set the<br />
            LastWriteTimeUTC on your cached images to match the source image file (and check<br />
            they match) &#8211; don&#8217;t simply check to see if the source file is newer than the cached<br />
            file, since that will break if you copy an older file over a source image. Always<br />
            use something like RoughCompare() to compare filesystem dates &#8211; *never* inequalities.<br />
            Remember that filesystem dates are less precise than DateTime, and get rounded.</p>
<pre name="code" class="brush:c-sharp">
/// &lt;summary&gt;
/// Returns true if both dates are equal (to the nearest 200th of a second)
/// &lt;/summary&gt;
/// &lt;param name=&quot;modifiedOn&quot;&gt;&lt;/param&gt;
/// &lt;param name=&quot;dateTime&quot;&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
private static bool RoughCompare(DateTime d1, DateTime d2)
{
	return (new TimeSpan((long)Math.Abs(d1.Ticks - d2.Ticks)).Milliseconds &lt;= 5);
}
</pre>
</li>
<li>Disk-caching without cleanup! Another do-it-yourself <a href="http://en.wikipedia.org/wiki/Denial-of-service_attack"><br />
            DOS</a> attack, although not quite as bad as the first. Left unchecked, your cache<br />
            directory could grow very large over a few years as orphaned<br />
            image versions accumulate. If a malicious visitor realizes that you have automatic resizing,<br />
            he could try to fill up your hard drive by requesting an endless variety of resolutions<br />
            for a given image. Of course, security-conscious developers will have cache-limiting<br />
            systems in place. I suggest cleaning out the least recently used 10-20% of the cache<br />
            directory whenever the file limit is reached. Handle locked files gracefully.</li>
<li>Checking the cache size for cleanup every image request. This will swamp your I/O.<br />
        Instead of running that directory listing each time, keep a static counter that  tracks how many <em>new</em> images have been resized<br />
        since the application started. Run the cache cleanup on the first image request and each time the counter passes the cleanup threshold.</li>
<li>Disk caching without protecting the cache directory. Unless you want anonymous users to potentially view the same images<br />
        as authorized users, you need your cache directory locked down. A Web.config file in the directory can do this &#8211; just verify your URL rewriting rules<br />
        don&#8217;t leave another way to access the directory. </p>
<p>
        The cache directory needs to stay inside the application to permit request rewriting to the cached files.  </p>
</li>
<li>Disk caching without proper locking code. This is a minor problem, since the<br />
            consequences are light &#8211; but it is good to remember that 2 image requests for the<br />
            same image size could happen at the same time, and (if they aren&#8217;t cached), they<br />
            may conflict when trying to write to the same file at the same time. You&#8217;ll probably<br />
            get a &#8220;The process cannot access the file because it is being used by another process.&#8221;<br />
            message if this happens. You can prevent this by creating a locking system so that<br />
            only one thread can save a give resized image at a time. Optimally, you want multiple resizes for different images to occur at the same time. If you&#8217;re<br />
            not as concerned about concurrency performance as I was, you could cheat at make the whole resizing method locked. (For new image requests only!) </li>
<li>Writing directly to the output stream. If you&#8217;re caching to disk, but still serving<br />
            the image contents in code, you&#8217;re only supporting a little bit of the HTTP standard,<br />
            and you&#8217;re bypassing all of the work <a id="A1" href="~/11081" runat="server">Thomas<br />
                Marquardt did to bring StaticFileHandler up to snuff</a> . Implement your resizer<br />
                as and HttpModule, not an HttpHandler or you&#8217;re stuck.</li>
<li>Serving a file from disk by loading it into memory. Think about how much RAM your server has,<br />
        how large a single image is, how long<br />
            it has to stay in memory before users finish downloading it, and how many users<br />
            you have requesting images. Don&#8217;t load anything into memory after the initial resize.</p>
<p><a href="http://msdn.microsoft.com/en-us/library/dyfzssz9.aspx">WriteFile()</a><br />
            serves directly from disk, and is *much* safer and more efficient. However &#8211; you<br />
            shouldn&#8217;t be using WriteFile() either if you can avoid it. Letting StaticFileHandler<br />
            do its job is a much better choice. </p>
</li>
<li>Making an HttpHandler instead of an HttpModule. I actually did this in<br />
            v1.0, and it was a *mess*, as well as being non-optimal from a performance standpoint.<br />
            There are several problems with doing this as an HttpHandler.</p>
<ol class="normallist">
<li>It&#8217;s very difficult to make an HttpHandler catch only *some* requests (i.e., those<br />
                    requesting resizing), for a certain extension. It&#8217;s very hard, in fact, and involves<br />
                    subclassing DefaultHttpHandler and re-implementing a lot of code. While that&#8217;s possible<br />
                    on IIS5/6/7 classic, it doesn&#8217;t work on IIS7 Integrated. So IIS7 integrated is a<br />
                    complete deal-breaker if you want to let standard images alone. </li>
<li>It&#8217;s difficult to pass a request from one HttpHandler to another. When building<br />
                    an image resizer, we don&#8217;t want to be responsible for serving the resized file,<br />
                    just making sure the resized version has been cached to disk, and then rewriting<br />
                    the request to point to that file. An HttpModule, on the other hand, is perfectly<br />
                    suited to checking for image resize requests, caching the results, and rewriting<br />
                    the request so StaticFileHandler or whatever is the default in IIS 8 , 9, or 10<br />
                    can take of it. I do this in PostAuthorizeRequest, by calling context.RewritePath(virtualPath, false);</li>
</ol>
</li>
<li>Not setting context.Response.ContentType properly. You&#8217;ll get all kinds of interesting,<br />
            varied, and peculiar results from browsers if you omit this step. Things can be really<br />
            interesting if the format is changed during the resize, since the extension will<br />
            match the original format. </li>
<li>Obvious, but you should have caching enabled for your images, regardless<br />
            of whether they are being resized or not. Disk caching is great, but memory caching allows for even faster responses to frequently requested images, and shouldn&#8217;t be omitted.<br />
            In addition, HttpCacheability.Public enables client and proxy caching too, so browsers and some firewalls will cache the result from the server. You can adjust the amount of time<br />
            the files are cached with SetExpires.</p>
<p>
            This is the code I use during PreSendRequestHeaders
        </p>
<pre name="code" class="brush:c-sharp">
HttpApplication app = sender as HttpApplication;
HttpContext context = (app != null) ? app.Context : null;

if (context != null &amp;&amp; context.Items != null &amp;&amp; context.Items[&quot;FinalContentType&quot;] != null &amp;&amp; context.Items[&quot;FinalCachedFile&quot;] != null)
{
	//Clear previous output
	//context.Response.Clear();
	context.Response.ContentType = context.Items[&quot;FinalContentType&quot;].ToString(); //FinalContentType is set to image/jpeg or whatever the image mime-type is earlier in code.
	//Add caching headers
	context.Response.AddFileDependency(context.Items[&quot;FinalCachedFile&quot;].ToString());

	if (context.Items[&quot;ContentExpires&quot;] != null)
		context.Response.Cache.SetExpires((DateTime)context.Items[&quot;ContentExpires&quot;]); //ContentExpires is set to DateTime.Now.AddMinutes(x), where x is how long the clients should locally cache the image before checking for updates.

	//Enables in-memory caching
	context.Response.Cache.SetCacheability(HttpCacheability.Public);
	context.Response.Cache.SetLastModifiedFromFileDependencies();
	context.Response.Cache.SetValidUntilExpires(false);
}
</pre>
</li>
<li>Accepting the file path as a querystring parameter. This mistake makes me cringe<br />
            &#8211; I find it amazing each time how much people trust their filtering code to prevent<br />
            abuse of this feature. (If they have path filtering code at all!) Just&#8230; don&#8217;t&#8230;<br />
            do it&#8230; please. Do you know how many ways there are to encode filenames and circumvent<br />
            pattern-matching techniques? Yes, there are ways to protect this kind of system, but why?</p>
<p>Why choose /resizeimage.ashx?path=~%2fimg%2fproducts%2fbox.jpg&amp;maxwidth=100&amp;maxheight=100<br />
            over /img/products/box.jpg?maxwidth=100&amp;maxheight=100 ?</p>
<p>
            If you&#8217;re stuck in IIS6 and you aren&#8217;t allowed to modify handler mappings, you should look for a better host.</p>
</li>
</ol>
<h2>
        Pitfalls in Image Resizing</h2>
<ol class="normallist">
<li>Using GetThumbnailImage(). <a href="http://msdn.microsoft.com/en-us/library/system.drawing.image.getthumbnailimage.aspx"><br />
            GetThumbnailImage</a>() seems the obvious choice, and many articles recommend its<br />
            use.<br />
            Unfortunately, it always grabs the embedded jpeg thumbnail if present.<br />
            Some photos have these, some don&#8217;t &#8211; it usually depends on your camera. You&#8217;ll wonder<br />
            why GetThumbnailImage works good on some photo, but on others is horribly<br />
            blurred. GetThumbnailImage() isn&#8217;t reliable for photos larger than 10px by 10px for that reason.</li>
<li>Forgetting to set InterpolationMode, SmoothingMode, CompositingQuality, and PixelOffsetMode.<br />
            With all these set properly, you<br />
            should be able to get resized images indistinguishable from Photoshop results. If<br />
            you don&#8217;t, you&#8217;ll end up with trash. GDI has dumb defaults. (BTW, the low-quality<br />
            settings aren&#8217;t always much faster) <a href="http://www.glennjones.net/Post/799/Highqualitydynamicallyresizedimageswithnet.htm"><br />
                This article</a> explains why those are needed to make DrawImage compose the<br />
            image well.</p>
<pre name="code" class="brush:c-sharp">
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode  = SmoothingMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
            </pre>
</li>
<li>Not maintaining aspect ratio. I see this often, and I&#8217;m not sure why &#8211; the math<br />
            isn&#8217;t too hard. Well, for those who are wondering how, I hope this code is rather<br />
            transparent (no pun intended).</p>
<pre name="code" class="brush:c-sharp">
double aspectRatio = imageWidth/imageHeight;
double boxRatio = maxWidth/maxHeight;
double scaleFactor = 0;
if (boxRatio &gt; aspectRatio)
 //Use height, since that is the most restrictive dimension of box.
 scaleFactor = maxHeight / imageHeight;
else
 scaleFactor = maxWidth / imageWidth;

double newWidth = imageWidth * scaleFactor;
double newHeight = imageHeight * scaleFactor;
</pre>
</li>
<li>Not setting the Jpeg quality to 90. You&#8217;ll get huge Jpegs from Image.Save unless<br />
            you pass in the proper parameters. 90 seems to be the magic value &#8211; great quality<br />
            and much lower file size than 100.</p>
<pre name="code" class="brush:c-sharp">
int quality = 90; //90 is the magic setting - really. It has excellent quality and file size.
System.Drawing.Imaging.EncoderParameters encoderParameters = new System.Drawing.Imaging.EncoderParameters(1);
encoderParameters.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)quality);
thumb.Save(stream, GetImageCodeInfo(&quot;image/jpeg&quot;), encoderParameters);

/// &lt;summary&gt;
/// Returns the first ImageCodeInfo instance with the specified mime type. Some people try to get the ImageCodeInfo instance by index - sounds rather fragile to me.
/// &lt;/summary&gt;
/// &lt;param name=&quot;mimeType&quot;&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static ImageCodecInfo GetImageCodeInfo(string mimeType)
{
	ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders();
	foreach (ImageCodecInfo ici in info)
		if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase)) return ici;
	return null;
}
</pre>
</li>
<li>Using the built-in quantization (palette creation) for GIFs, 8-bit PNGs and BMPs.<br />
            The default palette is truly terrible, and while you can specify your own set of<br />
            255 colors &#8211; which ones should they be? The process of determining which colors<br />
            to choose for the palette and to produce the best quality images is call quantization.<br />
            I recommend the very efficient and decent-quality <a href="http://codebetter.com/blogs/brendan.tompkins/archive/2004/01/26/use-gdi-to-save-crystal-clear-gif-images-with-net.aspx"><br />
                octree quantization algorithm</a>. It does have a number of bugs you will have<br />
            to patch. Follow the transparency patch instructions found in the comments. Use the safe version of the library. Patch the Marshal.ReadInt32() bug (original is ReadByte()).<br />
            Change any casts from IntPtr->int to IntPtr->long to make the code 64-bit safe.</p>
<p>I&#8217;m working on adding adjustable Floyd-Steinberg dithering to <a href="http://nathanaeljones.com/products/asp-net-image-resizer/">the version in my resizer </a>, and<br />
           the results have been very promising so far.</p>
</li>
<li>Inheriting the palette from the original image. While at first this seems like an<br />
            <a href="http://getlara.com/north-america/canada/alberta/edmonton/post/2008/10/13/png-jpg-gif-image-resize-with-net-with-transparency"><br />
                easy way to solve the palette problem</a> for GIFs, realize that the bicubic<br />
            resizing will have combined colors, and the new thumbnail may not have any of colors<br />
            of the original image. Also, any operations performed on the bitmap in 8-bit mode<br />
            will be poor quality, and this won&#8217;t allow conversion between image formats. There are<br />
            other ways to keep transparency. This is probably better than leaving the default palette, but YMMV.</li>
<li>Resizing images that don&#8217;t request it. Your code should only activate when an image has a querystring with one of the supported<br />
        commands. Pushing all images through your code is unnecessary. </li>
<li>And one last piece of advice.<strong> Have Good Defaults. Always.</strong>
<p>The output image type should default to the source image type, unless it&#8217;s a BMP or TIFF.<br />
        Default behavior should always preserve aspect ratio.
        </p>
<p>Many developers stop after making their code configurable. They don&#8217;t take that extra 10 minutes to give<br />
        everything smart defaults. Smart defaults distinguish good software from <em>great</em> software.</p>
</li>
</ol>
<p>I hope to post Part 2 soon. I plan on revealing the architecture that has evolved in my resizer,<br />
    and how to design a IIS5/6/7 compatible HttpModule.</p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/163/20-image-resizing-pitfalls/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Server-side Image Resizing Module for ASP.NET, ASP, &amp; PHP/IIS</title>
		<link>http://nathanaeljones.com/162/server-side-image-resizing-module-for-asp-net-asp-phpiis-3/</link>
		<comments>http://nathanaeljones.com/162/server-side-image-resizing-module-for-asp-net-asp-phpiis-3/#comments</comments>
		<pubDate>Sun, 17 May 2009 04:20:03 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[Image Resizing]]></category>
		<category><![CDATA[image resizer]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=162</guid>
		<description><![CDATA[Version 2.0 beta is now available!]]></description>
			<content:encoded><![CDATA[
<p style="position: absolute; height: 5px; width: 5px; top: -3000px">
Open-source image resizing module for ASP.NET. Great for thumbnails and slideshows. Features managed disk caching, jpeg/png/gif support, and bicubic resizing.
</p>
<p><script type="text/javascript">
	$(function() {
		$("#tabs").tabs();
	});
	</script></p>
<style type="text/css">
div.articleBody {
line-height:2.1em;
padding:15px 15px 15px 15px;
}
.smallcomments blockquote{
line-height:1em; font-size:8pt;
}
</style>
<div id="tabs">
<ul>
<li><a href="#why">Why</a></li>
<li><a href="#opensource">Open Source</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#quality">Quality</a></li>
<li><a href="#performance">Performance</a></li>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#changelog">Changelog</a></li>
</ul>
<div id="why">
<h3>You need this module if you work with images.</h3>
<p>Once installed, you can size, crop, and change the format of images from anywhere&#8230; HTML, ASP.NET, ASP, PHP, Flex, or Flash &#8230; Just add the desired behavior to the URL with &#8220;?width=100&#8243; or &#8220;?format=jpg&#8221;. It supports scaling, cropping, rotating, flipping, stretching, padding, borders, transparency, jpeg, png, and gif formats. Aspect ratio is <em>always</em> maintained unless &#038;stretch=fill is specified.
</p>
<p class="rightbox">The difference between the actual size and wanted size  of an uploaded image is inversely proportional to the quantiy of time available and the sluggishness of Photoshop.</p>
<p>Server-side image resizing is one of those little features that can have incredible ROI. It can save webmasters several hours each day, and gives you the ability to change the resolution of an image without having to hunt up the original. <%--However, many of the approaches currently on the web don't take security or performance into account.--%></p>
<p>I developed this image resizing system around two years ago. I needed something very intuitive, simple, secure, and efficient. We&#8217;ve been using this system heavily on a live, high-traffic site (<a href="http://youngfoundations.org">youngfoundations.org</a>) for two years, and we have had zero stability problems, memory leaks, or reliability issues with it. It&#8217;s very mature and stable. Version 2.0 is even faster and more scalable than the previous versions, and includes dozens of new features.</p>
<p>Check out the <a href="~/11201" runat="server">samples page</a> to see what it can do. The <a href="~/11151" runat="server">license</a> is much like the GPL, and allows you to redistribute the source code as part of a larger project. You&#8217;ll find the source code very clean, organized, and well commented. </p>
<div class="rightbox">
<a class="download" href="https://www.e-junkie.com/ecom/gb.php?i=254264&#038;c=single&#038;cl=41912">Download Source ($69)</a></p>
<p style="margin-bottom:0;padding-top:0;font-size:7.5pt; color:#909090;">Includes v2.0b &#8211; May 16, 2009</p>
<p><a href="~/11151" runat="server">License</a><br />
<a href="~/11201" runat="server">Samples</a><br />
<a href="~/11141" runat="server">Installation</a></p>
<p>If you have any questions after reading the documentation, leave a comment or e-mail <strong>nathanael.jones@gmail.com</strong>. </p>
</div>
<p>Although I typically release my components for free, I decided to charge a &#8216;download fee&#8217; for this one to help support my other open-source projects. Don&#8217;t worry, this component is still open-source, and the license permits source redistribution as part of a larger system. However, I&#8217;m asking that people who want to integrate this component purchase the download instead of ripping it out of another open-source project. My free to non-free LOC (lines of code) ratio is still over 40 to 1, and I plan on keeping it that way.  I trust this will keep everybody happy.</p>
<p>Your donations are the reason Version 2 exists.</p>
<h2>A few user comments&#8230;</h2>
<div class="smallcomments">
<blockquote>
<p>To anyone thinking of using this module, it worked great and saved me lots of custom-coding time for a client project. On top of that, Nathanael reworked some of the code to my needs for a very reasonable rate, so I have the custom solution I needed. </p>
<p>
If you&#8217;re working on an image display project in asp.net, just use this code and enjoy. It&#8217;s bullet proof!</p>
<p>Jason<br/><br />
<a href="http://webmanufactory.blogspot.com/">http://webmanufactory.blogspot.com/</a>
</p></blockquote>
<blockquote>
<p>Nathanael, I downloaded your code this weekend to use in our portal code base. I was tired of writing code to pick between icon/medium/large/original sizes of the images for different pickers / viewers etc. I was skeptical as to whether the component would be fast enough for the performance we are after in our software, but I am glad to report that your code is super fast and does exactly what you say it does. It was easy to install, and works perfectly. Thank you so much for building this tool, it is going to come in handy!</p>
<p>I do have one question though, which is why the tool will not resize an image to be larger than the original source image. I have a few images that are 800X600 jpegs, and when I tried ?width=1024 the code returned the original image size of 800X600? Is this by design? How hard would it be to make the code upsize and image? <em>[v2 allows this with &amp;scale=both]</em></p>
<p>
Again, thank you for the tool, and to other skeptic developers, Nathan&#8217;s code rocks out of the box.
</p>
<p>Thanks, <br/><br />
Steve
</p></blockquote>
<blockquote><p>
By Dave<br />
just like to say, since using the code (and getting working and donating in next to no time) we have moved three or four of our sites over to using this technique. Previously we had manually run batch resizes initially and then made clients create multiple sizes of the image&#8230; very time consuming and some clients weren&#8217;t technically up to the task. Having been after such a solution for so long, i was really worried that the time to generate images on the fly and the resultant quality would be a problem, but WOW&#8230;.. this code nails it entirely&#8230;i can only say if you are reading these comments, worry not and get this code straight away&#8230;. so much work seems to have gone into this solution, i can&#8217;t rate it too highly&#8230; my find of the year&#8230;&#8230;. thanks Nathanael&#8230;.
</p></blockquote>
</div>
</div>
<div id="buy">
</div>
<div id="features">
<p>Querystring-based resizing is super-simple and can be used from any language (HTML, ASP.NET, PHP, Flash, Flex, etc). </p>
<p>Supports scaling, cropping, rotating, flipping, stretching, padding, borders, transparency, and the 4 main image formats. Aspect ratio is maintained unless <strong>&amp;stretch=fill</strong> is specified.</p>
<p>For example, to make an image 400px or less wide, just add <strong>?width=400</strong>. If you want to set both the maximum height and width, use <strong>?maxwidth=400&amp;maxheight=400</strong>.</p>
<p>Sometimes you need all your images to be exactly the same size, regardless of the original aspect ratio. There are several solutions to this. If you specify both <strong>width</strong> and <strong>height</strong>, whitespace will be automatically added to two sides to make up the difference. Set whitespace color with <strong>&amp;bgcolor</strong>. If you don&#8217;t want whitespace, you can try <strong>&amp;crop=auto</strong>. This &#8220;smart cropping&#8221; will figure out the minimal amount of cropping needed to center the image in the box without changing the aspect ratio or leaving whitespace. And, of course, you could stretch your images with <strong>&amp;stretch=fill</strong>&#8230; if you really wanted to.</p>
<p>Convert between jpg, png, gif, tiff, and bmp images with <strong>&amp;format=jpg|png|gif</strong>. Adjust the quality/size tradeoff of Jpegs with <strong>&amp;quality=1-100</strong>, and the palette size of GIFs and PNGs with <strong>&amp;colors=2-255</strong></p>
<p>Not just for thumbnails &#8211; useful for resizing entire slideshows in real-time as the user changes the window size.</p>
<p>Check out the <a href="~/11201" runat="server">samples page</a> to see the features in action.</p>
<ul class="normallist">
<li>Supported input formats: BMP, GIF, JPG, PNG and TIFF</li>
<li>Supported output formats: <strong>JPG, PNG, and GIF</strong></li>
</li>
</ul>
</div>
<div id="quality">
<p>Bicubic resampling is used exclusively, resulting in very high-quality output. Jpeg compression is excellent, and uses the native windows JPEG encoder. Version 2.0 adds octree quantization for GIF and 8-bit PNG output, providing excellent palette generation for those also.</p>
<p>In my tests, the native windows JPEG encoder and Photoshop have the same visual quality and file size results. Expect photoshop-quality results for both JPEG and PNG output. </p>
<p>GIF and 8-bit PNG output is drastically improved in version 2.0 due to the addition of octree quantization. The primary difference you will notice between Photoshop and module output is the lack of dithering. In most scenarios this doesn&#8217;t affect visual quality much and actually makes the image look &#8216;cleaner&#8217;. Very gentle gradients do benefit from dithering, as do gradients against transparency. </p>
<p>File sizes are excellent &#8211; don&#8217;t expect a measurable improvement when you use Photoshop or ImageReady to compress your images with the same quality settings.</p>
</div>
<div id="performance">
<p>The key to the excellent performance of this module is the disk caching system. This allows even a relatively low-end server to handle the same number of dynamic requests as static file requests.</p>
<ul class="normallist">
<li>Caching doesn&#8217;t get in your way &#8211; if you modify the source file, the resized versions are also updated.</li>
<li>You can resize the same image to several different sizes, and the different versions are cached separately.</li>
<li><strong>Managed disk cache</strong>. If the number of cached files exceeds the configured threshold, a cleanup pass is made on the directory, and a block of the least recently used files are removed.</li>
<li>Client-side caching directives are sent, minimizing repetitive requests from the same client.</li>
<li>Thumbnail generation in <strong>under 20 milliseconds</strong> for most images.</li>
<li>Suitable for slideshows and lists of images. Even the initial resizing of 50 images concurrently isn&#8217;t noticeably slow.</li>
<li>Good with large source files (1-5MB)</li>
</ul>
</div>
<div id="requirements">
<ul class="normallist">
<li>Runs on any of the following following: Windows 2000,  Server 2000, XP, Server 2003, Vista, Server 2008, or Windows 7*. *Not tested, but I don&#8217;t know of any breaking changes to IIS occuring.</li>
<li>Requires .NET 2.0 or higher to be installed on the server and enabled on the website.</li>
<li>The customer must have the ability to copy and paste configuration settings, download files, copy files, and follow simple directions for configuring IIS 6 and below.</li>
<li>Users of version 1.0 should get the free upgrade to 1.2 if they wish to use IIS7 Integrated Mode.</li>
</ul>
</div>
<div id="opensource" style="padding-left:25px">
<p>While I do charge a download fee, the license allows you to redistribute the source as part of another open-source project. I&#8217;m trying to achieve several objectives: 1) Remove the need for users to track licenses, 2) Promote use in open-source projects, 3) Still provide financial support so the project can continue to evolve rapidly.</p>
<ul class="normallist">
<li>Complete source code and an example project is included</li>
<li>Clean, well documented, well designed code. </li>
<li>Great platform to build your own image processing systems on. You can use the disk caching system or image processing code independently from each other.</li>
<li>Very extensible, easy to add new features.</li>
</ul>
<p>CustomFolders.cs can be *easily* modified to do all kinds of work:</p>
<ul class="normallist">
<li>Build rules to automatically resize images based on folder name or other criteria. </li>
<li>Add a new syntax for abbreviating or simplifying common resizing operations. Like /resize(w,h)/.</li>
<li>Easily perform URL rewriting for your images with String.Replace calls. (Regexes work too, if you like them).</li>
</ul>
<p>By purchasing the download, you are permitted to </p>
<ol>
<li>Modify and use the component in all of your projects. </li>
<li>Redistribute the source code as part of another project, provided the component<br />
is less than 5% of the project (in lines of code), and you keep this information attached.</li>
<li>If you received the source code as part of another open source project,<br />
you cannot extract it (by itself) for use in another project without purchasing a download<br />
from http://nathanaeljones.com/. If nathanaeljones.com is no longer running, and a download<br />
cannot be purchased, then you may extract the code.</li>
</ol>
<p>This is basically a developer license &#8211; if you aren&#8217;t a developer (i.e., a hosting company wanting a site or server license), please e-mail me (nathanael.jones@gmail.com).</p>
</div>
<div id="changelog">
<h2>Releases</h2>
<ol class="normallist">
<li>1.0 &#8211; August 6, 2008 (Initial release to the public.) </li>
<li>1.2 &#8211; Nov 23, 2008 (<a href="~/11171" runat="server">Upgrade Notes from 1.0 to 1.2</a>) (<a href="~/11131" runat="server">Original product page</a>) </li>
<li>2.0a Jan 30, 2009 (E-mail distribution)</li>
<li>2.0a Mar 4, 2009 (E-mail distribution)</li>
<li>2.0b May 16, 2009  (<a href="~/11181" runat="server">Upgrade notes from 1.2 to 2.0</a>)
</li>
</div>
</div>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/162/server-side-image-resizing-module-for-asp-net-asp-phpiis-3/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Image Resizer 2.0 &#8211; Upgrade notes</title>
		<link>http://nathanaeljones.com/159/image-resizer-2-0-upgrade-notes/</link>
		<comments>http://nathanaeljones.com/159/image-resizer-2-0-upgrade-notes/#comments</comments>
		<pubDate>Sun, 17 May 2009 04:12:57 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[Image Resizing]]></category>
		<category><![CDATA[image resizer]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=159</guid>
		<description><![CDATA[Version 2.0 includes dozens of new features and performance enhancements. Upgrading is simple, but there are a few things you should know.]]></description>
			<content:encoded><![CDATA[<h2>Behavior changes</h2>
<ul class="normallist">
<li>When both <strong>?width=x&amp;height=y</strong> are specified, the default behavior has changed. Instead of stretching the image and losing aspect ratio, whitespace is added (controlled with <strong>&amp;bgcolor=color name|code</strong>). You can reinstate the original behavor by adding <strong>&amp;stretch=fill</strong>. Based on customer feedback.</li>
<li>ICC profiles are now read on source images, but changes are merged into the image before output. Since most browsers (including Firefox) do not read ICC profiles, this should produce consistent cross-browser behavior. If you want to ignore the original ICC profile for an image, add <strong>&amp;ignoreicc=true</strong>.</li>
<li>The CustomFolders.cs format has changed, and is now much, much more powerful. All querystring commands can be used, and URL rewriting can be performed. (AllowURLRewriting must be true).</li>
</ul>
<h2>New features</h2>
<p>Check out the <a href="~/11201">samples page</a> to see these features in action.</p>
<ul class="normallist">
<li>Automatic cropping and centering can be performed with <strong>?crop=auto&amp;width=x&amp;height=y</strong>. You can also choose a crop rectangle from the source image manually with <strong>&amp;crop=(x1,y1,x2,y2)</strong>. Negative values indicate distances relative to the bottom-right corner. (10,10,-10,-10) would crop a 10-pixel border off the source image.</li>
<li>Support for 8-bit PNG file output with <strong>&amp;colors=2-255</strong>. Sets the palette size for both PNG and GIF images.</li>
<li>GIF and 8-bit PNG files are now much, much better. Instead of using the GDI default palette, octree quantization is used to calculate the optimal color palette for each image.</li>
<li>Upscaling can be enabled using &amp;scale=both. The default is &amp;scale=downscaleonly. &amp;scale=upscaleonly is also available for certain zooming situations.</li>
<li> Image rotation is supported in degrees. Ex. &amp;rotate=45. Image sizing is performed before rotation, so rotation will cause the final image size to be larger than the size specified in the querystring.</li>
<li>Image flipping can be performed using <strong>&amp;flip=h|v|both.</strong> To flip the source image before other commands occur, use <strong>&amp;sourceFlip=h|v|both</strong>.</li>
<li>BMP and TIFF source files are now supported.</li>
<li>You can add padding around the image with <strong>&amp;paddingWidth=px&amp;paddingColor=color|hex</strong>. paddingColor defaults to bgcolor, which defaults to white.</li>
<li> You can add a border around the image with <strong>&amp;borderWidth=px&amp;borderColor=color|hex</strong>.</li>
<li>Transparency is maintained while resizng PNG files, and when converting from PNG to GIF.</li>
<li>DisableCacheCleanup=true|false is now supported in Web.config. If you have plenty of disk space and wish to manually purge old files from the cache, this setting will allow it. Leave the default value to false unless you have a very good reason.</li>
<li> DiskCacheAlwaysInvalid=true|false is a new Web.config setting for debugging. It forces every request to update the disk cache before responding. Very, very slow, so make *sure* this is false before you launch.</li>
<li>Websites located on UNC paths are now supported. (Ex. \\server\share\website).</li>
</ul>
<h2>Installation</h2>
<ol class="normallist">
<li>Replace the old files with the new files from the ImageResizer folder. If you are using the .DLL, replace the DLL in the bin folder.  Keep a copy of your old CustomFolders.cs if you have modified it.</li>
<li>Delete the /imagecache directory.</li>
<li>If you use both width and height to stretch any images, add <strong>&amp;stretch=fill</strong> to those URLs to maintain the same behavior.</li>
<li>Migrate CustomFolders.cs if you have modified it. The new file includes source comments explaining the process.</li>
</ol>
<p>If you haven&#8217;t upgraded to 1.2, follow <a href="~/11171"> these instructions first</a></p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/159/image-resizer-2-0-upgrade-notes/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Image Resizer v2 Examples</title>
		<link>http://nathanaeljones.com/155/image-resizer-v2-examples/</link>
		<comments>http://nathanaeljones.com/155/image-resizer-v2-examples/#comments</comments>
		<pubDate>Fri, 08 May 2009 19:23:59 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[Image Resizing]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=155</guid>
		<description><![CDATA[Here are some examples of what can be done with Version 2.0. ]]></description>
			<content:encoded><![CDATA[<p>Here are some examples of what can be done with Version 2.0.</p>
<p>Hover over the images to view the querystrings used. All commands can be combined.</p>
<h2>Resizing using maxwidth and/or maxheight</h2>
<p>Aspect ratio is always maintained with maxwidth and maxheight. The image is scaled to fit within those bounds.</p>
<p>    <img src="red-leaf.jpg?maxwidth=300" title="With ?maxwidth=300"/><br />
    <img  src="grass.jpg?maxheight=300"  title="With ?maxheight=300"/></p>
<p>    <img  src="tulip-leaf.jpg?maxwidth=300&amp;maxheight=300"  title="With ?maxwidth=300&amp;maxheight=300"/></p>
<h2>Resizing using width and height</h2>
<p>Specifying only one of <em>width</em> or <em>height</em> will behave the same as using <em>maxwidth</em><br />
    or <em>maxheight</em>. The difference is when you specify both.</p>
<p>Specifying both width and height will force the image to those exact dimensions, unless the<br />
    image is already smaller (see scale). This is done by adding whitespace to the image. To center and crop instead, use<br />
    <strong>&amp;crop=auto</strong>. To lose aspect ratio and fill the specified rectangle, use <strong>&amp;stretch=fill</strong>.</p>
<p>    <img style="border:1px solid gray;" src="grass.jpg?width=200&#038;height=200"  title="Shown with a border so you can see the added whitespace. ?width=200&#038;height=200"/><br />
     <img src="grass.jpg?width=200&#038;height=200&#038;bgcolor=black"  title="With ?width=200&#038;height=200&#038;bgcolor=black"/><br />
    <img  src="red-leaf.jpg?width=100&#038;height=200&#038;stretch=fill" title="Distorted to 100x200. ?width=100&#038;height=200&#038;stretch=fill"/><br />
    <img src="red-leaf.jpg?width=100&#038;height=200&#038;crop=auto"   title="Cropped to 100x200. ?width=100&#038;height=200&#038;crop=auto"/></p>
<h2>Scaling</h2>
<p>By default, images are not upscaled. If an image is already smaller than width/height/maxwidth/maxheight, it is not resized.<br />
    To upscale images, use <strong>?scale=both</strong>. <strong>?scale=downscaleonly</strong> is the default.</p>
<p>    <img src="tractor-tiny.jpg?scale=both&#038;width=200"  title="Upscaled from 100px to 200px using ?scale=both&#038;width=200" /><br />
    <br />
    The slight blur around the edges is a bug in Graphics.DrawImage(). You can control the color by setting <strong>&amp;bgcolor=color|hex</strong>.</p>
<h2>Cropping</h2>
<p>To enable cropping, you can use <strong>&amp;crop=auto</strong>, which minimally crops and centers to preserve aspect ratio, or custom cropping.</p>
<p><strong>&amp;crop=(x1,y1,x2,y2)</strong> specifies the rectangle to crop on the image. You can still resize and modify the cropped portion<br />
    using the other commands as normal. Negative coordinates are relative to the bottom-right corner &#8211;<br />
    which makes it easy to trim off a 50-pixel border by specifying <strong>&amp;crop=(50,50,-50,-50)</strong>.</p>
<p>    <img id="Img5" src="tractor.jpg?crop=(10,150,200,350)"   title="Cropping out a 200x200 square using ?crop=(10,150,200,350)" /><br />
    <img id="Img6" src="tractor.jpg?crop=(60,200,250,400)"    title="Cropping out a 200x200 square using ?crop=(60,200,250,400)"  /><br />
    <img id="Img7" src="tractor.jpg?crop=auto&#038;width=300&#038;height=150"    title="Cropping out to 300x150 square using ?crop=auto&#038;width=300&#038;height=150"/></p>
<h2>Rotation</h2>
<p>Rotation is easy &#8211; just specify the number of degrees. You may want to set bgcolor also.</p>
<p>    <img id="Img8" src="red-leaf.jpg?rotate=30&#038;maxwidth=100"   title="With ?rotate=30&#038;maxwidth=100"/></p>
<p>    <img id="Img9" src="tractor.jpg?rotate=-30&#038;crop=(60,200,250,400)"   title="Using ?rotate=-30&amp;crop=(60,200,250,400)" /></p>
<p>    <img id="Img10" src="grass.jpg?rotate=45&#038;width=200&#038;height=200&#038;crop=auto&#038;bgcolor=black"  title="With ?rotate=45&#038;width=200&#038;height=200&#038;crop=auto&#038;bgcolor=black"/></p>
<h2>Flipping</h2>
<p>You can horizontally or verically flip an image, as well as both. <strong>&amp;flip=h|v|both</strong></p>
<p>    <img id="Img11" src="tractor.jpg?flip=v&#038;crop=(60,200,250,400)"   title="Using ?flip=v&#038;crop=(60,200,250,400)"  /><br />
     <img id="Img12" src="tractor.jpg?flip=both&#038;crop=(60,200,250,400)"   title="Using ?flip=both&#038;crop=(60,200,250,400)"/><br />
    <img id="Img13" src="tractor.jpg?crop=(60,200,250,400)"   title="Using ?crop=(60,200,250,400)"  /><br />
     <img id="Img14" src="tractor.jpg?flip=h&#038;crop=(60,200,250,400)"   title="Using ?flip=h&#038;crop=(60,200,250,400)" /></p>
<h2>Source flipping</h2>
<p>Since normal flipping applies after rotation and cropping occur, it can be<br />
    difficult to work with if you are just wanting the source image flipped before the other<br />
    adjustments are applied. To flip the source prior to work, use <strong>&amp;sourceFlip=h|v|both</strong>.</p>
<p>Note how the same crop coordinates return different sections of the image. This is because the source image is flipped before *anything* happens.</p>
<p>    <img id="Img20" src="tractor.jpg?maxwidth=100"   title="Using ?maxwidth=100" /><br />
    <img id="Img15" src="tractor.jpg?crop=(0,0,100,100)"   title="Using ?crop=(0,0,100,100)"/><br />
    <img id="Img16" src="tractor.jpg?flip=both&#038;crop=(0,0,100,100)"   title="Using ?flip=both&#038;crop=(0,0,100,100)" /></p>
<h2>Stretching</h2>
<p>To stretch an image to width and height, use <strong>&amp;stretch=fill</strong>. </p>
<p>    <img id="Img17" src="tractor.jpg?stretch=fill&#038;width=200&#038;height=100"   title="Using ?stretch=fill&#038;width=200&#038;height=100)"  /></p>
<h2>Padding</h2>
<p>You can add padding around the image with <strong>&amp;paddingWidth=px</strong> and<br />
    <strong>&amp;paddingColor=color|hex</strong>. paddingColor defaults to bgcolor, which defaults to white.<br />
    This setting can be&#8230;. useful.</p>
<p>    <img id="Img18" src="grass.jpg?maxwidth=200&#038;paddingColor=black&#038;paddingwidth=20"  title="With ?maxwidth=200&#038;paddingColor=black&#038;paddingwidth=20"/></p>
<h2>Borders</h2>
<p>You can add a border around the image with <strong>&amp;borderWidth=px</strong>,<br />
    <strong>&amp;borderColor=color|hex</strong>.</p>
<p>    <img id="Img19" src="grass.jpg?maxwidth=200&#038;paddingColor=white&#038;paddingwidth=20&#038;borderWidth=8&#038;borderColor=808080" title="With ?maxwidth=200&#038;borderWidth=8&#038;borderColor=808080&#038;paddingwidth=20"/></p>
<h1>Output format</h1>
<p>GIF, JPG, and PNG output is supported. BMP and TIFF input fils are additionally supported, and every format can be converted to any other format with <strong>&amp;format=jpg|png|gif</strong></p>
<h2>Jpeg compression levels 0-100 (&amp;quality=0-100)</h2>
<p>The sizes of these images range from 855B to 12KB. The largest size jump is from 90 to 100 (5KB to 12KB). I think 90 is a good balance, and is therefore the default.</p>
<p>     <img id="Img31" src="tulip-leaf.jpg?quality=0&#038;maxwidth=100"  /><br />
    <img id="Img21" src="tulip-leaf.jpg?quality=10&#038;maxwidth=100"  /><br />
    <img id="Img22" src="tulip-leaf.jpg?quality=20&#038;maxwidth=100"  /><br />
    <img id="Img23" src="tulip-leaf.jpg?quality=30&#038;maxwidth=100"  /><br />
    <img id="Img24" src="tulip-leaf.jpg?quality=40&#038;maxwidth=100"  /><br />
    <img id="Img25" src="tulip-leaf.jpg?quality=50&#038;maxwidth=100"  /><br />
    <img id="Img26" src="tulip-leaf.jpg?quality=60&#038;maxwidth=100"  /><br />
    <img id="Img27" src="tulip-leaf.jpg?quality=70&#038;maxwidth=100"  /><br />
    <img id="Img28" src="tulip-leaf.jpg?quality=80&#038;maxwidth=100"  /><br />
    <img id="Img29" src="tulip-leaf.jpg?quality=90&#038;maxwidth=100"  /><br />
    <img id="Img30" src="tulip-leaf.jpg?quality=100&#038;maxwidth=100"  /></p>
<h2>Quantization (8-bit PNG and GIFs)</h2>
<p>The default GDI quantization is terrible, and produces lousy GIFs. Using the Octree quantizer, you can<br />
    control the number of colors (and therefore the size and quality) of an image using <strong>&amp;colors=2-255</strong></p>
<h3>GIFs, in 4,8,16,32,64,128, and 256 colors</h3>
<p>    <img id="Img32" src="tractor.jpg?&#038;maxwidth=100&#038;format=gif&#038;colors=4"  /><br />
    <img id="Img33" src="tractor.jpg?&#038;maxwidth=100&#038;format=gif&#038;colors=8"  /><br />
    <img id="Img34" src="tractor.jpg?&#038;maxwidth=100&#038;format=gif&#038;colors=16"  /><br />
    <img id="Img35" src="tractor.jpg?&#038;maxwidth=100&#038;format=gif&#038;colors=32"  /><br />
    <img id="Img36" src="tractor.jpg?&#038;maxwidth=100&#038;format=gif&#038;colors=64"  /><br />
    <img id="Img37" src="tractor.jpg?&#038;maxwidth=100&#038;format=gif&#038;colors=128"  /><br />
    <img id="Img38" src="tractor.jpg?&#038;maxwidth=100&#038;format=gif&#038;colors=256"  /></p>
<h3>PNGs, in 4,8,16,32,64,128, 256, and 16 million colors</h3>
<p>        <img id="Img39" src="tractor.jpg?&#038;maxwidth=100&#038;format=png&#038;colors=4"  /><br />
    <img id="Img40" src="tractor.jpg?&#038;maxwidth=100&#038;format=png&#038;colors=8"  /><br />
    <img id="Img41" src="tractor.jpg?&#038;maxwidth=100&#038;format=png&#038;colors=16"  /><br />
    <img id="Img42" src="tractor.jpg?&#038;maxwidth=100&#038;format=png&#038;colors=32"  /><br />
    <img id="Img43" src="tractor.jpg?&#038;maxwidth=100&#038;format=png&#038;colors=64"  /><br />
    <img id="Img44" src="tractor.jpg?&#038;maxwidth=100&#038;format=png&#038;colors=128"  /><br />
    <img id="Img45" src="tractor.jpg?&#038;maxwidth=100&#038;format=png&#038;colors=256"  /><br />
    <img id="Img46" src="tractor.jpg?&#038;maxwidth=100&#038;format=png"  /></p>
<h2>Transparent GIFs and PNGs</h2>
<h3>Transparency is maintained when resizing PNGs</h3>
<p>    <img id="Img49" src="/wp-content/uploads/2009/10/sun_256.png"  /><br />
    <img id="Img47" src="/wp-content/uploads/2009/10/sun_256.png?maxwidth=100"  /><br />
    <img id="Img48" style="background-color:black" src="/wp-content/uploads/2009/10/sun_256.png?&#038;maxwidth=200"  /></p>
<h3>Matte backgrounds can be applied with bgcolor. </h3>
<p>    <img id="Img50" src="/wp-content/uploads/2009/10/moon_256.png?maxwidth=100&#038;bgcolor=red"  /><br />
    <img id="Img51" src="/wp-content/uploads/2009/10/moon_256.png?maxwidth=100&#038;bgcolor=blue"  /><br />
    <img id="Img52" src="/wp-content/uploads/2009/10/moon_256.png?maxwidth=100&#038;bgcolor=green"  /></p>
<h3>Transparent PNGs can be converted to transparent GIFs</h3>
<p>It&#8217;s ugly, but GIFs only get 1 bit for transparency.</p>
<p>    <img id="Img1" src="/wp-content/uploads/2009/10/saturn_256.png?format=gif"  /></p>
<h2>Single frames can be extracted from animated GIFs with &amp;frame=x</h2>
<p>    <img id="Img2" src="/wp-content/uploads/2009/11/2_computers.gif"  /><br />
    <img id="Img3" src="/wp-content/uploads/2009/11/2_computers.gif?frame=5"  /><br />
    <img id="Img4" src="/wp-content/uploads/2009/11/2_computers.gif?frame=8"  /><br />
    <img id="Img53" src="/wp-content/uploads/2009/11/2_computers.gif?frame=10"  /></p>
<p></p>
<h2>/resize(width,height,format)/ syntax</h2>
<p>For the URL-conscious, you can replace <strong>img/image.jpg?maxwidth=x&amp;maxheight=y</strong> with<br />
     <strong>img/resize(x,y)/image.jpg</strong>. A third argument, format, is also allowed.</p>
<p><em>However</em>, enabling this (AllowURLRewriting) will make the ASP.NET URL authorization system unable to match URLs&#8230;. But that only affects you if you set permissions in web.config.</p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/155/image-resizer-v2-examples/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Performance killer: Disk I/O</title>
		<link>http://nathanaeljones.com/153/performance-killer-disk-io/</link>
		<comments>http://nathanaeljones.com/153/performance-killer-disk-io/#comments</comments>
		<pubDate>Mon, 16 Feb 2009 11:44:05 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[General Programming]]></category>
		<category><![CDATA[computerlinguist.com]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=153</guid>
		<description><![CDATA[Many people think of "performance tuning" as optimizing loops, algorithms, and memory use. In truth, however, you don't get the huge performance gains from optimizing CPU and memory use (which is good), but from eliminating I/O calls.]]></description>
			<content:encoded><![CDATA[<p>Many people think of &#8220;performance tuning&#8221; as optimizing loops, algorithms, and memory use. In truth, however, you don&#8217;t get the huge performance gains from optimizing CPU and memory use (which is good), but from eliminating I/O calls.</p>
<p>Disk I/O is responsible for almost all slow websites and desktop applications. It&#8217;s true. Watch your CPU use next time you open a program, or your server is under load. CPUs aren&#8217;t the bottleneck anymore &#8211; your hard drive is. At the hardware level, the hard drive is the slowest component by an incredibly large factor. Today&#8217;s memory ranges between <a href="http://en.wikipedia.org/wiki/DDR2_SDRAM"> 3200 and 10400 MB/s</a>. In contrast, today&#8217;s desktop hard drive speeds average about 50 MB/s (Seagate 500GB), with high-end drives getting 85MB/s (WD 640, Seagate 1TB). If you&#8217;re looking at bandwidth, hard drives are 200-300 times slower. Bandwidth, though, isn&#8217;t the killer &#8211; it&#8217;s the latency. Few modern hard drives have latencies under 13 milliseconds &#8211; while memory latency is usually about 5 <em>nanoseconds</em> &#8211; 2,000 times faster.</p>
<p>You&#8217;re probably looking at these numbers and thinking, &#8220;13ms is quite fast enough for me, and my app is only dealing with small files&#8221;. However, I have a question: what other applications are using that drive? If you&#8217;re on a shared server, the odds are high that between 25 and 2500 ASP.NET apps are being run on the same drive.</p>
<p>CPU, bandwidth, and memory throttling is becoming more and more common on shared servers and virtualization systems, but practical disk throttling isn&#8217;t even on the horizon from what I can tell. Improper I/O usage from <em>any</em> app affects <em>everybody</em>.</p>
<p>Since hard drives are slow, pending operations go into a queue. So even if your app only needs a single byte of data from the hard drive, it still has to wait its turn. It&#8217;s quite common for disk operations to take several <em><strong>seconds</strong></em> on a shared server under heavy load. If any application on the server is paging to disk from exessive memory use, it can take several minutes, causing a timeout.</p>
<p>Realistic I/O performance is really hard to simulate in a development environment. On a dedicated development machine, disk queues are short, and response times are usually near the optimal 13ms, which tends to give software developers gravely incrorrect ideas about the performance characteristics of their application.</p>
<h2>Load test!</h2>
<p>A good way to acid test your code is to run it on a cheap, overloaded shared server. Webhost4life.com has $10/month plans that are excellent for this purpose. I&#8217;m actually using Webhost4life.com for this site, and while I/O calls consistently take around 1 second each, page views are decently quick since everything is in memory. I can&#8217;t seem to get more than 30-40KB/s bandwidth though, so I&#8217;m probably going to switch hosts soon.</p>
<p>Stress testing can also be accomplished with load testing tools like the <a href="http://www.microsoft.com/downloads/details.aspx?familyid=e2c0585a-062a-439e-a67d-75a89aa36495&amp;displaylang=en">Web Application Stress Tool</a>. Getting realistic results often means multiplying user load by a factor of 1000 or more, since I/O speeds can vary by that much on a production server. So, if you want to handle 50 concurrent users gracefully, test with 50,000.</p>
<p>One good way to make sure your app can handle high load is to get your per-request time very low. There is an <a href="http://www.recommendedwebtools.com/index.php/594/test-your-website-loading-speed-with-lori-firefox-extension/">extension for Firefox</a> that provides TTFB (time-to-first-byte) information that will help you measure this. (Trace.axd is an invaluable tool here also). If requests complete in 13 milliseconds, the odds are good that you can handle 100 of those requests per second.</p>
<h2>Cache smart</h2>
<p>It&#8217;s not too hard to cache files in memory. Here&#8217;s one approach that uses ASP.NET&#8217;s built in caching system:</p>
<pre class="brush:c-sharp">namespace fbs.Filesystem
{
    /// &lt;summary&gt;
    /// Provides cached read access to small, frequently used files. Use carefully!
    //// Uses HostingEnvironment.Cache (same as app cache).
    /// &lt;/summary&gt;
    public static class CachedFileAccess
    {
        /// &lt;summary&gt;
        /// Retrieves the file from the cache, or from disk if neccessary. Exceptions from IO.File.ReadAllText are not caught.
        /// Make sure the file exists and can be accessed before attempting this function.
        /// &lt;/summary&gt;
        /// &lt;param name="file"&gt;Standard pathname (C:\..&lt;/param&gt;
        /// &lt;param name="encoding"&gt;&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public static string ReadAllText(string file, Encoding encoding)
        {

            if (HostingEnvironment.Cache == null) throw new InvalidOperationException("HostingEnvironment.Cache is null");
            string key = "cached_file(" + encoding.EncodingName + ")_" + file.ToLowerInvariant().GetHashCode();

            string value = HostingEnvironment.Cache[key] as string;
            if (value == null)
            {
                value = System.IO.File.ReadAllText(file, encoding);
                HostingEnvironment.Cache.Add(key, value, new System.Web.Caching.CacheDependency(file),
                    System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Low,null);

            }
            return value;
        }
    }
}</pre>
<p>If you must do I/O, File.Exists and File.GetLastWriteTimeUtc are much faster that actually opening a file. The filesystem &#8216;tables&#8217; are <em>often</em> cached in memory, and therefore have much higher <em>average</em> performance.</p>
<p>Don&#8217;t do memory leaks. In-memory caching is good, but if you have a memory leak the app&#8217;s memory space will get moved to disk, effectively slowing all memory access down the hard disk speeds. Not good. And you&#8217;re going to kill everyone else on the same disk. Make sure there is a finite and reasonable limit to what will be cached (especially if you&#8217;re using static variables instead of the ASP.NET cache object).</p>
<h3>Asynchronous=good, synchronous=evil.</h3>
<p>In any application, asynchronous IO calls should be used whenever possible. There&#8217;s a reason that I/O and web service calls in well-built applcations are async &#8211; it is impossible to predict how long I/O operations will take. Any  hard drive operation can take seconds to minutes &#8211; and there is no way to insure a responsive experience to users unless you make sure all I/O calls are out-of-band.</p>
<p>One nice thing about the web is that HTTP request are by nature  async &#8211; so if you have any I/O calls that *must* be made, you can  do them with AJAX later instead of holding up the entire page while they execute.</p>
<h2>Databases are a Good Thing</h2>
<p>Databases are Good. Build the right indexes, and queries will be quick. Most engines cache as much data as possible in memory (usually indexes get priority caching), so a well-designed database will almost alwasy beat standard I/O.</p>
<h2>Hybrid approaches can get the best of both worlds</h2>
<p>Being able to edit your files on disk is really nice &#8211; it tempts people away from database-driven websites all the time. But &#8211; you can have both ease-of-use and performance. nathanaeljones.com can be completely managed through an FTP connection and Notepad, if needed. It&#8217;s nice to drag-and drop files when I need to upload a bunch. It&#8217;s nice to use VS when I want to do some tricky coding.</p>
<p>Every few minutes, a ~1.5 second syncronization routine does a quick GetLastWriteTimeUTC check on the .aspx files that start with an ID, and pushes any changes to the database. The database is read only &#8211; basically a cache of the filesystem. But it allows instant, flexible querying/searching of all the articles, and provides the best of both worlds.</p>
<p>I don&#8217;t edit files manually much anymore, although it does come in handy. Right now I&#8217;m using the web-based article editor to write this &#8211; most of my work occurs through my online ASP.NET markup editor. But for those who like the comfort of Dreamweaver or VS, they can edit their articles in WYSIWYWYG mode all day long <img src='http://nathanaeljones.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>It can be difficult to write a filesystem to database synchronization system properly &#8211; there are a lot of wrong ways to go about it. Done properly, it can scale up to at least 50,000 articles. The routine must be located on a separate thread, and must use a ReaderWriterLock to prevent reads while database is build batch updated with SqlBulkCopy calls. A single call is used to get the listing of .aspx files: System.IO.Directory.GetFiles(appRoot, &#8220;*.aspx&#8221;, System.IO.SearchOption.AllDirectories); A database call downloads the paths and last-modified dates from the database, and the two are cross-referenced to compile a list of created, moved, changed, and deleted files. These changes are then commited to the database using SqlBulkCopy, as well as being logged. Changed files are compared with the SQL copy to generate a Diff report for the log.</p>
<h2>Filesystem state</h2>
<p>Another factor in disk performance is the state of the filesystem. While filesystems are usually built using very smart algorithms, all algorithms slow down as they have to crunch more data. The more files you get on a disk, the slower file lookup and access becomes. I&#8217;ve compared identical 15k disks with no difference except the number of files, and the response time difference would <em>almost</em> make you suspect the one with 500,000 files to be a floppy drive.</p>
<h2>In conclusion</h2>
<p>Disk performance slightly increases each year and SSDs are promising, but physical disks never seem to catch up to our need for space and speed. I/O operations on shared or highly-active servers will probably be slow for many years to come.</p>
<p>Think carefully before you use System.IO, and make sure there aren&#8217;t any alternatives. Pretend you&#8217;re calling a web service each time you use the File class, and be kind to your users.</p>
<p>Say NO to IO</p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/153/performance-killer-disk-io/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Extending Page: Adding Metadata and Stylesheet management</title>
		<link>http://nathanaeljones.com/152/extending-page-adding-metadata-and-stylesheet-management/</link>
		<comments>http://nathanaeljones.com/152/extending-page-adding-metadata-and-stylesheet-management/#comments</comments>
		<pubDate>Thu, 05 Feb 2009 14:01:01 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[PageClass]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=152</guid>
		<description><![CDATA[Adding simple metadata and stylesheet management to the Page class.]]></description>
			<content:encoded><![CDATA[<p>Adding stylesheets or changing meta tags is just <em>slighly</em> clunky with the default Page class provided by ASP.NET. We&#8217;re going to fix that.</p>
<p>Here&#8217;s how to add metadata and stylesheet management by making a subclass which we&#8217;ll call PageBase, since all markup pages will inherit from it.</p>
<h2>Features</h2>
<ul class="normallist">
<li>One-line method calls for  common operations like adding a stylsheet reference or changing metadata. </li>
<li>Duplicate stylsheet/meta tag prevention.</li>
<li>Only 2 instance members will be added to PageBase &#8211; .Stylesheets and .Metadata.<br />
Stylesheet (HtmlLink) and Metadata (HtmlMeta) management is encapsulated in separate classes to keep things clean, and reduce methods inside the page class.</li>
<li>Static PageBase.GetControlsOfType&lt;t>(Control parent) method simplifies all kinds of hierarchy querying.</li>
</ul>
<h2>StylesheetManager methods</h2>
<p>Complete documentation is in the XML comments in the code.</p>
<pre name="code" class="c-sharp">
Page.Stylesheets.AddLink("~/css/effects/css","stylesheet", "text/css");
Page.Stylesheets.AddLink("~/css/effects/css"); //Shorter syntax for typcial usage
Page.Stylesheets.AddLinkIfMissing("~/css/effects/css"); //So User Controls and multi-instance objects can safely include stylesheets without worrying about duplicates.
Page.Stylesheets.RemoveLinks("~/css/effects/css"); //Removes all links matching this path (ResolveUrl() is called on all paths prior to comparison)
Page.Stylesheets.GetControls() //returns all HtmlLink controls within the page header.
Page.Stylesheets.GetHrefs() //Returns a collection of all hrefs from the link tags on the page.
Page.Stylesheets.FindLinkControl(string href); //Case-insensitive, ResolveURL-cleaned search by href value.
</pre>
<h2>MetadataManager methods</h2>
<pre name="code" class="c-sharp">
Page.Metadata[&quot;description&quot;] = &quot;Why everybody ends up extending the Page class&quot;.
if (Page.Metadata[&quot;description&quot;] == &quot;Why everybody ends up extending the Page class&quot;){}

// Returns a collection of all HtmlMeta controls in the header
// Calls  PageBase.GetControlsOfType&lt;HtmlMeta&gt;(_page.Header);
Page.Metadata.GetControls();
Page.Metadata.GetNameContentPairs(); //Returns a NameValueCollection of the metadata name/content pairs.
Page.Metadata.GetControl(&quot;description&quot;) // Returns the first HtmlMeta instance matching the specified name attribute
Page.Metadata.RemoveControls(List&lt;HtmlMeta&gt;) //Detaches each item in the collection from its parent
Page.Metadata.HideControls(List&lt;HtmlMeta&gt;) //Sets .Visible and .EnableViewState to &quot;false&quot; on all items.
Page.Metadata.GetMatches(query) //Returns all HtmlMeta instances with matching Name attributes. Query can be &quot;*&quot; or a comma-delimited list of values like &quot;description,keywords,test&quot;
Page.Metadata.GetNonMatches(quer) //Returns all non-matching HtmlMeta instances
</pre>
<h2>Usage</h2>
<p>We can use this base class in any page my making a single change.<br />
If the page has a code-behind file, just change </p>
<pre name="code" class="c-sharp">
public partial class _Default : System.Web.UI.Page

to

public partial class _Default : fbs.PageBase</pre>
<p>If it is a standalone .aspx file, you can set <code>Inherits="fbs.PageBase"</code>.</p>
<h2>Internals</h2>
<p>All of the querying operations performed only affect controls of a certain type. To greatly simplify the code for both StylesheetManager and MetdataManager, we have added GetControlsOfType&lt;t>(Control parent) to the PageBase class.</p>
<p>This method efficiently builds a collection of all controls in the specified hierarchy that are of type T.</p>
<pre name="code" class="c-sharp">
/// &lt;summary&gt;
/// Iterates over the control structure of the specified object and returns all elements that are
/// of the specified type
/// &lt;/summary&gt;
/// &lt;param name=&quot;parent&quot;&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static List&lt;T&gt; GetControlsOfType&lt;T&gt;(Control parent) where T : Control
{
	return GetControlsOfType&lt;T&gt;(parent, false,false);
}
/// &lt;summary&gt;
/// Iterates over the control structure of the specified object and returns all elements that are
/// of the specified type. If there are two items of the specified type, and one is a child of the other,
/// the childrenOnly and parentOnly parameters can be used to control which is selected. If both are false, both controls are returned.
/// &lt;/summary&gt;
/// &lt;param name=&quot;parent&quot;&gt;The control to search&lt;/param&gt;
/// &lt;param name=&quot;childrenOnly&quot;&gt;If true, only the innermost matching children will be returned.&lt;/param&gt;
/// &lt;param name=&quot;parentsOnly&quot;&gt;If true, only the outermost matching parents will be returned.&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static List&lt;T&gt; GetControlsOfType&lt;T&gt;(Control parent, bool childrenOnly, bool parentsOnly) where T : Control
{
	if (parent == null) return null;
	if (childrenOnly &amp;&amp; parentsOnly) throw
		new ArgumentException(&quot;Only one of childrenOnly and parentsOnly may be true. They are mutually exclusive&quot;);

	//We are doing last-minute initialization to minimize the overhead of building one of these.
	//The List&lt;&gt; constructor should only be called n times, where n is the number of ContentPlaceHolder controls.
	List&lt;T&gt; temp = null;

	if (parent.Controls != null)
	{
		//Loop through all of the child controls
		foreach (Control child in parent.Controls)
		{
			//Recursively search them also.
			List&lt;T&gt; next = GetControlsOfType&lt;T&gt;(child,childrenOnly,parentsOnly);

			//To save on initialization costs.
			if (next != null)
			{
				if (temp == null)
				{
					temp = next; //Use existing collection from recursive call
				}
				else
				{
					//Merge the collections

					//If a the same object is the child of two different parents, this will
					//stop it.
					foreach (T c in next)
					{
						if (!temp.Contains(c)) temp.Add(c);
					}

				}
			}
		}
	}

	//If this item is of the target type, add it
	if ((parent is T))
	{
		//If there are no children or we are trying to discard children
		if (parentsOnly || temp == null)
		{
			//Clear the list and add the parent
			T item = (T)parent;

			temp = new List&lt;T&gt;();

			temp.Add(item);
		}
		else if (!childrenOnly)
		{
			//Append the parent with the children
			T item = (T)parent;

			if (temp == null) temp = new List&lt;T&gt;();

			temp.Add(item);
		}
	}

	return temp;
}
</pre>
<p>The remainder of the PageBase class</p>
<pre name="code" class="c-sharp">
/// <summary>
/// Extends System.Web.UI.Page
/// Adds metadata and stylehseet management
/// </summary>
public partial class PageBase : Page
{

/// &lt;summary&gt;
/// Creates a new PageBase instance.
/// &lt;/summary&gt;
public PageBase()
{
}

protected MetadataManager _metadata = null;
/// &lt;summary&gt;
/// Manages page metadata. Add, remove, and query metadata
/// (Only meta tags with a name attribute are affected, and only those located in the head section)
/// &lt;/summary&gt;
public MetadataManager Metadata {
	get {
		if (_metadata == null) _metadata = new MetadataManager(this);
		return _metadata;
	}
}

protected LinkManager _Stylesheets = null;
/// &lt;summary&gt;
/// Manages all of the HtmlLink controls in the head section of the page.
/// Register, delete, and enumerate all link tags.
/// &lt;/summary&gt;
public LinkManager Stylesheets
{
	get
	{
		if (_Stylesheets == null) _Stylesheets = new LinkManager(this);
		return _Stylesheets;
	}
}
</pre>
<h2>LinkManager</h2>
<pre name="code" class="c-sharp">

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections.Specialized;

namespace fbs
{
    public partial class PageBase
    {
        /// &lt;summary&gt;
        /// Manages &amp;lt;link&gt; tags (controls) on the current page.
        /// &lt;/summary&gt;
        public class LinkManager
        {
            protected Page _page = null;
            /// &lt;summary&gt;
            /// Creates a new Link Manager attached to the specified Page instance
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;parent&quot;&gt;&lt;/param&gt;
            public LinkManager(Page parent)
            {
                _page = parent;
            }
            /// &lt;summary&gt;
            /// Adds a CSS reference. Paths must be 1) relative to the page, 2) application-relative, or 3) absolute
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;href&quot;&gt;&lt;/param&gt;
            public void AddLink(string href)
            {
                AddLink(href, &quot;stylesheet&quot;, &quot;text/css&quot;);
            }
            /// &lt;summary&gt;
            /// Adds a CSS stylsheet reference, but only if there isn't one yet for that path.
            /// Paths must be 1) relative to the page, 2) application-relative, or 3) absolute
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;href&quot;&gt;&lt;/param&gt;
            /// &lt;param name=&quot;resolveFirst&quot;&gt;If true, compares resolved paths instead of raw paths&lt;/param&gt;
            public void AddLinkIfMissing(string href)
            {
                if (this.FindLinkControl(href) == null)
                {
                    AddLink(href);
                }
            }
            /// &lt;summary&gt;
            /// Adds a link tag with the specified rel and type attributes
            /// Paths must be 1) relative to the page, 2) application-relative, or 3) absolute
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;href&quot;&gt;&lt;/param&gt;
            /// &lt;param name=&quot;rel&quot;&gt;&lt;/param&gt;
            /// &lt;param name=&quot;type&quot;&gt;&lt;/param&gt;
            public void AddLink(string href, string rel, string type)
            {
                HtmlLink l = new HtmlLink();

                l.EnableViewState = false;
                l.Href = href;
                l.Attributes[&quot;type&quot;] = type;
                l.Attributes[&quot;rel&quot;] = rel;
                l.AppRelativeTemplateSourceDirectory = _page.AppRelativeTemplateSourceDirectory;
                _page.Header.Controls.Add(l);
            }
            /// &lt;summary&gt;
            /// Removes all meta tags with a matching href.
            /// Paths must be 1) relative to the page, 2) application-relative, or 3) absolute
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;href&quot;&gt;&lt;/param&gt;
            /// &lt;param name=&quot;resolveFirst&quot;&gt;If true, compares resolved paths instead of raw paths&lt;/param&gt;
            public void RemoveLinks(string href)
            {
                bool resolveFirst = false;
                List&lt;HtmlLink&gt; controls = GetControls();
                string searchfor = href;
                if (resolveFirst) searchfor = _page.ResolveUrl(searchfor);
                foreach (HtmlLink hl in controls)
                {
                    string thishref = hl.Href;

                    if (resolveFirst) thishref = _page.ResolveUrl(thishref);

                    if (thishref.Equals(searchfor, StringComparison.OrdinalIgnoreCase))
                    {
                        hl.Parent.Controls.Remove(hl);
                    }
                }

            }
            /// &lt;summary&gt;
            /// Returns a collection of all HtmlLink controls in the page header.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public List&lt;HtmlLink&gt; GetControls()
            {
                return PageBase.GetControlsOfType&lt;HtmlLink&gt;(_page.Header);
            }
            /// &lt;summary&gt;
            /// Returns a collection of the hrefs in each link tag in the head section.
            /// Paths are 1) relative to the page, 2) application-relative, or 3) absolute
            /// &lt;/summary&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public List&lt;string&gt; GetHrefs()
            {
                List&lt;HtmlLink&gt; list = GetControls();
                List&lt;string&gt; hrefs = new List&lt;string&gt;();
                foreach (HtmlLink l in list)
                {
                    hrefs.Add(l.Href);
                }
                return hrefs;
            }
            /// &lt;summary&gt;
            /// Case-insensitive. Returns the first HtmlLink control in the heirarchy that matches the href. Only scans inside the head tag.
            /// returns null if no match is found.
            ///
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;href&quot;&gt;Paths must be 1) relative to the page, 2) application-relative, or 3) absolute&lt;/param&gt;
            /// &lt;param name=&quot;resolveFirst&quot;&gt;If true, compares resolved paths instead of raw paths&lt;/param&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public HtmlLink FindLinkControl(string href)
            {
                bool resolveFirst = false;
                List&lt;HtmlLink&gt; controls = GetControls();
                string searchfor = href;
                if (resolveFirst) searchfor = _page.ResolveUrl(searchfor);
                foreach (HtmlLink hl in controls)
                {
                    string thishref = hl.Href;

                    if (resolveFirst) thishref = _page.ResolveUrl(thishref);

                    if (thishref.Equals(searchfor, StringComparison.OrdinalIgnoreCase)) return hl;
                }
                return null;
            }

        }
    }
}
</pre>
<h2>MetadataManager</h2>
<pre class="c-sharp" name="code">

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections.Specialized;

namespace fbs
{
    public partial class PageBase
    {
        /// &lt;summary&gt;
        /// Manages &amp;lt;meta&gt; tags (controls) on the current page. Not designed for HTTP-EQUIV tags - they are ignored and skipped unless they have a name attribute.
        /// Only deals with meta tags within the Head of the page. The page must have a server-side head tag.
        /// &lt;/summary&gt;
        public class MetadataManager
        {
            protected Page _page = null;

            /// &lt;summary&gt;
            /// Creates a new MetadataManager and attaches it to the current page.
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;parent&quot;&gt;&lt;/param&gt;

            public MetadataManager(Page parent)
            {
                _page = parent;
            }
            /// &lt;summary&gt;
            /// Gets or sets the Content attribute for the specified metadata tag.
            /// Returns null if pair does not exist.
            /// Creates a new metadata tag if it does not exist.
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;name&quot;&gt;&lt;/param&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public string this[string name]
            {

                get
                {
                    HtmlMeta m = FindMetaControl(name, _page.Header);
                    if (m == null) return null;
                    return m.Content;
                }
                set
                {
                    HtmlMeta m = FindMetaControl(name, _page.Header);
                    if (m != null)
                    {
                        m.Content = value;
                    }
                    else
                    {
                        HtmlMeta newm = new HtmlMeta();
                        newm.EnableViewState = false;
                        newm.Name = name;
                        newm.Content = value;
                        _page.Header.Controls.Add(newm);
                    }

                }

            }
            /// &lt;summary&gt;
            /// Returns a collection of all HtmlMeta controls in the header
            /// &lt;/summary&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public List&lt;HtmlMeta&gt; GetControls()
            {
                return PageBase.GetControlsOfType&lt;HtmlMeta&gt;(_page.Header);
            }
            /// &lt;summary&gt;
            /// Returns a name:value collection of meta name:content pairs from the page.
            /// If there are multiple meta tags with the same name, the contents are comma-delimited (NameValueCollection.Add behavior)
            /// &lt;/summary&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public NameValueCollection GetNameContentPairs()
            {
                List&lt;HtmlMeta&gt; list = PageBase.GetControlsOfType&lt;HtmlMeta&gt;(_page.Header);
                NameValueCollection pairs = new NameValueCollection();
                foreach (HtmlMeta m in list)
                {
                    pairs.Add(m.Name, m.Content);
                }
                return pairs;
            }
            /// &lt;summary&gt;
            /// Returns the first HtmlMeta control with the specified name
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;name&quot;&gt;&lt;/param&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public HtmlMeta GetControl(string name)
            {
                return FindMetaControl(name, this._page.Header);
            }
            /// &lt;summary&gt;
            /// Whether to include or exclude matches
            /// &lt;/summary&gt;
            public enum FilterType
            {
                ReturnMatches = 1,
                ReturnNonMatches = 2
            }
            /// &lt;summary&gt;
            /// Returns all meta tags that don't match 'pattern'
            /// To exclude all, specify &quot;*&quot;. Otherwise, specify a list of exclusions: &quot;date,expires,description,flags&quot;.
            /// Not regex, but case-insensitive.
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;pattern&quot;&gt;To exclude all, specify &quot;*&quot;. Otherwise, specify a list of exclusions: &quot;date,expires,description,flags&quot;.&lt;/param&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public List&lt;HtmlMeta&gt; GetNonMatches(string pattern)
            {
                return GetMatches(pattern, FilterType.ReturnNonMatches);
            }
            /// &lt;summary&gt;
            /// Returns all meta tags with a name that matches 'pattern'
            /// To match all, specify &quot;*&quot;. Otherwise, specify a list of possibilities: &quot;date,expires,description,flags&quot;.
            /// Not regex, but case-insensitive.
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;pattern&quot;&gt;To match all, specify &quot;*&quot;. Otherwise, specify a list of possibilities: &quot;date,expires,description,flags&quot;.&lt;/param&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public List&lt;HtmlMeta&gt; GetMatches(string pattern)
            {
                return GetMatches(pattern, FilterType.ReturnMatches);
            }

            /// &lt;summary&gt;
            /// Removes the specified HtmlMeta controls from their parents.
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;list&quot;&gt;&lt;/param&gt;
            public void RemoveControls(List&lt;HtmlMeta&gt; list)
            {
                foreach (HtmlMeta m in list)
                {
                    if (m.Parent != null)
                    {
                        m.Parent.Controls.Remove(m);
                    }
                }
            }

            /// &lt;summary&gt;
            /// Hides the specified HtmlMeta controls from rendering
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;list&quot;&gt;&lt;/param&gt;
            public void HideControls(List&lt;HtmlMeta&gt; list)
            {
                foreach (HtmlMeta m in list)
                {
                    m.Visible = false;
                    m.EnableViewState = false;
                }
            }
            /// &lt;summary&gt;
            /// Returns a collection of HtmlMeta tags that match 'pattern' (or don't match, depending on 'filter').
            /// Pattern is not a regex, but supports alternations and is case-insensitive. if Pattern=&quot;*&quot;, then everything matches.
            /// Pattern can be a single meta name, or a list of meta names (comma or | delimited).
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;pattern&quot;&gt;To match all, specify &quot;*&quot;. Otherwise, specify a list of possibilities: &quot;date,expires,description,flags&quot;.&lt;/param&gt;
            /// &lt;param name=&quot;filter&quot;&gt;&lt;/param&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            public List&lt;HtmlMeta&gt; GetMatches(string pattern, FilterType filter)
            {

                //List of all meta controls in the head
                List&lt;HtmlMeta&gt; list = PageBase.GetControlsOfType&lt;HtmlMeta&gt;(_page.Header);

                //Parse pattern string
                bool wildcard = (pattern.Equals(&quot;*&quot;, StringComparison.OrdinalIgnoreCase));

                string[] parts = pattern.Replace(',', '|').Split('|');
                for (int i = 0; i &lt; parts.Length; i++)
                    parts[i] = parts[i].Trim().ToLowerInvariant();

                //Index valid names in a binary tree
                List&lt;string&gt; names = new List&lt;string&gt;(parts);
                names.Sort();

                //Create collections to hold matches and non-matches.
                List&lt;HtmlMeta&gt; matches = new List&lt;HtmlMeta&gt;();
                List&lt;HtmlMeta&gt; nonmatches = new List&lt;HtmlMeta&gt;();

                //Loop throught controls and distribute to the appropriate collection.
                foreach (HtmlMeta m in list)
                {
                    //Skip meta tags with an no name (probably HTTP-EQIV)
                    if (m.Name == null) continue;

                    if (wildcard)
                    {
                        matches.Add(m);
                    }
                    else if ((names.BinarySearch(m.Name.ToLowerInvariant()) &gt; 0))
                    {
                        matches.Add(m);
                    }
                    else
                    {
                        nonmatches.Add(m);
                    }
                }

                //Return the correct collection based upon the filter type
                if (filter == FilterType.ReturnMatches) return matches;
                else if (filter == FilterType.ReturnNonMatches) return nonmatches;
                else throw new ArgumentException(&quot;filter must be a valid enumeration value&quot;, &quot;filter&quot;);
            }
            /// &lt;summary&gt;
            /// Recursively searches the hierarchy of 'parent' for the first HtmlMeta instance with the specified Name attribute.
            /// Case-insensitive.
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;name&quot;&gt;Case-insensitive. &lt;/param&gt;
            /// &lt;param name=&quot;parent&quot;&gt;Control tree to search&lt;/param&gt;
            /// &lt;returns&gt;&lt;/returns&gt;
            protected static HtmlMeta FindMetaControl(string name, Control parent)
            {
                if (parent is HtmlMeta)
                {
                    HtmlMeta m = parent as HtmlMeta;
                    if (m.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) return m;
                }
                foreach (Control c in parent.Controls)
                {
                    HtmlMeta m = FindMetaControl(name, c);
                    if (m != null) return m;
                }
                return null;
            }

        }
    }
}
</pre>
<h2>Integrating script support</h2>
<p>If you haven&#8217;t already read <em><a href="~/11011" runat="server">Referencing stylesheets and scripts from content pages</a></em>, give it a glance. Download the attached code files and combine them with this one. The only file you&#8217;ll have to merge is PageBase.cs. </p>
<p>Just add this code to the PageBase.cs included in the article, and make sure ContentPlaceHolderFixes.cs and the ScriptReference.cs files are included also.</p>
<pre class="c-sharp" name="code">
/// &lt;summary&gt;
/// Calls the ContentPlaceHolderHeadRepair.Repair method
/// &lt;/summary&gt;
/// &lt;param name=&quot;e&quot;&gt;&lt;/param&gt;
protected override void OnLoad(EventArgs e)
{
	//Parses link, meta, and script tags that got missed by the Head&gt;CPH bug.
	ContentPlaceHolderFixes.RepairPageHeader(this);

	//Fire the events later
	base.OnLoad(e);
}
</pre>
<p>This will allow you to use script tags from the head section and get proper ASP.NET URL resolution on them.
<p>If you have any questions, please use the comments form below.</p>
<p>Happy coding!</p>
<p><!--Todo: use IEnumerable instead of List when possible--></p>
<p>
<a href='http://nathanaeljones.com/wp-content/uploads/2009/02/MetadataStylesheetManagement.zip'>Download files</a></p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/152/extending-page-adding-metadata-and-stylesheet-management/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Image Resizer 1.2, 2.0 &#8211; Web.config</title>
		<link>http://nathanaeljones.com/149/image-resizer-1-2-2-0-web-config/</link>
		<comments>http://nathanaeljones.com/149/image-resizer-1-2-2-0-web-config/#comments</comments>
		<pubDate>Fri, 21 Nov 2008 22:44:56 +0000</pubDate>
		<dc:creator>Nathanael Jones</dc:creator>
				<category><![CDATA[Image Resizing]]></category>
		<category><![CDATA[image resizer]]></category>

		<guid isPermaLink="false">http://66.29.219.39/?p=149</guid>
		<description><![CDATA[Adding web.config settings]]></description>
			<content:encoded><![CDATA[
<p>Version 1.2 of the image resizer is much easier to install in web.config, since it is implemented as an HttpModule instead of an HttpHandler. It still integrates quite well with ASP.NET&#8217;s URL authorization system, and doesn&#8217;t allow access to protected files.</p>
<p>This integration isn&#8217;t valuable to most people, however, since they aren&#8217;t protecting any static files.<br />
Users who don&#8217;t need this protection can enable the &#8220;AllowFolderResizeNotation&#8221; setting to gain additional URL flexibility in exchange.</p>
<p>Version 1.2 now passes *all* file serving work back to IIS. The cached image is now written to disk first rather than last, allowing a simple RewritePath call to handle the work.</p>
<p>The same code can now be used for both IIS5, 6, 7c and 7i. The httpModules section handles IIS5/6/7c, and the modules section handles IIS7 integrated mode.</p>
<pre name="code" class="brush:xml">
&lt;system.web>
  ...
  &lt;httpModules>
    &lt;add name="ImageResizer" type="fbs.ImageResizer.InterceptModule"/>
  &lt;/httpModules>
  ...
&lt;/system.web>
&lt;system.webServer>
  &lt;validation validateIntegratedModeConfiguration="false"/>
  ...
  &lt;modules>
    &lt;add name="ImageResizer" type="fbs.ImageResizer.InterceptModule"/>
  &lt;/modules>
&lt;/system.webServer>
</pre>
<p></script></p>
]]></content:encoded>
			<wfw:commentRss>http://nathanaeljones.com/149/image-resizer-1-2-2-0-web-config/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
