<rss version="2.0" 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:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>Chris Falter</title>
        <link>http://geekswithblogs.net/chrisfalter/Default.aspx</link>
        <description>.NET Design and Best Practices</description>
        <language>en-US</language>
        <copyright>Chris Falter</copyright>
        <managingEditor>chrisfalter@yahoo.com</managingEditor>
        <generator>Subtext Version 0.0.0.0</generator>
        <image>
            <title>Chris Falter</title>
            <url>http://geekswithblogs.net/images/RSS2Image.gif</url>
            <link>http://geekswithblogs.net/chrisfalter/Default.aspx</link>
            <width>77</width>
            <height>60</height>
        </image>
        <item>
            <title>How to Reuse Code Without Creating an Implicit API</title>
            <category>Coding Practices and Design Patterns</category>
            <category>Testing &amp; Debugging</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2009/03/29/how-to-reuse-code-without-creating-an-implicit-api.aspx</link>
            <description>&lt;p&gt;I work for an ISV, and we have to be very cautious about the code that we declare with public scope.  If it's public, a customer can treat it as an API, which might not always be a good idea if the code is really just doing some internal task in a way that could easily change as we improve our software.  In other words, not all that is public should be an API.&lt;/p&gt;
&lt;p&gt;So what do you do if you need to share code across assembly boundaries?  Recently I was writing some unit tests for a class, and the tests needed access to a class method in order to imitate (and test) its behavior.  Of course this method was private--&lt;em&gt;of course!&lt;/em&gt;--so it appeared that I had to choose between 2 very unpalatable options:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Give the method public scope, which would make it visible to all our customers' implementations.  Essentially, this option would make the method part of our API.  Not happening. &lt;/li&gt;
    &lt;li&gt;Copy the 20 lines of code from the method, paste it into my new class, and supplicate the dark spirits of bad software design to supernaturally prevent the method from ever being modified.  But how could I perform the ritual without an eye of newt and toe of frog?* &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But appearances can be deceiving.  It turns out that the &lt;a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx"&gt;InternalsVisibleToAttribute&lt;/a&gt;, introduced by Microsoft in .NET 2.0, solves the problem quite neatly.  Using this attribute, you can designate one or more friend assemblies that will be allowed access to classes, methods and properties in the target assembly that have friend scope, while all other code--including customer code--is denied access.&lt;/p&gt;
&lt;p&gt;Using the attribute is a piece of cake:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Mark any classes/methods/properties that need to be visible to other assemblies with "Friend" scope. &lt;/li&gt;
    &lt;li&gt;
    &lt;p&gt;Import the System.Runtime.CompilerServices namespace into your assembly's AssemblyInfo.vb file.&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;&lt;font color="#0000ff"&gt;Imports&lt;/font&gt; System.Runtime.InteropServices&lt;/p&gt;
    &lt;/blockquote&gt;&lt;/li&gt;
    &lt;li&gt;
    &lt;p&gt;In AssemblyInfo.vb, mark the assembly with the InternalsVisibleToAttribute for each friend assembly that should have access to the friend scope.&lt;/p&gt;
    &lt;blockquote&gt;
    &lt;p&gt;&amp;lt;Assembly: InternalsVisibleTo(&lt;font color="#800000"&gt;"MySystem.Assembly1"&lt;/font&gt;)&amp;gt; &lt;br /&gt;
    &amp;lt;Assembly: InternalsVisibleTo(&lt;font color="#800000"&gt;"MySystem.Assembly2"&lt;/font&gt;)&amp;gt; &lt;/p&gt;
    &lt;/blockquote&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you are working in C#, you will make your changes in the AssemblyInfo.cs file for the assembly that contains the code to be re-used, and you will use "using" instead of "Imports."&lt;/p&gt;
&lt;p&gt;So I could have my cake (re-use of existing code) &lt;em&gt;and&lt;/em&gt; shield it from inappropriate eyes at the same time.  While this technique is obviously useful for writing tests, it can be applied anywhere that code needs to be shared between your system's assemblies without exposing it customers as part of an API.  &lt;/p&gt;
&lt;p&gt;&lt;font size="1"&gt;&lt;/font&gt;&lt;/p&gt;
&lt;font size="1"&gt;* Not to mention the wool of bat and tongue of dog.&lt;/font&gt;
&lt;p&gt; &lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=130511"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=130511" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/130511.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2009/03/29/how-to-reuse-code-without-creating-an-implicit-api.aspx</guid>
            <pubDate>Sun, 29 Mar 2009 19:44:23 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/130511.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2009/03/29/how-to-reuse-code-without-creating-an-implicit-api.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/130511.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/130511.aspx</trackback:ping>
        </item>
        <item>
            <title>Turbotax Customer Service to the Rescue</title>
            <category>Technology and Society</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2009/03/29/turbotax-customer-service-to-the-rescue.aspx</link>
            <description>&lt;p&gt;I've got to give the Turbotax customer service organization credit: they responded quickly to my inquiry, and figured out a way to solve the problem.  Yeah, the user interface design was stinky,  but plan B worked out, so my faith in Turbotax is restored.&lt;/p&gt;
&lt;p&gt;Lesson: a good customer service organization is essential if you want to keep your customers in spite of the occasional and inevitable screw-up.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=130509"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=130509" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/130509.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2009/03/29/turbotax-customer-service-to-the-rescue.aspx</guid>
            <pubDate>Sun, 29 Mar 2009 19:21:10 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/130509.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2009/03/29/turbotax-customer-service-to-the-rescue.aspx#feedback</comments>
            <slash:comments>3</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/130509.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/130509.aspx</trackback:ping>
        </item>
        <item>
            <title>Bad Software Design: Turbotax Web Edition</title>
            <category>Testing &amp; Debugging</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2009/03/22/bad-software-design-turbotax-web-edition.aspx</link>
            <description>&lt;p&gt;After I completed my federal taxes, Turbotax insisted on checking for errors before allowing me to file for the refund Uncle Sam owes me.  No problem there, I want my software to catch any possible errors.  The supposed errors that Turbotax wanted me to correct, however, were so completely misguided that I have to nominate the 2009 Turbotax Web Edition for the Software Hall of Shame.&lt;/p&gt;
&lt;p&gt;First, Turbotax insisted that I had to file a 1099-INT for my bank savings account because I had filed one last year.  And it would not allow me to state that I did not receive a form; I had to provide some dollars and cents before Turbotax would let me file my 1040.  Wait a second, though; my bank didn't send me a 1099-INT this year because my account earned so little interest!  (It's a pathetic little savings account, really.)  So Turbotax was refusing to let me file until I provided data &lt;em&gt;that do not even exist.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let's talk about software design for a minute.  How on God's green earth does Turbotax know that I must have overlooked a form, just because I filed it in a previous year?  Is it not possible that I might have even closed the pathetic little savings account before 2008 even began?  It just so happened that I could visit my bank's web site and sum up the few pennies in interest paid on the account in 2008, so I was able to feed Turbotax's demand for data and pass on to the next hurdle. Not that I was happy about what had happened.&lt;/p&gt;
&lt;p&gt;The second design flaw was so bad that &lt;em&gt;I may not even be able to file my taxes&lt;/em&gt; (with Turbotax, anyway).  Several years ago I worked briefly as a consultant, so I filed a Schedule C that included the analysis of vehicle mileage and expenses for business vs. personal use.  In the subsequent years, I have not worked as a consultant, and I have never again filed a Schedule C.  So guess what supposed error Turbotax insisted that I had to correct?  That's right, Turbotax is refusing to allow me to file my taxes until I file a Schedule C that includes business and personal use of my vehicle.&lt;/p&gt;
&lt;p&gt;I had not claimed any income from consulting for 2008.&lt;/p&gt;
&lt;p&gt;I no longer own the vehicle that I supposedly have to file the mileage reports for.&lt;/p&gt;
&lt;p&gt;But Turbotax will not allow me to indicate that I do not need to file the form.  It's their way on Schedule C, or the highway.&lt;/p&gt;
&lt;p&gt;So I'm about to contact Turbotax support.  If it's anywhere close to the quality of the actual software design, I will no doubt have to write off the hours I spent working with their web software, hit the highway, and find a better tax software provider. But maybe not; maybe their support is world-class and will save the day.  I'll report on the results as they become available.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=130317"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=130317" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/130317.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2009/03/22/bad-software-design-turbotax-web-edition.aspx</guid>
            <pubDate>Sun, 22 Mar 2009 20:44:12 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/130317.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2009/03/22/bad-software-design-turbotax-web-edition.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/130317.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/130317.aspx</trackback:ping>
        </item>
        <item>
            <title>Simplicity vs. Performance</title>
            <link>http://geekswithblogs.net/chrisfalter/archive/2009/02/05/simplicityversusperformance.aspx</link>
            <description>A colleague and I had a recent discussion about two versions of a trigger.  One was simple, containing a single query.  The other performed 14% better, but was a lot more complex--double the lines of code, and a branch that had to be evaluated.  My colleague advocated for checking in the more complex version right from the start, while I advocated for starting with simplicity.  What do you think?  Post a comment!&lt;br /&gt;
