<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>David Boike's Blog</title>
	
	<link>http://www.make-awesome.com</link>
	<description>Build. Optimize. Make Awesome.</description>
	<lastBuildDate>Wed, 11 Jan 2012 17:40:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2</generator>

   <image>
    <title>David Boike's Blog</title>
    <url>http://0.gravatar.com/avatar/618a5078721cbfeb92fcd6393c8126f5.png?s=48</url>
    <link>http://www.make-awesome.com</link>
   </image>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/MakeAwesome" /><feedburner:info uri="makeawesome" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>Wanted: C# Property Initializers</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/dWJErOzTuwc/</link>
		<comments>http://www.make-awesome.com/2012/01/wanted-c-property-initializers/#comments</comments>
		<pubDate>Wed, 11 Jan 2012 14:00:00 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[syntactical sugar]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/2012/01/wanted-c-property-initializers/</guid>
		<description><![CDATA[I love to write code and I’m a great typist, but I hate typing if it’s not necessary. I’m a big fan of syntactical sugar and I think it’s high time C# got some more of it. By now we’re &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2012/01/wanted-c-property-initializers/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>I love to write code and I’m a great typist, but I hate typing if it’s not necessary. I’m a big fan of <a href="http://en.wikipedia.org/wiki/Syntactic_sugar" target="_blank">syntactical sugar</a> and I think it’s high time C# got some more of it.</p>
<p>By now we’re all used to seeing this:</p>
<div id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:17de67d0-b6b9-465b-92fa-87f48f72201a" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;">
<pre class="brush: csharp; pad-line-numbers: true; title: ; notranslate">
public string MyProperty { get; set; }
</pre>
</div>
<p>This eliminates the need to separately declare a private member variable and public property, because the compiler takes care of it for you.</p>
<p>What it doesn’t do is give you the option to set the initial value, like you could with a private member variable. Instead we have to do this:</p>
<div id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:7eae12df-016c-4dd3-a62c-5e646df48950" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;">
<pre class="brush: csharp; title: ; notranslate">
public class MyClass
{
    public string MyProperty { get; set; }

    public MyClass()
    {
        MyProperty = &quot;initial value&quot;;
    }
}
</pre>
</div>
<p>This really becomes more of a pain point now that I have delved into <a href="http://www.nservicebus.com" target="_blank">NServiceBus</a> and <a href="http://www.ravendb.net/" target="_blank">RavenDB</a>. With NServiceBus, I create a lot of message classes, and when the message includes a collection, it’s usually a good idea to initialize the empty collection so that I don’t get annoying null reference exceptions when I try to use them. The same is true of RavenDB when creating objects with collections to persist to the document store.</p>
<p>What if we could do this?</p>
<div id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:9fa566dc-b4f5-4fa5-95cd-61e41cf31e48" class="wlWriterEditableSmartContent" style="margin: 0px; display: inline; float: none; padding: 0px;">
<pre class="brush: csharp; title: ; notranslate">
public string MyString { get; set; default &quot;initial value&quot;; }

public List&lt;int&gt; MyList { get; set; default new List&lt;int&gt;(); }
</pre>
</div>
<p>It would personally save me a LOT of time.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/dWJErOzTuwc" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2012/01/wanted-c-property-initializers/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2012/01/wanted-c-property-initializers/</feedburner:origLink></item>
		<item>
		<title>Implementing Global Log Off</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/LAMjibmplrs/</link>
		<comments>http://www.make-awesome.com/2012/01/implementing-global-log-off/#comments</comments>
		<pubDate>Tue, 10 Jan 2012 02:23:25 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[login]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/2012/01/implementing-global-log-off/</guid>
		<description><![CDATA[Every year users are conducting more of their lives online, but they’re also doing so from more screens than ever before. It&#8217;s not uncommon for me to access the same web application from my work computer, home computer, iPhone, and &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2012/01/implementing-global-log-off/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>Every year users are conducting more of their lives online, but they’re also doing so from more screens than ever before. It&#8217;s not uncommon for me to access the same web application from my work computer, home computer, iPhone, and iPad, and those are just the devices that I consider “mine”. (Not that I own my work computer, but that I am its only user.)</p>
<p>If your web application contains anything of real value, the real danger comes from public computers that your users may use. It’s all too easy for a user to forget to log off from a website, and most webapps make it impossible to log off those sessions remotely.</p>
<p>I believe it’s becoming more important to allow your users the ability to globally log off from your website, clearing not only local credentials stored in cookies, but invalidating all the user’s sessions across all devices.</p>
<p><span id="more-443"></span>
<p>Some websites have already begun to do this:</p>
<ul>
<li>Facebook allows you to <a href="https://www.facebook.com/notes/facebook-security/forget-to-log-out-help-is-on-the-way/425136200765" target="_blank">remotely log off from other devices</a>, although the functionality is hidden within the Security area under Account Settings, at least it is today. Facebook also shows the approximate location (based on IP) of the activity, as well as a device type. Sometimes this device type is something my mom could understand (Firefox on WinVista, Facebook for iPhone) but in other cases it displays a full browser user agent, which I believe is not helpful for the vast majority of Facebook’s users.&#160; While that is nice for me as a geek, it’s probably overkill for most applications.</li>
<li>Stack Overflow (and the entire Stack Exchange network of websites) say on their Log Out page that “Clicking Log Out will clear all local credentials in your browser, and log you out on all devices.”</li>
</ul>
<p>So how to implement global log off?</p>
<h3>Solution 1: Session Table</h3>
<p>This solution would look a little like the Facebook example.</p>
<p>When the user logs in, write a session row to a database, including the User ID and Session ID. This could include the IP address, location, and user agent information like the Facebook example, but this is not required. Write a session cookie to the user’s browser containing Session ID, salted and encrypted of course.</p>
<p>When the user logs off, delete all session rows for the User ID in order to effect a global log off.</p>
<p>This solution has the advantage that you can support disabling individual sessions without killing off all of them.</p>
<h3>Solution 2: User Generation</h3>
<p>A simpler solution is to add a Generation concept to your User information. This could be an integer (where every user starts at zero) or a Guid; it really doesn’t matter.</p>
<p>The user cookie should now include both the User ID and the Generation, again salted and encrypted.</p>
<p>Presumably every page request will use a (cached) User object of some type to render the obligatory “Welcome, David” user management header near the top of the page, if nothing else. When fetching the current user, the User object would be loaded by UserID, and then the Generation from the cookie is compared to the Generation in the object. If they match, the login is successful. If not, the cookie is invalid and is discarded.</p>
<p>When the user wants to globally log off, simply increment the Generation on the user object, and then all cookies referencing that User ID will be rendered invalid.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/LAMjibmplrs" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2012/01/implementing-global-log-off/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2012/01/implementing-global-log-off/</feedburner:origLink></item>
		<item>
		<title>Destroying Security by Increasing Security</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/hSSdBZFXn2s/</link>
		<comments>http://www.make-awesome.com/2011/11/destroying-security-by-increasing-security/#comments</comments>
		<pubDate>Wed, 23 Nov 2011 23:38:50 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[passwords]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[xkcd]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/2011/11/destroying-security-by-increasing-security/</guid>
		<description><![CDATA[I have a friend.&#160; Let’s call him “Steve”. Steve was recently complaining to me about the password requirements imposed on him by Corporate IT.&#160; He was using all sorts of words to describe them.&#160; The only one suitable for reprinting &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2011/11/destroying-security-by-increasing-security/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>I have a friend.&#160; Let’s call him “Steve”.</p>
<p>Steve was recently complaining to me about the password requirements imposed on him by Corporate IT.&#160; He was using all sorts of words to describe them.&#160; The only one suitable for reprinting was “stupid”.&#160; They went downhill from there rather quickly.</p>
<p>Here are the password requirements Steve has to live with:</p>
<ul>
<li>Must contain at least one uppercase letter A-Z.</li>
<li>Must contain at least one lowercase letter a-z.</li>
<li>Must contain at least one numeral 0-9.</li>
<li>Must contain at least one special character</li>
<li>Must be longer than 6 characters. (So &gt;= 7)</li>
<li>Must be shorter than 9 characters. (So…7 or 8, but not 9)</li>
<li>Must begin and end with an alpha character A-Z or a-z.</li>
<li>The change periods (how often you must reset) vary.</li>
<li>You may not use any password you have used in the last year.</li>
</ul>
<p>Seriously, did they just check every available box in the security setup? They may think they’re making things more secure, but in fact, the addition of all these options, especially with the addition of the very restrictive length requirement (7 or 8 characters, really?) conspires to <em>drastically reduce security</em>.</p>
<p>It reminded me a lot of this <a href="http://xkcd.com/936/" target="_blank">XKCD comic</a>:</p>
<p><a title="XKCD: Password Strength" href="http://xkcd.com/936/" target="_blank"><img style="margin: 10px 0px" src="http://imgs.xkcd.com/comics/password_strength.png" /></a></p>
<p>When you take social psychology into account, you can pretty much bet the farm on the following:</p>
<ul>
<li>The requirements to change passwords several times a year and never repeat passwords in one year means that the month and date have to be in there.At the time of this writing, it is November 2011, so if I were trying to break a password on this system I could be reasonably sure that the password contains either 1111, 1011, 1110, 0911, 1109. That’s 5 possibilities.</li>
<li>It’s pretty safe to assume the date-based string of 4 characters will appear at the end of the password, but the fact that an alpha is required at the end means users will back it up one character. Therefore the password probably fits the regex [0-9]{4}[A-Za-z]$. The addition of the letter (26 * 2 for caps) possibilities means there are only 26*2*5 = 260 likely possibilities for the last 5 characters of the password.</li>
<li>If we assume 8 character passwords (would be stronger than 7 after all) then there are 3 characters left. It wouldn’t be ridiculous to assume:</li>
<ul>
<li>First character caps: 26 possibilities</li>
<li>Second character lowercase: 26 possibilities</li>
<li>Third character a symbol easily reachable from a Shift+NumberKey sequence (there are 10) plus I’ll throw in a few more for good measure that are accessible by the right pinky finger.&#160; Let’s say 20 possibilities.</li>
<li>Total possibilities for the first 3 chars = 26 * 26 * 20 = 13,520</li>
</ul>
<li>Total likely passwords to attempt = 13,250 * 260 = 3,515,200.</li>
</ul>
<p>3.5 million possibilities.&#160; Using XKCD’s assumption of 1000 guesses/second, that’s less than an hour! I sure hope they have some lockout routines on top of that password policy. Considering that they must have checked every box, I suppose I can assume they did.</p>
<p>You may disagree with my math or my assumptions, but that the point is that adding additional security requirements doesn’t always increase security. So get it out of your head that your users are going to pick truly random passwords and think about how they are likely to act before you consider your system to be secure.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/hSSdBZFXn2s" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2011/11/destroying-security-by-increasing-security/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2011/11/destroying-security-by-increasing-security/</feedburner:origLink></item>
		<item>
		<title>Robust 3rd Party Integrations with NServiceBus</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/0qGEV0v6qpI/</link>
		<comments>http://www.make-awesome.com/2011/11/robust-3rd-party-integrations-with-nservicebus/#comments</comments>
		<pubDate>Fri, 04 Nov 2011 02:08:49 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[integration]]></category>
		<category><![CDATA[NServiceBus]]></category>
		<category><![CDATA[source code]]></category>
		<category><![CDATA[web service]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/?p=437</guid>
		<description><![CDATA[A common question about NServiceBus is how to use it to integrate with an external partner. The requirements usually go something like this: The third party will contact us via a web service, passing us a transaction identifier and a &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2011/11/robust-3rd-party-integrations-with-nservicebus/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>A common question about NServiceBus is how to use it to integrate with an external partner. The requirements usually go something like this:</p>
<ul>
<li>The third party will contact us via a web service, passing us a transaction identifier and a collection of fields. </li>
<li>If we successfully receive the message in the web service, we respond with a HTTP 200 OK status code.&#160; If they do not receive the acknowledgement, they will assume a failure and attempt to retry the web service later.</li>
<li>Once we receive the message from the third party, we need to distribute (think publish) the contents of the message to more than one internal process, each of which are completely independent of each other.</li>
<li>We need to logically receive each message once and only once. In other words, it would be a “Very Bad Thing” for one of the internal subscribing processes to receive the same notification more than once.</li>
</ul>
<p>This was most recently asked in <a href="http://stackoverflow.com/questions/7768464/nservicebus-design-ideas/">this StackOverflow question</a>, where it became difficult to explain more within the 600 character comment limit. The best explanation is example code, so here it is.</p>
<p><span id="more-437"></span>
<p>Check out <a href="https://github.com/DuctTapeMan/NServiceBus-External-WebService-Example">NServiceBus External WebService Example on GitHub</a>. Here is a high-level overview of the project:</p>
<ul>
<li><strong>WebServiceHost</strong>
<ul>
<li>This project implements a simple ASMX web service. </li>
<li>The web request information is translated into an NServiceBus command message, and then Sent on the Bus. </li>
</ul>
</li>
<li><strong>TestClient</strong></li>
<ul>
<li>This console app project tests invoking the web service using a standard .NET web service proxy. No NServiceBus to be found here.</li>
</ul>
<li><strong>InternalService</strong></li>
<ul>
<li>An NServiceBus endpoint containing a Saga that receives the NServiceBus message from the web service.</li>
<li>Even if the web service received the message successfully, we can’t know that our partner’s server didn’t fail before they received, or were able to record the acknowledgement, or that a network failure didn’t prevent our reply from arriving at all.</li>
<li>Because of this, it’s possible that our partner may retry sending the message even though we’ve already received it once.&#160; This means we may receive duplicate messages, and we need to insulate our internal processes from that.</li>
<ul>
<li>To do that, we accept and publish an event corresponding to the first message received.&#160; We also store the fact that we received that message in saga data so that we will know to ignore any duplicate messages.</li>
<li>We also request a timeout notification from the Timeout Manager so that after some reasonable period (after we know the partner could no longer possibly be retrying) we can clean up the saga data.</li>
</ul>
</ul>
<li><strong>InternalService.Messages</strong></li>
<ul>
<li>This assembly contains all the message schema for the project, including:</li>
<ul>
<li>ExternalServiceMsg – the web service sends this to InternalService.</li>
<li>IExternalMessageReceivedEvent – the event that is published upon receipt of a non-duplicated ExternalServiceMsg. We are using an interface to define the event, which is recommended as it <a href="http://www.nservicebus.com/MessagesAsInterfaces.aspx">enables easier versioning later on thanks to an interface’s multiple inheritance abilities</a>.</li>
<li>ExternalServiceSagaData – this defines the state data our saga will use to keep track of which messages it has received.</li>
</ul>
<li>Note that it is probably <em><u>NOT</u></em> best practice to keep all these things in the same assembly.&#160; Udi would probably recommend that the command and saga data (which are internal to the logical service) be segregated from the event (which forms the external contract for the service).</li>
</ul>
<li><strong>Subscriber1</strong> and <strong>Subscriber2</strong></li>
<ul>
<li>These endpoints subscribe to the IExternalMessageReceivedEvent and pump some info to the Console so that we can watch it happen.</li>
</ul>
<li><strong>ExampleTimeoutManager</strong></li>
<ul>
<li>For active development, you should probably just keep a Timeout Manager running as a service on your development machine, but this is provided to keep the example self-contained and as an example of how to configure the <a href="http://www.nuget.org/List/Packages/NServiceBus.TimeoutManager">Timeout Manager package available from NuGet</a>.&#160; I used a fairly nonstandard queue name so that it won’t conflict if you do already have a running timeout manager on your system.</li>
</ul>
</ul>
<p>The project uses the following NuGet packages to make it as easy as possible to get started:</p>
<ul>
<li><a href="http://nuget.org/List/Packages/Log4Net">Log4Net</a>, as a dependency of NServiceBus.</li>
<li><a href="http://nuget.org/List/Packages/NServiceBus">NServiceBus</a> – for the core NServiceBus DLLs.</li>
<ul>
<li>A messages assembly, however, doesn’t need NServiceBus.Core.dll or Log4Net.dll, so you can just “Install-Package NServiceBus” on this assembly and then manually remove those two references.</li>
</ul>
<li>NServiceBus.Host – for the InternalService endpoint.</li>
<li><a href="http://nuget.org/List/Packages/NServiceBus.TimeoutManager">NServiceBus.TimeoutManager</a></li>
</ul>
<p>The project also uses the <a href="http://www.nuget.org/List/Packages/NuGetPowerTools">NuGetPowerTools</a> package to automatically download all the required packages when you build the solution <a href="http://blog.davidebbo.com/2011/08/easy-way-to-set-up-nuget-to-restore.html">as described in this article from David Ebbo</a>.&#160; I highly recommend it.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/0qGEV0v6qpI" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2011/11/robust-3rd-party-integrations-with-nservicebus/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2011/11/robust-3rd-party-integrations-with-nservicebus/</feedburner:origLink></item>
		<item>
		<title>Hidden iOS URL Keyboard Gem</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/bGq6nNYuExg/</link>
		<comments>http://www.make-awesome.com/2011/08/hidden-ios-url-keyboard-gem/#comments</comments>
		<pubDate>Sun, 28 Aug 2011 21:46:46 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Technology]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/?p=431</guid>
		<description><![CDATA[For a long time I thought that the addition of a &#8220;.com&#8221; button on the iOS keyboard was a fantastic idea.  But what about .net, .org, and .edu domains?  Where&#8217;s the love for them? I got to thinking, wouldn&#8217;t it &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2011/08/hidden-ios-url-keyboard-gem/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>For a long time I thought that the addition of a &#8220;.com&#8221; button on the iOS keyboard was a fantastic idea.  But what about .net, .org, and .edu domains?  Where&#8217;s the love for them?</p>
<p>I got to thinking, wouldn&#8217;t it be awesome if you could hover over the .com button and get a popup with options for the other common top-level domains?</p>
<p>Turns out the Apple engineers were way ahead of me.  Give it a shot sometime!</p>
<p>On my iPad (set to English/US keyboard layout of course) hovering over the .com button gives me additional options for .net, .org, .us, and .edu.  It does make me wonder if I would get something like .co.uk if I had a British keyboard setting.</p>
<p>What you apparently can NOT do is take a screenshot with the popup menu activated.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/bGq6nNYuExg" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2011/08/hidden-ios-url-keyboard-gem/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2011/08/hidden-ios-url-keyboard-gem/</feedburner:origLink></item>
		<item>
		<title>System.Security.Cryptography and Thread Safety</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/KSDkN3es-V0/</link>
		<comments>http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/#comments</comments>
		<pubDate>Tue, 26 Jul 2011 15:09:10 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/?p=427</guid>
		<description><![CDATA[Are you experiencing either of these exceptions? System.Security.Cryptography.CryptographicException Padding is invalid and cannot be removed. System.IndexOutOfRangeException Index was outside the bounds of the array. System.IndexOutOfRangeException Probable I/O race condition detected while copying memory. The I/O package is not thread safe &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>Are you experiencing either of these exceptions?</p>
<blockquote><p><strong>System.Security.Cryptography.CryptographicException</strong><br />
Padding is invalid and cannot be removed.</p>
<p><strong>System.IndexOutOfRangeException</strong><br />
Index was outside the bounds of the array.</p>
<p><strong>System.IndexOutOfRangeException</strong><br />
Probable I/O race condition detected while copying memory. The I/O package is not thread safe by default. In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader&#8217;s or TextWriter&#8217;s Synchronized methods. This also applies to classes like StreamWriter and StreamReader.</p></blockquote>
<p>Well at least the last one is descriptive, but that is the LEAST likely to occur.</p>
<p>If you&#8217;re seeing any of these exceptions, there&#8217;s a good chance you&#8217;ve run afoul of a secret of the System.Cryptography namespace.  Almost nothing is thread safe.</p>
<p>It&#8217;s an easy error to make. We know encryption is processor intensive, and it seems like it would be smart to incur the costs of setting up ICryptoTransform objects for encryptors and decryptors once and then store them in a static variable. Any state they might share would seem to be reference data like keys and salt and init vectors, so as long as we use a new CryptoStream for each operation, what could go wrong?</p>
<p>Well, lots.</p>
<p>Internally, the implementations of ICryptoTransform (and I assume other objects) use objects from the System.IO namespace like buffers and streams that we would never think of sharing between threads, but it&#8217;s hard to know that from a simple call to ICryptoTransform.TransformBlock().</p>
<p>So, if you run into any of the exceptions above, try either creating your System.Cryptography objects each time you need them, or mark them with the ThreadStaticAttribute.  Remember that with [ThreadStatic], a static initializer will not execute for each thread, so check it for null before you use it, then initialize if null.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/KSDkN3es-V0" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/</feedburner:origLink></item>
		<item>
		<title>Stuck between Netflix and a hard place</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/OvzigfoIEmo/</link>
		<comments>http://www.make-awesome.com/2011/07/stuck-between-netflix-and-a-hard-place/#comments</comments>
		<pubDate>Wed, 13 Jul 2011 13:07:03 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Netflix]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/2011/07/stuck-between-netflix-and-a-hard-place/</guid>
		<description><![CDATA[There are two types of people in the world right now: those who are angry at Netflix and those who don’t have Netflix. Like everyone else, I received the email yesterday notifying me that as of September 1, 2011, my &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2011/07/stuck-between-netflix-and-a-hard-place/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>There are two types of people in the world right now: those who are angry at Netflix and those who don’t have Netflix.</p>
<p>Like everyone else, I received the email yesterday notifying me that as of September 1, 2011, my $10 Netflix plan that includes 1 DVD at a time and online unlimited streaming will be discontinued. Instead, they offer separate plans for DVDs and for streaming.&#160; 1 DVD at a time will now cost $8, and unlimited streaming will now also cost $8.&#160; There is no discount for bundling, so if I want to continue the same level of service, it will now cost me $16 per month.</p>
<p>It’s not the money that bothers me. Prices were bound to go up.&#160; Maybe this is a pretty severe jump all at once, but it’s not completely unexpected.</p>
<p>What bothers me is the false choice it represents. If money does indeed talk (and I believe it does) then Netflix is asking me to choose from these options:</p>
<ol>
<li>I like getting DVDs from you, but I don’t care for your streaming service. Please take my money and keep the DVDs coming.</li>
<li>I love your streaming service, but DVDs in the mail is so 2003. Please take my money and let me stream to my heart’s content, but don’t make me walk out to the mailbox.</li>
<li>I like DVDs and I also like streaming, and I’m willing to pay more money for both.</li>
<li>Netflix, you suck. Cancel my subscription.</li>
</ol>
<p>I don’t believe that any of these four options correctly captures my real intent:</p>
<p><strong>I would be willing to pay $16 per month, maybe even more, just for the streaming service, provided that the streaming selection didn’t suck.</strong></p>
<p><span id="more-425"></span>
<p>The streaming selection Netflix (or any other provider) has to offer is simply unacceptable.&#160; When I started Netflix I was impressed by the streaming selection <em>under the assumption that as time progressed, its size would only increase.</em> But that has not been the case. As licensing contracts come and go, some movies appear and disappear from my online queue, but by and large, my opinion of the offerings as a whole has always been a resounding “Meh”.</p>
<p>There aren’t a lot of other market options either.&#160; Let’s review some <a href="http://www.pcworld.com/article/235629/netflix_alternatives_other_places_you_can_get_streaming_media_dvd_rentals.html">alternatives to Netflix</a>:</p>
<ul>
<li><strong>Amazon Prime</strong> – Cheaper than Netflix at $79/year, but all indications are that <a href="http://www.pcworld.com/article/220399/amazon_prime_vs_netflix_video_streaming_feature_showdown.html">their selection is even worse than Netflix Streaming</a>. Plus I’d need to buy a <a href="http://www.roku.com/">Roku box</a> because my Blu-Ray player only has Netflix built in.</li>
<li><strong>Blockbuster</strong> – the whole appeal of Blockbuster’s DVD program was that you could return it to a store and get a new DVD faster than Netflix’s mail turnaround. With nearly every Blockbuster in my area closing in the wake of their bankruptcy filing (I honestly don’t know of a location nearby that’s still open) this advantage is moot. Fail.</li>
<li><strong>Hulu</strong> – seems to be focused more on competing with Dish and Cable than with movies. Maybe they offer some hope for the future though.</li>
<li><strong>Redbox</strong> – honestly I can see the potential appeal, but it’s never in the right place at the right time. I see people lined up at the Redbox at my local supermarket like hamsters trying to get at a feeder bar, but I want to make my entertainment decisions on my couch, not while I’m buying groceries.</li>
<li><strong>Apple TV</strong> – perhaps with an Apple TV I could rent the movies I really want to watch (starting at $3.99 each, which beats the old bricks and mortar Blockbuster price or Dish Network’s Pay-Per-View price, and can be initiated from my couch) but its other feature seem pretty limited in comparison to a Roku box.</li>
</ul>
<p>Of course the problem isn’t Netflix at all.&#160; Netflix can only stream what they have licensing rights to.&#160; Unfortunately, it seems Hollywood is <a href="http://www.slate.com/id/2298871/pagenum/all/">repeating all the mistakes of the music industry a decade ago</a>:</p>
<blockquote><p>Right now, in fact, the movie and TV business looks a lot like the music one did in the early 2000s. And as we&#8217;ve seen, that decade didn&#8217;t work out too well for the labels. So it&#8217;s worth looking at the situation and wondering how things are going to fare in the TV and movie world in the decade ahead.</p>
<p>[…]</p>
<p>…the music industry, when it began to feel the effects of the technological change coming, doubled down on stupid.</p>
</blockquote>
<p>And so it seems the prospects of being able to stream any movie, including new releases, at any time, <em>even if we’re willing to pay for it,</em> are nil. </p>
<p>I still don’t know what I will do with my Netflix account come September 1. I want the streaming service to be great and succeed, but I’m not sure I can pump more money into a broken status quo.</p>
<p>Until Hollywood gets over itself, it seems I’m stuck between Netflix and a hard place.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/OvzigfoIEmo" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2011/07/stuck-between-netflix-and-a-hard-place/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2011/07/stuck-between-netflix-and-a-hard-place/</feedburner:origLink></item>
		<item>
		<title>Flexible Reporting with LINQ and C# 4.0 dynamic keyword</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/0MGiVJwL7eM/</link>
		<comments>http://www.make-awesome.com/2011/07/flexible-reporting-with-linq-and-csharp-4-0-dynamic-keyword/#comments</comments>
		<pubDate>Sun, 10 Jul 2011 20:00:41 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[.NET 4.0]]></category>
		<category><![CDATA[dynamic]]></category>
		<category><![CDATA[LINQ]]></category>
		<category><![CDATA[reporting]]></category>
		<category><![CDATA[source code]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/?p=415</guid>
		<description><![CDATA[It’s commonly very difficult to question business people about reporting requirements.&#160; It’s not really their fault either – they just can’t know exactly what they want until they’re trying to answer a question and can’t easily do it with the &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2011/07/flexible-reporting-with-linq-and-csharp-4-0-dynamic-keyword/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>It’s commonly very difficult to question business people about reporting requirements.&#160; It’s not really their fault either – they just can’t know exactly what they want until they’re trying to answer a question and can’t easily do it with the reports you’ve given them.</p>
<p>This is why it’s good to make reports as flexible and updateable as possible, but with as little developer required to update the reports as possible.</p>
<p>If you’re operating in an environment where all database access must be via stored procedures, this is a really big problem.&#160; It’s really unlikely that the changes requested by business can be implemented with the same stored procedure you naïvely created for your first attempt.&#160; I’ve seen scenarios where a database has stored procedures with the suffixes GetReport, GetReport2, GetReport3, GetReport4, etc.&#160; Yuck.</p>
<p>Even if you’re using Entity Framework, LINQ to SQL, or some other data layer framework that enables more free-form access to the database, you can’t always ensure that all report queries will result in good execution costs and actually be performant.</p>
<p>This is why it can sometimes be a good idea to perform a very basic database query (via stored procedure if necessary) to get a base set of data, and then perform more conditional operations on it in memory with LINQ.&#160; It’s a pain to do a “Name Contains” filter in a stored procedure (especially if there are a dozen other options) but with LINQ it’s no big deal.</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:d04faceb-eae3-4846-b525-1797c7bc8820" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; pad-line-numbers: true; title: ; notranslate">
IEnumerable&lt;DataItem&gt; data = GetBaseData();

if (!String.IsNullOrEmpty(nameFilter))
	data = data.Where(d =&gt; d.Name.IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) &gt;= 0);
</pre>
</pre>
</div>
<p>This is really great for simple filters, but gets difficult when we want to do more complex grouping and aggregating functions, such as grouping by Hourly/Daily/Weekly/Monthly and/or by other data points.</p>
<p>The remainder of this article will show how this can be done with static code, and then how we can drastically increase the maintainability of this same code by employing the dynamic keyword introduced in C# 4.0.</p>
<p><span id="more-415"></span><br />
<h2>The .NET 3.5 Way</h2>
<p>Let’s consider the following data type.&#160; It represents a simplified sort of reporting data you might see from an ad platform that is utilized on the web and also on mobile devices (iOS and Android).</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:19ff4052-dbc5-4507-ac2f-96cf4b3defaf" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; title: ; notranslate">
public class ReportData
{
	public DateTime Date { get; set; }
	public string Platform { get; set; }
	public string Size { get; set; }
	public int Views { get; set; }
	public int Clicks { get; set; }
}
</pre>
</pre>
</div>
<p>We want the ability to do any of these things independently or together:</p>
<ul>
<li>Group Date by Hour, Day, Week, Hour, or an overall Summary</li>
<li>Group all platforms together</li>
<li>Group all sizes together</li>
</ul>
<p>Of course, no matter what we do, we will always sum the Views and Clicks.</p>
<p>Let’s start with some code to generate quite a bit of random data and then show it to us:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:ab182670-e01b-4db5-974a-147628452842" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; title: ; notranslate">
class Program
{
	static void Main(string[] args)
	{
		List&lt;ReportData&gt; data = CreateRandomData();
		Report(&quot;Raw Data, Top 10&quot;, data.Take(10));
		Console.ReadLine();
	}

	private static List&lt;ReportData&gt; CreateRandomData()
	{
		List&lt;ReportData&gt; data = new List&lt;ReportData&gt;();
		Random rand = new Random();
		DateTime start = new DateTime(2011,1,1);
		DateTime end = new DateTime(2011, 7, 1);

		for (DateTime dt = start; dt &lt; end; dt = dt.AddHours(1))
		{
			foreach (string platform in new string[] { &quot;Web&quot;, &quot;iOS&quot;, &quot;Android&quot; })
			{
				foreach (string size in new string[] { &quot;Standard&quot;, &quot;Enhanced&quot; })
				{
					int views = rand.Next(0, 50);
					int clicks = rand.Next(0, views / 3);
					data.Add(new ReportData
					{
						Date = dt,
						Platform = platform,
						Size = size,
						Views = views,
						Clicks = clicks
					});
				}
			}
		}

		Console.WriteLine(&quot;Generated {0} Sample Rows&quot;, data.Count);
		return data;
	}

	private static void Report(string label, IEnumerable&lt;ReportData&gt; data)
	{
		Console.WriteLine();
		Console.WriteLine(label);
		string format = &quot;{0:MM/dd/yyyy HH:mm}   {1,-8}  {2,-8}  {3,6}  {4,6}&quot;;
		Console.WriteLine(format, &quot;Date            &quot;, &quot;Platform&quot;, &quot;Size&quot;, &quot;Views&quot;, &quot;Clicks&quot;);
		foreach (ReportData d in data)
			Console.WriteLine(format,d.Date, d.Platform, d.Size, d.Views, d.Clicks);
	}
}
</pre>
</pre>
</div>
<p>This results in the following output.&#160; (The Views and Clicks will be different each time because they are generated randomly.)</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:a60e4a13-1977-4024-bcce-b69e9fe52172" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: plain; title: ; notranslate">
Generated 26064 Sample Rows

Raw Data, Top 10
Date               Platform  Size       Views  Clicks
01/01/2011 00:00   Web       Standard      15       2
01/01/2011 00:00   Web       Enhanced      34       1
01/01/2011 00:00   iOS       Standard      36       2
01/01/2011 00:00   iOS       Enhanced      20       5
01/01/2011 00:00   Android   Standard      44       8
01/01/2011 00:00   Android   Enhanced      29       4
01/01/2011 01:00   Web       Standard      45       9
01/01/2011 01:00   Web       Enhanced      30       7
01/01/2011 01:00   iOS       Standard      25       5
01/01/2011 01:00   iOS       Enhanced      47       9
</pre>
</pre>
</div>
<p>So how would we do this filtering statically?&#160; For each item we want to group by, we have to do a combination GroupBy-Select that groups on everything ELSE.</p>
<p>Here is the example for Platform and Size, which are a lot simpler than for Date.</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:85afaa5e-3055-47eb-822d-405781e40156" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: cpp; title: ; notranslate">
private static IEnumerable&lt;ReportData&gt; ProcessVersion1(IEnumerable&lt;ReportData&gt; data, DateMode dateMode, bool platform, bool size)
{
	if(platform)
	{
		data = data.GroupBy(d =&gt; new
		{
			Date = d.Date,
			Size = d.Size,
		})
		.Select(g =&gt; new ReportData
		{
			Date = g.Key.Date,
			Platform = &quot;All&quot;,
			Size = g.Key.Size,
			Views = g.Sum(d =&gt; d.Views),
			Clicks = g.Sum(d =&gt; d.Clicks)
		});
	}

	if (size)
	{
		data = data.GroupBy(d =&gt; new
		{
			Date = d.Date,
			Platform = d.Platform,
		})
		.Select(g =&gt; new ReportData
		{
			Date = g.Key.Date,
			Platform = g.Key.Platform,
			Size = &quot;All&quot;,
			Views = g.Sum(d =&gt; d.Views),
			Clicks = g.Sum(d =&gt; d.Clicks)
		});
	}

	// Date to come later
}
</pre>
</pre>
</div>
<p>So to group all the Platforms together, we must group by Date and Size, then emit new data items that have “All” for the Platform, emit the Date and Size from the grouping key, and sum the Views and Clicks.&#160; To group all Sizes together, we must group by Date and Platform, then emit new data items that have “All” for the Size, emit the Date and Platform from the grouping key, and sum the Views and Clicks.</p>
<p>In short, to group an axis together, you must (for the most part) focus on all of the OTHER axes.</p>
<p>It gets worse for a Date grouping:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:60f7f25c-7103-4a67-82d9-d4118b668375" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; title: ; notranslate">
public enum DateMode
{
	Hourly,
	Daily,
	Weekly,
	Monthly,
	Summary,
}

private static IEnumerable&lt;ReportData&gt; ProcessVersion1(IEnumerable&lt;ReportData&gt; data, DateMode dateMode, bool platform, bool size)
{
	// Platform and Size, handled previously

	switch (dateMode)
	{
		case DateMode.Hourly:
			// No need to modify the data, we're assuming it comes from
			// the database already grouped by Hour
			break;
		case DateMode.Daily:
			data = data.GroupBy(d =&gt; new
			{
				Date = d.Date.Date,
				Platform = d.Platform,
				Size = d.Size
			})
			.Select(g =&gt; new ReportData
			{
				Date = g.Key.Date,
				Platform = g.Key.Platform,
				Size = g.Key.Size,
				Views = g.Sum(d =&gt; d.Views),
				Clicks = g.Sum(d =&gt; d.Clicks)
			});
			break;
		case DateMode.Weekly:
			data = data.GroupBy(d =&gt; new
			{
				Date = d.Date.Date.AddDays(-((int)d.Date.Date.DayOfWeek)),
				Platform = d.Platform,
				Size = d.Size
			})
			.Select(g =&gt; new ReportData
			{
				Date = g.Key.Date,
				Platform = g.Key.Platform,
				Size = g.Key.Size,
				Views = g.Sum(d =&gt; d.Views),
				Clicks = g.Sum(d =&gt; d.Clicks)
			});
			break;
		case DateMode.Monthly:
			data = data.GroupBy(d =&gt; new
			{
				Date = new DateTime(d.Date.Year, d.Date.Month, 1),
				Platform = d.Platform,
				Size = d.Size
			})
			.Select(g =&gt; new ReportData
			{
				Date = g.Key.Date,
				Platform = g.Key.Platform,
				Size = g.Key.Size,
				Views = g.Sum(d =&gt; d.Views),
				Clicks = g.Sum(d =&gt; d.Clicks)
			});
			break;
		case DateMode.Summary:
			data = data.GroupBy(d =&gt; new
			{
				Platform = d.Platform,
				Size = d.Size
			})
			.Select(g =&gt; new ReportData
			{
				Date = DateTime.MinValue,
				Platform = g.Key.Platform,
				Size = g.Key.Size,
				Views = g.Sum(d =&gt; d.Views),
				Clicks = g.Sum(d =&gt; d.Clicks)
			});
			break;

	}

	return data;
}
</pre>
</pre>
</div>
<p>Can you imagine how much of a mess it would be if we had to add a new property to this mix?&#160; If we were to add another property that was similar to Platform or Size, we would need to reference it twice for each of the 4 date cases (in the GroupBy and in the Select) and then twice each for the existing Platform and Size groupings, not to mention its own grouping logic.&#160; That’s 12 edit points needed to add one property!</p>
<p>That simply is not maintainable, and there must be a better way.</p>
<p>One answer to the Stack Overflow question <a href="http://stackoverflow.com/questions/3929041/dynamic-linq-groupby-multiple-columns#3929455">Dynamic LINQ GroupBy Multiple Columns</a> suggests a way that would make this still possible <em>without</em> using dynamic code.&#160; It involves making an EntryGrouper class that implements IEquatable&lt;T&gt; and serves as the grouping key for the GroupBy operations.&#160; While this method should work, it is by no means short, and would have to be re-implemented for every new use case.&#160; The use of the dynamic keyword I present can be reused over and over.</p>
<h2>Introducing the dynamic keyword</h2>
<p><a href="http://msdn.microsoft.com/en-us/library/dd264736.aspx">C# 4.0 introduces the dynamic keyword</a> (and indeed a whole new dynamic runtime) that enables us to declare any properties we want on an object, and it is not evaluated until runtime.</p>
<p>Now, this isn’t completely magical – you could cast any object to a dynamic and it would compile, but at runtime there must be some mechanism to provide the values and methods you try to use.</p>
<p>Most of the time, we will use <a href="http://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject.aspx">System.Dynamic.ExpandoObject</a> to accomplish this for us.&#160; ExpandoObject by itself is another statically-typed object that we cannot declare random properties on.&#160; Only when we assign it to a dynamic variable does it get this power, but under the hood, the ExpandoObject essentially stores values in a Dictionary&lt;string, object&gt;, and we can exploit that for our purposes.</p>
<p>By itself, ExpandoObject does not override GetHashCode() or Equals() from the base object, and LINQ’s GroupBy method needs this in order to do its work.&#160; So first, we must create a wrapper that enables us to use the power of the dynamic keyword, compute equivalence and a hash code, and enable us to get back at the original data for the Select afterwards.</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:61eca718-271d-4b23-b5a8-8de8e6506507" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: bash; title: ; notranslate">
public class DynamicHashWrapper
{
	public dynamic Value { get; private set; }

	private Dictionary&lt;string, object&gt; dict;
	private HashSet&lt;string&gt; keys;
	private static IEqualityComparer&lt;HashSet&lt;string&gt;&gt; keyComparer = HashSet&lt;string&gt;.CreateSetComparer();

	public DynamicHashWrapper(dynamic value)
	{
		this.Value = value;
		dict = new Dictionary&lt;string,object&gt;(value as IDictionary&lt;string, object&gt;);
		keys = new HashSet&lt;string&gt;(dict.Keys);
	}

	private int? hashCode;
	public override int GetHashCode()
	{
		if (!this.hashCode.HasValue)
		{
			int code = 0;
			foreach (var entry in dict.OrderBy(p =&gt; p.Key))
				code ^= entry.Value.GetHashCode();
			this.hashCode = code;
		}
		return this.hashCode.Value;
	}

	public override bool Equals(object obj)
	{
		if (!(obj is DynamicHashWrapper))
			return false;

		DynamicHashWrapper other = obj as DynamicHashWrapper;

		return keyComparer.Equals(this.keys, other.keys)
			&amp;&amp; this.GetHashCode() == other.GetHashCode();
	}
}
</pre>
</pre>
</div>
<p>After creating a dynamic variable, we can wrap it with this class.&#160; It does the following:</p>
<ul>
<li>Stores the dynamic value itself so we can retrieve its properties back later.</li>
<li>Converts the dynamic ExpandoObject to a Dictionary that we can enumerate.</li>
<li>Enables computation of a hash code by XOR-ing the values of the dictionary.</li>
<li>Determines if two values are equal by ensuring that the dictionary sizes are equal and that the hash codes are equal.</li>
</ul>
<p>With this addition to the dynamic object, we can now re-implement our data processing routine:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:f7b46a46-2b81-4aa5-80a2-2eb07f154997" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; title: ; notranslate">
private static IEnumerable&lt;ReportData&gt; ProcessVersion2(IEnumerable&lt;ReportData&gt; data, DateMode dateMode, bool platform, bool size)
{
	return data
		.GroupBy(d =&gt;
		{
			dynamic key = new ExpandoObject();

			switch (dateMode)
			{
				case DateMode.Hourly: key.Date = d.Date; break;
				case DateMode.Daily: key.Date = d.Date.Date; break;
				case DateMode.Weekly: key.Date = d.Date.Date.AddDays(-((int)d.Date.DayOfWeek)); break;
				case DateMode.Monthly: key.Date = new DateTime(d.Date.Year, d.Date.Month, 1); break;
				case DateMode.Summary: key.Date = DateTime.MinValue; break;
			}

			if (!platform)
				key.Platform = d.Platform;

			if (!size)
				key.Size = d.Size;

			return new DynamicHashWrapper(key);
		})
		.Select(g =&gt; new ReportData
		{
			Date = g.Key.Value.Date,
			Platform = platform ? &quot;All&quot; : g.Key.Value.Platform,
			Size = size ? &quot;All&quot; : g.Key.Value.Size,
			Views = g.Sum(d =&gt; d.Views),
			Clicks = g.Sum(d =&gt; d.Clicks)
		});
}
</pre>
</pre>
</div>
<p>Now we have a single GroupBy and a single Select.&#160; By the way, grouping and selecting only once means we should get a performance improvement because we aren’t generating a bunch of intermediate objects each time we do a grouping – we create one dynamic grouping key and spin through the collection only once.&#160; Of course, the dynamic object probably has its own overhead from not being statically compiled, but the improvement in maintainability is WELL worth that small setback.</p>
<p>As a comparison, this new implementation takes 33 lines of code, whereas the original was 111 lines.&#160; Who wants to try to maintain a 111-line method!?</p>
<p>Now we can try a bunch of different groupings and see them in action.</p>
<p><div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:0edd6e80-2fca-44d7-91f2-522e8e53a500" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; title: ; notranslate">
static void Main(string[] args)
{
	List&lt;ReportData&gt; data = CreateRandomData();
	Report(&quot;Raw Data, Top 10&quot;, data.Take(10));
	Report(&quot;Group Platform&quot;, Process(data, DateMode.Hourly, true, false).Take(10));
	Report(&quot;Group Size&quot;, Process(data, DateMode.Hourly, false, true).Take(10));
	Report(&quot;Group Platform &amp; Size&quot;, Process(data, DateMode.Hourly, true, true).Take(10));
	Report(&quot;Group All by Day&quot;, Process(data, DateMode.Daily, true, true).Take(10));
	Report(&quot;Group All by Week&quot;, Process(data, DateMode.Weekly, true, true).Take(10));
	Report(&quot;Group All by Month&quot;, Process(data, DateMode.Monthly, true, true).Take(10));
	Report(&quot;Group All Summary&quot;, Process(data, DateMode.Summary, true, true).Take(10));
	Report(&quot;Summary By Types&quot;, Process(data, DateMode.Summary, false, false).Take(10));
	Console.ReadLine();
}
</pre>
</pre>
</div>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:71ff2427-218f-4666-9764-d8ed19cacf72" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: plain; title: ; notranslate">
Generated 26064 Sample Rows

Raw Data, Top 10
Date               Platform  Size       Views  Clicks
01/01/2011 00:00   Web       Standard      15       3
01/01/2011 00:00   Web       Enhanced      45       5
01/01/2011 00:00   iOS       Standard       9       1
01/01/2011 00:00   iOS       Enhanced       6       1
01/01/2011 00:00   Android   Standard       0       0
01/01/2011 00:00   Android   Enhanced       7       0
01/01/2011 01:00   Web       Standard      33       7
01/01/2011 01:00   Web       Enhanced       1       0
01/01/2011 01:00   iOS       Standard      28       7
01/01/2011 01:00   iOS       Enhanced      25       5

Group Platform
Date               Platform  Size       Views  Clicks
01/01/2011 00:00   All       Standard      24       4
01/01/2011 00:00   All       Enhanced      58       6
01/01/2011 01:00   All       Standard     105      14
01/01/2011 01:00   All       Enhanced      49       8
01/01/2011 02:00   All       Standard     133      13
01/01/2011 02:00   All       Enhanced      36       6
01/01/2011 03:00   All       Standard      95      16
01/01/2011 03:00   All       Enhanced      94      25
01/01/2011 04:00   All       Standard      58      14
01/01/2011 04:00   All       Enhanced      78       3

Group Size
Date               Platform  Size       Views  Clicks
01/01/2011 00:00   Web       All           60       8
01/01/2011 00:00   iOS       All           15       2
01/01/2011 00:00   Android   All            7       0
01/01/2011 01:00   Web       All           34       7
01/01/2011 01:00   iOS       All           53      12
01/01/2011 01:00   Android   All           67       3
01/01/2011 02:00   Web       All           55       0
01/01/2011 02:00   iOS       All           42      10
01/01/2011 02:00   Android   All           72       9
01/01/2011 03:00   Web       All           53      11

Group Platform &amp; Size
Date               Platform  Size       Views  Clicks
01/01/2011 00:00   All       All           82      10
01/01/2011 01:00   All       All          154      22
01/01/2011 02:00   All       All          169      19
01/01/2011 03:00   All       All          189      41
01/01/2011 04:00   All       All          136      17
01/01/2011 05:00   All       All          142      17
01/01/2011 06:00   All       All          150      15
01/01/2011 07:00   All       All          181      33
01/01/2011 08:00   All       All          143      22
01/01/2011 09:00   All       All          208      34

Group All by Day
Date               Platform  Size       Views  Clicks
01/01/2011 00:00   All       All         3756     552
01/02/2011 00:00   All       All         3370     479
01/03/2011 00:00   All       All         3537     503
01/04/2011 00:00   All       All         3298     469
01/05/2011 00:00   All       All         3605     573
01/06/2011 00:00   All       All         3617     518
01/07/2011 00:00   All       All         3193     450
01/08/2011 00:00   All       All         3485     513
01/09/2011 00:00   All       All         3629     504
01/10/2011 00:00   All       All         3628     533

Group All by Week
Date               Platform  Size       Views  Clicks
12/26/2010 00:00   All       All         3756     552
01/02/2011 00:00   All       All        24105    3505
01/09/2011 00:00   All       All        24585    3495
01/16/2011 00:00   All       All        24618    3553
01/23/2011 00:00   All       All        24875    3471
01/30/2011 00:00   All       All        24845    3634
02/06/2011 00:00   All       All        24528    3391
02/13/2011 00:00   All       All        24561    3467
02/20/2011 00:00   All       All        24280    3621
02/27/2011 00:00   All       All        24614    3507

Group All by Month
Date               Platform  Size       Views  Clicks
01/01/2011 00:00   All       All       109101   15593
02/01/2011 00:00   All       All        98057   14078
03/01/2011 00:00   All       All       108231   15308
04/01/2011 00:00   All       All       106527   15286
05/01/2011 00:00   All       All       110096   15742
06/01/2011 00:00   All       All       106354   14982

Group All Summary
Date               Platform  Size       Views  Clicks
01/01/0001 00:00   All       All       638366   90989

Summary By Types
Date               Platform  Size       Views  Clicks
01/01/0001 00:00   Web       Standard  105615   14983
01/01/0001 00:00   Web       Enhanced  107740   15448
01/01/0001 00:00   iOS       Standard  107203   15417
01/01/0001 00:00   iOS       Enhanced  106457   15117
01/01/0001 00:00   Android   Standard  104772   14840
01/01/0001 00:00   Android   Enhanced  106579   15184
</pre>
</pre>
</div>
<h2>Summary and Code Download</h2>
<p>With the static alternatives, we get a lot of spaghetti code that is impossible to maintain, and even if it was, the many GroupBy/Select iterations required for multiple axes result in unnecessary object creation, loops through the data collection, and eventual garbage collection.</p>
<p>Using the dynamic keyword and a small, reusable trick to enable the dynamic object to generate meaningful hash codes, we can loop through the data collection once.&#160; Our results are just as good, and the next time someone visits this code, they’re much less likely to screw it up.</p>
<p>The code in this article is fairly fragmented.  <a href="http://www.make-awesome.com/wp-content/plugins/download-monitor/download.php?id=5">Click here to download the full source code.</a></p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/0MGiVJwL7eM" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2011/07/flexible-reporting-with-linq-and-csharp-4-0-dynamic-keyword/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2011/07/flexible-reporting-with-linq-and-csharp-4-0-dynamic-keyword/</feedburner:origLink></item>
		<item>
		<title>Backing up Hudson, with Hudson</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/0Wlx03hiGnE/</link>
		<comments>http://www.make-awesome.com/2011/07/backing-up-hudson-with-hudson/#comments</comments>
		<pubDate>Sat, 09 Jul 2011 23:32:57 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[build server]]></category>
		<category><![CDATA[continuous integration]]></category>
		<category><![CDATA[Fog Creek]]></category>
		<category><![CDATA[Hudson]]></category>
		<category><![CDATA[kiln]]></category>
		<category><![CDATA[Mercurial]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/?p=396</guid>
		<description><![CDATA[At work we use Hudson Continuous Integration for our build servers because, among other reasons: It&#8217;s FREE! It runs on Windows (for our C# builds) and on Mac/Linux (for our iOS/Android builds). It has a web-based GUI that is MUCH &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2011/07/backing-up-hudson-with-hudson/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>At work we use Hudson Continuous Integration for our build servers because, among other reasons:</p>
<ul>
<li>It&#8217;s <strong>FREE!</strong></li>
<li>It runs on Windows (for our C# builds) and on Mac/Linux (for our iOS/Android builds).</li>
<li>It has a web-based GUI that is MUCH easier to use than the XML-driven config used by <a href="http://sourceforge.net/projects/ccnet/">CruiseControl.NET</a>, which we used before switching to Hudson.</li>
<li>It has a rich system of plugins for adding functionality.</li>
<li>Did I mention it&#8217;s <strong>FREE?</strong></li>
</ul>
<p>The one nice thing about CruiseControl.NET was that because it had one complex XML configuration file, I would only edit that file in source control so that I could back out my changes if I screwed it up.  Now I need a way to back up the Hudson configuration files so that if one of my build servers goes up in flames, I can get my team back in business quickly.</p>
<p>A good backup solution needs to be automatic and offsite, and due to the magic of distributed version control and the inherent job execution nature of Hudson, we can back up Hudson <i>with Hudson</i>.  If this isn&#8217;t the ultimate in universe folding in on itself awesome, I don&#8217;t know what is.</p>
<p><span id="more-396"></span></p>
<p>To get your own backup started:</p>
<ol>
<li>Get an offsite <a href="http://hginit.com/">Mercurial</a> repository that you can push to over the Internet using the credentials that your Hudson server runs under. We use (and I heavily recommend) <a href="http://www.fogcreek.com/kiln/">Kiln</a> from <a href="http://www.fogcreek.com/">Fog Creek Software</a>, which combines hosted Mercurial source control with killer code review tools and a host of other power tools, such as a code search facility that I now could not live without.  Check it out &#8211; it will change your life.</li>
<li>Make sure you have Hudson&#8217;s Mercurial plugin installed.</li>
<li>Create a new Hudson project named &#8220;Hudson Backup&#8221; or something similar.</li>
<li>Under Source Code Management, enter your Mercurial repository URL.  Unlike a normal project where you pull code from a repository and then build it, we will instead be pushing backups to this repository, but defining it here identifies it as Hudson&#8217;s workspace.</li>
<li>Under Build Triggers, check &#8220;Build Periodically&#8221; and in the Schedule textbox, enter &#8220;@hourly&#8221;.  This will run the backup job every hour, but will only push new changes to your Mercurial repository when there are changes (when you&#8217;ve modified something in the Web UI).</li>
<li>Under Build, add a new build step of type &#8220;Execute Windows batch command&#8221;.  The script follows:</li>
</ol>
<pre class="brush: plain; title: ; notranslate">
:: Copy the main Hudson config file
copy &quot;%BASE%\config.xml&quot; &quot;%WORKSPACE%\config.xml&quot;

:: Copy each job's config file
for /f &quot;delims=|&quot; %%f in ('dir /b %BASE%\jobs') do (
mkdir &quot;%WORKSPACE%\jobs\%%f&quot;
copy &quot;%BASE%\jobs\%%f\config.xml&quot; &quot;%WORKSPACE%\jobs\%%f\config.xml&quot; /Y
)

:: If there are changes, commit them to local repository then push
hg commit --addremove --message &quot;Backing up changes to Hudson configuration.&quot;
hg push
</pre>
<p>Now, the job will run every hour, and if there are any changes, they will be committed and pushed to your remote Mercurial repository.  <i>Even your backup job is backed up by your backup job!</i></p>
<p>If you were to ever lose your build server, you could set up a new Hudson server, clone your Mercurial repository locally, copy the backup config files to your Hudson directory, and then execute the Reload Configuration from Disk command in Hudson, and you&#8217;re back in business.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/0Wlx03hiGnE" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2011/07/backing-up-hudson-with-hudson/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2011/07/backing-up-hudson-with-hudson/</feedburner:origLink></item>
		<item>
		<title>Adventures in Screen Scraping with YQL</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/gO8Ud_EbtSU/</link>
		<comments>http://www.make-awesome.com/2011/07/adventures-in-screen-scraping-with-yql/#comments</comments>
		<pubDate>Wed, 06 Jul 2011 01:33:26 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Google Maps API]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[mapping]]></category>
		<category><![CDATA[screen scraping]]></category>
		<category><![CDATA[YQL]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/?p=371</guid>
		<description><![CDATA[When coding for work, everything of course has to be done the Right Way&#174;. This isn&#8217;t always super exciting, so it is sometimes liberating to cut loose and work on a side project that mashes together a whole bunch of &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2011/07/adventures-in-screen-scraping-with-yql/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>When coding for work, everything of course has to be done the Right Way&reg;.  This isn&#8217;t always super exciting, so it is sometimes liberating to cut loose and work on a side project that mashes together a whole bunch of technologies without worrying too much about stability, reliability, scalability, <i>or even if it will continue to run tomorrow.</i>  These R&#038;D projects will never have even a single line of code directly pushed into even a development repository, but more often than not I find that I take concepts learned and tested during these coding sessions and apply them in some later project.  Even if the entire project is thrown away in relatively short order, some concept of value survives for the long haul.</p>
<p>Plus, it&#8217;s just fun.</p>
<p>Recently my wife and I got the very exciting (and scary!) news that we were pregnant with our first child.  The little guy or girl&#8217;s arrival is still over 5 months away, but already we&#8217;re wrestling with tons of difficult questions, and one particularly overwhelming one is &#8220;How are we going to decide where to send our child for day care?&#8221;</p>
<p>We live in the great state of Minnesota where the Department of Human Services maintains a searchable <a href="http://licensinglookup.dhs.state.mn.us/">Licensing Info Lookup</a> website for all sorts of things, including (but not limited to) family child care.  Anyone with a child care license can be found here, along with address, phone number, if they can accept newborn infants and how many, etc.</p>
<p>Just one problem.  We live on the border of two big suburbs, so you do a search for both cities and together you get over 150 results, and <strong>no map</strong>.</p>
<p>This is where my inner geek starts to get excited.  I&#8217;ve got a copy of Visual Studio.  I can fix this problem.  Let&#8217;s do it.</p>
<p><span id="more-371"></span></p>
<h3>Screen Scraping with YQL</h3>
<p>Of course it&#8217;s not possible for a web page to directly access data from a different domain, but using a proxy capable of JSONP, it&#8217;s possible to grab data from any website, format it into JSON, and then surround it with a callback parameter so that you can inject it as a script tag into your document&#8217;s HEAD, where it will execute your callback function by passing the data inside.  If you&#8217;re not familiar with this, check out <a href="http://en.wikipedia.org/wiki/JSONP">Wikipedia&#8217;s JSONP page</a>.</p>
<p>It turns out that <a href="http://developer.yahoo.com/yql/">YQL (Yahoo! Query Language)</a> can serve as the perfect proxy in this situation.  In a real-life project, I would be hesitant to rely on YQL as Yahoo could begin to reject an application for high traffic, or just pull the plug on YQL altogether.  But for a low traffic site (hit by only myself and my wife) it&#8217;s a perfect match.</p>
<p>First you need to analyze the page you wish to scrape.  On each of the two search result pages (one for each city) I wished to scrape, the HTML content for each search result looked something like this, where I&#8217;ve replaced any real content with {Placeholders}.</p>
<pre class="brush: xml; title: ; notranslate">
&lt;table  border=&quot;0&quot; summary=&quot;&quot; class=&quot;LicTable1&quot;&gt;
	&lt;tr&gt;
		&lt;td width=&quot;70%&quot; class=&quot;LicTitle1&quot;&gt;&lt;a href=&quot;Details.aspx?l={ResultID}&quot; class=&quot;resultsNote&quot;&gt;{ResultName}&lt;/a&gt;&lt;/td&gt;
		&lt;td  width=&quot;30%&quot; class=&quot;LicStatus1&quot;&gt;Active&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;

&lt;table  border=&quot;0&quot; summary=&quot;&quot; class=&quot;LicTable&quot;&gt;
	&lt;tr&gt;
		&lt;td width=&quot;30%&quot; class=&quot;LicContentL&quot;&gt;{StreetAddress}&lt;br&gt;{CityStateZip}&lt;br&gt;
		{PhoneNumber}&lt;br&gt;
		{CountyName}&lt;/td&gt;
		&lt;td width=&quot;70%&quot;  class=&quot;LicContentR&quot;&gt;License number: {LicenseNumber}
		&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Type of service: Family Child Care&lt;/td&gt;
	&lt;/tr&gt;
	&lt;!-- SECTION : Optional Row --&gt;
&lt;/table&gt;

&lt;img height=&quot;10&quot; src=&quot;Images/blank.gif&quot; alt=&quot;&quot; /&gt;
</pre>
<p>Really, Minnesota?  Table-driven design?  Ever heard of semantic markup?  But I digress.  As much as I detest this markup (I would instantly reject it if I saw it in a code review from my own developers) it has enough detail that I can work with this.</p>
<p>The YQL expression goes like this:</p>
<pre class="brush: sql; title: ; notranslate">
select * from html
where url = &quot;{Url}&quot;
	and xpath='//table[@class=&quot;LicTable1&quot; or @class=&quot;LicTable&quot;]'
</pre>
<p>YQL can fetch the original HTML, perform an xpath to locate any table node with the class name &#8220;LicTable1&#8243; or &#8220;LicTable&#8221; and then return those results.  To try it for yourself, head over to the <a href="http://licensinglookup.dhs.state.mn.us/">Minnesota Department of Human Services Licensing Lookup</a>, perform any search you like, drop the search results URL into the format above, and then drop that into the <a href="http://developer.yahoo.com/yql/console/">YQL Console</a>.  If it doesn&#8217;t work anymore, it&#8217;s probably because the MN-DHS changed the markup on the website.  That&#8217;s what you get when you try to screen scrape &#8211; nobody is under any obligation to adhere to any sort of contract in their HTML markup.</p>
<p>You&#8217;ll find that YQL can return its results in XML or in JSON, where in JSON the HTML markup is converted into JSON objects.  If you implement the callback function (the YQL Console uses &#8220;cbfunc&#8221; by default, I&#8217;m using $.parseData) you can parse through the HTML structure shown above like this:</p>
<pre class="brush: jscript; title: ; notranslate">
var data = { List: [] };

$.parseData = function (d) {
	var cur = null;
	$(d.query.results.table).each(function (i, tbl) {
		if (tbl.class == &quot;LicTable1&quot;) {
			cur = {
				name: tbl.tr.td[0].a.content,
				href: &quot;http://licensinglookup.dhs.state.mn.us/&quot; + tbl.tr.td[0].a.href,
				enabled: true
			};
		}
		else if (tbl.class == &quot;LicTable&quot; &amp;&amp; cur != null) {
			var lines = tbl.tr.td[0].p.content.split('\n');
			cur.address1 = $.trim(lines[0]);
			cur.address2 = $.trim(lines[1]);
			cur.phone = $.trim(lines[2]);
			data.List.push(cur);
			cur = null;
		}
	});
};
</pre>
<p>Now I&#8217;ve got all my data added to a JSON data model on the client side.  With this in hand, it became pretty straightforward to:</p>
<ul>
<li>Transfer the data to a server-side data model with an ASP.NET Script Service.</li>
<li>Persist the data to a flat file with XML Serialization.</li>
<li>Geocode the addresses to latitude/longitude pairs with the <a href="http://code.google.com/apis/maps/documentation/javascript/">Google Maps API</a>.</li>
<li>Display a pin for each location on a map, along with a detail pane, where clicking on the pin or the left-side summary would highlight the other.</li>
<li>Add a textarea to the left-side summary so that we can take down notes when we call each daycare location, then save that data server-side as well.</li>
</ul>
<p>The possibilities for extension are endless, but with this level of sophistication, my wife and I were able to pick out several home day cares that are located conveniently close to the route of our commute, and start with that list when making our calls.</p>
<p>Of course as it turns out, we are grossly ahead of schedule, and most calls resulted in being told we were calling way too early.</p>
<p>But as a software developer, I&#8217;ll never object to being called ahead of schedule.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/gO8Ud_EbtSU" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2011/07/adventures-in-screen-scraping-with-yql/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2011/07/adventures-in-screen-scraping-with-yql/</feedburner:origLink></item>
	</channel>
</rss>

