<?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>Fri, 16 Mar 2012 20:17:54 +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>Configuring Hudson/Jenkins to parse MSBuild output</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/P8NjjZeqT-I/</link>
		<comments>http://www.make-awesome.com/2012/03/configuring-hudson-to-parse-msbuild-output/#comments</comments>
		<pubDate>Wed, 14 Mar 2012 13:00:00 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[continuous integration]]></category>
		<category><![CDATA[Hudson]]></category>
		<category><![CDATA[Jenkins]]></category>
		<category><![CDATA[regular expressions]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/?p=460</guid>
		<description><![CDATA[When I started with continuous integration, I used CruiseControl.NET, but the XML configuration quickly got to be really tedious. It didn’t take me long to switch to Hudson CI.&#160; It’s free, and it’s easily managed through an intuitive web interface. &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2012/03/configuring-hudson-to-parse-msbuild-output/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>When I started with continuous integration, I used <a href="http://www.cruisecontrolnet.org/" target="_blank">CruiseControl.NET</a>, but the XML configuration quickly got to be really tedious.</p>
<p>It didn’t take me long to switch to <a href="http://hudson-ci.org/" target="_blank">Hudson CI</a>.&nbsp; It’s free, and it’s easily managed through an intuitive web interface. Not that I can’t grok XML configuration files; I’d just rather be spending my time building things instead of figuring out how to get them built.</p>
<p>For a primer on how to use Hudson CI for .NET projects, take a look at <a href="http://blog.bobcravens.com/2010/03/getting-started-with-ci-using-hudson-for-your-net-projects/" target="_blank">Getting Started With CI Using Hudson For Your .NET Projects</a> by Bob Cravens, or <a href="http://www.refactor.co.za/2009/12/04/continuous-integration-with-hudson-and-net/" target="_blank">Continuous Integration with Hudson and .NET</a></p>
<p>The only problem is Hudson is Java-based, so it’s not exactly plug-n-play when it comes to .NET, MSBuild, and Visual Studio. Particularly, when a build failed, I would have to scan through hundreds of lines looking for the cause of the error, which is difficult to do with CTRL-F when this line is completely normal:</p>
<pre>Compile complete -- 0 errors, 0 warnings</pre>
<pre>&nbsp;</pre>
<p>What is needed is the <a href="https://wiki.jenkins-ci.org/display/JENKINS/Log+Parser+Plugin" target="_blank">Log Parser Plugin</a> (link includes formatting rules), which allows you to define regular expressions that determine how to interpret the lines of content in the MSBuild output, so that warnings and errors can be called out in different colors the same way that Visual Studio does. This plugin can be installed through Hudson’s built-in plugin manager, but you have to configure it yourself.</p>
<p><span id="more-460"></span>
<p>First, you need to create a text file on the server that hosts Hudson. If you have a cluster of servers, <a href="http://stackoverflow.com/questions/4285701/how-to-fail-a-hudson-job-if-a-certain-string-occurs-in-console-output#4304608" target="_blank">this must be done on the master server</a>.</p>
<p>Then, define the rules for the Log Parser Plugin to use. For this, it is helpful to have a reference of </p>
<p><a href="http://blogs.msdn.com/b/msbuild/archive/2006/11/03/msbuild-visual-studio-aware-error-messages-and-message-formats.aspx" target="_blank">MSBuild / Visual Studio aware error messages and message formats</a> handy.</p>
<p>Here are the rules that I came up with, which I saved to C:\hudson\VSParsingRules.txt:</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:b7df0763-e08d-4c0f-a6a2-2f1c30dc48da" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: plain; pad-line-numbers: true; title: ; notranslate">
# Divide into sections based on project compile start
start /^------/

# Compiler Error
error /(?i)error [A-Z]+[0-9]+:/

# Compiler Warning
warning /(?i)warning [A-Z]+[0-9]+:/
</pre>
</pre>
</div>
<p>It’s really not complex.&nbsp; A line starting with 5 dashes defines the start of a section where MSBuild starts building a project. Then there are rules to define an error and a warning.</p>
<p>Once this file is in place, go to Manage Hudson &gt; Configure System and enter the path to the rules file under Console Output Parsing. Then, in the configuration for each project, under “Post-build Actions”, check “Console output (build log) parsing” and select the appropriate rules from the drop-down.</p>
<p>Once the next build is finished, you will see a new “Parsed Console Output” link in the left-menu on each build.</p>
<p>One last tip: you will want to disable the Automatic Refresh setting (in the top-right corner of your browser window) while viewing parsed log output so that your screen doesn’t flicker and refresh every few seconds, causing you to lose your place.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/P8NjjZeqT-I" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2012/03/configuring-hudson-to-parse-msbuild-output/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2012/03/configuring-hudson-to-parse-msbuild-output/</feedburner:origLink></item>
		<item>
		<title>Loading external resources without killing ASP.NET</title>
		<link>http://feedproxy.google.com/~r/MakeAwesome/~3/kV4MqJhamAA/</link>
		<comments>http://www.make-awesome.com/2012/03/loading-external-resources-without-killing-asp-net/#comments</comments>
		<pubDate>Mon, 12 Mar 2012 13:00:00 +0000</pubDate>
		<dc:creator>David Boike</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[external requests]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[Mustache]]></category>
		<category><![CDATA[NServiceBus]]></category>
		<category><![CDATA[SignalR]]></category>
		<category><![CDATA[source code]]></category>

		<guid isPermaLink="false">http://www.make-awesome.com/?p=447</guid>
		<description><![CDATA[If you run an ASP.NET website with any non-trivial amount of traffic, the last thing you want to do is try to load an external resource with a web request. Even if you cache it, you&#8217;ve taken on that external &#8230;<p class="read-more"><a href="http://www.make-awesome.com/2012/03/loading-external-resources-without-killing-asp-net/">Read more &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<!-- google_ad_section_start -->
<p>If you run an ASP.NET website with any non-trivial amount of traffic, the last thing you want to do is try to load an external resource with a web request. Even if you cache it, you&#8217;ve taken on that external source as a dependency to your system, which means that your website is only as reliable as the external source you depend on.</p>
<p>The primary strategy I&#8217;ve seen to cope with this is to request the data from the remote service on the first request from the web tier, cache it locally, and then have a background process periodically refresh that cache so that subsequent requests can use locally cached data.</p>
<p>Even with this background-updated cache, eventually the external resource will suffer some sort of issue. The data in the cache grows old and is invalidated, and then the web tier starts requesting the data again, except now, 100 different requests try to open 100 different connections to the remote service to get the exact same data, because either they all fail or none of them can complete quickly enough to store the cache item for the other threads to use. The Thread Pool fills up, and ASP.NET hangs its head and gives up.</p>
<h2>The Solution</h2>
<p>Let&#8217;s assume that we are talking about a current weather tease that is a small part of a very large page, and that all our current weather information for the end-user&#8217;s zip code comes to us from an external provider.</p>
<p>So how do we avoid this mess? We can do it quite handily with a little <a href="http://www.nservicebus.com">NServiceBus</a> and <a href="http://jquery.com/">jQuery</a>.</p>
<p><span id="more-447"></span></p>
<p>Do not even try to access the data during the standard page load. Instead, ship markup to the client that displays either an empty div or perhaps an AJAX loading spinner. Generate one <a href="http://ajaxload.info/">here</a>, <a href="http://cssload.net/">here</a>, <a href="http://loadinfo.net/">here</a>, or a variety of other places if, like me, you just don&#8217;t do graphics. </p>
<p>Use jQuery to request the weather data from a web service on your own server, to be returned as JSON. This request should only look for locally cached data sources. If none are found, it should return a JSON response that says &quot;Sorry, I don&#8217;t have anything for you now. Please try back later.&quot; At the same time, the web page will use NServiceBus to send a DownloadRemoteWeatherDataCmd to a background service.</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:d4509bc5-a69c-4154-9be6-034d7fe4b4c4" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; pad-line-numbers: true; title: ; notranslate">
public ActionResult GetCurrentConditions(string zip)
{
	var conditions = CurrentConditions.GetFromLocalSourcesOnly(zip);

	if (conditions == null)
	{
		// Really we should wrap conditions with a container that we
		// keep in cache, even if the value is null, where we can
		// keep track of how often we send this command, so that we
		// don't flood our back-end service with requests.
		Bus.Send(new DownloadRemoteWeatherDataCmd { ZipCode = zip });
	}

	return Json(new {
		hasData = conditions != null,
		conditions = conditions
	}, JsonRequestBehavior.AllowGet);
}
</pre>
</pre>
</div>
<p>While your client side has set a timeout to request the data again after a few seconds, the background process has received the DownloadRemoteWeatherDataCmd. Using a <a href="http://www.nservicebus.com/Sagas.aspx">Saga</a>, the endpoint manages duplicate requests (from multiple servers in the same server farm, for example) and makes sure that for each unique data request, only one request to the external resource is being made.</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:8e8f62e5-7575-4645-9e9e-a2cd80bbefe4" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: delphi; title: ; notranslate">
public class CurrentWeatherSagaData : ISagaEntity
{
	public string ZipCode { get; set; }
	public DateTime DoNotDownloadAgainUntil { get; set; }

	// Required for NServiceBus Sagas - don't touch!
	public Guid Id { get; set; }
	public string OriginalMessageId { get; set; }
	public string Originator { get; set; }
}

public class RemoteDataAccessSaga : Saga&amp;amp;amp;lt;CurrentWeatherSagaData&amp;amp;amp;gt;,
	IAmStartedByMessages&amp;amp;amp;lt;DownloadRemoteWeatherDataCmd&amp;amp;amp;gt;
{
	public override void ConfigureHowToFindSaga()
	{
		this.ConfigureMapping&amp;amp;amp;lt;DownloadRemoteWeatherDataCmd&amp;amp;amp;gt;(
			data =&amp;amp;amp;gt; data.ZipCode,
			cmd =&amp;amp;amp;gt; cmd.ZipCode);
	}

	public void Handle(DownloadRemoteWeatherDataCmd cmd)
	{
		this.Data.ZipCode = cmd.ZipCode;

		if (DateTime.UtcNow &amp;amp;amp;gt; this.Data.DoNotDownloadAgainUntil)
		{
			Bus.Send(new RateLimitedDownloadRemoteWeatherDataCmd {
				ZipCode = cmd.ZipCode
			});
			Data.DoNotDownloadAgainUntil = DateTime.UtcNow.AddMinutes(10);
		}
	}
}
</pre>
</pre>
</div>
<p>When the external request completes, the background endpoint stores the data locally (probably in a database) and publishes an INewWeatherDataAvailable.</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:3407c2ea-644d-42e4-b071-2e3fd6d587e8" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; title: ; notranslate">
public class DataDownloader : IHandleMessages&amp;amp;amp;lt;RateLimitedDownloadRemoteWeatherDataCmd&amp;amp;amp;gt;
{
	public IBus Bus { get; set; }

	public void Handle(RateLimitedDownloadRemoteWeatherDataCmd cmd)
	{
		// Download the data from the weather provider

		// Then, insert it into the local database

		// Then, announce it to everyone who cares
		Bus.Publish&amp;amp;amp;lt;INewWeatherDataAvailable&amp;amp;amp;gt;(e =&amp;amp;amp;gt; { e.ZipCode = cmd.ZipCode; });
	}
}
</pre>
</pre>
</div>
<p>The web tier subscribes to INewWeatherDataAvailable, and drops any cache items it might be holding. The next time the client side hits the web service, it will go back to the database and deliver the newly arrived data. </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:7db4d415-a961-4790-8c43-6013cc4a4c46" class="wlWriterEditableSmartContent">
<pre style=white-space:normal>
<pre class="brush: csharp; title: ; notranslate">
public class CurrentConditionsCacheManager : IHandleMessages&amp;amp;amp;lt;INewWeatherDataAvailable&amp;amp;amp;gt;
{
	public void Handle(INewWeatherDataAvailable evt)
	{
		string zipCode = evt.ZipCode;

		// Now, drop any cache items related to this zip code
	}
}
</pre>
</pre>
</div>
<p>&#160;</p>
<p>In the event of a downtime on the remote resource, compensating actions can be taken on the client side, depending upon business requirements. For example, the spinner could continue to spin indefinitely, or perhaps after 30 seconds, the spinner could be replaced by an apology message that the data couldn&#8217;t be loaded right now. As disappointing as that might be, it&#8217;s certainly better than an unresponsive web server or Error 503 Server Not Available. </p>
<h2>Extra Credit</h2>
<p>This is a solid approach, but there are other things we can do to make it even better.</p>
<h3>Server/Client Templating</h3>
<p>A valid criticism of this approach is that any content delivered via JSON cannot be indexed by search engines. In order to get around this, you can use a templating language that works on the server and the client.&#160; <a href="http://mustache.github.com/">Mustache </a>is just such a language, and is available for <a href="https://github.com/janl/mustache.js">JavaScript</a> as well as <a href="https://github.com/jdiamond/Nustache">.NET</a>.</p>
<p>On the server side, if data is available, render the data to HTML so that Google can index it. If it is not, fall back on the AJAX loader image and include the template so that it can be populated the exact same way on the client.</p>
<h3>Async Signaling</h3>
<p>Once the INewWeatherDataAvailable arrives at the web server, why wait for the client to check back for the update when we can push the data straight to the client? SignalR provides us with the ability to push data directly from the server to the client when it arrives.</p>
<!-- google_ad_section_end -->
<img src="http://feeds.feedburner.com/~r/MakeAwesome/~4/kV4MqJhamAA" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.make-awesome.com/2012/03/loading-external-resources-without-killing-asp-net/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://www.make-awesome.com/2012/03/loading-external-resources-without-killing-asp-net/</feedburner:origLink></item>
		<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>3</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>3</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>
	</channel>
</rss>