&lt;br /&gt;
Here's how our discussion went:&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color: rgb(128, 0, 0); font-weight: bold;"&gt;COLLEAGUE:&lt;/span&gt; &lt;br style="color: rgb(128, 0, 0);" /&gt;
&lt;br style="color: rgb(128, 0, 0);" /&gt;
&lt;div style="color: rgb(128, 0, 0);"&gt;Is your first example more than 14% more maintainable than the second  example?  If not (which is my opinion, since there's really only one line, the  "select inserted.NAME +..." line, that would change over time; the rest is  boilerplate code), I think I'd go with the second for the performance  increase.&lt;/div&gt;
&lt;br /&gt;
&lt;span style="color: rgb(0, 0, 128); font-weight: bold;"&gt;ME:&lt;/span&gt;&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;span style="color: rgb(0, 0, 128);"&gt;My experience has been that it's a lot more expensive for an organization to maintain complex code than it is to tweak performance.  Additional complexity tends to work like Adam Smith's invisible hand--except that this invisible hand works for evil, rather than for good.  It makes staff spend more time trying to figure out how to fix bugs and add new functionality than they would with simpler code.  It makes the software organization slower to respond to customer requests and changing market conditions.  &lt;/span&gt;&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;span style="color: rgb(0, 0, 128);"&gt;The problem is that this complexity tax that the organization has to keep paying cycle after cycle is truly invisible because it is hard to quantify (unlike a small bump in performance).  But ask any developer whether s/he has had to spend significant extra time in development or troubleshooting because the code base was complex (i.e., whether s/he has had to pay the code complexity tax), and invariably the answer is yes, a thousand times yes. &lt;/span&gt;&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;span style="color: rgb(0, 0, 128);"&gt;Granted, a single trigger that improves performance by 14% itself will not cause all that hardship.  It's the cumulative effect of millions of lines of code that are a little too complex that make the development process harder and more expensive than it has to be/should be. &lt;/span&gt;&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;span style="color: rgb(0, 0, 128);"&gt;I guess the other way of pitching this philosophy is that if you tend to choose slightly better performance over simpler code,  when it comes time to optimize the system you're asking the question: which of these millions of lines of code could be simplified at only a small performance cost?  And we don't have the tools to answer that question very well.  OTOH, if we tend to choose simpler code over slightly better performance, then optimization means we're asking: where are the bottlenecks where we can get a big performance boost at a small cost in code complexity?  And we have a variety of profiling tools that help us answer that question very well. &lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color: rgb(128, 0, 0); font-weight: bold;"&gt;COLLEAGUE:&lt;/span&gt;&lt;br style="color: rgb(128, 0, 0);" /&gt;
&lt;br style="color: rgb(128, 0, 0);" /&gt;
&lt;div&gt;&lt;span style="color: rgb(128, 0, 0);"&gt;I think you're letting a general philosophy get in the way of optimizing  the one example you've shown.  A 14% improvement in performance is more than  enough reason to have branching code that's only slightly less maintainable than  the non-branching code.  I just can't imagine a scenario where a maintenance  programmer sits down to fix a bug in this trigger and is so completely  confounded by its complexity that fixing it would bring down the entire  organization as you have suggested.&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;span style="color: rgb(0, 0, 128); font-weight: bold;"&gt;ME:&lt;/span&gt;&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;br style="color: rgb(0, 0, 128);" /&gt;
&lt;div style="color: rgb(0, 0, 128);"&gt;Actually, the maintainability difference is quite dramatic.  You're buying  the slightly better performance at a cost of doubling the lines of code and  doubling the cyclomatic complexity.  &lt;br /&gt;
&lt;br /&gt;
Granted, this loss of  maintainability occurs over a tiny portion of the code base.  On the other hand,  the performance gain, measured as a percentage of total transactions against the  database, is essentially imperceptible as well.  I.e., it's not as if a 14% perf  gain in one of the most lightly used triggers among tens of thousands will amount to  more than a sneeze. &lt;br /&gt;
&lt;br /&gt;
What I'm saying is that the best strategy to  optimize performance is to start with a good, simple code base, find the biggest  bottlenecks in a realistic load test, and optimize just those code sections.   Usually, the vast majority of perf gains emerge from a small number of  trade-offs against simplicity. &lt;br /&gt;
&lt;br /&gt;
The opposite approach (always choosing  performance over simplicity during initial development) results in getting to market more slowly (which has  a cost), and--through the accretion of thousands of small complexities--a gnarly  code base that is expensive and time-consuming to maintain.  The cumulative  effect of small decisions makes a big difference. &lt;br /&gt;
&lt;br /&gt;
If you look at this  scenario in the small--one decision about one trigger--I can understand why  you might choose performance over simplicity.  Now multiply that bias for perf  over simplicity across dozens of developers writing code every day, and you end  up with an entire system that is twice as expensive and time-consuming to  maintain.  And the system would have lower quality to boot, since when you  multiply lines of code and cyclomatic complexity, quality is pretty much lost at  an exponential rate.  Such a loss of quality is surely expensive to an  organization over the long-term. &lt;br /&gt;
&lt;br /&gt;
That's why I advocate choosing  simplicity as a matter of initial development.  Then as a QA process, look for  the performance optimizations that will yield the biggest gains, and implement  just those.  The 80/20 rule will apply: you'll get 80% of the performance gains,  while giving up only 20% of the simplicity. &lt;br /&gt;
&lt;br /&gt;
The problem of trying to go  in the reverse direction is that we don't have very good tools for identifying  which code simplifications can be purchased at the lowest performance expense.   So you just give up, and end up bearing all the expenses of a code base which is  twice as big and twice as complex.  And those expenses are far, far greater than  the expense of system performance that is a few percent slower.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=129222"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=129222" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/129222.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2009/02/05/simplicityversusperformance.aspx</guid>
            <pubDate>Thu, 05 Feb 2009 11:33:03 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/129222.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2009/02/05/simplicityversusperformance.aspx#feedback</comments>
            <slash:comments>1</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/129222.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/129222.aspx</trackback:ping>
        </item>
        <item>
            <title>How To Search Encrypted Text in SQL Server 2005/2008</title>
            <category>Database Considerations</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/10/06/how-to-search-encrypted-text.aspx</link>
            <description>&lt;br /&gt;
&lt;p&gt;Recently I was discussing SQL Server encryption with some friends who have been using it to encrypt short strings such as Social Security numbers at their shop.  I commented, "Just try searching those Social Security Numbers," they shared my lamentation, and we moved on to other subjects.  Later that evening, though, I thought there must be a way to search those wretched encrypted blocks--&lt;em&gt;somehow&lt;/em&gt;--and worked out the solution you are about to read.  &lt;/p&gt;
&lt;h4&gt;The Problem&lt;/h4&gt;
&lt;p&gt;The difficulty lies in the fact that you cannot compare varbinaries in a predicate.  Combine this with the fact that encrypted text is stored as a varbinary, and the result is that you cannot search the varbinary encrypted text, even if you run the search term through the encryption algorithm before performing the comparison.  &lt;/p&gt;
&lt;p&gt;To illustrate, suppose you have a Person table that stores a name and a Social Security Number (SSN) encrypted with a certificate, defined like this:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;CREATE TABLE&lt;/font&gt;&lt;font color="#000000"&gt; [dbo].[Person]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Id] [int] &lt;/font&gt;&lt;font color="#0000ff"&gt;IDENTITY&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;1&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt;1&lt;/font&gt;&lt;font color="#808080"&gt;) NOT NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [SSN] [varbinary]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;128&lt;/font&gt;&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#808080"&gt; NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Name] [varchar]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;50&lt;/font&gt;&lt;font color="#808080"&gt;) NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;CONSTRAINT&lt;/font&gt;&lt;font color="#000000"&gt; [PK_Person] &lt;/font&gt;&lt;font color="#0000ff"&gt;PRIMARY KEY CLUSTERED&lt;/font&gt; &lt;br /&gt;
&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;[Id] &lt;/font&gt;&lt;font color="#0000ff"&gt;ASC&lt;/font&gt;&lt;br /&gt;
&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#0000ff"&gt;ON&lt;/font&gt;&lt;font color="#000000"&gt; [PRIMARY]&lt;/font&gt;&lt;br /&gt;
&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#0000ff"&gt;ON&lt;/font&gt;&lt;font color="#000000"&gt; [PRIMARY]&lt;/font&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;It would be nice if you could just encrypt the search text and build a predicate that compares the term and the encrypted column, like this:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;declare&lt;/font&gt;&lt;font color="#000000"&gt; @encryptedSSN &lt;/font&gt;&lt;font color="#0000ff"&gt;varbinary&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;128&lt;/font&gt;&lt;font color="#808080"&gt;);&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;set&lt;/font&gt;&lt;font color="#000000"&gt; @encryptedSSN &lt;/font&gt;&lt;font color="#808080"&gt;= &lt;/font&gt;&lt;font color="#ff00ff"&gt;ENCRYPTBYCERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;CERT_ID&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'certKey'&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; @ssn&lt;/font&gt;&lt;font color="#808080"&gt;);&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;select&lt;/font&gt;&lt;font color="#000000"&gt; name&lt;/font&gt;&lt;font color="#808080"&gt;, &lt;/font&gt;&lt;font color="#ff00ff"&gt;CONVERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;9&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#ff00ff"&gt;DECRYPTBYCERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;CERT_ID&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'certKey'&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; SSN&lt;/font&gt;&lt;font color="#808080"&gt;) &lt;/font&gt;&lt;font color="#0000ff"&gt;as&lt;/font&gt;&lt;font color="#000000"&gt; SSN&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;from&lt;/font&gt;&lt;font color="#000000"&gt; dbo.Person&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;where&lt;/font&gt;&lt;font color="#000000"&gt; ssn &lt;/font&gt;&lt;font color="#808080"&gt;=&lt;/font&gt;&lt;font color="#000000"&gt; @encryptedSSN&lt;/font&gt;&lt;font color="#808080"&gt;;&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;As I mentioned previously, though, this approach simply does not work.  &lt;/p&gt;
&lt;p&gt;Of course, you &lt;em&gt;could&lt;/em&gt; make the predicate succeed by setting the datatype of the encrypted text column to char(128), and then converting the encryption products (stored SSNs plus the search term) from varbinary to char(128) before using them.  However, indexing a column that large, and performing comparisons with it, would consume a lot of disk and processing resources.&lt;/p&gt;
&lt;h4&gt;The Solution&lt;/h4&gt;
&lt;p&gt;Instead of converting the large encrypted block into char(128) data, you can work with the hash of the SSN instead.  If you calculate the hash using the SHA1 algorithm, the hash will require only 20 bytes of storage, so the burden on disk and processing resources will drop significantly.  And since the SHA1 algorithm is considered to be very secure, you will not add any significant security risk to your solution.  From start to finish, here's how the approach will work:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Include a char(20) column in your table to hold the hash of the SSN.&lt;/strong&gt;  The new table definition will look like this:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;CREATE TABLE&lt;/font&gt;&lt;font color="#000000"&gt; [dbo].[Person]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Id] [int] &lt;/font&gt;&lt;font color="#0000ff"&gt;IDENTITY&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;1&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt;1&lt;/font&gt;&lt;font color="#808080"&gt;) NOT NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [SSN] [varbinary]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;128&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#808080"&gt; NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Name] [varchar]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;50&lt;/font&gt;&lt;font color="#808080"&gt;) NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  [Hash] [char]&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;20&lt;/font&gt;&lt;font color="#808080"&gt;) NULL,&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;CONSTRAINT&lt;/font&gt;&lt;font color="#000000"&gt; [PK_Person] &lt;/font&gt;&lt;font color="#0000ff"&gt;PRIMARY KEY CLUSTERED&lt;/font&gt; &lt;br /&gt;
&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;[Id] &lt;/font&gt;&lt;font color="#0000ff"&gt;ASC&lt;/font&gt;&lt;br /&gt;
&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#0000ff"&gt;ON&lt;/font&gt;&lt;font color="#000000"&gt; [PRIMARY]&lt;/font&gt;&lt;br /&gt;
&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;font color="#0000ff"&gt;ON&lt;/font&gt;&lt;font color="#000000"&gt; [PRIMARY]&lt;/font&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;You should also index the Hash column to improve the performance of searches by SSN, of course. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Whenever you insert a record, store the hash of the SSN along with the encrypted bytes.&lt;/strong&gt;  Your insert stored procedure should use the built-in HASHBYTES function, like this:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;CREATE PROCEDURE&lt;/font&gt;&lt;font color="#000000"&gt; [dbo].[usp_Insert_Person] &lt;br /&gt;
  @ssn &lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;9&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  @name &lt;/font&gt;&lt;font color="#0000ff"&gt;varchar&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;50&lt;/font&gt;&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;AS&lt;br /&gt;
BEGIN&lt;/font&gt;&lt;br /&gt;
  insert into&lt;font color="#000000"&gt; dbo.Person&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;SSN&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt; Name&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt;Hash&lt;/font&gt;&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  values&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;ENCRYPTBYCERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;CERT_ID&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'certKey'&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; @ssn&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; @name&lt;/font&gt;&lt;font color="#808080"&gt;, &lt;/font&gt;&lt;font color="#ff00ff"&gt;convert&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;20&lt;/font&gt;&lt;font color="#808080"&gt;), &lt;/font&gt;&lt;font color="#ff00ff"&gt;HASHBYTES&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'SHA1'&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt; @ssn&lt;/font&gt;&lt;font color="#808080"&gt;)))&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;END&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. When you search by SSN, compare the search term hash to the stored hash in your predicate&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;CREATE PROCEDURE&lt;/font&gt;&lt;font color="#000000"&gt; usp_Search_Person_BySsn &lt;/font&gt;&lt;br /&gt;
&lt;font color="#000000"&gt;  @ssn &lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;9&lt;/font&gt;&lt;font color="#808080"&gt;)&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;AS&lt;br /&gt;
BEGIN&lt;/font&gt;&lt;br /&gt;
&lt;font color="#008000"&gt;  --SET NOCOUNT ON added to prevent extra result sets from&lt;br /&gt;
  --interfering with SELECT statements.&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  SET NOCOUNT ON&lt;/font&gt;&lt;font color="#808080"&gt;;&lt;/font&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;font color="#0000ff"&gt;  declare&lt;/font&gt;&lt;font color="#000000"&gt; @hash &lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;20&lt;/font&gt;&lt;font color="#808080"&gt;);&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  set&lt;/font&gt;&lt;font color="#000000"&gt; @hash &lt;/font&gt;&lt;font color="#808080"&gt;= &lt;/font&gt;&lt;font color="#ff00ff"&gt;convert&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;20&lt;/font&gt;&lt;font color="#808080"&gt;), &lt;/font&gt;&lt;font color="#ff00ff"&gt;HASHBYTES&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'SHA1'&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt; @ssn&lt;/font&gt;&lt;font color="#808080"&gt;));&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  select &lt;/font&gt;&lt;font color="#ff00ff"&gt;convert&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#0000ff"&gt;char&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#000000"&gt;9&lt;/font&gt;&lt;font color="#808080"&gt;), &lt;/font&gt;&lt;font color="#ff00ff"&gt;DECRYPTBYCERT&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff00ff"&gt;cert_id&lt;/font&gt;&lt;font color="#808080"&gt;(&lt;/font&gt;&lt;font color="#ff0000"&gt;'certkey'&lt;/font&gt;&lt;font color="#808080"&gt;),&lt;/font&gt;&lt;font color="#000000"&gt; ssn&lt;/font&gt;&lt;font color="#808080"&gt;)) &lt;/font&gt;&lt;font color="#0000ff"&gt;as&lt;/font&gt;&lt;font color="#000000"&gt; SSN&lt;/font&gt;&lt;font color="#808080"&gt;,&lt;/font&gt;&lt;font color="#000000"&gt; name&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  from&lt;/font&gt;&lt;font color="#000000"&gt; dbo.Person&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;  where Hash&lt;/font&gt;&lt;font color="#808080"&gt;=&lt;/font&gt;&lt;font color="#000000"&gt; @hash&lt;/font&gt;&lt;font color="#808080"&gt;;&lt;/font&gt;&lt;br /&gt;
