<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0"><channel><title>Scott on Writing</title><link>http://scottonwriting.net/sowblog/</link><description>Musings on technical writing...</description><dc:language>en-US</dc:language><generator>RSS Generated by Dottext 0.94</generator><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/ScottOnWriting" type="application/rss+xml" /><feedburner:browserFriendly>This is an XML content feed. It is intended to be viewed in a newsreader or syndicated to another site, subject to copyright and fair use.</feedburner:browserFriendly><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><item><dc:creator>Scott Mitchell</dc:creator><title>November's Toolbox Column Now Online</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/m3_8dmEk1ZE/14022.aspx</link><pubDate>Thu, 05 Nov 2009 12:18:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/14022.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/14022.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/14022.aspx#feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/14022.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=14022</trackback:ping><description>&lt;p&gt;&lt;a href="http://msdn.microsoft.com/en-us/magazine/ee335714.aspx"&gt;My &lt;em&gt;Toolbox&lt;/em&gt; column&lt;/a&gt; in the November 2009 issue of &lt;em&gt;&lt;a href="http://msdn.microsoft.com/en-us/magazine/"&gt;MSDN Magazine&lt;/a&gt;&lt;/em&gt; is available online and includes the following reviews:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.pragmaticworks.com/products/business-intelligence/bidocumenter/default.aspx"&gt;&lt;strong&gt;BI Documenter&lt;/strong&gt;&lt;/a&gt; - point BI Documenter to the schema of your database and select which database objects to include. Then, with the click of a button, BI Documenter will create a Compiled Html Help File (.chm) or HTML pages that documents the selected database objects. You can also add rich database diagrams created using BI Documenter's built-in diagramming tool, as well as include your own image and Microsoft Word files. 
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Blogs of Note: &lt;/strong&gt;&lt;a&gt;&lt;strong&gt;Beth Massi&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; - &lt;/strong&gt;Microsoftie Beth Massi has a great blog with tips, tricks, and tutorials on Office development, WPF, ADO.NET Data Services, and more. What makes Beth's blog unique is her passion for Visual Basic - all of her code samples are in VB and she often posts about upcoming language features and enhancements. 
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://conditions.codeplex.com/"&gt;CuttingEdge.Conditions&lt;/a&gt;&lt;/strong&gt; - CuttingEdge.Conditions is an open source library that enables developers to define pre- and post-conditions using a &lt;em&gt;&lt;a href="http://en.wikipedia.org/wiki/Fluent_interface"&gt;fluent interface&lt;/a&gt;&lt;/em&gt;, which is an API design style that aims at maximizing readability through the use of descriptive names and method chaining. For example, you can define a pre-condition on the input parameter id like so:&lt;/li&gt;&lt;/ul&gt;&lt;pre class="brush: c-sharp"&gt;public void DoSomething(int id)
{
    // Check all preconditions:
    Condition.Requires(id, "id")
            .IsInRange(1, 999)         // ArgumentOutOfRangeException on failure
            .IsNotEqualTo(128);        // throws ArgumentException on failure

    ...
}
&lt;/pre&gt;
&lt;p&gt;This issue reviewed &lt;em&gt;&lt;a href="http://www.amazon.com/gp/product/0672329980?ie=UTF8&amp;amp;tag=4guysfromrollaco&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=390957&amp;amp;creativeASIN=0672329980"&gt;ASP.NET MVC Framework Unleashed&lt;/a&gt;&lt;/em&gt;, by Stephen Walther. An exerpt from my review follows:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;Getting started with ASP.NET MVC involves a bit of a learning curve, even for experienced ASP.NET developers, because of the numerous differences between the two frameworks. For example, when creating an ASP.NET MVC application in Visual Studio, you are prompted to create a unit test project. With ASP.NET MVC, you design your Web pages using HTML along with a few helper classes—there are no Web controls to drag and drop onto the page. And unlike Web Forms, there are no baked-in postback or Web control event models. In short, there are a lot of new techniques to learn when moving from Web Forms to ASP.NET MVC. If you are an intermediate to experienced ASP.NET developer who wants to learn ASP.NET MVC, check out Stephen Walther’s latest book, “ASP.NET MVC Framework Unleashed” (Sams, 2009). Walther does an excellent job introducing new concepts and showing how they’re used—without overwhelming the reader with an avalanche of information.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Enjoy! - &lt;a href="http://msdn.microsoft.com/en-us/magazine/ee335714.aspx"&gt;http://msdn.microsoft.com/en-us/magazine/ee335714.aspx&lt;/a&gt;&lt;/p&gt;</description><body xmlns="http://www.w3.org/1999/xhtml"><p><a href="http://msdn.microsoft.com/en-us/magazine/ee335714.aspx">My <em>Toolbox</em> column</a> in the November 2009 issue of <em><a href="http://msdn.microsoft.com/en-us/magazine/">MSDN Magazine</a></em> is available online and includes the following reviews:</p>
<ul>
<li><a href="http://www.pragmaticworks.com/products/business-intelligence/bidocumenter/default.aspx"><strong>BI Documenter</strong></a> - point BI Documenter to the schema of your database and select which database objects to include. Then, with the click of a button, BI Documenter will create a Compiled Html Help File (.chm) or HTML pages that documents the selected database objects. You can also add rich database diagrams created using BI Documenter's built-in diagramming tool, as well as include your own image and Microsoft Word files. 
</li><li><strong>Blogs of Note: </strong><a><strong>Beth Massi</strong></a><strong> - </strong>Microsoftie Beth Massi has a great blog with tips, tricks, and tutorials on Office development, WPF, ADO.NET Data Services, and more. What makes Beth's blog unique is her passion for Visual Basic - all of her code samples are in VB and she often posts about upcoming language features and enhancements. 
</li><li><strong><a href="http://conditions.codeplex.com/">CuttingEdge.Conditions</a></strong> - CuttingEdge.Conditions is an open source library that enables developers to define pre- and post-conditions using a <em><a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a></em>, which is an API design style that aims at maximizing readability through the use of descriptive names and method chaining. For example, you can define a pre-condition on the input parameter id like so:</li></ul><pre class="brush: c-sharp">public void DoSomething(int id)
{
    // Check all preconditions:
    Condition.Requires(id, "id")
            .IsInRange(1, 999)         // ArgumentOutOfRangeException on failure
            .IsNotEqualTo(128);        // throws ArgumentException on failure

    ...
}
</pre>
<p>This issue reviewed <em><a href="http://www.amazon.com/gp/product/0672329980?ie=UTF8&amp;tag=4guysfromrollaco&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0672329980">ASP.NET MVC Framework Unleashed</a></em>, by Stephen Walther. An exerpt from my review follows:</p>
<blockquote style="MARGIN-RIGHT: 0px" dir="ltr">
<p>Getting started with ASP.NET MVC involves a bit of a learning curve, even for experienced ASP.NET developers, because of the numerous differences between the two frameworks. For example, when creating an ASP.NET MVC application in Visual Studio, you are prompted to create a unit test project. With ASP.NET MVC, you design your Web pages using HTML along with a few helper classes—there are no Web controls to drag and drop onto the page. And unlike Web Forms, there are no baked-in postback or Web control event models. In short, there are a lot of new techniques to learn when moving from Web Forms to ASP.NET MVC. If you are an intermediate to experienced ASP.NET developer who wants to learn ASP.NET MVC, check out Stephen Walther’s latest book, “ASP.NET MVC Framework Unleashed” (Sams, 2009). Walther does an excellent job introducing new concepts and showing how they’re used—without overwhelming the reader with an avalanche of information.</p></blockquote>
<p>Enjoy! - <a href="http://msdn.microsoft.com/en-us/magazine/ee335714.aspx">http://msdn.microsoft.com/en-us/magazine/ee335714.aspx</a></p></body><feedburner:origLink>http://scottonwriting.net/sowblog/posts/14022.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>TIP: How To Generate a Fully Qualified URL in ASP.NET (E.g., http://www.yourserver.com/folder/file.aspx)</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/pAr7o128DGs/14011.aspx</link><pubDate>Mon, 26 Oct 2009 15:59:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/14011.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/14011.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/14011.aspx#feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/14011.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=14011</trackback:ping><description>&lt;p&gt;Imagine that you've got an ASP.NET page that is generating an email message that needs to include links back to the website. Perhaps you're writing the next greatest online message board application and when someone replies to a thread you want to send out emails to the other thread participants indicating that a new message has been posted along with a link to view the just-posted message. You know that the URL to view a particular thread is, say, &lt;font face="Courier New"&gt;~/Threads/View.aspx?ThreadId=&lt;em&gt;threadId&lt;/em&gt;&lt;/font&gt;. But how do you turn that relative URL into an absolute URL like &lt;font face="Courier New"&gt;http://www.yourserver.com/Threads/View.aspx?ThreadId=&lt;em&gt;threadId&lt;/em&gt;&lt;/font&gt;?&lt;/p&gt;
&lt;p&gt;The simplest answer is to hard code the domain name into the URL when generating the email's content, but that approach is naive and wouldn't work if you were building an application that others could buy and install on their servers. It also would cause a headache if you change your domain name. Finally, it's a headache because oftentimes an ASP.NET application has different URLs depending on the environment. From the development environment the URL might be &lt;font face="Courier New"&gt;http://localhost:&lt;em&gt;somePortNumber&lt;/em&gt;/MyMessageboard/Threads/View.aspx?ThreadId=&lt;em&gt;threadId&lt;/em&gt;&lt;/font&gt;, whereas on the staging environment it might be &lt;font face="Courier New"&gt;http://stagingserver/MB/Threads/View.aspx?ThreadId=&lt;em&gt;threadId&lt;/em&gt;&lt;/font&gt;. Ideally, the email generated from a particular environment would contain the appropriate URL back to said environment.&lt;/p&gt;
&lt;p&gt;The good news is that generating a fully qualified URL in ASP.NET is quite simple. The following code will pick up the correct HTTP or HTTPS setting, the correct server name - be it &lt;font face="Courier New"&gt;localhost&lt;/font&gt;, &lt;font face="Courier New"&gt;localhost:&lt;em&gt;portNumber&lt;/em&gt;&lt;/font&gt;, &lt;font face="Courier New"&gt;serverInIntranet&lt;/font&gt;, an IP address, or a domain name (www.example.com) - and the applicaiton path, if any. Here's the code in both C# and VB:&lt;/p&gt;
&lt;pre class="brush: c-sharp"&gt;// C#
string relativeUrl = "~/Threads/View.aspx?ThreadId=" + threadId;
string fullyQualifiedUrl = Request.Url.GetLeftPart(UriPartial.Authority) + Page.ResolveUrl(relativeUrl);
&lt;/pre&gt;
&lt;pre class="brush: vb"&gt;
' VB
Dim relativeUrl As String = "~/Threads/View.aspx?ThreadId=" + threadId
Dim fullyQualifiedUrl As String = Request.Url.GetLeftPart(UriPartial.Authority) &amp;amp; Page.ResolveUrl(relativeUrl)
&lt;/pre&gt;&lt;p&gt;That's it! Pretty easy.&lt;/p&gt;
&lt;p&gt;The &lt;font face="Courier New"&gt;Request.Url&lt;/font&gt; property returns the fully qualified URL of the currently requesting page. The &lt;font face="Courier New"&gt;GetLeftPart(UriPartial.Authority)&lt;/font&gt; method returns just the protocol and domain name, which is then concatenated with the resolved relative URL to give us the fully qualified URL for the relative URL.&lt;/p&gt;
&lt;p&gt;For more on &lt;font face="Courier New"&gt;Request.Url&lt;/font&gt;, along with a look at the many other path-related properties available of the &lt;font face="Courier New"&gt;HttpRequest&lt;/font&gt; class, see &lt;a href="http://www.west-wind.com/Weblog/default.aspx"&gt;Rick Strahl&lt;/a&gt;'s blog entry, &lt;a href="http://www.west-wind.com/weblog/posts/269.aspx"&gt;Making Sense of ASP.NET Paths&lt;/a&gt;.&lt;/p&gt;</description><body xmlns="http://www.w3.org/1999/xhtml"><p>Imagine that you've got an ASP.NET page that is generating an email message that needs to include links back to the website. Perhaps you're writing the next greatest online message board application and when someone replies to a thread you want to send out emails to the other thread participants indicating that a new message has been posted along with a link to view the just-posted message. You know that the URL to view a particular thread is, say, <font face="Courier New">~/Threads/View.aspx?ThreadId=<em>threadId</em></font>. But how do you turn that relative URL into an absolute URL like <font face="Courier New">http://www.yourserver.com/Threads/View.aspx?ThreadId=<em>threadId</em></font>?</p>
<p>The simplest answer is to hard code the domain name into the URL when generating the email's content, but that approach is naive and wouldn't work if you were building an application that others could buy and install on their servers. It also would cause a headache if you change your domain name. Finally, it's a headache because oftentimes an ASP.NET application has different URLs depending on the environment. From the development environment the URL might be <font face="Courier New">http://localhost:<em>somePortNumber</em>/MyMessageboard/Threads/View.aspx?ThreadId=<em>threadId</em></font>, whereas on the staging environment it might be <font face="Courier New">http://stagingserver/MB/Threads/View.aspx?ThreadId=<em>threadId</em></font>. Ideally, the email generated from a particular environment would contain the appropriate URL back to said environment.</p>
<p>The good news is that generating a fully qualified URL in ASP.NET is quite simple. The following code will pick up the correct HTTP or HTTPS setting, the correct server name - be it <font face="Courier New">localhost</font>, <font face="Courier New">localhost:<em>portNumber</em></font>, <font face="Courier New">serverInIntranet</font>, an IP address, or a domain name (www.example.com) - and the applicaiton path, if any. Here's the code in both C# and VB:</p>
<pre class="brush: c-sharp">// C#
string relativeUrl = "~/Threads/View.aspx?ThreadId=" + threadId;
string fullyQualifiedUrl = Request.Url.GetLeftPart(UriPartial.Authority) + Page.ResolveUrl(relativeUrl);
</pre>
<pre class="brush: vb">
' VB
Dim relativeUrl As String = "~/Threads/View.aspx?ThreadId=" + threadId
Dim fullyQualifiedUrl As String = Request.Url.GetLeftPart(UriPartial.Authority) &amp; Page.ResolveUrl(relativeUrl)
</pre><p>That's it! Pretty easy.</p>
<p>The <font face="Courier New">Request.Url</font> property returns the fully qualified URL of the currently requesting page. The <font face="Courier New">GetLeftPart(UriPartial.Authority)</font> method returns just the protocol and domain name, which is then concatenated with the resolved relative URL to give us the fully qualified URL for the relative URL.</p>
<p>For more on <font face="Courier New">Request.Url</font>, along with a look at the many other path-related properties available of the <font face="Courier New">HttpRequest</font> class, see <a href="http://www.west-wind.com/Weblog/default.aspx">Rick Strahl</a>'s blog entry, <a href="http://www.west-wind.com/weblog/posts/269.aspx">Making Sense of ASP.NET Paths</a>.</p></body><feedburner:origLink>http://scottonwriting.net/sowblog/posts/14011.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>Rich Tooltips With jQuery</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/N3F8Diyvk6g/14005.aspx</link><pubDate>Fri, 23 Oct 2009 15:17:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/14005.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/14005.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/14005.aspx#feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/14005.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=14005</trackback:ping><description>&lt;p&gt;I was recently working on an application and needed the ability to present the user with a list of links when they hovered over a particular line of text. HTML elements include a &lt;font face="Courier New"&gt;title&lt;/font&gt; attribute that, when set, displays the attribute's value in a small yellow window when the user hovers over the element. Problem is, the tooltip is a text-only interface. There's no way to embed a hyperlink or image or other rich markup into the tooltip.&lt;/p&gt;
&lt;p&gt;Since we are using &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; in this project I spent some time exploring various techniques for implementing rich tooltips in jQuery. Specifically, I needed a solution that met the following criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The tooltip would appear near or next to the text that the user was hovering over, and not on some fixed, predefined spot on the screen.&lt;/li&gt;
&lt;li&gt;I could, from server-side code, specify what elements would display tooltips when hovered over.&lt;/li&gt;
&lt;li&gt;I could, from server-side code, customize the tooltip message.&lt;/li&gt;
&lt;li&gt;The tooltip could include any sort of HTML markup I wanted to throw into it - links, images, etc. - and the user could interact with this markup (click on a link, for instance).&lt;/li&gt;
&lt;li&gt;The tooltip would remain displayed for a specified amount of time (say, 500 milliseconds) after the user moused off of the element that displayed the tooltip.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Unfortunately, I was unable to find a pre-built solution that would meet our needs, so I created my own, which I'd like to share now.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Please keep in mind that I know just enough JavaScript to shoot myself in the foot. Also, my experience with jQuery is limited, to be kind.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In order to display rich tooltips using my approach you need to do three things.&lt;/p&gt;
&lt;p&gt;First, add the following CSS to those pages that use the rich tooltips:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;.skmTooltipHost&lt;br /&gt;{&lt;br /&gt; cursor: help;&lt;br /&gt; border-bottom: dotted 1px brown;&lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;.skmTooltipContainer&lt;br /&gt;{&lt;br /&gt;    padding-left: 10px;&lt;br /&gt;    padding-right: 10px;&lt;br /&gt;    padding-top: 3px;&lt;br /&gt;    padding-bottom: 3px;&lt;br /&gt;    display: none;&lt;br /&gt;    position: absolute;&lt;br /&gt;    background-color: #ff9;&lt;br /&gt;    border: solid 1px #333;&lt;br /&gt;    z-index: 999;&lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;Those elements that should display a tooltip when hovered over need to use the &lt;font face="Courier New"&gt;skmTooltipHost&lt;/font&gt; class. The &lt;font face="Courier New"&gt;skmTooltipContainer&lt;/font&gt; class defines the styles for the tooltip itself.&lt;/p&gt;
&lt;p&gt;Next, add the following &lt;a href="http://www.learningjquery.com/2006/09/introducing-document-ready"&gt;&lt;font face="Courier New"&gt;$(document).ready&lt;/font&gt; function&lt;/a&gt; to your page (and add a script reference to the appropriate &lt;font face="Courier New"&gt;jquery.js&lt;/font&gt; file, if you haven't already):&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;  $(document).ready(function() {&lt;br /&gt;     $(".skmTooltipHost").hover(&lt;br /&gt;       function() {&lt;br /&gt;       $(this).append('&amp;lt;div class="skmTooltipContainer"&amp;gt;' + $(this).attr('tooltip') + '&amp;lt;/div&amp;gt;');&lt;br /&gt;  &lt;br /&gt;       $(this).find('.skmTooltipContainer').css("left", $(this).position().left + 20);&lt;br /&gt;       $(this).find('.skmTooltipContainer').css("top", $(this).position().top + $(this).height());&lt;br /&gt;        $(".skmTooltipContainer").fadeIn(500);&lt;br /&gt;      },&lt;br /&gt; &lt;br /&gt;       function() {&lt;br /&gt;        $(".skmTooltipContainer").fadeTo(500, 1.0, function() { $(this).remove(); });&lt;br /&gt;       }&lt;br /&gt;     );&lt;br /&gt;  });&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;Finally, decorate those HTML elements that you want to display a tooltip with the &lt;font face="Courier New"&gt;skmTooltipHost&lt;/font&gt; class and add an attribute named &lt;font face="Courier New"&gt;tooltip&lt;/font&gt; that contains the content to display in the tooltip. For example, imagine I had a document with the following markup:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;&amp;lt;p&amp;gt;ASP.NET simplifies the process of creating dynamic web applications!&amp;lt;/p&amp;gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I could turn add a tooltip to the text “ASP.NET” by adding the additional markup:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;&amp;lt;p&amp;gt;&lt;font color="#ff0000"&gt;&amp;lt;span class="skmTooltipHost" tooltip="ASP.NET was created by Microsoft in 2001."&amp;gt;&lt;/font&gt;ASP.NET&lt;font color="#ff0000"&gt;&amp;lt;/span&amp;gt;&lt;/font&gt; simplifies the process of creating dynamic web applications!&amp;lt;/p&amp;gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;To get rich markup in the &lt;font face="Courier New"&gt;tooltip&lt;/font&gt; attribute you'll need to escape the &amp;lt;, &amp;gt;, and " characters with &amp;amp;lt;, &amp;amp;gt;, and &amp;amp;quot;, respectively. For instance, we could add a hyperlink in the above tooltip like so:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;&lt;font color="#000000"&gt;&amp;lt;p&amp;gt;&amp;lt;span class="skmTooltipHost" tooltip="ASP.NET was created by Microsoft in 2001. &lt;font color="#ff0000"&gt;Learn more at &amp;amp;lt;a &lt;font color="#ff0000"&gt;target="_blank"&lt;/font&gt; href=&amp;amp;quot;http://www.4guysfromrolla.com/&amp;amp;quot;&amp;amp;gt;4GuysFromRolla.com&amp;amp;lt;/a&amp;amp;gt;!&lt;/font&gt;"&amp;gt;ASP.NET&amp;lt;/span&amp;gt; simplifies the process of creating dynamic web applications!&amp;lt;/p&amp;gt;&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;You can see these rich tooltips in action here - &lt;a href="http://scottonwriting.net/sowBlog/CodeProjectFiles/JQueryTooltipDemo.htm"&gt;http://scottonwriting.net/sowBlog/CodeProjectFiles/JQueryTooltipDemo.htm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And lastly, to do this programmatically from server-side code, you would add a Label Web control to your page named, say, &lt;font face="Courier New"&gt;lblASPNET&lt;/font&gt;. You'd set its &lt;font face="Courier New"&gt;CssClass&lt;/font&gt; property to &lt;font face="Courier New"&gt;skmTooltipHost&lt;/font&gt;. To set its &lt;font face="Courier New"&gt;tooltip&lt;/font&gt; attribute add the following (untested) code to the &lt;font face="Courier New"&gt;Page_Load&lt;/font&gt; event handler:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;lblASPNET.Attributes.Add("tooltip", Server.HtmlEncode("ASP.NET was created by Microsoft in 2001. Learn more at &amp;lt;a target="_blank" href="http://www.4guysfromrolla.com/"&amp;gt;4GuysFromRolla.com&amp;lt;/a&amp;gt;!") &lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/14005.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>SOLUTION: JSLint.VS Add-In Always Reports "No Errors" Even For Invalid JavaScript Files</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/-2jZFk-1PPU/13995.aspx</link><pubDate>Thu, 15 Oct 2009 15:49:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13995.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13995.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13995.aspx#feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13995.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13995</trackback:ping><description>&lt;p&gt;&lt;a href="http://jslint.com/"&gt;&lt;strong&gt;JSLint&lt;/strong&gt;&lt;/a&gt; is a free JavaScript code quality tool created by &lt;a href="http://www.crockford.com/"&gt;Douglas Crockford&lt;/a&gt;. At the &lt;a href="http://jslint.com/"&gt;JSLint.com website&lt;/a&gt; you can paste in a block of JavaScript code and JSLint will examine the code and warn you when it encounters any script that violates its list of rules. Many of the rules JSLint checks against are configurable, and include checks for usage of undefined variables, use of the &lt;font face="Courier New"&gt;eval&lt;/font&gt; function, statements not terminated with semicolons, and other 'trouble waiting for a place to happen' coding patterns.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jslint.codeplex.com/"&gt;&lt;strong&gt;JSLint.VS&lt;/strong&gt;&lt;/a&gt; is a free, open source Visual Studio Add-In created by Predrag Tomasevic that brings JSLint to the Visual Studio IDE. &lt;/p&gt;
&lt;p&gt;Today I installed JSLint.VS for the first time on a machine. The installation went smoothly, but anytime I used the Add-In it reported &lt;strong&gt;No Errors&lt;/strong&gt;, even though the JSLint.com website was finding errors with the same block of JavaScript code. The good news is that I was able to identify the problem and fix it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Just the Workaround, Please&lt;/strong&gt;&lt;br /&gt;For JSLint.VS to work the &lt;font face="Courier New"&gt;.js&lt;/font&gt; extension must be associated with the JScript WSH engine. Chances are, you have the JScript WSH engine already installed, but another program has claimed the association. In my case, the &lt;font face="Courier New"&gt;.js&lt;/font&gt; extension was associated with &lt;a href="http://www.ultraedit.com/"&gt;UltraEdit&lt;/a&gt;. Once I reassociated it with the JScript engine, the JSLint.VS Add-In worked as expected.&lt;/p&gt;
&lt;p&gt;To see what program the &lt;font face="Courier New"&gt;.js&lt;/font&gt; extension is associated with, drop to the command line and enter:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;assoc .js&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The output should be: &lt;font face="Courier New"&gt;.js=JScript&lt;/font&gt;. If it's not, enter:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;assoc .js=JScript&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Now try JSLint.VS again.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For Those Who Care... The Why...&lt;/strong&gt;&lt;br /&gt;When you launch JSLint.VS from within the IDE it creates three files in the Application Data folder (&lt;font face="Courier New"&gt;My Documents\&lt;em&gt;User&lt;/em&gt;\Application Data&lt;/font&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;font face="Courier New"&gt;&lt;strong&gt;wsh.js&lt;/strong&gt;&lt;/font&gt; - contains the JSLint JavaScript code, which you can download from &lt;a href="http://www.jslint.com/fulljslint.js"&gt;http://www.jslint.com/fulljslint.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;font face="Courier New"&gt;&lt;strong&gt;temp.js&lt;/strong&gt;&lt;/font&gt; - contains the JavaScript code to check with JSLint&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font face="Courier New"&gt;csh.cmd&lt;/font&gt;&lt;/strong&gt; - a batch file that executes the &lt;font face="Courier New"&gt;wsh.js&lt;/font&gt; file, passing in the &lt;font face="Courier New"&gt;temp.js&lt;/font&gt; contents as input.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Specifically, JSLint.VS executes the following command:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;csh.cmd wsh.js &amp;lt; temp.js&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;If the &lt;font face="Courier New"&gt;.js&lt;/font&gt; extension is not mapped to the JScript WSH engine the above command will result in the following error message: “There is no script engine for file extension .js”&lt;/p&gt;
&lt;p&gt;In the face of that error, JSLint.VS simply returns, “No Errors.” Once you associate the &lt;font face="Courier New"&gt;.js&lt;/font&gt; extension with the JScript WSH engine you should be good to go!&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13995.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>October's Toolbox Column Now Online</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/DGi1-dfbFAM/13970.aspx</link><pubDate>Fri, 02 Oct 2009 10:10:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13970.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13970.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13970.aspx#feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13970.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13970</trackback:ping><description>&lt;p&gt;&lt;a href="http://msdn.microsoft.com/en-us/magazine/ee309513.aspx"&gt;My &lt;em&gt;Toolbox&lt;/em&gt; column&lt;/a&gt; in the October 2009 issue of &lt;em&gt;&lt;a href="http://msdn.microsoft.com/en-us/magazine/"&gt;MSDN Magazine&lt;/a&gt;&lt;/em&gt; is available online and includes the following reviews:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://nukeation.com/machine.aspx"&gt;&lt;strong&gt;nukeationMachine&lt;/strong&gt;&lt;/a&gt; - nukeationMachine is a Visual Studio Add In that streamlines implementing WPF, WinForms, and ASP.NET user interfaces. nukeationMachine includes 1,600 &lt;em&gt;UI bits&lt;/em&gt;, which are common groupings of user interface elements, such as Ok, Cancel, Retry buttons. You can add any UI bit to a design surface with a click of the mouse and in a fraction of the time it would take to add and position each user interface element separately.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Podcasts of Note: &lt;/strong&gt;&lt;a href="http://dotnetrocks.com/"&gt;&lt;strong&gt;.NET Rocks&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; - &lt;/strong&gt;each week hosts Carl Franklin and Richard Campbell spend about an hour exploring a particular technology, product, or person of interest to the .NET community. Franklin and Campbell are both natural interviewers and do a great job picking the brains of their guests while maintaining a smooth and natural flow to the discussion. Listening to a .NET Rocks podcast is a lot like overhearing a conversation among experienced developers at a user group meeting or conference - you're bound to learn something new, hear an interesting anecdote or two, and discover how other knowledgeable developers are using .NET and related technologies in their daily jobs.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://fluentnhibernate.org/"&gt;&lt;strong&gt;Fluent NHibernate&lt;/strong&gt;&lt;/a&gt; - &lt;a href="https://www.hibernate.org/343.html"&gt;NHibernate&lt;/a&gt; is a popular &lt;a href="http://en.wikipedia.org/wiki/Object-relational_mapping"&gt;ORM&lt;/a&gt; for .NET whose relational to domain mapping is typically specified via XML. Fluent NHibernate is an open source library that enables developers to specify these mappings using a &lt;em&gt;&lt;a href="http://en.wikipedia.org/wiki/Fluent_interface"&gt;fluent interface&lt;/a&gt;&lt;/em&gt;, an API design style that aims at maximizing readability through the use of descriptive names and method chaining.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;This issue reviewed &lt;em&gt;&lt;a href="http://www.amazon.com/gp/product/0321606396?ie=UTF8&amp;amp;tag=4guysfromrollaco&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=390957&amp;amp;creativeASIN=0321606396"&gt;The Nomadic Developer&lt;/a&gt;&lt;/em&gt;, by Aaron Erickson. An exerpt from my review follows:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;If you currently work for a technology consulting company, or are weighing the pros and cons of doing so, then Aaron Erickson's book "The Nomadic Developer" is for you. ... [It] should be required reading for anyone currently seeking a job at a consulting company, especially those who have not previously worked as consultants. The book's second chapter, "The Seven Deadly Firms," describes seven dysfunctional traits that, if present, will negatively impact your time at the company. For each dysfunction, Erickson supplies a detailed description, explains what life is like at a consulting firm when that trait is present, and provides tips for spotting the dysfunction during a job interview. There are also chapters on the top 10 traits a technology consulting firm is looking for in applicants, and another chapter that suggests questions applicants should ask during the interview process.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Enjoy! - &lt;a href="http://msdn.microsoft.com/en-us/magazine/ee309513.aspx"&gt;http://msdn.microsoft.com/en-us/magazine/ee309513.aspx&lt;/a&gt;&lt;/p&gt;
&lt;p dir="ltr" align="center" font="franklingothicbookregular" xmlid="PARAJULY2007TOOLBOX023"&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;As always, if you have any suggestions for products, blogs, or books to review for the Toolbox column, please send them to &lt;/font&gt;&lt;/em&gt;&lt;a href="mailto:toolsmm@microsoft.com"&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;toolsmm@microsoft.com&lt;/font&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;.&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13970.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>Deleting All Records In a Table EXCEPT For the N Most Recently Added Records</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/_WU15teWBTI/13964.aspx</link><pubDate>Thu, 01 Oct 2009 08:50:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13964.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13964.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13964.aspx#feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13964.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13964</trackback:ping><description>&lt;p&gt;I recently ran into a situation where I needed to delete all records from a table &lt;em&gt;except&lt;/em&gt; for the 1,000 most recently added records. Specifically, I was working on a site that used &lt;a href="http://code.google.com/p/elmah/"&gt;Error Logging Modules And Handlers (ELMAH)&lt;/a&gt;, a free, open source error logging library for ASP.NET applications. ELMAH doesn't automatically prune its error log. If you are using the database as a log source and if your web hosting provider has a database quota in effect, it is possible to have ELMAH's error log grow so large that the quota is surpassed and the database is taken offline. To help mitigate this problem, it is a good idea to take steps to &lt;a href="http://scottonwriting.net/sowblog/posts/13882.aspx"&gt;keep ELAMH's error log size in check&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;As with any sort of logging service is is important that ELMAH's log be periodically pruned. If you let ELMAH's log grow unchecked it can reduce performance when querying the log and suck up disk space, which is especially important in hosted environments where there are typically hard disk quote limits for each user on the database server. The good news is that there are a number of techniques you can employ to help ensure that your ELMAH error log stays a reasonable size.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use error filtering. 
&lt;/li&gt;&lt;li&gt;Setup a weekly job in SQL Server to delete entries older than, say, three months. 
&lt;/li&gt;&lt;li&gt;Update the &lt;font face="Courier New"&gt;ELMAH_LogError&lt;/font&gt; stored procedure to delete old log entries.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Keep in mind that trimming the error log brings with it a tradeoff: you are removing error log entries that might be important for analysis later in time. If this is the case, if you think you might need to review that error log from more than 90 days in the past, then before deleting records from the error log you should archive them somewhere.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;While these approaches are certainly good measures to implement, they don't handle the case where there's a flood of errors in a short amount of time. If ELMAH's error log is being inundated with errors from the past 24 hours, a script that deletes error log entries older than three months is going to have no effect.&lt;/p&gt;
&lt;p&gt;An alternative approach is to delete all entries in ELMAH other than the most recently added &lt;em&gt;N&lt;/em&gt; error log entries. This ensures that the error log never exceeds a maximum number of records (&lt;em&gt;N&lt;/em&gt;), while keeping the &lt;em&gt;N&lt;/em&gt; most recent records. The following query will do the trick:&lt;/p&gt;
&lt;pre class="brush: sql;"&gt;
DELETE FROM ELMAH_Error
WHERE ErrorId NOT IN
        (SELECT TOP 1000 ErrorId
         FROM ELAMH_Error
         ORDER BY TimeUtc DESC)&lt;/pre&gt;
&lt;p&gt;The &lt;font face="Courier New"&gt;ErrorId&lt;/font&gt; column is the primary key in the &lt;font face="Courier New"&gt;ELMAH_Error&lt;/font&gt; table, and &lt;font face="Courier New"&gt;TimeUtc&lt;/font&gt; stores the UTC date/time the error was recorded. The subquery gets the first 1,000 &lt;font face="Courier New"&gt;ErrorId&lt;/font&gt; values ordered by the time the error was logged, from the most recently logged to the oldest. The query deletes all error records whose &lt;font face="Courier New"&gt;ErrorId&lt;/font&gt; values are &lt;em&gt;not&lt;/em&gt; in that set of the 1,000 most recently added errors.&lt;/p&gt;
&lt;p&gt;If you are using SQL Server 2005 or beyond, you can use &lt;a href="http://www.4guysfromrolla.com/webtech/010406-1.shtml"&gt;the &lt;font face="Courier New"&gt;ROW_NUMBER()&lt;/font&gt; keyword&lt;/a&gt; in a query like so:&lt;/p&gt;
&lt;pre class="brush: sql;"&gt;
DELETE FROM ELMAH_Error
WHERE ErrorId IN (
      SELECT ErrorId
      FROM
        (
           SELECT ErrorId, ROW_NUMBER() OVER(ORDER BY TimeUtc DESC) AS RowIndex
           FROM ELMAH_Error
        ) AS ErrorsWithRowNumbers
      WHERE RowIndex &amp;gt; 1000
)&lt;/pre&gt;
&lt;p&gt;The innermost query uses the &lt;font face="Courier New"&gt;ROW_NUMBER()&lt;/font&gt; keyword to assign a row number to each record from &lt;font face="Courier New"&gt;ELMAH_Error&lt;/font&gt;, numbering the most recently logged error 1, the next one 2, and so forth, such that the oldest error will have the largest row number assigned. This innermost query is used as a derived table. The query that uses it gets the &lt;font face="Courier New"&gt;ErrorId&lt;/font&gt; values whose row number is greater than 1,000, which returns those &lt;font face="Courier New"&gt;ErrorId&lt;/font&gt; values for errors that are “ranked” 1,001 or higher. In other words, it returns those &lt;font face="Courier New"&gt;ErrorId&lt;/font&gt; values for errors that are not one of the 1,000 most recently added errors. Finally, this set of &lt;font face="Courier New"&gt;ErrorId&lt;/font&gt;s are deleted from the table.&lt;/p&gt;
&lt;p&gt;I ended up using the second query (the one with &lt;font face="Courier New"&gt;ROW_NUMBER()&lt;/font&gt;), placing it in the &lt;font face="Courier New"&gt;ELMAH_LogError&lt;/font&gt; stored procedure (along with a &lt;font face="Courier New"&gt;DELETE&lt;/font&gt; statement that removes any error log entries older than 3 weeks). If you use either one of these queries, consider adding an index on the &lt;font face="Courier New"&gt;TimeUtc&lt;/font&gt; column sorted in descending order.&lt;/p&gt;
&lt;p&gt;Happy Programming!&lt;/p&gt;</description><body xmlns="http://www.w3.org/1999/xhtml"><p>I recently ran into a situation where I needed to delete all records from a table <em>except</em> for the 1,000 most recently added records. Specifically, I was working on a site that used <a href="http://code.google.com/p/elmah/">Error Logging Modules And Handlers (ELMAH)</a>, a free, open source error logging library for ASP.NET applications. ELMAH doesn't automatically prune its error log. If you are using the database as a log source and if your web hosting provider has a database quota in effect, it is possible to have ELMAH's error log grow so large that the quota is surpassed and the database is taken offline. To help mitigate this problem, it is a good idea to take steps to <a href="http://scottonwriting.net/sowblog/posts/13882.aspx">keep ELAMH's error log size in check</a>:</p>
<blockquote style="MARGIN-RIGHT: 0px" dir="ltr">
<p>As with any sort of logging service is is important that ELMAH's log be periodically pruned. If you let ELMAH's log grow unchecked it can reduce performance when querying the log and suck up disk space, which is especially important in hosted environments where there are typically hard disk quote limits for each user on the database server. The good news is that there are a number of techniques you can employ to help ensure that your ELMAH error log stays a reasonable size.</p>
<ul>
<li>Use error filtering. 
</li><li>Setup a weekly job in SQL Server to delete entries older than, say, three months. 
</li><li>Update the <font face="Courier New">ELMAH_LogError</font> stored procedure to delete old log entries.</li></ul>
<p>Keep in mind that trimming the error log brings with it a tradeoff: you are removing error log entries that might be important for analysis later in time. If this is the case, if you think you might need to review that error log from more than 90 days in the past, then before deleting records from the error log you should archive them somewhere.</p></blockquote>
<p>While these approaches are certainly good measures to implement, they don't handle the case where there's a flood of errors in a short amount of time. If ELMAH's error log is being inundated with errors from the past 24 hours, a script that deletes error log entries older than three months is going to have no effect.</p>
<p>An alternative approach is to delete all entries in ELMAH other than the most recently added <em>N</em> error log entries. This ensures that the error log never exceeds a maximum number of records (<em>N</em>), while keeping the <em>N</em> most recent records. The following query will do the trick:</p>
<pre class="brush: sql;">
DELETE FROM ELMAH_Error
WHERE ErrorId NOT IN
        (SELECT TOP 1000 ErrorId
         FROM ELAMH_Error
         ORDER BY TimeUtc DESC)</pre>
<p>The <font face="Courier New">ErrorId</font> column is the primary key in the <font face="Courier New">ELMAH_Error</font> table, and <font face="Courier New">TimeUtc</font> stores the UTC date/time the error was recorded. The subquery gets the first 1,000 <font face="Courier New">ErrorId</font> values ordered by the time the error was logged, from the most recently logged to the oldest. The query deletes all error records whose <font face="Courier New">ErrorId</font> values are <em>not</em> in that set of the 1,000 most recently added errors.</p>
<p>If you are using SQL Server 2005 or beyond, you can use <a href="http://www.4guysfromrolla.com/webtech/010406-1.shtml">the <font face="Courier New">ROW_NUMBER()</font> keyword</a> in a query like so:</p>
<pre class="brush: sql;">
DELETE FROM ELMAH_Error
WHERE ErrorId IN (
      SELECT ErrorId
      FROM
        (
           SELECT ErrorId, ROW_NUMBER() OVER(ORDER BY TimeUtc DESC) AS RowIndex
           FROM ELMAH_Error
        ) AS ErrorsWithRowNumbers
      WHERE RowIndex &gt; 1000
)</pre>
<p>The innermost query uses the <font face="Courier New">ROW_NUMBER()</font> keyword to assign a row number to each record from <font face="Courier New">ELMAH_Error</font>, numbering the most recently logged error 1, the next one 2, and so forth, such that the oldest error will have the largest row number assigned. This innermost query is used as a derived table. The query that uses it gets the <font face="Courier New">ErrorId</font> values whose row number is greater than 1,000, which returns those <font face="Courier New">ErrorId</font> values for errors that are “ranked” 1,001 or higher. In other words, it returns those <font face="Courier New">ErrorId</font> values for errors that are not one of the 1,000 most recently added errors. Finally, this set of <font face="Courier New">ErrorId</font>s are deleted from the table.</p>
<p>I ended up using the second query (the one with <font face="Courier New">ROW_NUMBER()</font>), placing it in the <font face="Courier New">ELMAH_LogError</font> stored procedure (along with a <font face="Courier New">DELETE</font> statement that removes any error log entries older than 3 weeks). If you use either one of these queries, consider adding an index on the <font face="Courier New">TimeUtc</font> column sorted in descending order.</p>
<p>Happy Programming!</p></body><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13964.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>PROBLEM: CSS Styles No Longer Apply For Anonymous Users</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/yVJnxf807AU/13962.aspx</link><pubDate>Mon, 28 Sep 2009 08:34:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13962.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13962.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13962.aspx#feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13962.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13962</trackback:ping><description>&lt;p&gt;I teach two ASP.NET courses as the &lt;a href="http://extension.ucsd.edu/"&gt;University of California - San Diego Extension&lt;/a&gt;. In the second course students use &lt;a href="http://www.4guysfromrolla.com/webtech/110701-1.shtml"&gt;forms-based authentication&lt;/a&gt; and &lt;a href="http://aspnet.4guysfromrolla.com/articles/120705-1.aspx"&gt;ASP.NET's membership framework&lt;/a&gt; to create a web application that supports user accounts. Invariably, at least one student bumps into the following scenario:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The website's formatting and layout is defined via CSS rules, which are located in one or more &lt;font face="Courier New"&gt;.css&lt;/font&gt; files.&lt;/li&gt;
&lt;li&gt;The URL authorization settings have configured such that web pages on the site can only be accessed by authenticated users. That is, the &lt;font face="Courier New"&gt;&amp;lt;authorization&amp;gt;&lt;/font&gt; section in the &lt;font face="Courier New"&gt;Web.config&lt;/font&gt; file in the root folder contains the following markup:&lt;br /&gt;&lt;br /&gt;&lt;font face="Courier New"&gt;&amp;lt;authorization&amp;gt;&lt;br /&gt;    &amp;lt;deny users="?" /&amp;gt;&lt;br /&gt;&amp;lt;/authorization&amp;gt;&lt;/font&gt;&lt;br /&gt;&lt;br /&gt;Some students lock down the entire site to authenticated users only. Others may have one or more &lt;font face="Courier New"&gt;&amp;lt;location&amp;gt;&lt;/font&gt; elements that open up access to specific pages to anonymous users, or have a separate folder with its own &lt;font face="Courier New"&gt;&amp;lt;authorization&amp;gt;&lt;/font&gt; settings that allow anonymous access to the pages within.&lt;/li&gt;
&lt;li&gt;The website is served using the ASP.NET Development Server, which is the lightweight web server that ships with Visual Studio and is launched when you run a file system-based web application from the IDE.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;When the above conditions hold, students find that when visiting their site those CSS formatting and layout rules defined in the &lt;font face="Courier New"&gt;.css&lt;/font&gt; file(s) are not applied for anonymous visitors. For example, when visiting the login page, the website's colors and fonts and layouts defined in the &lt;font face="Courier New"&gt;.css&lt;/font&gt; file(s) are not in effect. However, once the visitor signs into the site, the CSS rules take effect.&lt;/p&gt;
&lt;p&gt;What's going on here?&lt;/p&gt;
&lt;p&gt;The reason the CSS rules are not applied when the site is visited by an anonymous user is because of how the ASP.NET Development Server handles web requests. In a nutshell, every single request that arrives to the ASP.NET Development Server - be it for an ASP.NET page or a CSS file - is dispatched to the ASP.NET engine for processing. Consequently, the URL authorization rules defined in &lt;font face="Courier New"&gt;Web.config&lt;/font&gt; apply to the CSS files as well as the ASP.NET pages. So when an anonymous user visits, the page includes the &lt;font face="Courier New"&gt;&amp;lt;link&amp;gt;&lt;/font&gt; elements to the CSS files and the browser requests them, but the web server responds with a redirect response to the login page. As a result, the browser does not get the CSS content and that's why CSS rules do not apply for anonymous users (and why they start applying once the visitor signs in).&lt;/p&gt;
&lt;p&gt;The simplest workaround is to configure the URL authorization rules to allow anonymous users to access the CSS files. If you have the CSS files in a separate folder (such as &lt;font face="Courier New"&gt;/Styles&lt;/font&gt;), then you can simply add a &lt;font face="Courier New"&gt;Web.config&lt;/font&gt; file to that folder with the following &lt;font face="Courier New"&gt;&amp;lt;authorization&amp;gt;&lt;/font&gt; settings:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&amp;lt;authorization&amp;gt;&lt;br /&gt;    &amp;lt;allow users="*" /&amp;gt;&lt;br /&gt;&amp;lt;/authorization&amp;gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;If the CSS files are in the root folder, then you will need to add a &lt;font face="Courier New"&gt;&amp;lt;location&amp;gt;&lt;/font&gt; element in the root folder's &lt;font face="Courier New"&gt;Web.config&lt;/font&gt; file for each CSS file and use the above markup to permit anonymous users to access those files. For more on using the &lt;font face="Courier New"&gt;&amp;lt;location&amp;gt;&lt;/font&gt; element, refer to &lt;a href="http://msdn.microsoft.com/en-us/library/b6x6shw7.aspx"&gt;location Element (ASP.NET Settings Schema).&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Finally, keep in mind that the behavior described here only occurs when the web server dispatches requests for CSS files to the ASP.NET engine. As aforementioned, this is the behavior of the ASP.NET Development Server; however, this is not the default behavior of IIS. By default, IIS handles request for static content itself, meaning that ASP.NET's URL authorization rules will not apply to CSS files, JavaScript files, images, ZIP files, and so on, although it is possible to instruct IIS 7 to integrate it's security checks with ASP.NET's configuration via the Integrated Pipeline mode. See &lt;a href="http://aspnet.4guysfromrolla.com/articles/122408-1.aspx"&gt;Apply ASP.NET Authentication and Authorization Rules to Static Content with IIS 7.0's Integrated Pipeline Feature&lt;/a&gt; for more information.&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13962.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>September's Toolbox Column Now Online</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/ER2_JBMvCEk/13954.aspx</link><pubDate>Thu, 10 Sep 2009 08:12:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13954.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13954.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13954.aspx#feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13954.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13954</trackback:ping><description>&lt;p&gt;&lt;a href="http://msdn.microsoft.com/en-us/magazine/ee413550.aspx"&gt;My &lt;em&gt;Toolbox&lt;/em&gt; column&lt;/a&gt; in the September 2009 issue of &lt;em&gt;&lt;a href="http://msdn.microsoft.com/en-us/magazine/"&gt;MSDN Magazine&lt;/a&gt;&lt;/em&gt; is available online and includes the following reviews/discussions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Improving Web Application Performance With Distributed Caching&lt;/strong&gt; - provides an overview of distributed caching for web applications, with discussions on three products: &lt;a href="http://danga.com/memcached/"&gt;memcached&lt;/a&gt;, an open source option that powers many high-profile sites like LiveJournal, Wikipedia, and SourceForge; &lt;a href="http://msdn.microsoft.com/en-us/magazine/cc655792.aspx"&gt;Velocity&lt;/a&gt;, which is Microsoft's foray into the distributed caching market; and third-party commercial implementations, like ScaleOut Software's ScaleOut StateServer and Alachisoft's NCache, which was reviewed in &lt;a href="http://msdn.microsoft.com/en-us/magazine/cc163343.aspx"&gt;the October 2007 issue&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blogs of Note: &lt;/strong&gt;&lt;a&gt;&lt;strong&gt;Udi Dahan&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; - &lt;/strong&gt;Udi is a speaker, trainer, and consultant on software architecture and design of distributed systems. He has worked on several large-scale, service-oriented applications for enterprises, and blogs about his experiences building enterprise applications on his blog. Check out the &lt;a href="http://www.udidahan.com/first-time-here/"&gt;'First time here?' page&lt;/a&gt; on Udi's blog, which has links to his most popular and engaging articles and blog posts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="http://www.codeplex.com/AutoMapper"&gt;AutoMapper&lt;/a&gt;&lt;/strong&gt; - it's not uncommon to need to transfer objects of one 'shape' into a different shape. This is particularly common when exposing data through a service layer. Internally, you work with your entities in terms of business objects that model your domain, but when exposing this data you may need to return a more appropriate object type that contains fewer properties. These objects used in the service layer are referred to as Data Transfer Objects (DTOs). Writing the domain object to DTO mapping code is tedious. AutoMapper, a free, open source project - helps relieve that tedium by making such object-object mappings as easy as writing two lines of code.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;This issue did not include a book review.&lt;/p&gt;
&lt;p&gt;Enjoy! - &lt;a href="http://msdn.microsoft.com/en-us/magazine/ee413550.aspx"&gt;http://msdn.microsoft.com/en-us/magazine/ee413550.aspx&lt;/a&gt;&lt;/p&gt;
&lt;p dir="ltr" align="center" font="franklingothicbookregular" xmlid="PARAJULY2007TOOLBOX023"&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;As always, if you have any suggestions for products, blogs, or books to review for the Toolbox column, please send them to &lt;/font&gt;&lt;/em&gt;&lt;a href="mailto:toolsmm@microsoft.com"&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;toolsmm@microsoft.com&lt;/font&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;.&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13954.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>A Tool For Querying Multiple Databases</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/BbBmUdzNj24/13939.aspx</link><pubDate>Tue, 08 Sep 2009 15:15:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13939.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13939.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13939.aspx#feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13939.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13939</trackback:ping><description>&lt;p&gt;I recently blogged about &lt;a href="http://scottonwriting.net/sowblog/posts/13924.aspx"&gt;different multi-tenant data architectures&lt;/a&gt;, comparing and constrasting the &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;font color="#a52a2a"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; architectures, as well as noting what sorts of questions to ask when trying to ascertain which model to use. One of the disadvantages of the &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases &lt;/font&gt;&lt;/strong&gt;approach is that it can be difficult to view data aggregated across the databases:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;Viewing data aggregated across the databases is difficult. I've touched upon this topic in an earlier blog post, &lt;a href="http://scottonwriting.net/sowblog/posts/13587.aspx"&gt;Running the Same Query Against Multiple Databases&lt;/a&gt;. When you find a bug on one database and need to see whether it affects data in other databases there are not many tools at your disposal. One poor man's tool is &lt;font face="Courier New"&gt;sp_msForEachDb&lt;/font&gt;, but it's less than ideal.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p dir="ltr"&gt;And reader &lt;a href="http://jaychapman.blogspot.com/"&gt;John Chapman&lt;/a&gt; added his two cents in the comments regarding this issue:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p dir="ltr"&gt;For lots of applications you actually have situations where there are users who need to be able to see data from multiple customers at the same time. For example you may have situations where you have external customers who see only their data, but yet have internal liaisons who need to oversee the activities of multiple customers. Therefore necessitating that they see data from multiple customers on a single screen.&lt;/p&gt;
&lt;p dir="ltr"&gt;I have ran into these situations before, which was a key reason why we used a single database shared schema. The application we replaced used separate databases and was unable to provide this sort of functionality.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p dir="ltr"&gt;Over the years I have created a very (very!) rough tool for querying multiple databases in a &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases &lt;/font&gt;&lt;/strong&gt;architecture. In short, you enter a query, select which databases to query against, and then the tool runs that query against each selected database and combines the results into a single &lt;font face="Courier New"&gt;&amp;lt;table&amp;gt;&lt;/font&gt; on the page. As you can see from the screen shot below (&lt;a href="http://www.4guysfromrolla.com/images/QueryMultiDB.gif"&gt;click for a larger version&lt;/a&gt;), the tool includes a multi-line textbox for entering the query to execute and a CheckBoxList of the databases to query. The results are included in a single &lt;font face="Courier New"&gt;&amp;lt;table&amp;gt;&lt;/font&gt;.&lt;/p&gt;
&lt;p dir="ltr" align="center"&gt;&lt;a href="http://www.4guysfromrolla.com/images/QueryMultiDB.gif"&gt;&lt;img border="1" src="http://www.4guysfromrolla.com/images/QueryMultiDB.gif" width="400" height="345" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p dir="ltr"&gt;While the above screen shot shows a query that just returns a scalar value (one column, one row), it certainly works with queries that return multiple rows and columns. And with a little bit of legwork the tool could be enhanced to include rollup-type functionality, showing subtotals per database and grand totals across all selected databases for numeric columns.&lt;/p&gt;
&lt;p dir="ltr"&gt;To learn more about how I created this tool, check out my latest &lt;a href="http://www.4guysfromrolla.com/"&gt;4GuysFromRolla.com&lt;/a&gt; article: &lt;a href="http://www.4guysfromrolla.com/articles/090909-1.aspx"&gt;Querying a Multi-Tenant Data Architecture&lt;/a&gt;.&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13939.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>Ruminations on Multi-Tenant Data Architectures</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/K3qsTu8wvKo/13924.aspx</link><pubDate>Wed, 19 Aug 2009 16:09:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13924.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13924.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13924.aspx#feedback</comments><slash:comments>9</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13924.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13924</trackback:ping><description>&lt;p&gt;One of the key advantages of web applications is that they can be deployed as a hosted service and accessed over the Internet rather than needing to be locally installed at a customer's site. Over the years I've helped numerous clients build web applications of this sort. When building such an application there are bound to be questions on the best way to model and store data from the different customers that access the hosted application. Do you co-mingle customer data in a single database with a single schema? Do you use a single database, but separate customer data into different schemas? Or do you create a separate database with separate tables for each customer? &lt;/p&gt;
&lt;p&gt;These three data architectures are examined and discussed in detail in the article &lt;a href="http://msdn.microsoft.com/en-us/library/aa479086.aspx"&gt;Multi-Tenant Data Architecture&lt;/a&gt;. The authors introduce three different multi-tenant data architectures, which they name:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; - in this architecture each customer's data is stored in a separate database. The databases may all be on the same database server or they could be partitioned across multiple database servers. This approach provides for maximum isolation of customer data. If we were building a hosted application using the Northwind database and had three customers then with this approach we would have three databases - Northwind01, Northwind02, and Northwind03 - and each database would have the same tables, views, stored procedures, and so on. Customer 1's data would all be located in the Northwind01 database, while Customer 2's data would be over in the Northwind02 database.&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;font color="#000080"&gt;Shared Databases, Separate Schemas&lt;/font&gt;&lt;/strong&gt; - SQL Server 2005 introduced the concept of &lt;em&gt;schemas&lt;/em&gt;, which offer a way to group a set of tables. With this approach you can have a single database with one schema for each customer. Returning to the Northwind example, with this approach there would be a single database, but there would be three schemas - Customer01Schema, Customer02Schema, and Customer03 schema. Each schema would have the same set of tables, views, stored procedures, and so forth. Customer1's product information would be found in the &lt;font face="Courier New"&gt;Customer01Schema.Products&lt;/font&gt; table, whereas Customer2's product information would be stored in &lt;font face="Courier New"&gt;Customer02Schema.Products&lt;/font&gt;.&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;font color="#a52a2a"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; - here we have only a single database and a single schema. There would be only one &lt;font face="Courier New"&gt;Products&lt;/font&gt; table. To differentiate one customer's products from another we'd need to add a &lt;font face="Courier New"&gt;NorthwindCustomers&lt;/font&gt; table that would have a record for each customer and then add a &lt;font face="Courier New"&gt;NorthwindCustomerID&lt;/font&gt; foreign key to the &lt;font face="Courier New"&gt;Products&lt;/font&gt; table (and to the other pertinent tables).&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The &lt;a href="http://msdn.microsoft.com/en-us/library/aa479086.aspx"&gt;Multi-Tenant Data Architecture&lt;/a&gt; examines these three approaches in much more detail with screen shots, T-SQL snippets, and more in-depth examples, and is definitely worth reading.&lt;/p&gt;
&lt;p&gt;I'd like to talk a little bit about my “from the trenches” experience with multi-tenant data architectures. First, a little background. &lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;&lt;strong&gt;Medical Software&lt;/strong&gt;&lt;br /&gt;In 2003 I started work with a client we'll call &lt;strong&gt;Acme Medical&lt;/strong&gt;, which was building a hosted, ASP.NET medical software application. This application had been in existence for a couple of years by this point, but as a Microsoft Access application that was installed and run locally from a couple of hospitals and clinics. I joined the project with the aim of moving the application from Microsoft Access to ASP.NET and SQL Server. We chose to use a Separate Databases architecture. I continue to work on this project today. Originally, there were two customers, each with a few thousand records. The data model contained maybe 20-30 tables. Today there are more than 15 customers, each with hundreds of thousands if not millions of records. There are nearly 350 database tables, hundreds of ASP.NET pages, and several automated backend processes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Print Management&lt;br /&gt;&lt;/strong&gt;Another client, &lt;strong&gt;Acme Printers&lt;/strong&gt;, was a prominent player in the print shop marketplace, selling in-house applications to large-scale print shops. I helped this company build an online, hosted version for order placement and fulfillment. We used a Shared Database, Shared Schema data model. In a nutshell, there is a &lt;font face="Courier New"&gt;Customers&lt;/font&gt; table with a &lt;font face="Courier New"&gt;CustomerID&lt;/font&gt; primary key value uniquely identifying each customer. Every other database table has a &lt;font face="Courier New"&gt;CustomerID&lt;/font&gt; field that identifies what data belongs to what customer. When a user signs on, we look up what customer the user is associated with and then store this &lt;font face="Courier New"&gt;CustomerID&lt;/font&gt; in session. This &lt;font face="Courier New"&gt;CustomerID&lt;/font&gt; variable is then used in other pages on the site to pull back the data pertinent to that customer.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p dir="ltr"&gt;I have never used the &lt;strong&gt;&lt;font color="#a52a2a"&gt;Shared Database, Separate Schema&lt;/font&gt;&lt;/strong&gt; approach. I recently spoke with &lt;a href="http://www.sqlserveraudits.com/consulting/"&gt;Michael Campbell&lt;/a&gt; about this topic, and he shared the following advice: “I'd recommend against using shared DBs and separate schemas. In my experience that almost NEVER works out as advertised, adds all sorts of difficulty in terms of disaster recovery, management, and isolation, and really doesn't offer any worthwhile benefits. It also becomes absolutely insane in terms of managing permissions/security as well.”&lt;/p&gt;
&lt;p dir="ltr"&gt;The factors that should influence whether you go with a &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Database&lt;/font&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; architecture are not technical, but rather are regulatory-, security-, or business-related. If you find yourself deciding to use one architecture over another because of some technological reason you're probably asking the wrong questions and evaluating the wrong criteria. &lt;/p&gt;
&lt;p dir="ltr"&gt;Here's how I tackle this problem. I start by assuming that I'm going to use the &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; architecture, as it's usually the best fit for the types of projects I work on. It easier to setup and implement and test and debug and scale up (to a point) than the &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Database&lt;/font&gt;&lt;/strong&gt; architecture. For the majority of web applications, the &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; architecture is the best approach when weighing just the technological- and development-related factors.&lt;/p&gt;
&lt;p dir="ltr"&gt;Sadly, in the real-world there are many factors that outweigh technological ones. While the &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; architecture has more friction associated with it than the &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt;, it does have certain advantages in terms of security, privacy, isolation, and so on. When evaluating which architecture to use, I like to ask my client the following questions:&lt;/p&gt;
&lt;ul dir="ltr"&gt;
&lt;li&gt;
&lt;div&gt;&lt;strong&gt;How important is the security and privacy of the data?&lt;/strong&gt; &lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;div&gt;For the medical software, security and privacy is paramount. Customers (the hospitals using the software) are entering social security numbers, lab test results, diagnoses from specialists and psychologists, and so on. The data is rife with very personal and sensitive patient information, not to mention information about the health care practitioners, including social security numbers, license information, and disciplinary actions.&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;/li&gt;&lt;li&gt;
&lt;div&gt;&lt;strong&gt;Are there any regulatory reasons for choosing one architecture over the other?&lt;/strong&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;div&gt;Depending on the line of business, there may be governmental or professional regulations that require a clean separation of data. For medical providers in the US there is a regulation known as &lt;a href="http://www.hhs.gov/ocr/privacy/"&gt;HIPAA&lt;/a&gt;, which defines guidelines for patient privacy. It's been several years since I've explored HIPAA, but from my recollection HIPAA does not require that a &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Database&lt;/font&gt;&lt;/strong&gt; architecture be used, but using one ensures that patient data in one hospital is isolated from the doctors and staff at other hospitals.&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;/li&gt;&lt;li&gt;
&lt;div&gt;&lt;strong&gt;Do your customers want/need access to the database?&lt;/strong&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;div&gt;In some industries, customers using a hosted application demand access to the data. For the medical software, there have been a handful of clients who, before signing up, stipulated that they have access to a weekly backup of their data, which they could store on their computer system. With the &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; approach it has been easy to service these requests - schedule a weekly backup for that customer's database and add an ASP.NET page that allows that customer's administrative users the ability to see past backups and download those backup files to their computer.&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;/li&gt;&lt;li&gt;
&lt;div&gt;&lt;strong&gt;How many customers do you expect to be using the system?&lt;/strong&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;div&gt;There is a maintenance cost associated with each new database. If you expect hundreds or thousands of customers then a &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; architecture is probably going to be prohibitive from a maintenance standpoint.&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Note that the above questions don't touch on technological or development issues. Rather, they are focusing on security, privacy, regulations, and the end user's needs or expectations.&lt;/p&gt;
&lt;p&gt;While the &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; architecture provides a higher degree of security and privacy through data isolation, there are a couple of challenges worth noting.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rolling out changes to the data model is more difficult.&lt;/strong&gt; Imagine that you've added a new feature, which entailed adding two database tables, a new view, and six new stored procedures. In a &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; architecture, rolling out that change is a matter of adding those database objects to the production database. With a &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; architecture, you need to make sure to roll out those changes to &lt;em&gt;all&lt;/em&gt; of the databases. With a systematic, tested, scripted process this is not much of a challenge, but if you don't have such a system defined - if this work is done manually, for instance - you're asking for trouble as you're undoubtedly going to have a scenario where the database changes get rolled out to some databases, but not all. This can be especially challenging if you have decided to customize the data model on a per-customer basis. If database X has certain tables or fields that are not found in database Y then this makes rolling out changes in an automated fashion more challenging. For this reason I would discourage making table-level customizations per customer if at all possible.&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Viewing data aggregated across the databases is difficult. &lt;/strong&gt;I've touched upon this topic in an earlier blog post, &lt;a href="http://scottonwriting.net/sowblog/posts/13587.aspx"&gt;Running the Same Query Against Multiple Databases&lt;/a&gt;. When you find a bug on one database and need to see whether it affects data in other databases there are not many tools at your disposal. One poor man's tool is &lt;font face="Courier New"&gt;sp_msForEachDb&lt;/font&gt;, but it's less than ideal.&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Adding a new customer requires creating a new database.&lt;/strong&gt; Signing up a new customer is great news for the company's bottom line, but what changes does it entail in the data model? For a &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; architecture, adding a new customer is as simple as adding a new record to the &lt;font face="Courier New"&gt;Customers&lt;/font&gt; table. For a &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; architecture, adding a new customer means creating a new database and adding the appropriate database objects and any initial data in these tables. Using the SQL Server &lt;font face="Courier New"&gt;model&lt;/font&gt; database you can simplify this process, but in order to do so you need to make sure that any changes to the data model - new stored procedures, tables, views, UDFs, etc. - need to also be added to the &lt;a href="http://msdn.microsoft.com/en-us/library/ms186388.aspx"&gt;&lt;font face="Courier New"&gt;model&lt;/font&gt; database&lt;/a&gt;. Similarly, any changes to the initial state - new default records in a lookup table, say - need to also be added to the appropriate tables in &lt;font face="Courier New"&gt;model&lt;/font&gt;.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Given the pros and cons of these two multi-tenant architectures, you can guess what architecture a company is using based on their price point and their targeted industry. If there is a high cost to sign up to the site then chances are the company has only a “few” clients (maybe just a few, maybe dozens, but probably not hundreds or thousands), and if the application is for medical, financial, or legal purposes then they probably use a &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; architecture. For web applications with a lower price point and geared to industries where data privacy and security is less important and less encumbered by regulations, chances are a &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; approach is being used. As you might expect, the medical software has around 15 customers, each paying thousands of dollars per month to use the service, whereas the print shop software has a couple hundred clients who pay a one time setup fee with the option to buy an annual support contract, which amortizes to hundreds of dollars per month per cusotmer.&lt;/p&gt;
&lt;p&gt;Whether you choose the &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; architecture depends largely on non-technological factors. As I noted earlier, I typically choose the &lt;strong&gt;&lt;font color="#000080"&gt;Shared Database, Shared Schema&lt;/font&gt;&lt;/strong&gt; architecture by default, only switching to a &lt;strong&gt;&lt;font color="#ff0000"&gt;Separate Databases&lt;/font&gt;&lt;/strong&gt; architecture if there are particular security, privacy, regulatory, or other business needs that necessitate it.&lt;/p&gt;</description><body xmlns="http://www.w3.org/1999/xhtml"><p>One of the key advantages of web applications is that they can be deployed as a hosted service and accessed over the Internet rather than needing to be locally installed at a customer's site. Over the years I've helped numerous clients build web applications of this sort. When building such an application there are bound to be questions on the best way to model and store data from the different customers that access the hosted application. Do you co-mingle customer data in a single database with a single schema? Do you use a single database, but separate customer data into different schemas? Or do you create a separate database with separate tables for each customer? </p>
<p>These three data architectures are examined and discussed in detail in the article <a href="http://msdn.microsoft.com/en-us/library/aa479086.aspx">Multi-Tenant Data Architecture</a>. The authors introduce three different multi-tenant data architectures, which they name:</p>
<ul>
<li><strong><font color="#ff0000">Separate Databases</font></strong> - in this architecture each customer's data is stored in a separate database. The databases may all be on the same database server or they could be partitioned across multiple database servers. This approach provides for maximum isolation of customer data. If we were building a hosted application using the Northwind database and had three customers then with this approach we would have three databases - Northwind01, Northwind02, and Northwind03 - and each database would have the same tables, views, stored procedures, and so on. Customer 1's data would all be located in the Northwind01 database, while Customer 2's data would be over in the Northwind02 database.<br />
</li><li><strong><font color="#000080">Shared Databases, Separate Schemas</font></strong> - SQL Server 2005 introduced the concept of <em>schemas</em>, which offer a way to group a set of tables. With this approach you can have a single database with one schema for each customer. Returning to the Northwind example, with this approach there would be a single database, but there would be three schemas - Customer01Schema, Customer02Schema, and Customer03 schema. Each schema would have the same set of tables, views, stored procedures, and so forth. Customer1's product information would be found in the <font face="Courier New">Customer01Schema.Products</font> table, whereas Customer2's product information would be stored in <font face="Courier New">Customer02Schema.Products</font>.<br />
</li><li><strong><font color="#a52a2a">Shared Database, Shared Schema</font></strong> - here we have only a single database and a single schema. There would be only one <font face="Courier New">Products</font> table. To differentiate one customer's products from another we'd need to add a <font face="Courier New">NorthwindCustomers</font> table that would have a record for each customer and then add a <font face="Courier New">NorthwindCustomerID</font> foreign key to the <font face="Courier New">Products</font> table (and to the other pertinent tables).</li></ul>
<p>The <a href="http://msdn.microsoft.com/en-us/library/aa479086.aspx">Multi-Tenant Data Architecture</a> examines these three approaches in much more detail with screen shots, T-SQL snippets, and more in-depth examples, and is definitely worth reading.</p>
<p>I'd like to talk a little bit about my “from the trenches” experience with multi-tenant data architectures. First, a little background. </p>
<blockquote style="MARGIN-RIGHT: 0px" dir="ltr">
<p><strong>Medical Software</strong><br />In 2003 I started work with a client we'll call <strong>Acme Medical</strong>, which was building a hosted, ASP.NET medical software application. This application had been in existence for a couple of years by this point, but as a Microsoft Access application that was installed and run locally from a couple of hospitals and clinics. I joined the project with the aim of moving the application from Microsoft Access to ASP.NET and SQL Server. We chose to use a Separate Databases architecture. I continue to work on this project today. Originally, there were two customers, each with a few thousand records. The data model contained maybe 20-30 tables. Today there are more than 15 customers, each with hundreds of thousands if not millions of records. There are nearly 350 database tables, hundreds of ASP.NET pages, and several automated backend processes.</p>
<p><strong>Print Management<br /></strong>Another client, <strong>Acme Printers</strong>, was a prominent player in the print shop marketplace, selling in-house applications to large-scale print shops. I helped this company build an online, hosted version for order placement and fulfillment. We used a Shared Database, Shared Schema data model. In a nutshell, there is a <font face="Courier New">Customers</font> table with a <font face="Courier New">CustomerID</font> primary key value uniquely identifying each customer. Every other database table has a <font face="Courier New">CustomerID</font> field that identifies what data belongs to what customer. When a user signs on, we look up what customer the user is associated with and then store this <font face="Courier New">CustomerID</font> in session. This <font face="Courier New">CustomerID</font> variable is then used in other pages on the site to pull back the data pertinent to that customer.</p></blockquote>
<p dir="ltr">I have never used the <strong><font color="#a52a2a">Shared Database, Separate Schema</font></strong> approach. I recently spoke with <a href="http://www.sqlserveraudits.com/consulting/">Michael Campbell</a> about this topic, and he shared the following advice: “I'd recommend against using shared DBs and separate schemas. In my experience that almost NEVER works out as advertised, adds all sorts of difficulty in terms of disaster recovery, management, and isolation, and really doesn't offer any worthwhile benefits. It also becomes absolutely insane in terms of managing permissions/security as well.”</p>
<p dir="ltr">The factors that should influence whether you go with a <strong><font color="#ff0000">Separate Database</font></strong> or <strong><font color="#000080">Shared Database, Shared Schema</font></strong> architecture are not technical, but rather are regulatory-, security-, or business-related. If you find yourself deciding to use one architecture over another because of some technological reason you're probably asking the wrong questions and evaluating the wrong criteria. </p>
<p dir="ltr">Here's how I tackle this problem. I start by assuming that I'm going to use the <strong><font color="#000080">Shared Database, Shared Schema</font></strong> architecture, as it's usually the best fit for the types of projects I work on. It easier to setup and implement and test and debug and scale up (to a point) than the <strong><font color="#ff0000">Separate Database</font></strong> architecture. For the majority of web applications, the <strong><font color="#000080">Shared Database, Shared Schema</font></strong> architecture is the best approach when weighing just the technological- and development-related factors.</p>
<p dir="ltr">Sadly, in the real-world there are many factors that outweigh technological ones. While the <strong><font color="#ff0000">Separate Databases</font></strong> architecture has more friction associated with it than the <strong><font color="#000080">Shared Database, Shared Schema</font></strong>, it does have certain advantages in terms of security, privacy, isolation, and so on. When evaluating which architecture to use, I like to ask my client the following questions:</p>
<ul dir="ltr">
<li>
<div><strong>How important is the security and privacy of the data?</strong> </div>
<ul>
<li>
<div>For the medical software, security and privacy is paramount. Customers (the hospitals using the software) are entering social security numbers, lab test results, diagnoses from specialists and psychologists, and so on. The data is rife with very personal and sensitive patient information, not to mention information about the health care practitioners, including social security numbers, license information, and disciplinary actions.</div></li></ul>
</li><li>
<div><strong>Are there any regulatory reasons for choosing one architecture over the other?</strong></div>
<ul>
<li>
<div>Depending on the line of business, there may be governmental or professional regulations that require a clean separation of data. For medical providers in the US there is a regulation known as <a href="http://www.hhs.gov/ocr/privacy/">HIPAA</a>, which defines guidelines for patient privacy. It's been several years since I've explored HIPAA, but from my recollection HIPAA does not require that a <strong><font color="#ff0000">Separate Database</font></strong> architecture be used, but using one ensures that patient data in one hospital is isolated from the doctors and staff at other hospitals.</div></li></ul>
</li><li>
<div><strong>Do your customers want/need access to the database?</strong></div>
<ul>
<li>
<div>In some industries, customers using a hosted application demand access to the data. For the medical software, there have been a handful of clients who, before signing up, stipulated that they have access to a weekly backup of their data, which they could store on their computer system. With the <strong><font color="#ff0000">Separate Databases</font></strong> approach it has been easy to service these requests - schedule a weekly backup for that customer's database and add an ASP.NET page that allows that customer's administrative users the ability to see past backups and download those backup files to their computer.</div></li></ul>
</li><li>
<div><strong>How many customers do you expect to be using the system?</strong></div>
<ul>
<li>
<div>There is a maintenance cost associated with each new database. If you expect hundreds or thousands of customers then a <strong><font color="#ff0000">Separate Databases</font></strong> architecture is probably going to be prohibitive from a maintenance standpoint.</div></li></ul></li></ul>
<p>Note that the above questions don't touch on technological or development issues. Rather, they are focusing on security, privacy, regulations, and the end user's needs or expectations.</p>
<p>While the <strong><font color="#ff0000">Separate Databases</font></strong> architecture provides a higher degree of security and privacy through data isolation, there are a couple of challenges worth noting.</p>
<ul>
<li><strong>Rolling out changes to the data model is more difficult.</strong> Imagine that you've added a new feature, which entailed adding two database tables, a new view, and six new stored procedures. In a <strong><font color="#000080">Shared Database, Shared Schema</font></strong> architecture, rolling out that change is a matter of adding those database objects to the production database. With a <strong><font color="#ff0000">Separate Databases</font></strong> architecture, you need to make sure to roll out those changes to <em>all</em> of the databases. With a systematic, tested, scripted process this is not much of a challenge, but if you don't have such a system defined - if this work is done manually, for instance - you're asking for trouble as you're undoubtedly going to have a scenario where the database changes get rolled out to some databases, but not all. This can be especially challenging if you have decided to customize the data model on a per-customer basis. If database X has certain tables or fields that are not found in database Y then this makes rolling out changes in an automated fashion more challenging. For this reason I would discourage making table-level customizations per customer if at all possible.<br />
</li><li><strong>Viewing data aggregated across the databases is difficult. </strong>I've touched upon this topic in an earlier blog post, <a href="http://scottonwriting.net/sowblog/posts/13587.aspx">Running the Same Query Against Multiple Databases</a>. When you find a bug on one database and need to see whether it affects data in other databases there are not many tools at your disposal. One poor man's tool is <font face="Courier New">sp_msForEachDb</font>, but it's less than ideal.<br />
</li><li><strong>Adding a new customer requires creating a new database.</strong> Signing up a new customer is great news for the company's bottom line, but what changes does it entail in the data model? For a <strong><font color="#000080">Shared Database, Shared Schema</font></strong> architecture, adding a new customer is as simple as adding a new record to the <font face="Courier New">Customers</font> table. For a <strong><font color="#ff0000">Separate Databases</font></strong> architecture, adding a new customer means creating a new database and adding the appropriate database objects and any initial data in these tables. Using the SQL Server <font face="Courier New">model</font> database you can simplify this process, but in order to do so you need to make sure that any changes to the data model - new stored procedures, tables, views, UDFs, etc. - need to also be added to the <a href="http://msdn.microsoft.com/en-us/library/ms186388.aspx"><font face="Courier New">model</font> database</a>. Similarly, any changes to the initial state - new default records in a lookup table, say - need to also be added to the appropriate tables in <font face="Courier New">model</font>.</li></ul>
<p>Given the pros and cons of these two multi-tenant architectures, you can guess what architecture a company is using based on their price point and their targeted industry. If there is a high cost to sign up to the site then chances are the company has only a “few” clients (maybe just a few, maybe dozens, but probably not hundreds or thousands), and if the application is for medical, financial, or legal purposes then they probably use a <strong><font color="#ff0000">Separate Databases</font></strong> architecture. For web applications with a lower price point and geared to industries where data privacy and security is less important and less encumbered by regulations, chances are a <strong><font color="#000080">Shared Database, Shared Schema</font></strong> approach is being used. As you might expect, the medical software has around 15 customers, each paying thousands of dollars per month to use the service, whereas the print shop software has a couple hundred clients who pay a one time setup fee with the option to buy an annual support contract, which amortizes to hundreds of dollars per month per cusotmer.</p>
<p>Whether you choose the <strong><font color="#000080">Shared Database, Shared Schema</font></strong> or <strong><font color="#ff0000">Separate Databases</font></strong> architecture depends largely on non-technological factors. As I noted earlier, I typically choose the <strong><font color="#000080">Shared Database, Shared Schema</font></strong> architecture by default, only switching to a <strong><font color="#ff0000">Separate Databases</font></strong> architecture if there are particular security, privacy, regulatory, or other business needs that necessitate it.</p></body><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13924.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>Range-Specific Requests in ASP.NET</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/h-8dJ7HAuvY/13917.aspx</link><pubDate>Fri, 14 Aug 2009 07:15:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13917.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13917.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13917.aspx#feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13917.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13917</trackback:ping><description>&lt;p&gt;The HTTP/1.1 protocol include support for &lt;em&gt;range-specific requests&lt;/em&gt;, which allow a client to optionally request a particular range of bytes rather than request the entire file. This functionality is most commonly used by download manager programs, which allow users to pause and resume downloads. In a nutshell, a download manager will start by asking for the entire contents of a file (the default behavior). If the user pauses the download or if the download manager is shut down, the last downloaded byte is remembered. Later, the download manager can resume the download by sending a request to the server for the file starting from the last downloaded byte.&lt;/p&gt;
&lt;p&gt;The following diagram illustrates the range-specific request workflow just described when downloading a large file named &lt;font face="Courier New"&gt;DancingHampsters.zip&lt;/font&gt;. The diagram shows what happens when the download is paused (or interrupted) after having downloaded the first 500,000 bytes, and how using range-specific requests the client can resume the download starting from a specified location in the file (rather than having to re-download the file in its entirety).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;center&gt;&lt;img border="0" src="http://datawebcontrols.com/images/SpecificASPNETRequests01.jpg" width="400" height="539" /&gt;&lt;/center&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;While IIS natively handles range-specific requests, ASP.NET does not. If you are serving binary content from an ASP.NET HTTP Handler and if you want (or need) to support range-specific requests then you'll need to add such functionality yourself. I bumped into this requirement when working on a project that serves videos to Apple's handheld devices - the iPhone and iPod Touch. The iPhone and iPod Touch request video files using range-specific requests. Therefore, if you are serving these videos straight from the file system via IIS then everything will work as expected, but if you serve the video from an HTTP Handler in order to implement authorization rules or if the files are dynamically generated then you'll need to write code in your HTTP Handler that will handle the range-specific requests sent by the Apple devices.&lt;/p&gt;
&lt;p&gt;In my research I stumbled upon an article by Alexander Schaaf titled &lt;a href="http://www.devx.com/dotnet/Article/22533/1954"&gt;Tracking and Resuming Large File Downloads in ASP.NET&lt;/a&gt;, which presented Visual Basic code showing how to implement range-specific requests in an ASP.NET 1.x application. I took this code, refactored it, ported it to C#, and utilized a number of language enhancements added since the .NET 1.x days. This code, along with a discussion on how range-specific requests work and a look at how to use the code, is now available on &lt;a href="http://dotnetslackers.com/"&gt;DotNetSlackers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Read more at: &lt;a href="http://dotnetslackers.com/articles/aspnet/Range-Specific-Requests-in-ASP-NET.aspx"&gt;&lt;strong&gt;Range-Specific Requests in ASP.NET&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy Programming!&lt;/p&gt;</description><body xmlns="http://www.w3.org/1999/xhtml"><p>The HTTP/1.1 protocol include support for <em>range-specific requests</em>, which allow a client to optionally request a particular range of bytes rather than request the entire file. This functionality is most commonly used by download manager programs, which allow users to pause and resume downloads. In a nutshell, a download manager will start by asking for the entire contents of a file (the default behavior). If the user pauses the download or if the download manager is shut down, the last downloaded byte is remembered. Later, the download manager can resume the download by sending a request to the server for the file starting from the last downloaded byte.</p>
<p>The following diagram illustrates the range-specific request workflow just described when downloading a large file named <font face="Courier New">DancingHampsters.zip</font>. The diagram shows what happens when the download is paused (or interrupted) after having downloaded the first 500,000 bytes, and how using range-specific requests the client can resume the download starting from a specified location in the file (rather than having to re-download the file in its entirety).</p>
<p />
<center><img border="0" src="http://datawebcontrols.com/images/SpecificASPNETRequests01.jpg" width="400" height="539" /></center>
<p />
<p>While IIS natively handles range-specific requests, ASP.NET does not. If you are serving binary content from an ASP.NET HTTP Handler and if you want (or need) to support range-specific requests then you'll need to add such functionality yourself. I bumped into this requirement when working on a project that serves videos to Apple's handheld devices - the iPhone and iPod Touch. The iPhone and iPod Touch request video files using range-specific requests. Therefore, if you are serving these videos straight from the file system via IIS then everything will work as expected, but if you serve the video from an HTTP Handler in order to implement authorization rules or if the files are dynamically generated then you'll need to write code in your HTTP Handler that will handle the range-specific requests sent by the Apple devices.</p>
<p>In my research I stumbled upon an article by Alexander Schaaf titled <a href="http://www.devx.com/dotnet/Article/22533/1954">Tracking and Resuming Large File Downloads in ASP.NET</a>, which presented Visual Basic code showing how to implement range-specific requests in an ASP.NET 1.x application. I took this code, refactored it, ported it to C#, and utilized a number of language enhancements added since the .NET 1.x days. This code, along with a discussion on how range-specific requests work and a look at how to use the code, is now available on <a href="http://dotnetslackers.com/">DotNetSlackers</a>.</p>
<p>Read more at: <a href="http://dotnetslackers.com/articles/aspnet/Range-Specific-Requests-in-ASP-NET.aspx"><strong>Range-Specific Requests in ASP.NET</strong></a>.</p>
<p>Happy Programming!</p></body><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13917.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>Upcoming Talk: Content Syndication With ASP.NET 3.5 (San Diego ASP.NET SIG)</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/gRIsCharMtQ/13914.aspx</link><pubDate>Tue, 11 Aug 2009 14:12:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13914.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13914.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13914.aspx#feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13914.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13914</trackback:ping><description>&lt;p&gt;I'll be speaking this coming Tuesday (August 18th) at the &lt;a href="http://asp.sig.webpublishing.com/"&gt;San Diego ASP.NET SIG&lt;/a&gt;. My talk will focus on how to create and consume syndicated content (RSS and Atom) using the classes in the &lt;font face="Courier New"&gt;System.ServiceModel.Syndication&lt;/font&gt;, which were added to the .NET Framework version 3.5. Here is a synopsis of the talk:&lt;/p&gt;
&lt;blockquote style="MARGIN-RIGHT: 0px" dir="ltr"&gt;
&lt;p&gt;A syndication feed is an XML file that summarizes a website's most recently published content. It is commonly used in blogs, news sites, sports sites, social networking sites, and other content producing websites to provide a machine-readable format of the latest content. Microsoft added a number of classes to the .NET Framework version 3.5 to ease creating and reading RSS 2.0 and Atom 1.0 syndication feeds. In this talk we'll explore the history of online syndication and see how to consume and create syndicated content in an ASP.NET web application. &lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The San Diego ASP.NET SIG meetings are held at the Microsoft offices in UTC and are free to attend. Best of all, there will be &lt;strong&gt;&lt;font color="#ff0000"&gt;&lt;em&gt;free pizza and soft drinks&lt;/em&gt;&lt;/font&gt;&lt;/strong&gt; starting at 6:00 PM!!!&lt;/p&gt;
&lt;p&gt;Hope to see you there! If you cannot make it, but are interested in learning more, you can download the slides from &lt;a href="http://datawebcontrols.com/classes/Syndication.zip"&gt;http://datawebcontrols.com/classes/Syndication.zip&lt;/a&gt;. Also be sure to check out my article, &lt;a href="http://dotnetslackers.com/articles/aspnet/How-to-create-a-syndication-feed-for-your-website.aspx"&gt;How to create a syndication feed for your website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy Programming&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13914.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>August's Toolbox Column Now Online</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/MQzZOzL2H2w/13906.aspx</link><pubDate>Thu, 30 Jul 2009 11:33:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13906.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13906.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13906.aspx#feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13906.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13906</trackback:ping><description>&lt;p&gt;&lt;a href="http://msdn.microsoft.com/en-us/magazine/ee294454.aspx"&gt;My &lt;em&gt;Toolbox&lt;/em&gt; column&lt;/a&gt; in the August 2009 issue of &lt;em&gt;&lt;a href="http://msdn.microsoft.com/en-us/magazine/"&gt;MSDN Magazine&lt;/a&gt;&lt;/em&gt; is available online and includes the following reviews:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="http://sqlsets.com/"&gt;SQL Sets&lt;/a&gt;&lt;/strong&gt; - SQL Sets is a neat tool that allows you to save database queries in a self-contained, portable file format. In a nutshell, you can run a database query, save the results into a file, and then email the file to a colleague who can annotate, sort, filter, and explore the results without needing access to the database or having any knowledge of the SQL syntax. It's a nifty tool for for sharing data with stakeholders without giving them access to the database; it can also be used to compare sets or as a tool for archiving data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blogs of Note: &lt;/strong&gt;&lt;a href="http://stephenwalther.com/blog"&gt;&lt;strong&gt;Stephen&lt;/strong&gt; &lt;strong&gt;Walther on ASP.NET MVC&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; - &lt;/strong&gt;every developer using ASP.NET MVC should subscribe to author, speaker, and Microsoftie Stephen Walther's blog.  They you'll find more than 50 in-depth detailed tips on using ASP.NET MVC along with end-to-end tutorials, and sample chapters from his books.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="http://visualsvn.com/server/"&gt;Visual SVN Server&lt;/a&gt;&lt;/strong&gt; - Subversion is a popular and free source code control application. Unfortunately, getting started with Subversion entails a bit of a learning curve. For example, in order to access the Subversion repository over HTTP you need to install and configure the Apache web server. Moreover, many of Subversion's configuration options require tinkering with text-based configuration files. Visual SVN Server is a free product that makes using Subversion as easy as point and click. With a few clicks of the mouse, Visual SVN Server will install and configure Subversion (and Apache) for you. And, once installed, it offers a GUI interface for creating and managing repositories and users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="http://narrange.net/"&gt;NArrange&lt;/a&gt;&lt;/strong&gt; - NArrange is a free, open source tool that automatically rearranges your C# or Visual Basic source code into a more readable format. From the review: 'By default, NArrange groups constructors, fields, properties, methods, and events into regions and alphabetizes the members within each region. Consecutive blank lines are removed, tabs are converted into spaces, and the &lt;font face="Courier New"&gt;using&lt;/font&gt; or &lt;font face="Courier New"&gt;Import&lt;/font&gt; directives within a class file are consolidated and sorted.' You can have NArrange rearrange a particular file, all files in a directory, or all files in a Visual Studio Project or Solution, and NArrange can be launched from the command-line or from within Visual Studio.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;This issue did not include a book review.&lt;/p&gt;
&lt;p&gt;Enjoy! - &lt;a href="http://msdn.microsoft.com/en-us/magazine/ee294454.aspx"&gt;http://msdn.microsoft.com/en-us/magazine/ee294454.aspx&lt;/a&gt;&lt;/p&gt;
&lt;p dir="ltr" align="center" xmlid="PARAJULY2007TOOLBOX023" font="franklingothicbookregular"&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;As always, if you have any suggestions for products, blogs, or books to review for the Toolbox column, please send them to &lt;/font&gt;&lt;/em&gt;&lt;a href="mailto:toolsmm@microsoft.com"&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;toolsmm@microsoft.com&lt;/font&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;.&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13906.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>ASP.NET Membership Tip: Requiring New Users To Change Their Password When Logging On For The First Time</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/i0kfk8fKC8g/13901.aspx</link><pubDate>Mon, 27 Jul 2009 10:13:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13901.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13901.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13901.aspx#feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13901.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13901</trackback:ping><description>&lt;p&gt;Most Internet-facing websites that support user accounts allow visitors to register an account on their own. Take a site like Facebook, for example. A visitor who wishes to create a new account may do so by visiting the registration page, choosing a username and password, and entering their email address. Implementing such a workflow in an ASP.NET application is relatively straightforward: enable &lt;a href="http://aspnet.4guysfromrolla.com/articles/120705-1.aspx"&gt;Membership&lt;/a&gt; and then create a registration web page, using a CreateUserWizard control to collect user input and create the new account. Out of the box, the CreateUserWizard control prompts a registering user for their username, password, email address, and security question and answer, and then creates a new account and signs the user in once the process completes.&lt;/p&gt;
&lt;p&gt;Like the othe Login-related Web controls, the CreateUserWizard can be customized both in its appearance and behavior. There are articles on 4Guys, like &lt;a href="http://www.4guysfromrolla.com/articles/070506-1.aspx"&gt;Customizing the CreateUserWizard Control&lt;/a&gt;, that show how to configure the CreateUserWizard control to include additional questions to the registering user. &lt;a href="http://www.4guysfromrolla.com/articles/062508-1.aspx"&gt;Examining ASP.NET's Membership, Roles, and Profile - Part 11&lt;/a&gt; explores how to verify a new user's email address by requiring them to click on a link sent in an email message before being signing in for the first time. It's also possible to use the CreateUserWizard control to create user accounts &lt;em&gt;for other people&lt;/em&gt;. This is useful for websites that don't allow anonymous users to register, but rather require that the site's administrators manually create each user account.&lt;/p&gt;
&lt;p&gt;I recently got an email from a reader who had a site where user accounts were created by a site administrator. Upon creating the account, the new user would receive an email with the username and password the administrator chose for them, along with a link to the sign in page. What this reader wanted to do was require these new users to immediately change their password after signing in for the first time. This functionality is easy to implement with a slight enhancement to a previous article of mine.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.4guysfromrolla.com/articles/070809-1.aspx"&gt;Examining ASP.NET's Membership, Roles, and Profile - Part 16&lt;/a&gt; shows how to set up a password expiry policy for an ASP.NET application that uses Membership. In a nutshell, the Membership system exposes a particular user's last password changed date/time via the &lt;font face="Courier New"&gt;MembershipUser&lt;/font&gt; class's &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.security.membershipuser.lastpasswordchangeddate.aspx"&gt;&lt;font face="Courier New"&gt;LastPasswordChangedDate&lt;/font&gt; property&lt;/a&gt;. To quote from the article: “This property is set to the current date and time &lt;strong&gt;when the user account is first created&lt;/strong&gt; or whenever the user changes her password.”&lt;/p&gt;
&lt;p&gt;Part 16 shows how to create a page where the user can change their password as well as how to determine if the user's password has expired when they sign on. This latter task is accomplished by creating an event handler for the Login control's &lt;font face="Courier New"&gt;Authenticate&lt;/font&gt; event and verifying that the number of days since the user last changed their password has not exceeded the password expiry window:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;Protected Sub myLogin_Authenticate(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.AuthenticateEventArgs) Handles myLogin.Authenticate&lt;br /&gt;   'Are the credentials valid?&lt;br /&gt;   If Membership.ValidateUser(myLogin.UserName, myLogin.Password) Then&lt;br /&gt;      &lt;font color="#ff0000"&gt;'Has the password expired?&lt;br /&gt;      Dim usrInfo As MembershipUser = Membership.GetUser(myLogin.UserName)&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#ff0000"&gt;      Dim daysSincePwdChange As Integer = Convert.ToInt32(DateTime.Now.Subtract(usrInfo.LastPasswordChangedDate).TotalDays)&lt;br /&gt;      If daysSincePwdChange &amp;gt; SecurityUtils.DefaultPasswordExpiryInDays Then&lt;br /&gt;         'Password expired, send user to change password&lt;br /&gt;         Response.Redirect("~/ChangePassword.aspx?UserName=" &amp;amp; Server.UrlEncode(myLogin.UserName))&lt;br /&gt;&lt;/font&gt;      Else&lt;br /&gt;         e.Authenticated = True 'Credentials valid &amp;amp; password is current&lt;br /&gt;      End If&lt;br /&gt;   Else&lt;br /&gt;      e.Authenticated = False    'Invalid!&lt;br /&gt;   End If&lt;br /&gt;End Sub&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;To force &lt;em&gt;new&lt;/em&gt; users to change their password upon signing in for the first time, simply add a condition to the If statement to check whether the user's &lt;font face="Courier New"&gt;CreationDate&lt;/font&gt; and &lt;font face="Courier New"&gt;LastPasswordChangedDate&lt;/font&gt; properties are one in the same:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;If daysSincePwdChange &amp;gt; SecurityUtils.DefaultPasswordExpiryInDays &lt;font color="#ff0000"&gt;OrElse userInfo.CreationDate = userInfo.LastPasswordChangedDate&lt;/font&gt; Then&lt;br /&gt;    ...&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;That's it!&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13901.aspx</feedburner:origLink></item><item><dc:creator>Scott Mitchell</dc:creator><title>Session State Not Working? Check Your Web Garden!</title><link>http://feedproxy.google.com/~r/ScottOnWriting/~3/p8Y-O6XvLik/13896.aspx</link><pubDate>Thu, 23 Jul 2009 08:59:00 GMT</pubDate><guid isPermaLink="false">http://scottonwriting.net/sowblog/posts/13896.aspx</guid><wfw:comment>http://scottonwriting.net/sowblog/comments/13896.aspx</wfw:comment><comments>http://scottonwriting.net/sowblog/posts/13896.aspx#feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://scottonwriting.net/sowblog/comments/commentRss/13896.aspx</wfw:commentRss><trackback:ping>http://scottonwriting.net/sowblog/trackback.aspx?ID=13896</trackback:ping><description>&lt;p&gt;I recently had a client with an urgent issue with a legacy classic ASP web application - users to his site were getting logged out immediately after logging into the site, or after visiting one or two pages. As with most classic ASP applications, user authentication information was being persisted in session state so this symptom implied that session state was not being maintained.&lt;/p&gt;
&lt;p&gt;Session state problems usually arise from one of two causes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The user is not properly maintaining the session token.&lt;/strong&gt; By default, the web server is able to associate a user with a particular session store through the use of a SessionID value that is stored on the user's browser via a cookie. Each time a user makes a request to the server, this cookie is sent along and, lo, the web server knows the user's SessionID. If the user's browser is configured to reject cookies, or if there is a proxy or firewall on the user's side that is stripping out this cookie, then each time the user makes a request to the server it will appear as if it's the user's first request and the server will generate a new SessionID and a new cookie for the user.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A proxy server or firewall on the web server side is stripping out the cookie.&lt;/strong&gt; It may be that the user is correctly storing and sending the session cookie, but that the hardware on the web server's side is stripping out the cookie before it can be read by the ASP or ASP.NET engine, causing the server to generate a new SessionID and a new cookie for the user on each request.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;To narrow down the cause of the session problem, the first step is to fire up &lt;a href="http://www.fiddler2.com/fiddler2/"&gt;Fiddler&lt;/a&gt; and examine the HTTP traffic when visiting the site through a browser. In particular, look at the HTTP headers to see the &lt;strong&gt;Set-Cookie&lt;/strong&gt; values sent &lt;em&gt;from &lt;/em&gt;the server to the browser, and the &lt;strong&gt;Cookie&lt;/strong&gt; value sent from the browser to the server. In running these initial tests, I saw that the browser was clearly sending the SessionID cookie to the server, but that the server was responding with a new SessionID cookie on most requests. However, every now and then the server would &lt;em&gt;not&lt;/em&gt; send back a cookie at all, as if it had correctly identified the user by their SessioniD. Hrm....&lt;/p&gt;
&lt;p&gt;My first inclination was to make sure that there was no hardware of software stripping out the cookie before it reached the server. To test this, create a page that dumps out the server variables to the screen, which include the cookies sent by the browser. (For code to do this in classic ASP, see: &lt;a href="http://www.4guysfromrolla.com/webtech/092298-3.shtml"&gt;Using the Request.ServerVariables Object&lt;/a&gt;; for code in ASP.NET, see: &lt;a href="http://www.tamilcodes.com/tamilcodes/document.aspx?content=Snippets&amp;amp;id=43"&gt;Iterating Through ServerVariables in ASP.NET and C#&lt;/a&gt;.) If there was something stripping out the session cookie then when visiting this page through a browser I should see two things: in Fiddler I should see that the browser sends a &lt;strong&gt;Cookie&lt;/strong&gt; HTTP Header, but in the output of the web page that lists the server variables I should &lt;em&gt;not &lt;/em&gt;see that cookie information in the &lt;strong&gt;HTTP_COOKIE&lt;/strong&gt; server variable.&lt;/p&gt;
&lt;p&gt;However, when I ran the above test I did see the cookie values. So the browser was correctly storing and sending the SessionID cookie, the server was receiving it, but for some reason it was creating a new session store on (almost) every request. What was going on?&lt;/p&gt;
&lt;p&gt;After nearly two hours of searching the Internet and poking through IIS settings, it finally became clear - the client had configured the IIS Application Pool to use a 20-thread web garden. When you use a web garden you instruct IIS to spin up multiple worker processes to handle incoming requests. The idea is that each worker process can be migrated to a different core on a multi-core CPU and thereby improve scalability, performance, and reliability. (&lt;a href="http://nicholas.piasecki.name/blog/"&gt;Nicholas Piasecki&lt;/a&gt; has an excellent overview of web gardens, their benefits, and common pitfalls over at his blog: &lt;a href="http://nicholas.piasecki.name/blog/2009/02/on-web-gardens-aspnet-and-iis-60/"&gt;On Web Gardens, ASP.NET, and IIS 6.0&lt;/a&gt;.) One potential issue with web gardens is that they do not support InProc Session State, as with InProc Session State the session data is stored in the same memory space as each worker process. &lt;/p&gt;
&lt;p&gt;What was happening with this client is that when a user visited the site the request would be randomly dispatched to one fo the worker processes in the garden and it would create (and store) session state for that user, sending back a valid SessionID value. When the user went to the next page the browser sent back the SessionID as expected, but there was a 19/20 chance (95%) that the user would be sent to a &lt;strong&gt;different&lt;/strong&gt; worker process in the garden. This different worker process would not have access to the session store in the original worker process and would therefore create a new session store and create a new SessionID cookie.&lt;/p&gt;
&lt;p&gt;In ASP.NET you can configure session state to use the OutOfProc setting, which stores session data using a state server rather than at the worker process level; alternatively, you could configure session state to store session in SQL Server. Either configuration could be used to have session state work in a web garden. With classic ASP applications, there are no configuration options for session state storage. Consequently, the only fix was to disable the web garden. And anyway, I think that setting was made accidentally, as I don't know why this client would want to suddently increase the number of worker processes from 1 to 20, as this web application is hosted on a dedicated server and gets at most a few dozen unique users per day.&lt;/p&gt;
&lt;p&gt;Long story short, the vast majority of session state problems center around the cookie not being properly saved by the client or by the server not getting the cookie due to some hardware or software stripping it out of the incoming request. But if you've exhausted those avenues, be sure to check the web garden setting for the application pool. It may save you a couple of hours! :-)&lt;/p&gt;</description><feedburner:origLink>http://scottonwriting.net/sowblog/posts/13896.aspx</feedburner:origLink></item></channel></rss>