&lt;font color="#0000ff"&gt;END&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;This approach has worked well for me in my experimentation, and I recommend it to you if you need to search on an encrypted field.  You should note, however, that the approach does have a limitation: you cannot search on an arbitrary substring of the encrypted text.  In other words, a "begins with" or "contains" search is not possible.  As long as you can live with this limitation, you should be able to employ this approach for your own encrypted data.&lt;/p&gt;
&lt;p&gt;As always, I invite you, my reader, to leave a comment if you have found this useful or if you can think of an improvement.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=125658"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=125658" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/125658.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/10/06/how-to-search-encrypted-text.aspx</guid>
            <pubDate>Mon, 06 Oct 2008 05:14:56 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/125658.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/10/06/how-to-search-encrypted-text.aspx#feedback</comments>
            <slash:comments>5</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/125658.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/125658.aspx</trackback:ping>
        </item>
        <item>
            <title>Five Steps to Evaluate Technology Options</title>
            <category>Software Architecture</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/08/05/five-steps-to-evaluate-technology-options.aspx</link>
            <description>&lt;p&gt;Recently a project leadership team I was assisting had the difficult task of selecting between implementation technologies for a distributed architecture.  We had brainstormed a list of candidates, and after some investigation were able to enumerate their strengths and weaknesses well enough.  But then the decision-making process started to come unhinged as we thrashed about, weighing the various options.  How do you narrow the field to a winner, anyway?&lt;/p&gt;
&lt;p&gt;When I was able to frame the process in terms of the five steps you are about to read, though, the path to a confident decision became clear.  While I am not suggesting that this is the evaluation methodology to end all evaluation methodologies, it certainly helped us.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Reduce the Candidate List via Head-to-Head Comparisons.&lt;/strong&gt;  You will make your job easier if you can quickly knock out some of the contenders.  Among the options we were considering for synchronizing data were &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;using SQL Server replication, and &lt;/li&gt;
    &lt;li&gt;writing our own application to read database records marked with an update flag, update records in a partner node with the data, and then remove the flags. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately, we realized that the application we were thinking of writing would be remarkably similar in structure and purpose to SQL Server's merge replication.  Since we had no intention of re-inventing the wheel, we removed the idea of writing such an application from our candidate list.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Build an Analysis Table.&lt;/strong&gt;  Since the goal is to learn how the technology options will influence the development of your system, the next step is to build a table that will compare the options according to how well they implement potential system features.  Start by lining up the remaining technology options on the x-axis, and system features on the y-axis.  The level of difficulty in implementing a feature, using a technology option, will be the data at the intersection of an option and a feature.  Here is a sample of how an analysis grid might look for a distributed architecture:&lt;/p&gt;
&lt;table cellspacing="1" cellpadding="1" width="90%" align="center" border="1"&gt;
    &lt;tbody&gt;
        &lt;tr bgcolor="#00bbbb"&gt;
            &lt;td&gt;Feature&lt;/td&gt;
            &lt;td&gt;Option 1&lt;/td&gt;
            &lt;td&gt;Option 2&lt;/td&gt;
            &lt;td&gt;Option 3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Workflow: Overnight Batch&lt;/td&gt;
            &lt;td&gt;Simple&lt;/td&gt;
            &lt;td&gt;Simple&lt;/td&gt;
            &lt;td&gt;Relatively Easy&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Workflow: Straight-Through (Central Only)&lt;/td&gt;
            &lt;td&gt;Difficult&lt;/td&gt;
            &lt;td&gt;Very Difficult&lt;/td&gt;
            &lt;td&gt;Moderately Difficult&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Workflow: Straight-Through (All Nodes)&lt;/td&gt;
            &lt;td&gt;Very Difficult&lt;/td&gt;
            &lt;td&gt;Impossible&lt;/td&gt;
            &lt;td&gt;Difficult&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Data Replication: Near Real-time&lt;/td&gt;
            &lt;td&gt;Very Difficult&lt;/td&gt;
            &lt;td&gt;Moderately Difficult&lt;/td&gt;
            &lt;td&gt;Moderately Difficult&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Data Replication: Every Hour&lt;/td&gt;
            &lt;td&gt;Moderately Difficult&lt;/td&gt;
            &lt;td&gt;Impossible&lt;/td&gt;
            &lt;td&gt;Relatively Easy&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Post-failure Data Re-sync: overnight&lt;/td&gt;
            &lt;td&gt;Simple&lt;/td&gt;
            &lt;td&gt;Relatively Easy&lt;/td&gt;
            &lt;td&gt;Relatively Easy&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Post-failure Data Re-sync: immediate&lt;/td&gt;
            &lt;td&gt;Moderately Difficult&lt;/td&gt;
            &lt;td&gt;Relatively Easy&lt;/td&gt;
            &lt;td&gt;Impossible&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;br /&gt;
&lt;p&gt;In this example we see that straight-through workflow processing at the central node will be difficult using option 1, very difficult using option 2, and moderately difficult using option 3.  The ease of implementation (for all the feature/option pairs) ranges from simple to impossible, with four intermediate values (relatively easy, moderately difficult, difficult, and very difficult).  Given the murkiness of IT crystal balls, there is probably no point in attempting a finer-grained analysis of the difficulty level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3. Assign Scores.&lt;/strong&gt;  In order to compare the various combinations of features and options, you need to assign a score to every combination.  If you assign a score for a level of difficulty, you can quantify the comparison.  The &lt;a href="http://www.scrum-breakfast.com/2008/02/explaining-story-points-to-management.html"&gt;Cohn scale&lt;/a&gt;, a pseudo-Fibonacci series in which each score is about 50% greater than its predecessor, is a good choice.  Many shops are already using the Cohn scale to estimate user stories in agile methodologies, so the familiarity can help.  If you convert a level of difficulty into a score on the Cohn scale, you are assuming that it is about 50% more difficult than the next lower level, as you can see below:&lt;/p&gt;
&lt;table cellspacing="1" cellpadding="1" width="50%" align="center" border="1"&gt;
    &lt;tbody&gt;
        &lt;tr bgcolor="#00bbbb"&gt;
            &lt;td&gt;Description&lt;/td&gt;
            &lt;td&gt;Points&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Simple&lt;/td&gt;
            &lt;td&gt;1&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Relatively Easy&lt;/td&gt;
            &lt;td&gt;2&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Moderately Difficult&lt;/td&gt;
            &lt;td&gt;3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Difficult&lt;/td&gt;
            &lt;td&gt;5&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Very Difficult&lt;/td&gt;
            &lt;td&gt;8&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Impossible&lt;/td&gt;
            &lt;td&gt;10,000&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td colspan="2"&gt;&lt;em&gt;Converting Descriptions Into Points (Cohn Scale)&lt;/em&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;br /&gt;
&lt;p&gt;"Impossible" is converted to an extremely high number in order to allow a numeric comparison.  &lt;/p&gt;
&lt;p&gt;After assigning the scores, the example table looks like this:&lt;/p&gt;
&lt;table cellspacing="1" cellpadding="1" width="90%" align="center" border="1"&gt;
    &lt;tbody&gt;
        &lt;tr bgcolor="#00bbbb"&gt;
            &lt;td&gt;Feature&lt;/td&gt;
            &lt;td&gt;Option 1&lt;/td&gt;
            &lt;td&gt;Option 2&lt;/td&gt;
            &lt;td&gt;Option 3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Workflow: Overnight Batch&lt;/td&gt;
            &lt;td&gt;1&lt;/td&gt;
            &lt;td&gt;1&lt;/td&gt;
            &lt;td&gt;2&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Workflow: Straight-Through (Central Only)&lt;/td&gt;
            &lt;td&gt;5&lt;/td&gt;
            &lt;td&gt;8&lt;/td&gt;
            &lt;td&gt;3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Workflow: Straight-Through (All Nodes)&lt;/td&gt;
            &lt;td&gt;8&lt;/td&gt;
            &lt;td&gt;10000&lt;/td&gt;
            &lt;td&gt;5&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Data Replication: Near Real-time&lt;/td&gt;
            &lt;td&gt;8&lt;/td&gt;
            &lt;td&gt;3&lt;/td&gt;
            &lt;td&gt;3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Data Replication: Every Hour&lt;/td&gt;
            &lt;td&gt;3&lt;/td&gt;
            &lt;td&gt;10000&lt;/td&gt;
            &lt;td&gt;2&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Post-failure Data Re-sync: overnight&lt;/td&gt;
            &lt;td&gt;1&lt;/td&gt;
            &lt;td&gt;2&lt;/td&gt;
            &lt;td&gt;2&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td bgcolor="#aaffff"&gt;Post-failure Data Re-sync: immediate&lt;/td&gt;
            &lt;td&gt;3&lt;/td&gt;
            &lt;td&gt;2&lt;/td&gt;
            &lt;td&gt;10000&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;Step 4. Discuss Features and Effort with Your Customer.&lt;/strong&gt;  Of course your customer will want to understand the choices they have.  Using the analysis table from step 3, you could tell your customer that if they desperately desire straight-through workflow processing on all nodes in the distributed system, near real-time data replication, and immediate re-synchronization of data after a failure in the link between distributed nodes, the only technology available is option 1, which will "cost" 19 points.  You could then point out that if it is acceptable to wait until overnight to re-synchronize data after a communications link failure, you could implement option 3 at a cost of 10 points--about half the effort.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What if my customer wants an estimate in terms of actual resources (person-days)?&lt;/em&gt;  Your customer certainly has the right to get a ball-park estimate of the costs, of course.  Since it is impractical to work up an estimate for each combination of features and options, you could perform an estimate for the lowest scoring combination instead, which will then become the basis for a conversion factor between points and effort.  In the example, the lowest scoring combination is overnight batch workflow, hourly data replication, and overnight re-synchronization of nodes after a communication failure, using option 1 (5 points).  If you estimate this combination as requiring 10 person-weeks of effort, then the conversion factor is 2 person-weeks per point.  As a result, you can estimate that the desperately desired combination (costing 19 points) will require 38 person-weeks.  Obviously, your project plan should not rely on this ball-park estimate; it is only precise enough to help the project team make an informed technology choice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5. Choose the Lowest Scoring Option for the Desired Feature Set.&lt;/strong&gt;  In most projects, the biggest cost factor (and biggest risk) is the use of human resources, so the option which requires the least effort should usually win.  However, if there is a near tie between first and second place, you might want to weigh other factors such as licensing costs and vendor support in order to make your choice.&lt;/p&gt;
&lt;p&gt;What methodology have you used for choosing between technology options?  Have you used an analysis table like this?  Leave a comment!&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=124255"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=124255" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/124255.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/08/05/five-steps-to-evaluate-technology-options.aspx</guid>
            <pubDate>Tue, 05 Aug 2008 17:33:13 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/124255.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/08/05/five-steps-to-evaluate-technology-options.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/124255.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/124255.aspx</trackback:ping>
        </item>
        <item>
            <title>Health Monitoring in ASP.NET</title>
            <category>Performance &amp; Tuning</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/06/06/health-monitoring-in-asp.net.aspx</link>
            <description>&lt;p&gt;While I was busy customizing Microsoft's Exception Management Application Block to classify and log all exceptions thrown in our web and Windows apps, and &lt;a href="http://geekswithblogs.net/chrisfalter/archive/2007/07/13/113923.aspx"&gt;writing instrumentation code&lt;/a&gt; that published timing events via System.Diagnostics.Trace, Microsoft was busy writing ASP.NET Health Monitoring.  Microsoft has more resources, so their product is a little more advanced and customizable.  Here's what it provides:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;An event model:&lt;/strong&gt; There are event classes for web request failures, for authentication failures, for errors, for SQL errors, for heartbeats, and so forth.  You can subclass the WebBaseEvent in order to define your own custom events, as well. &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;A provider model:&lt;/strong&gt; Health monitoring can publish events with 4 built-in providers (email notification, SQL Server, WMI, and event log).  The WMI provider is especially interesting, but you will have to write your own application to manage and report WMI events, &lt;a href="http://msdn.microsoft.com/en-us/library/ms178713(VS.80).aspx"&gt;according to Microsoft&lt;/a&gt;. &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;A customization model:&lt;/strong&gt; You can edit web.config in order to map events to providers and to configure providers (for example, to specify a connection string for the SQL Server provider). &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since we have already customized so much infrastructure, especially by writing a bug portal that makes use of the customized Exception Management block, we will probably not be migrating to the ASP.NET Health Monitor any time soon.  However, for those of you who have been whistling in the dark with respect to the health of your web applications and services, get busy!  I highly recommend Scott Allen's &lt;a href="http://odetocode.com/Blogs/scott/archive/2005/11/01/2402.aspx"&gt;post&lt;/a&gt; as a starting place for your efforts.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122683"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122683" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/122683.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/06/06/health-monitoring-in-asp.net.aspx</guid>
            <pubDate>Fri, 06 Jun 2008 18:03:56 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/122683.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/06/06/health-monitoring-in-asp.net.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/122683.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/122683.aspx</trackback:ping>
        </item>
        <item>
            <title>How To Create a Windows Form Singleton</title>
            <category>Coding Practices and Design Patterns</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/06/06/how-to-create-a-windows-form-singleton.aspx</link>
            <description>&lt;p&gt;Recently a friend asked me how you might create a Windows Forms application that only allows a single instance per computer.  A print driver might make use of this functionality, for example, to launch a print job management dialog whenever a document prints.  Never having needed this sort of functionality before, my initial answer wasn't very helpful.  But being both curious and disinclined to back down from a technical challenge, I just had to figure this one out.&lt;/p&gt;
&lt;p&gt;As I was looking for an inter-process synchronization mechanism, I came across the &lt;a href="http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx"&gt;Semaphore&lt;/a&gt; and &lt;a href="http://msdn.microsoft.com/en-us/library/system.threading.mutex.aspx"&gt;Mutex&lt;/a&gt; in the System.Threading namespace.  A semaphore can be used to manage a pooled resource (memory buffer, thread pool, connection pool, etc.) by tracking the number of available resources.  You instantiate a semaphore with an invariant maximum corresponding to the quantity of pooled resource entities (e.g., the number of database connections).  Whenever a thread wants to use the resource, it calls .WaitOne() on the appropriate semaphore and blocks until the semaphore count is greater than zero.  If the count is positive when the call is made, the thread will not need to block at all.  &lt;/p&gt;
&lt;p&gt;When a thread enters a semaphore, the semaphore decrements its count by one.  The calling thread is responsible for calling the semaphore's .Release() method when it has completed using the resource.  This allows the semaphore to increment its count--and as a result, gives another thread access to the pooled resource.  You must call Release() inside of a finally block as soon as feasible in your codepath; my testing shows that the CLR will not release an unreleased semaphore when the semaphore reference goes out of scope, or even when the thread of execution terminates.&lt;/p&gt;
&lt;p&gt;What we need, then, is a semaphore that has a maximum count of one (since we want only one instance of our application to be running) and will increment its count no matter how the thread that entered it terminates.  Happily, this would be a pretty good one-sentence description of the Mutex class.  The thread that calls WaitOne on a mutex (&lt;a href="http://en.wikipedia.org/wiki/Mutex"&gt;"mutual exclusion"&lt;/a&gt;) gains ownership when it enters the mutex.  It can release the mutex at any time by calling the ReleaseMutex method, but should it fail to do so (whether by logic error or run-time failure) before it terminates, the CLR will release the mutex on its behalf.  As a result, the mutex should prove more reliable for our purposes.&lt;/p&gt;
&lt;p&gt;Here is the singleton code in a nutshell:&lt;/p&gt;
&lt;div style="FONT-SIZE: 10pt; FONT-FAMILY: Monospace; BACKGROUND-COLOR: white"&gt;&lt;span style="COLOR: blue"&gt;using&lt;/span&gt;&lt;span style="COLOR: black"&gt; System;&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;using&lt;/span&gt;&lt;span style="COLOR: black"&gt; System.Windows.Forms;&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;using&lt;/span&gt;&lt;span style="COLOR: black"&gt; System.Threading;&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;namespace&lt;/span&gt;&lt;span style="COLOR: black"&gt; MutexTest&lt;br /&gt;
{&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;static&lt;/span&gt;&lt;span style="COLOR: black"&gt; &lt;/span&gt;&lt;span style="COLOR: blue"&gt;class&lt;/span&gt;&lt;span style="COLOR: black"&gt; &lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;Program&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    {&lt;br /&gt;
        [&lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;STAThread&lt;/span&gt;&lt;span style="COLOR: black"&gt;]&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;static&lt;/span&gt;&lt;span style="COLOR: black"&gt; &lt;/span&gt;&lt;span style="COLOR: blue"&gt;void&lt;/span&gt;&lt;span style="COLOR: black"&gt; Main()&lt;br /&gt;
        {&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;Application&lt;/span&gt;&lt;span style="COLOR: black"&gt;.EnableVisualStyles();&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;Application&lt;/span&gt;&lt;span style="COLOR: black"&gt;.SetCompatibleTextRenderingDefault(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;false&lt;/span&gt;&lt;span style="COLOR: black"&gt;);&lt;br /&gt;
&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: green"&gt;// make sure no other instances of this app are running&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;bool&lt;/span&gt;&lt;span style="COLOR: black"&gt; mutexIsAvailable = &lt;/span&gt;&lt;span style="COLOR: blue"&gt;false&lt;/span&gt;&lt;span style="COLOR: black"&gt;;&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;Mutex&lt;/span&gt;&lt;span style="COLOR: black"&gt; m = &lt;/span&gt;&lt;span style="COLOR: blue"&gt;null&lt;/span&gt;&lt;span style="COLOR: black"&gt;;&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;try&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;            {&lt;br /&gt;
                m = &lt;/span&gt;&lt;span style="COLOR: blue"&gt;new&lt;/span&gt;&lt;span style="COLOR: black"&gt; &lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;Mutex&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;true&lt;/span&gt;&lt;span style="COLOR: black"&gt;, &lt;/span&gt;&lt;span style="COLOR: #a31515"&gt;"MutexTest.Singleton"&lt;/span&gt;&lt;span style="COLOR: black"&gt;);&lt;br /&gt;
                mutexIsAvailable = m.WaitOne(1, &lt;/span&gt;&lt;span style="COLOR: blue"&gt;false&lt;/span&gt;&lt;span style="COLOR: black"&gt;); &lt;/span&gt;&lt;span style="COLOR: green"&gt;// wait only 1 ms&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;            }&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;catch&lt;/span&gt;&lt;span style="COLOR: black"&gt; (&lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;AbandonedMutexException&lt;/span&gt;&lt;span style="COLOR: black"&gt;)&lt;br /&gt;
            {&lt;br /&gt;
                &lt;/span&gt;&lt;span style="COLOR: green"&gt;// don't worry about the abandonment; &lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;                &lt;/span&gt;&lt;span style="COLOR: green"&gt;// the mutex only guards app instantiation&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;                mutexIsAvailable = &lt;/span&gt;&lt;span style="COLOR: blue"&gt;true&lt;/span&gt;&lt;span style="COLOR: black"&gt;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;if&lt;/span&gt;&lt;span style="COLOR: black"&gt; (mutexIsAvailable)&lt;br /&gt;
            {&lt;br /&gt;
                &lt;/span&gt;&lt;span style="COLOR: blue"&gt;try&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;                {&lt;br /&gt;
                    &lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;Application&lt;/span&gt;&lt;span style="COLOR: black"&gt;.Run(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;new&lt;/span&gt;&lt;span style="COLOR: black"&gt; &lt;/span&gt;&lt;span style="COLOR: #2b91af"&gt;Form1&lt;/span&gt;&lt;span style="COLOR: black"&gt;());&lt;br /&gt;
                }&lt;br /&gt;
                &lt;/span&gt;&lt;span style="COLOR: blue"&gt;finally&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;                {&lt;br /&gt;
                    m.ReleaseMutex();&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;One of three things can happen when the Main() thread calls m.WaitOne(1, false):&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;If the mutex is unowned (or becomes unowned within one millisecond), mutexIsAvailable is true and the thread enters/owns the mutex. &lt;/li&gt;
    &lt;li&gt;If the mutex remains owned by another instance of the Windows Form app for the one millisecond duration, mutexIsAvailable is false. &lt;/li&gt;
    &lt;li&gt;If the mutex is available because another instance abandoned it without releasing it, the method throws an AbandonedMutexException.  In this case, the thread owns the mutex and may safely launch the application instance, so it sets mutexIsAvailable true. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the Main thread owns the mutex, it runs the application, using a try/finally block in order to guarantee a call to ReleaseMutex.  While strictly speaking it is not necessary to call ReleaseMutex (the CLR will do so on the thread's behalf when it terminates), I have written the code this way in order to maintain a best practice.  In some other situation, the thread that enters the mutex might continue performing more work (or enter a suspended state) after finishing its use of the resource and might not call ReleaseMutex, so it's a good idea to write the code in this fashion.&lt;/p&gt;
&lt;p&gt;What do you think?  Is this code helpful?  Do you have any suggestions for improvement?  Leave a comment!&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122680"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122680" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/122680.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/06/06/how-to-create-a-windows-form-singleton.aspx</guid>
            <pubDate>Fri, 06 Jun 2008 14:31:25 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/122680.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/06/06/how-to-create-a-windows-form-singleton.aspx#feedback</comments>
            <slash:comments>1</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/122680.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/122680.aspx</trackback:ping>
        </item>
        <item>
            <title>.NET Entity Framework: Is It Ready for Web Prime-Time?</title>
            <category>Database Considerations</category>
            <category>Software Architecture</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/05/27/ef-is-is-ready-for-prime-time.aspx</link>
            <description>&lt;p&gt;Our shop has been looking for a way to simplify the interaction between our domain layer and data layer.  The data layer uses typed datasets for all its operations.  When it retrieves data, it populates a dataset; when it stores data, it checks the DataRowState of all rows in the dataset, and calls the appropriate stored procedure to insert, update, or delete the data.  The domain layer then uses dataset elements as the in-memory backing store for properties and collections.  A collection typically uses a DataTable, for example, and a collection member would use a DataRow in the DataTable.&lt;/p&gt;
&lt;p&gt;This arrangement has some advantages:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Between requests, domain objects can cache their state in session by caching a dataset. &lt;/li&gt;
    &lt;li&gt;DataRowState can inform the logic about which CRUD operation to perform on a DataRow.  Unmodified data can also be skipped over, yielding improved efficiency. &lt;/li&gt;
    &lt;li&gt;We have considerable freedom in designing domain classes.  We can make a property read-only, for example, if it corresponds to an immutable database field, such as an identity column. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This arrangement also has a big disadvantage, however.  We end up with lots of properties that look like this:&lt;/p&gt;
&lt;div style="FONT-SIZE: 9pt; FONT-FAMILY: monospace; BACKGROUND-COLOR: white"&gt;&lt;span style="COLOR: blue"&gt;public&lt;/span&gt;&lt;span style="COLOR: black"&gt; &lt;/span&gt;&lt;span style="COLOR: blue"&gt;string&lt;/span&gt;&lt;span style="COLOR: black"&gt; Phone&lt;br /&gt;
{&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;get&lt;/span&gt;&lt;span style="COLOR: black"&gt; { &lt;/span&gt;&lt;span style="COLOR: blue"&gt;return&lt;/span&gt;&lt;span style="COLOR: black"&gt; dr.PHONE; }&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set&lt;/span&gt;&lt;span style="COLOR: black"&gt; { dr.PHONE = &lt;/span&gt;&lt;span style="COLOR: blue"&gt;value&lt;/span&gt;&lt;span style="COLOR: black"&gt;; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;This property's one and only use is to map a domain attribute (an entity's phone number) to a particular field in a DataRow &lt;em&gt;(dr)&lt;/em&gt;.  Our domain layer has hundreds of these pass-through properties, which are a very verbose and not especially readable way of mapping between the domain and data layers.  So we've been looking for a better way to handle this mapping.&lt;/p&gt;
&lt;p&gt;Along comes the &lt;a href="http://msdn.microsoft.com/en-us/data/aa937723.aspx"&gt;.NET Entity Framework&lt;/a&gt; (EF), which provides a tool that creates a domain-to-data map in XML format.  Our first reaction was: this is just what the doctor ordered!  And supported by Microsoft, to boot.  &lt;/p&gt;
&lt;p&gt;On closer examination, though, the EF toolset has some growing up to do before we could consider using it in our web applications.  It can generate only public get/set accessors in the entity domain model (EDM) classes, for example.  You cannot make a property read-only, or internal/protected/private, or virtual.  You cannot map a non-generated property to a database field.  And programming in a straitjacket was not what we had in mind for a new approach.&lt;/p&gt;
&lt;p&gt;Second, EDM classes generated by EF do not have any supported way to cache their state.  A Microsoftie is working on &lt;a href="http://code.msdn.microsoft.com/entitybag"&gt;a tool ("Perseus")&lt;/a&gt; that can serialize the state of an entity using an EntityBag&amp;lt;T&amp;gt; instance, but it seems &lt;a href="http://blogs.msdn.com/dsimmons/archive/2008/01/28/entitybag-wrap-up-and-future-directions.aspx"&gt;somewhat incomplete&lt;/a&gt; (and definitely not supported) at the moment.  Since our web apps do cache domain entity state, adopting the EF right now would entail an important risk.&lt;/p&gt;
&lt;p&gt;What alternatives exist?  We can license &lt;a href="http://www.ideablade.com/DevForceEF_summary.html"&gt;DevForce EF&lt;/a&gt;, which is built on top of the EF and seems to offer pretty much everything that it lacks.  Or we can try &lt;a href="http://sourceforge.net/projects/nhibernate"&gt;NHibernate&lt;/a&gt;, an open-source .NET port of the Hibernate tool which is quite popular in the Java development world.&lt;/p&gt;
&lt;p&gt;Or maybe we can just wait for the EF to mature.  Microsoft has &lt;a href="http://blogs.msdn.com/dsimmons/archive/2008/05/17/why-use-the-entity-framework.aspx"&gt;big plans&lt;/a&gt; for the EF, and intends to make REST-oriented web services, Reporting Services, workflows, etc. EDM-aware.  So an EF investment today can yield important dividends in the future.&lt;/p&gt;
&lt;p&gt;Even though I cannot render a definitive answer, I hope that this discussion will help readers make an informed discussion about their &lt;a href="http://en.wikipedia.org/wiki/Object-relational_mapping"&gt;ORM&lt;/a&gt; choices in the .NET world.  What do you think?  Leave a comment with your insights or questions!&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122423"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122423" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/122423.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/05/27/ef-is-is-ready-for-prime-time.aspx</guid>
            <pubDate>Tue, 27 May 2008 14:50:29 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/122423.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/05/27/ef-is-is-ready-for-prime-time.aspx#feedback</comments>
            <slash:comments>4</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/122423.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/122423.aspx</trackback:ping>
        </item>
        <item>
            <title>BOOK REVIEW: Don't Make Me Think, 2d Ed. by Steve Krug</title>
            <category>Coding Practices and Design Patterns</category>
            <category>Testing &amp; Debugging</category>
            <category>Software Architecture</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/05/13/review-of-dont-make-me-think.aspx</link>
            <description>&lt;p&gt;You've mastered web forms and controls.  You've prototyped a Silverlight 2.0 application.  AJAX? You're all over it.  But have you &lt;em&gt;really&lt;/em&gt; learned how to design a good web page or web site?  &lt;/p&gt;
&lt;p&gt;Steve Krug's "Common Sense Approach to Web Usability" provides surprising and sometimes counterintuitive principles that every good website must follow.  Krug preaches the importance of removing clutter in order to make the purpose and functionality of a site (or page) clear--and happily, he practices what he preaches in this remarkably lucid book.  Here are some of Krug's key insights: &lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;Don't make users think! &lt;/strong&gt;Your job is to make sure users do not have to puzzle over a site's purpose, or how to use it.  Krug offers the search functionality at Amazon.com as a great example of this principle in action.  A user does not have to decide what type of search she wants (by author, by title, by ISBN, etc.); instead, she can just enter whatever text interests her, and Amazon offers a list of matches, ranked by relevance. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Users don't behave the way you think they do.&lt;/strong&gt;  You've been poring over your site--reading everything ten times or more--so you tend to think that users will do the same.  But they don't.  Instead, the users...
    &lt;ul&gt;
        &lt;li&gt;&lt;em&gt;"don't read pages, they scan them."&lt;/em&gt; (In fact you only decided to read this review after you scanned the intro and decided it would be worth your while.) &lt;/li&gt;
        &lt;li&gt;&lt;em&gt;"don't figure out how things work, they muddle through&lt;/em&gt;."&lt;br /&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Design pages for scanning.&lt;/strong&gt;  Since users are going to treat your site like a billboard going by at 70mph (rather than a textbook that they carefully work through), you should learn to design great billboards!
    &lt;ul&gt;
        &lt;li&gt;&lt;em&gt;Create a clear visual hierarchy. &lt;/em&gt;Highlight the important stuff, and indicate relationship by grouping. &lt;/li&gt;
        &lt;li&gt;&lt;em&gt;Use conventions.&lt;/em&gt;  If your site design conforms to what users generally expect, they can more easily understand it at a glance. &lt;/li&gt;
        &lt;li&gt;&lt;em&gt;Break pages into clearly defined areas.&lt;/em&gt; &lt;/li&gt;
        &lt;li&gt;&lt;em&gt;Make what's clickable obvious. &lt;/em&gt; Use arrows or underlines to indicate that text is clickable, for example. &lt;/li&gt;
        &lt;li&gt;&lt;em&gt;Minimize noise. &lt;/em&gt; Prefer clarity to pizzazz.&lt;br /&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Give users simple\mindless choices.&lt;/strong&gt; It's okay to make a user traverse 4 or 5 links to get to his desired destination as long as each choice along the way is clear. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;"Omit needless words.&lt;/strong&gt;"  &lt;em&gt;"Get rid of half the words on each page, then get rid of half of what's left"&lt;/em&gt; is Krug's Third Law of Usability.  More words just make the site look more daunting, which can discourage the user in a hurry.  Krug's Third Law has 2 corollaries:
    &lt;ul&gt;
        &lt;li&gt;&lt;em&gt;Happy talk must die. &lt;/em&gt; "We're so glad you're at our site!  We think you'll like your experience here..." Oops, you just lost your user, who's too busy to keep reading. &lt;/li&gt;
        &lt;li&gt;&lt;em&gt;Instructions must die.  &lt;/em&gt;Users almost never read them anyway (remember, they don't figure out, they muddle through).  So keep instructions brief and simple. &lt;/li&gt;
    &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Krug then discusses some details about 2 features every site must have: navigation and a home page.  Krug recommends that the sections and subsections of a site be indicated with a clear set of links or tabs on the home page, and that the navigation remain available  no matter where the user goes.  (This answers the questions: what can I do on this site?  and Where can I go from here?)  Each page should have a visible name, and the site should a breadcrumb trail with arrows between the levels in order to allow a user to know where he's at in the site's hierarchy.  (These answer the questions: where am I at now? and How do I get back to where I was before?). &lt;/p&gt;
&lt;p&gt;Krug's discussion of the home page acknowledges that the forces conspiring against simplicity are enormous in any ambitious site, because there are so many organizational interests competing for the valuable home page real estate.  Krug provides some useful tips for managing the problems, though, by suggesting how to use logos, taglines, and home page navigation.  &lt;/p&gt;
&lt;p&gt;Next Krug addresses the often religious arguments that site development teams often endure (pulldowns or menus?  Flash animation or simple text?) with this simple advice: forget the arguments and start testing!  Simple usability testing will reveal the key problems with a site, and often they have nothing to do with what the development team has been arguing about.  Krug believes that frequent tests are more important than comprehensive (often expensive tests), and he offers advice on how to do testing on a tight budget (use 3 or 4 subjects; use an inexpensive screen recorder like Camtasia; try to get all the project stakeholders to observe).  He concludes with an extremely useful script of an interaction between a test subject and a test guide.&lt;/p&gt;
&lt;p&gt;Those who have already read the first edition will be pleased to know that Krug has included some very helpful new material in the second edition.  Is your site accessible to sight-impaired users?  If not, Krug offers a top 5 list of tips for making sites accessible, along with a pointer on how to use Cascading Style Sheets to make your site more accessible.  Not to mention that CSS makes your site a lot easier to manage.... And what do you do when your boss (or the customer) wants you to do something that violates every known law of web usability?  Just compose an email that borrows liberally from one of Krug's friendly "here's how it should be done" missives!&lt;/p&gt;
&lt;p&gt;Krug sprinkles his book with examples of sites that work well (and a few that don't) to illustrate his ideas.  He often offers a few "How does (or does not) this page implement the principles we've been discussing?" tests, with his own answers on the following pages.  I found these examples to be enormously helpful.  And Krug offers a wonderful set of additional resources for those who want to pursue the subject further, both within the text and in a notated bibliography at the end.&lt;/p&gt;
&lt;p&gt;Because I am a long-time web user and web developer, I thought I understood just about everything I needed to know about usability to design a good site.  Then I read this book, and learned a ton.  If you work on web sites in any way (whether as designer, developer, or tester), the few hours you spend on Steve Krug's little gem will pay rich dividends.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122116"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122116" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/122116.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/05/13/review-of-dont-make-me-think.aspx</guid>
            <pubDate>Tue, 13 May 2008 20:31:39 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/122116.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/05/13/review-of-dont-make-me-think.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/122116.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/122116.aspx</trackback:ping>
        </item>
        <item>
            <title>How To: Encrypt and Manage Documents with SQL Server 2005</title>
            <category>Database Considerations</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/05/08/encrypt-documents-with-sql-server.aspx</link>
            <description>&lt;p&gt;When one of our departments decided to store sensitive reports, the architecture I first considered was storing them, unencrypted, in our imaging system.  The imaging system cannot manage encrypted documents, but its security capabilities allow it to to deny unauthorized users access to the reports.  This ability to restrict access seemed to meet our security requirements.  &lt;/p&gt;
&lt;p&gt;Then I started asking questions about data backups.  Offsite backups can be a major security vulnerability, as we have recently learned.  The names, addresses, social insurance numbers, and (in many cases) bank account details of almost half of England &lt;a href="http://www.cnn.com/2007/WORLD/europe/11/21/britain.personal/index.html"&gt;ended up who-knows-where last year because database backups disappeared in the postal system&lt;/a&gt;.  And our company, like any responsible organization, backs up our data and stores it offsite.  So leaving the sensitive data unencrypted was not acceptable.&lt;/p&gt;
&lt;p&gt;Fortunately, SQL Server 2005 comes with native encryption capabilities that are simple to use. In the absence of an affordable imaging system with built-in encryption capabilities, here's the approach I hammered out:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Create a table in a SQL Server 2005 database to store the encrypted documents. &lt;/li&gt;
    &lt;li&gt;Create a symmetric key in the same database.  To facilitate recovery from database failures, store the instructions for re-creating the key in a safe, remote location.  &lt;/li&gt;
    &lt;li&gt;Create stored procedures that use the key to encrypt/decrypt documents that are stored in the table. &lt;/li&gt;
    &lt;li&gt;Create a service account under which the service that stores the report will run. &lt;/li&gt;
    &lt;li&gt;Create a (presumably internal) website for displaying the reports.
    &lt;ul&gt;
        &lt;li&gt;Use the same service account for the site's app pool identity. &lt;/li&gt;
        &lt;li&gt;Restrict access to authorized groups via Windows authentication. &lt;/li&gt;
        &lt;li&gt;Require secure http so that a network trace cannot intercept decrypted documents on their way to the browser. &lt;/li&gt;
    &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;On SQL Server, deny permissions for the stored procedures and table to anyone other than the service account.  The security context of the backup job must also be granted the db_backupoperator role on the database, of course. &lt;/li&gt;
    &lt;li&gt;Implement network encryption between SQL Server and any services that insert or select sensitive documents, using &lt;a href="http://support.microsoft.com/kb/816514"&gt;IPSec&lt;/a&gt; or &lt;a href="http://support.microsoft.com/kb/316898"&gt;SQL Server Network Encryption&lt;/a&gt;. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Below is a diagram of the system that allows authorized users to view sensitive documents.&lt;/p&gt;
&lt;p&gt;&lt;img height="256" alt="Viewing Sensitive Documents" width="714" src="/images/geekswithblogs_net/chrisfalter/SensitiveDocuments/Doc Viewer Network Diagram(1).jpg" /&gt;&lt;/p&gt;
&lt;p&gt;There is scant MSDN documentation for accomplishing the first 3 steps, but the rest are well understood in the world of Microsoft development.  The remainder of this article will therefore focus on creating and managing the tables, keys, and stored procedures.&lt;/p&gt;
&lt;h4&gt;Step #1: Create a Table to Store the Encrypted Documents&lt;/h4&gt;
&lt;p&gt;Several considerations drive the design of the table:  &lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;The column that stores a block of encrypted data must have the datatype of varbinary(8000) because the result of an encryption operation is always a varbinary(8000).  &lt;/li&gt;
    &lt;li&gt;Unless you are dealing with very small documents, 8000 bytes will not hold an entire document.  Therefore a single document will require multiple records, which will be associated with one another by a common unique identifier.  A foreign key to the business entity associated with the document would be a good candidate.  In our system, the encrypted document is associated with an "Order," so the identifier is an OrderId. &lt;/li&gt;
    &lt;li&gt;In order to thwart brute force decryption attacks, you should "salt" the encryption with a unique integer for each report.  Unless your sensitive documents are very large, the foreign key (OrderId) will serve the purpose nicely. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the table definition that emerges from these considerations:&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;font color="#0000ff"&gt;CREATE TABLE&lt;/font&gt; [dbo].[EncryptedReport](&lt;br /&gt;
   [OrderId] [int] &lt;font color="#808080"&gt;&lt;font color="#666699"&gt;NOT NULL&lt;/font&gt;,&lt;/font&gt;&lt;br /&gt;
   [BlockNum] [int] &lt;font color="#808080"&gt;&lt;font color="#666699"&gt;NOT NULL&lt;/font&gt;,&lt;/font&gt;&lt;br /&gt;
   [Block] [varbinary](8000) &lt;font color="#666699"&gt;NULL&lt;/font&gt;,&lt;br /&gt;
 &lt;font color="#0000ff"&gt;CONSTRAINT&lt;/font&gt; [PK_EncryptedReport] &lt;font color="#0000ff"&gt;PRIMARY KEY CLUSTERED&lt;/font&gt; &lt;br /&gt;
(&lt;br /&gt;
   [OrderId] &lt;font color="#000080"&gt;&lt;font color="#0000ff"&gt;ASC,&lt;/font&gt;&lt;br /&gt;
&lt;/font&gt;   [BlockNum] &lt;font color="#000080"&gt;&lt;font color="#0000ff"&gt;ASC&lt;/font&gt;&lt;br /&gt;
&lt;/font&gt;)&lt;br /&gt;
) &lt;font color="#0000ff"&gt;ON&lt;/font&gt; [PRIMARY]&lt;/font&gt;&lt;/p&gt;
&lt;h4&gt;Step #2: Create a Symmetric Key&lt;/h4&gt;
&lt;p&gt;To encrypt data in SQL Server 2005, you may use a certificate or a symmetric key.  A certificate provides somewhat stronger protection than a symmetric key, so it is the encryption method of choice for a small amount of data (such as a Social Security Number). However, its far greater demand for server resources makes it impractical for large amounts of text or other data.  Therefore, if you are encrypting a sensitive document, you should create a symmetric key and encrypt it with a certificate.  At run-time, SQL Server will use the certificate to decrypt the key, then use the key to encrypt or decrypt the document.  &lt;/p&gt;
&lt;p&gt;&lt;img height="324" alt="Key Generation" width="775" src="/images/geekswithblogs_net/chrisfalter/SensitiveDocuments/Key Generation Design.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Before SQL Server can encrypt or decrypt a sensitive document for you, it must traverse a chain of encrypted secrets:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;A certificate available only to the SQL Server process identity encrypts SQL Server's Service Master Key. &lt;/li&gt;
    &lt;li&gt;The Service Master Key encrypts the Database Master Key. &lt;/li&gt;
    &lt;li&gt;The Database Master key encrypts a certificate ("certDocEncryption" in this example). &lt;/li&gt;
    &lt;li&gt;The certificate encrypts a symmetric key ("keyReportEncryption" in this example). &lt;/li&gt;
    &lt;li&gt;(Finally!) the symmetric key encrypts your sensitive documents. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SQL Server stores all of these secrets for you, except for the process identity's certificate (which is stored in the Windows certificate store).  Consequently, you do not need to worry about making a "secret" available to your run-time environment by, for example, storing it in a configuration file.  Database backups will not include the process identity's certificate (which the operating system maintains for you), so the only way the bad guy that steals your backup can decrypt your sensitive documents is by a brute force attack.  &lt;/p&gt;
&lt;p&gt;This design does place certain responsibilities on your IT organization:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;You must be very careful about using this design with a virtual server.&lt;/strong&gt;  While creating an image of an entire virtual server  (Windows OS, SQL Server, and data) facilitates quick disaster recovery, it can also facilitate the theft of sensitive documents, since the virtual server image will have everything (from Windows certificate down to the symmetric key) that a hacker needs to read the data. &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;To recover data in the event of a server failure, you must be able to re-create the symmetric key.&lt;/strong&gt;  While it is impossible to back up a symmetric key, you can always create an identical key on &lt;em&gt;any&lt;/em&gt; instance of SQL Server 2005 by issuing the "CREATE SYMMETRIC KEY" statement with the same algorithm, key source, and identity value.  Consequently, you must store the statement used to create your symmetric key in a secure location, such as a safe deposit box at a local bank.  And although this statement is quite obvious, I'll say it anyway: don't store the statement with your data backups! &lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Step #3: Create Stored Procedures&lt;/h4&gt;
&lt;p&gt;Encrypting a sensitive document is a four-step process:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Open the symmetric key. &lt;/li&gt;
    &lt;li&gt;Break the document into blocks that, when encrypted, will consume less than 8000 bytes. When I applied &lt;a href="http://blogs.msdn.com/yukondoit/archive/2005/11/24/496521.aspx"&gt;Microsoft's published formula&lt;/a&gt; to my encryption key process, I concluded that 7876 bytes was the maximum size of unencrypted data that I could safely fit into a varbinary(8000). &lt;/li&gt;
    &lt;li&gt;Encrypt each block. &lt;/li&gt;
    &lt;li&gt;Insert each encrypted block into the table. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the definition of the stored procedure that does the trick:&lt;/p&gt;
&lt;div style="FONT-SIZE: 9pt; FONT-FAMILY: monospace; BACKGROUND-COLOR: white"&gt;&lt;span style="COLOR: blue"&gt;CREATE PROCEDURE &lt;/span&gt;&lt;span style="COLOR: black"&gt;[dbo].[usp_StoreReportWithEncryption] &lt;br /&gt;
    @OrderId &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;/span&gt;&lt;span style="COLOR: black"&gt;, &lt;br /&gt;
    @ReportText &lt;/span&gt;&lt;span style="COLOR: blue"&gt;varchar&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;max&lt;/span&gt;&lt;span style="COLOR: black"&gt;) = ''&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;AS&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@idx &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@textLength &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockSize &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockNum &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@block &lt;/span&gt;&lt;span style="COLOR: blue"&gt;varbinary&lt;/span&gt;&lt;span style="COLOR: black"&gt;(8000)&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@keyGuid &lt;/span&gt;&lt;span style="COLOR: blue"&gt;uniqueidentifier&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Open &lt;/span&gt;&lt;span style="COLOR: black"&gt;Symmetric &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Key &lt;/span&gt;&lt;span style="COLOR: black"&gt;keyReportEncryption&lt;br /&gt;
        decryption &lt;/span&gt;&lt;span style="COLOR: blue"&gt;by &lt;/span&gt;&lt;span style="COLOR: black"&gt;certificate certDocEncryption&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@keyGuid = Key_GUID('keyReportEncryption')&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockSize = 7876&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockNum = 1&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@idx = 1&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@textLength = &lt;/span&gt;&lt;span style="COLOR: blue"&gt;datalength&lt;/span&gt;&lt;span style="COLOR: black"&gt;(@ReportText)&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;WHILE &lt;/span&gt;&lt;span style="COLOR: black"&gt;@idx &amp;lt; @textLength&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@block = EncryptByKey(@keyGuid, &lt;/span&gt;&lt;span style="COLOR: blue"&gt;SUBSTRING&lt;/span&gt;&lt;span style="COLOR: black"&gt;(@ReportText, @idx, @blockSize), 1, &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Convert&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;varbinary&lt;/span&gt;&lt;span style="COLOR: black"&gt;, @OrderId))&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;insert into &lt;/span&gt;&lt;span style="COLOR: black"&gt;dbo.EncryptedReport(OrderId, BlockNum, Block)&lt;br /&gt;
                &lt;/span&gt;&lt;span style="COLOR: blue"&gt;values&lt;/span&gt;&lt;span style="COLOR: black"&gt;(@OrderId, @blockNum, @block)&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@idx = @idx + @blockSize&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@blockNum = @blockNum + 1&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;br /&gt;
&lt;br /&gt;
END &lt;/span&gt;&lt;/div&gt;
&lt;p&gt;To decrypt the document, you must:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Open the symmetric key, then &lt;/li&gt;
    &lt;li&gt;Reassemble the document by
    &lt;ul&gt;
        &lt;li&gt;Decrypting each block, and &lt;/li&gt;
        &lt;li&gt;Concatenating the results.  &lt;/li&gt;
    &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the definition of the stored procedure that will fetch the document associated with a particular OrderId:&lt;/p&gt;
&lt;div style="FONT-SIZE: 9pt; FONT-FAMILY: monospace; BACKGROUND-COLOR: white"&gt;&lt;span style="COLOR: blue"&gt;CREATE PROCEDURE &lt;/span&gt;&lt;span style="COLOR: black"&gt;[dbo].[usp_FetchEncryptedReport] &lt;br /&gt;
    @OrderId &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int &lt;/span&gt;&lt;span style="COLOR: black"&gt;= 0&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;AS&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: green"&gt;-- SET NOCOUNT ON added to prevent extra result sets from&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: green"&gt;-- interfering with SELECT statements.&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;SET NOCOUNT ON&lt;/span&gt;&lt;span style="COLOR: black"&gt;;&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@numBlocks &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@rowIdx &lt;/span&gt;&lt;span style="COLOR: blue"&gt;int&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@text &lt;/span&gt;&lt;span style="COLOR: blue"&gt;varchar&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;max&lt;/span&gt;&lt;span style="COLOR: black"&gt;)&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Declare &lt;/span&gt;&lt;span style="COLOR: black"&gt;@block &lt;/span&gt;&lt;span style="COLOR: blue"&gt;varbinary&lt;/span&gt;&lt;span style="COLOR: black"&gt;(8000)&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Open &lt;/span&gt;&lt;span style="COLOR: black"&gt;Symmetric &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Key &lt;/span&gt;&lt;span style="COLOR: black"&gt;keyReportEncryption&lt;br /&gt;
        decryption &lt;/span&gt;&lt;span style="COLOR: blue"&gt;by &lt;/span&gt;&lt;span style="COLOR: black"&gt;certificate certDocEncryption&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@rowIdx = 1&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@text = ''&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@numBlocks = (&lt;/span&gt;&lt;span style="COLOR: blue"&gt;select IsNull&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;max&lt;/span&gt;&lt;span style="COLOR: black"&gt;(blocknum), 0) &lt;/span&gt;&lt;span style="COLOR: blue"&gt;from &lt;/span&gt;&lt;span style="COLOR: black"&gt;dbo.EncryptedReport &lt;/span&gt;&lt;span style="COLOR: blue"&gt;where &lt;/span&gt;&lt;span style="COLOR: black"&gt;OrderId = @OrderId)&lt;br /&gt;
&lt;br /&gt;
    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;     &lt;/span&gt;&lt;span style="COLOR: blue"&gt;WHILE &lt;/span&gt;&lt;span style="COLOR: black"&gt;@rowIdx &amp;lt;= @numBlocks&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;BEGIN&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@block = (&lt;/span&gt;&lt;span style="COLOR: blue"&gt;Select &lt;/span&gt;&lt;span style="COLOR: black"&gt;Block &lt;/span&gt;&lt;span style="COLOR: blue"&gt;from &lt;/span&gt;&lt;span style="COLOR: black"&gt;dbo.EncryptedReport &lt;/span&gt;&lt;span style="COLOR: blue"&gt;where &lt;/span&gt;&lt;span style="COLOR: black"&gt;OrderId = @OrderId &lt;/span&gt;&lt;span style="COLOR: blue"&gt;and &lt;/span&gt;&lt;span style="COLOR: black"&gt;BlockNum = @rowIdx)&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@text = @text + &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Convert&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;varchar&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;max&lt;/span&gt;&lt;span style="COLOR: black"&gt;), DecryptByKey(@block, 1, &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Convert&lt;/span&gt;&lt;span style="COLOR: black"&gt;(&lt;/span&gt;&lt;span style="COLOR: blue"&gt;varbinary&lt;/span&gt;&lt;span style="COLOR: black"&gt;,@OrderId)))&lt;br /&gt;
            &lt;/span&gt;&lt;span style="COLOR: blue"&gt;set &lt;/span&gt;&lt;span style="COLOR: black"&gt;@rowIdx = @rowIdx + 1&lt;br /&gt;
        &lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: black"&gt;    &lt;/span&gt;&lt;span style="COLOR: blue"&gt;Select &lt;/span&gt;&lt;span style="COLOR: black"&gt;@text&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;span style="COLOR: blue"&gt;END&lt;/span&gt;&lt;/div&gt;
&lt;h4&gt;Finishing Your Security Infrastructure&lt;/h4&gt;
&lt;p&gt;There is no such thing as a perfectly secure architecture that provides a 100% guarantee against the theft or compromise of your sensitive documents.  An authorized user can give up his network credentials to a social engineering attack; an unpatched or badly configured database server might allow a hacker to gain administrator access to it; a trusted network administrator can steal data outright.   What SQL Server 2005 does very well is protecting data "at rest"--i.e., sensitive documents stored in your database.  By following the first 3 steps in this article, you should be able to get a solution up and running quickly.  Just remember: make sure you implement other forms of network security (including the last 4 steps mentioned in the introduction) to make your entire infrastructure for handling sensitive documents resilient against theft and hacking.  &lt;/p&gt;
&lt;p&gt;If you have suggestions or questions, please leave a comment!  Thanks for reading; I trust you have learned something useful from this article.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122004"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=122004" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/122004.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/05/08/encrypt-documents-with-sql-server.aspx</guid>
            <pubDate>Thu, 08 May 2008 21:13:56 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/122004.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/05/08/encrypt-documents-with-sql-server.aspx#feedback</comments>
            <slash:comments>4</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/122004.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/122004.aspx</trackback:ping>
        </item>
        <item>
            <title>Want to Write Great Business Software? Create a Great Domain Model!</title>
            <category>Coding Practices and Design Patterns</category>
            <category>Software Architecture</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/03/25/value-of-domain-model.aspx</link>
            <description>&lt;p&gt;&lt;font face="Arial"&gt;When you are coding in a hurry, it is very tempting to write business logic in the first place that comes to mind, such as a button click handler.  However, for all but the simplest systems, such a practice leads very quickly to a chaotic system whose business logic is scattered like the ash from an erupting volcano. &lt;/font&gt; So let's take a step back and see how you can develop an application more intelligently.&lt;/p&gt;
&lt;h4&gt;"Divide and Conquer"&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;“Divide and Conquer”&lt;/em&gt; is a useful metaphor for organizing the effort of developing a complex system.  A software architect can assign major system responsibilities to various components or layers (which I will refer to as a “part”).  As long as the responsibilities of a part are understood, a developer can develop the part in such a way as to satisfy those responsibilities.  And as long as the parts integrate well, a useful system can emerge. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Divide and Conquer”&lt;/em&gt; has many advantages: &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;A team can work in a coordinated fashion on several parts of the system simultaneously, without stepping on each other’s toes. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Assigning a limited set of related responsibilities to a system part simplifies its design. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Modifying a system is easier when the part that is responsible for the functionality that must be changed can be identified. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Ultimately, the complexity of the system decreases as its various responsibilities, and the strategies for handling them, become clear.  Unnecessary complexity is what can make “the last 10%” of a project go into deep cost and time overruns, as quality issues and poor design make their effects known. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;“Divide and Conquer”&lt;/em&gt; is the driving force behind creating logical layers of a user interface, an application layer, a data layer, and a domain model in an enterprise software application.  &lt;/p&gt;
&lt;p&gt;While the advantages of separating UI, application, and data logic into their own layers are well understood and frequently practiced, the point behind creating a domain model is not as widely grasped.  A domain model defines the business entities represented by a system, the activities associated with them, the relationships between them, and the rules and constraints that govern them. These are clearly very important aspects of the system, even though (from the end user’s perspective) they are not as visible as the user interface, application flow, or database.  I will illustrate the importance of a domain model by examining how insurance endorsements work in the homeowners point-of-sale system that my team has developed.&lt;/p&gt;
&lt;h4&gt;Example of a Complex Business Domain&lt;/h4&gt;
&lt;p&gt;Let’s start by examining the data model for endorsements: &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;An endorsement can be associated with either an application (future policy) or with a risk (such as a dwelling or a vehicle).  Therefore the Endorsement table has 2 foreign keys: one that relates an endorsement to an application (via the Quotenumber) and the other that relates it to a risk (via the RiskId). &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Endorsements come in a bewildering variety.  Some endorsements have no related data; some have one or more limits; some have one or more attributes, few of which are common between endorsements.  Clearly it is unwise to attempt to create a single table with identified columns for each of the possible limits or attributes that an endorsement may possess.  In addition, any attempt to create a unique table for each endorsement’s associated attributes is going to result in a very large number of tables—along with the responsibility of defining a new table each time a new endorsement type is created.  Therefore, the design we chose was to create 2 dependent tables for Endorsement: an EndorsementLimit table and an EndorsementDescription table.  Each table has 3 fields: an EndorsementId (foreign key back to the associated endorsement), a “type” field that allows the system to distinguish between all the related descriptions (or limits) of an endorsement, and the associated data field (a numerical limit or a varchar description).  Note that segmentation and history are irrelevant to a point-of-sale system, so the schema is not designed with that capability. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Some endorsements are concerned with parties to an insurance application other than the applicant, such as an additional interest or an additional insured.  Since these additional entities reside in a different table (the OtherParty table), we use a join table (EndorsementParty) to manage the association between Endorsement and OtherParty. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This portion of the database schema is represented graphically below: &lt;/p&gt;
&lt;p&gt;&lt;img height="573" alt="Entity-Relationship Diagram for Endorsements" width="701" src="/images/geekswithblogs_net/chrisfalter/DomainModel/EndorsementDataDiagram.JPG" /&gt;&lt;/p&gt;
&lt;p&gt;So if we have a data model, why do we need a domain model?  Why can’t some UI code simply write its data directly to the tables?  Clearly this one way of designing a system—you can, after all, do anything with software (provided you have sufficient time and resources).&lt;/p&gt;
&lt;p&gt;Let us consider, though, the knowledge about the business domain that the point-of-sale application must manage with regard to endorsements:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;The relationship of endorsement attributes to an endorsement.&lt;/strong&gt;  A homeowners “HO 05 28” (golf cart) endorsement has several related attributes: a limit (cart value), a make, a model, a serial number, and an indicator of whether collision coverage is included.  Each one of these attributes is stored in its own record in the EndorsementLimit or EndorsementDesc table.  The DescType field distinguishes the four EndorsementDesc records from one another; however, the system must somehow keep track of which DescType is used for which attribute. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Differences between the format of stored data and how it is used.&lt;/strong&gt;  In a database system that does not have a built-in boolean type (such as the DB2 flavor used at my shop), there are probably as many different ways to store boolean data as there are programmers.  True vs. false could be represented as “0” vs. “1”, or as “1” vs. “2”, or as “T” vs. “F”, or as “t” vs. “f”, or as “true” vs. “false”, or as “Y” vs. “N”, etc.  (In one of my consulting engagements, I actually saw all of these &lt;em&gt;plus&lt;/em&gt; a couple more in a single application.) To handle a golf cart endorsement’s indicator of collision coverage, the system must be able to reliably map between the stored data and a run-time choice of true/false. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;The relationships between endorsements and other entities.&lt;/strong&gt;  Some endorsements are related to the risk being insured (such as the earthquake endorsement), and some are related to the (potential) policy itself (such as an “Additional Insured” endorsement). &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;The relationships between endorsement attributes.&lt;/strong&gt;  The “HO 04 65” endorsement (scheduled property) contains a set of schedules; each schedule is designated by a letter such as “A” or “J” and has a description (the personal property being insured) and a limit.  Since descriptions and limits are stored in separate tables, the system must have some way of identifying and associating the schedules’ descriptions and limits. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Rules and constraints on various endorsements.&lt;/strong&gt;  The 6 schedules on a “HO 04 65” endorsement have base (default) limits, for example, such that the total limit of a schedule can be described as the base limit plus an additional limit.  Mathematically speaking, we use the formula &lt;em&gt;Total Limit = Base (fixed by rule) + Additional (stored in EndorsementLimit table)&lt;/em&gt;.  So the system must be able both to provide base, additional, and total limits (given the stored additional limit) and to update the stored additional limit (based on user entry). &lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Complexity Resolved: A Domain Model&lt;/h4&gt;
&lt;p&gt;The beauty of our team's domain model is that it completely hides all of this complexity from the user interface. &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;An endorsement class is defined for each type of endorsement, and it provides an interface that is radically simple for the UI developer to use.  To give just one example, the golf cart endorsement provides a boolean &lt;em&gt;CollisionIncluded&lt;/em&gt; property that can be mapped to a checkbox.  &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;All of the other issues (which EndorsementDesc record the indicator is stored in, how the boolean is mapped to a varchar, etc.) are managed inside an endorsement class—frequently by using capabilities inherited from the EndorsementBase class or borrowed from utility classes in the domain layer. &lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Responsibilities are clearly grouped and assigned in this way of designing a system.  Tasks and attributes that are common to all endorsements are the responsibility of the EndorsementBase class.  The tasks and attributes that are unique to a particular endorsement are the responsibility of the corresponding subclass of EndorsementBase (which already has the common endorsement capabilities via its inheritance relationship with EndorsementBase).  Neither of these classes needs to know anything about the application or user interface which will use them, which in turn need to know nothing at all about the internal implementation details of an endorsement class. The diagram below depicts the assignment of some endorsement responsibilities to classes in our domain layer.&lt;br /&gt;
    &lt;br /&gt;
    &lt;img height="688" alt="Endorsement Class Diagram" width="619" src="/images/geekswithblogs_net/chrisfalter/DomainModel/EndorsementClassDiagram.jpg" /&gt; &lt;/li&gt;
    &lt;li&gt;The association of an endorsement with a policy or with a risk is managed via its membership in an EndorsementCollection that is accessed via the &lt;em&gt;HomeApplication&lt;/em&gt; object or the &lt;em&gt;HomeApplication.Home&lt;/em&gt; object (of type &lt;em&gt;HomeRisk&lt;/em&gt;), respectively. The relationship between these various entities is depicted below.&lt;br /&gt;
    &lt;br /&gt;
    &lt;img height="442" alt="Relationship of Endorsements to HomeApplication and HomeRisk" width="384" src="/images/geekswithblogs_net/chrisfalter/DomainModel/UsingEndorsementsClassDiag.jpg" /&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;There are some things that should never leak.  Space shuttle O-rings.  Tires.  Diapers.  And now that you've read this article, add your business domain model to the list.  Always isolate your business domain logic in a cohesive model; do not let the logic leak into a "smart UI" or into the data layer!  If you follow this rule, you will save yourself and your team much time and effort when you need to modify the business logic in your application.&lt;/p&gt;
&lt;p&gt;Also, keep the importance of your domain model in mind when it is time to choose a development tool.  A rapid application development (RAD) tool that can only design screens and logic flows might be a very good choice for building a very simple system that needs little complexity in its business logic.  However, it is very difficult to envision how a RAD tool would be able to represent the relationships and constraints inherent to a complex business domain, such as homeowners insurance endorsements.  Given the inherent limitations of a RAD tool, the complexity that my team's domain model currently manages quite capably would have to spill out into the flows and screens themselves.  &lt;/p&gt;
&lt;p&gt;While the result may initially seem counterintuitive, a RAD tool that at first glance makes everything look simple might very well in the end produce a system far more complex than a system produced with a set of tools that offer a robust and complete set of programming capabilities.  And this unanticipated complexity can make the systems you develop far more difficult to implement on time and to extend with new customer-requested capabilities.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=120748"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=120748" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/120748.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/03/25/value-of-domain-model.aspx</guid>
            <pubDate>Tue, 25 Mar 2008 14:01:28 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/120748.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/03/25/value-of-domain-model.aspx#feedback</comments>
            <slash:comments>6</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/120748.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/120748.aspx</trackback:ping>
        </item>
        <item>
            <title>Refactoring to Comply with the DRY principle (Don't Repeat Yourself)</title>
            <category>Coding Practices and Design Patterns</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/03/07/refactor-to-dry.aspx</link>
            <description>&lt;p&gt;If you're not living DRY, you're not a good programmer.&lt;/p&gt;
&lt;p&gt;Okay, I'm not talking about alcohol consumption!  I'm talking about the &lt;strong&gt;Don't Repeat Yourself&lt;/strong&gt; principle--a principle every programmer should live by.  Recently I did a code review for a project we outsourced, and demonstrated how you can refactor bad cut-and-paste code into well-designed code.  You will note that the capabilities of the &lt;a href="http://www.eggheadcafe.com/tutorials/aspnet/b916e3a9-d056-4669-8bf3-aa98ed6669c3/a-good-solution-for-magi.aspx"&gt;MagicStringTranslator&lt;/a&gt; class really help to reduce code clutter. Without further ado, I present the relevant portion of my code review:&lt;/p&gt;
&lt;p&gt;...your branching logic has 12 lines of code in each branch except the final one.  I am inserting an excerpt for reference:&lt;/p&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;if&lt;/span&gt; (m_record.Text.Substring(13, 6).Trim() == &lt;span style="COLOR: #a31515"&gt;"IDENT"&lt;/span&gt;)&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        {&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteBreak();&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"Table style='width: 100%' border='1'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"tr style='background-color:#ccffcc;'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"td style='width: 100%;' colspan='2' align='center'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"span style='font-size: 9pt;"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"strong"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.Write(&lt;span style="COLOR: #a31515"&gt;"SUBJECT INFORMATION"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"strong"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"span"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"TD"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"TR"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"Table"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        }&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;if&lt;/span&gt; (m_record.Text.Substring(13, 6).Trim() == &lt;span style="COLOR: #a31515"&gt;"SUMRY"&lt;/span&gt;)&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        {&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteBreak();&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"Table style='width: 100%' border='1'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"tr style='background-color:#ccffcc;'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"td style='width: 100%;' colspan='2' align='center'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"span style='font-size: 9pt;"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"strong"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.Write(&lt;span style="COLOR: #a31515"&gt;"SUMMARY"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"strong"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                        writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"span"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"TD"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"TR"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"Table"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        }&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;if&lt;/span&gt; (m_record.Text.Substring(13, 6).Trim() == &lt;span style="COLOR: #a31515"&gt;"COLLT"&lt;/span&gt;)&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        {&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;           &lt;font color="#006600"&gt;// this code and several more near-identical branches are omitted for brevity’s sake&lt;/font&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        }&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;p&gt;Eleven of these lines of code are identical.  This is bad for 3 reasons: &lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;What would happen if one of the lines of code had a bug?  You would have to fix it in each branch, and be sure to do it the same way in each one.  This code structure is &lt;strong&gt;bug-prone&lt;/strong&gt;. &lt;/li&gt;
    &lt;li&gt;The structure implies that each branch does different things, when the code in fact does essentially the same thing.  As a result, the code structure is &lt;strong&gt;misleading&lt;/strong&gt;. &lt;/li&gt;
    &lt;li&gt;The code ends up being way too verbose; it is saying the same thing over and over and over.  As a result, the code structure is &lt;strong&gt;hard to read and understand&lt;/strong&gt;. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In fact, your code does suffer from an insidious little bug.  You are comparing a substring of length 6 to a literal of length 5.&lt;/p&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;if&lt;/span&gt; (m_record.Text.Substring(13, 6).Trim() == &lt;span style="COLOR: #a31515"&gt;"IDENT"&lt;/span&gt;)&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;p&gt;This only happens to work when the 6th character is a blank, because in this circumstance the Trim() method reduces the string to length 5, and the comparison is valid.  What would happen if the 6th character did not happen to be a blank?&lt;/p&gt;
&lt;p&gt;But don't try to fix this bug in the seven places where it occurs!  There are better ways to improve the code.  One way is to restrict the branching logic to the one line of code, then use that one line together with the other 11 exactly once.&lt;/p&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; title = &lt;span style="COLOR: #2b91af"&gt;String&lt;/span&gt;.Empty;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;switch&lt;/span&gt; (m_record.Identifier)&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        {&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            &lt;span style="COLOR: blue"&gt;case&lt;/span&gt; &lt;span style="COLOR: #a31515"&gt;"IDENT"&lt;/span&gt;: &lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                title = &lt;span style="COLOR: #a31515"&gt;"SUBJECT INFORMATION"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                &lt;span style="COLOR: blue"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            &lt;span style="COLOR: blue"&gt;case&lt;/span&gt; &lt;span style="COLOR: #a31515"&gt;"SUMRY"&lt;/span&gt;: &lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                title = &lt;span style="COLOR: #a31515"&gt;"SUMMARY"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                &lt;span style="COLOR: blue"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            &lt;span style="COLOR: blue"&gt;case&lt;/span&gt; &lt;span style="COLOR: #a31515"&gt;"COLLT"&lt;/span&gt;: &lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                title = &lt;span style="COLOR: #a31515"&gt;"COLLECTION ITEMS"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                &lt;span style="COLOR: blue"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            &lt;span style="COLOR: blue"&gt;case&lt;/span&gt; &lt;span style="COLOR: #a31515"&gt;"ACCNT"&lt;/span&gt;: &lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                title = &lt;span style="COLOR: #a31515"&gt;"TRADE ACCOUNT ACTIVITY"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                &lt;span style="COLOR: blue"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            &lt;span style="COLOR: blue"&gt;case&lt;/span&gt; &lt;span style="COLOR: #a31515"&gt;"MSSGE"&lt;/span&gt;: &lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                title = &lt;span style="COLOR: #a31515"&gt;"MESSAGE"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                &lt;span style="COLOR: blue"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            &lt;span style="COLOR: blue"&gt;case&lt;/span&gt; &lt;span style="COLOR: #a31515"&gt;"EMPLY"&lt;/span&gt;: &lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                title = &lt;span style="COLOR: #a31515"&gt;"EMPLOYMENT"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                &lt;span style="COLOR: blue"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            &lt;span style="COLOR: blue"&gt;case&lt;/span&gt; &lt;span style="COLOR: #a31515"&gt;"INQHS"&lt;/span&gt;: &lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                title = &lt;span style="COLOR: #a31515"&gt;"INQUIRY HISTORY"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                &lt;span style="COLOR: blue"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        }&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"Table style='width: 100%' border='1'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"tr style='background-color:#ccffcc;'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"td style='width: 100%;' colspan='2' align='center'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"span style='font-size: 9pt;"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"strong"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.Write(title);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"strong"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"span"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"TD"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"TR"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"Table"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;An even better way is to delegate the relationship between magic string (e.g., “INQHS”) and description (e.g., “INQUIRY HISTORY”) to a class specialized for this purpose.  Then you can simply use the class directly, without writing any branching logic at all.  In this situation, define a &lt;a href="http://www.eggheadcafe.com/tutorials/aspnet/b916e3a9-d056-4669-8bf3-aa98ed6669c3/a-good-solution-for-magi.aspx"&gt;MagicStringTranslator&lt;/a&gt; subclass for the Identifier property:&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;    &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: blue"&gt;class&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;IdentifierCode&lt;/span&gt; : &lt;span style="COLOR: #2b91af"&gt;MagicStringTranslator&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;    {&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: blue"&gt;const&lt;/span&gt; &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; SUBJECT_INFO = &lt;span style="COLOR: #a31515"&gt;"IDENT"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: blue"&gt;const&lt;/span&gt; &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; SUMMARY = &lt;span style="COLOR: #a31515"&gt;"SUMMARY"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: blue"&gt;const&lt;/span&gt; &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; COLLECTION_ITEMS = &lt;span style="COLOR: #a31515"&gt;"COLLT"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: blue"&gt;const&lt;/span&gt; &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; TRADE_ACCOUNT_ACTIVITY = &lt;span style="COLOR: #a31515"&gt;"ACCNT"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: blue"&gt;const&lt;/span&gt; &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; MESSAGE = &lt;span style="COLOR: #a31515"&gt;"MSSGE"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: blue"&gt;const&lt;/span&gt; &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; EMPLOYMENT = &lt;span style="COLOR: #a31515"&gt;"EMPLY"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: blue"&gt;const&lt;/span&gt; &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; INQUIRY_HISTORY = &lt;span style="COLOR: #a31515"&gt;"INQHS"&lt;/span&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;    }&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;Then make the class’ Identifier property of type IdentifierCode:&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        &lt;span style="COLOR: blue"&gt;public&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;IdentifierCode&lt;/span&gt; Identifier&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            {&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                  &lt;span style="COLOR: blue"&gt;get&lt;/span&gt;{ &lt;span style="COLOR: blue"&gt;return&lt;/span&gt; &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;IdentifierCode&lt;/span&gt;(segments[(&lt;span style="COLOR: blue"&gt;int&lt;/span&gt;)&lt;span style="COLOR: #2b91af"&gt;Fields&lt;/span&gt;.Identifier].Data); }&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                  &lt;span style="COLOR: blue"&gt;set&lt;/span&gt; { } &lt;span style="COLOR: green"&gt;// no-op setter provided so that XmlSerializer can serialize this property&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            }&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;Finally, use the Identifier directly, without any need for branching logic:&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"Table style='width: 100%' border='1'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"tr style='background-color:#ccffcc;'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"td style='width: 100%;' colspan='2' align='center'"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"span style='font-size: 9pt;"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteFullBeginTag(&lt;span style="COLOR: #a31515"&gt;"strong"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 10pt; COLOR: #76923c"&gt;                    writer.Write(m_record.Identifier.Description);&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"strong"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                    writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"span"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;                writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"TD"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;            writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"TR"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;&lt;span style="FONT-SIZE: 10pt"&gt;        writer.WriteEndTag(&lt;span style="COLOR: #a31515"&gt;"Table"&lt;/span&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt; &lt;/div&gt;
&lt;div style="MARGIN: 0in 0in 0pt"&gt;And you're done.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=120367"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=120367" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/120367.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/03/07/refactor-to-dry.aspx</guid>
            <pubDate>Fri, 07 Mar 2008 13:45:40 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/120367.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/03/07/refactor-to-dry.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/120367.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/120367.aspx</trackback:ping>
        </item>
        <item>
            <title>A Good Solution for "Magic String" Data</title>
            <category>Coding Practices and Design Patterns</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/03/06/good-solution-for-magic-string-data.aspx</link>
            <description>&lt;p&gt;&lt;font face="Arial"&gt;Dealing with vendor data (or your own) in the form of "codes" can pose significant challenges. You must ensure that your source code remains readable, that data are properly validated, and that data can be displayed as user-friendly descriptions. The built-in solutions (named constants and enums) help, but they have some significant shortcomings. If you derive a class of named constants from the MagicStringTranslator class, though, you can vanquish all 3 challenges in one fell swoop!&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;Egghead Cafe has kindly published the improved version of my article &lt;a href="http://www.eggheadcafe.com/tutorials/aspnet/b916e3a9-d056-4669-8bf3-aa98ed6669c3/a-good-solution-for-magi.aspx"&gt;here&lt;/a&gt;.  Enjoy!&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=120321"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=120321" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/120321.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/03/06/good-solution-for-magic-string-data.aspx</guid>
            <pubDate>Thu, 06 Mar 2008 16:08:45 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/120321.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/03/06/good-solution-for-magic-string-data.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/120321.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/120321.aspx</trackback:ping>
        </item>
        <item>
            <title>Make Magic Strings Easy to Understand and Type Safe</title>
            <category>Coding Practices and Design Patterns</category>
            <link>http://geekswithblogs.net/chrisfalter/archive/2008/02/27/120016.aspx</link>
            <description>&lt;p&gt;As organizations pass data back and forth, they often use codes to represent the data.  For example, a marital status of divorced might be represented as "D", married as "M", and so forth.  You have to solve three problems when you are dealing with magic strings:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;When you write logic to handle the data, things can get chaotic on a hurry if you are not careful; the use of literal magic strings in your source code can make it incomprehensible.  &lt;/li&gt;
    &lt;li&gt;You can get into trouble by passing a string that is not in the set of valid codes as a parameter to a method that is expecting one of the codes.  Since the parameter type is typically string, the compiler will not help you detect the error. &lt;/li&gt;
    &lt;li&gt;You often have to translate codes into it description so a user of your system will know which data have been gathered. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Using named constants can help make your source code more readable, but you still have more work to do: you must write extra logic to check whether a string belongs to the set of valid codes, and to translate a code into a description that a user can understand.  If you declare your named constants in a class that inherits from the MagicStringTranslator class, though, you will get the data validation and the translation for free.  Check out the &lt;a href="http://www.eggheadcafe.com/tutorials/aspnet/b916e3a9-d056-4669-8bf3-aa98ed6669c3/a-good-solution-for-magi.aspx"&gt;article&lt;/a&gt; I wrote for the details!&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=120016"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=120016" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/chrisfalter/aggbug/120016.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Chris Falter</dc:creator>
            <guid>http://geekswithblogs.net/chrisfalter/archive/2008/02/27/120016.aspx</guid>
            <pubDate>Wed, 27 Feb 2008 07:40:16 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/chrisfalter/comments/120016.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/chrisfalter/archive/2008/02/27/120016.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/chrisfalter/comments/commentRss/120016.aspx</wfw:commentRss>
            <trackback:ping>http://geekswithblogs.net/chrisfalter/services/trackbacks/120016.aspx</trackback:ping>
        </item>
    </channel>
</rss>