<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;DUEFR34_cCp7ImA9WhVbEU8.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168</id><updated>2012-05-27T16:06:56.048+02:00</updated><category term="Visual Studio" /><category term="Twitter" /><category term="LOL" /><category term="Microsoft" /><category term="MVC" /><category term="javascript" /><category term="SQL" /><category term="Hack" /><category term="AJAX" /><category term="Windows" /><category term="Bookreview" /><category term="Interview" /><category term="ASP.NET MVC" /><category term="ASP.NET" /><category term="Reporting" /><category term="Browsers" /><category term="Games" /><category term="RSS" /><category term="Nerddinner" /><category term="General" /><category term="Opensource" /><category term="Resources" /><category term="Travel" /><category term="ARealDeveloper" /><category term="Ramblings" /><category term="Google Adsense" /><category term="CodeSnippets" /><category term="database" /><category term="HTML5" /><category term="E-book" /><category term="jQuery" /><category term="WebMatrix" /><category term="CSS" /><category term="Techdays" /><category term="Video review" /><category term="GridView" /><category term="PostBroadcaster" /><category term="Windows Services" /><category term="VISUG" /><category term="Tips" /><category term="Best Practices" /><category term="Refactoring" /><category term="industry" /><category term="Blogging" /><category term="teched" /><category term="Telerik" /><category term="Active Directory" /><category term="Linq" /><category term="Tools" /><category term="Hardware" /><category term="Hacking" /><category term="Event" /><category term="Bing API" /><category term=".NET" /><category term="Silverlight" /><title>Jef Claes</title><subtitle type="html">On software and life</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.jefclaes.be/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.jefclaes.be/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>235</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/DiaryOfAnetDeveloperByJefClaes" /><feedburner:info uri="diaryofanetdeveloperbyjefclaes" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:emailServiceId>DiaryOfAnetDeveloperByJefClaes</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><entry gd:etag="W/&quot;D0ANQng7fyp7ImA9WhVbEU8.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-1209675754519828376</id><published>2012-05-27T15:36:00.000+02:00</published><updated>2012-05-27T15:36:33.607+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-27T15:36:33.607+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ramblings" /><title>The open plan fallacy testimonials</title><content type="html">I wrote an article titled '&lt;a href="http://www.jefclaes.be/2012/05/open-plan-fallacy.html"&gt;The open plan fallacy&lt;/a&gt;' just two weeks ago. Earlier this week &lt;a href="http://www.nytimes.com/2012/05/20/science/when-buzz-at-your-cubicle-is-too-loud-for-work.html?_r=3&amp;amp;pagewanted=all"&gt;a similar article&lt;/a&gt; was published by the New York Times. The content of that article wasn't particularly extraordinary, but the comments were. I waded through all of them on my daily commute, and it's really hard to find one in favor of open plan offices - people seem to be enraged.&lt;br /&gt;
&lt;br /&gt;
I handpicked some of the most interesting ones.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
Research by Emberson et.al. (Psychological Science, 2010) demonstrate both the impairment in performance for people forced to listen to half a phone conversation and the neurological impossibility of "tuning out" these overheard conversations. The office experience has been described as trying to drive a car full of people all talking on their cell phones.&lt;/blockquote&gt;
&lt;blockquote class="tr_bq"&gt;
It's preferable to have to make the effort to collaborate than the effort to focus on one's work amidst a distracting open workplace. My profession, architecture, does require a high degree of collaboration, but also a need for intense, individual concentration working on complicated 3D computer models and details.. and long hours being in "the zone". My firm provides private offices for all of its staff and a variety of collaborative and recreational environments.. particularly the latter, recognizing that just by hanging out together for part of the day, individuals from different project teams and pursuits "cross-pollinate", come up with imaginative ideas, influence and inspire eachother.&lt;/blockquote&gt;
&lt;blockquote class="tr_bq"&gt;
Cubicles were invented as a way to shove more people into a smaller space and save money for corporations. The entire meme about "improved workplace communications" was invented by consultants as a smokescreen. Period. End of story.&amp;nbsp;&lt;/blockquote&gt;
&lt;blockquote class="tr_bq"&gt;
"For example, someone whose job requires intense concentration (e.g. computer programmer) needs absolute silence. Programmers who do not get this silence are likely to make mistakes."&lt;br /&gt;
I've been around a lot of programmers, among lots of other kinds of workers, and I'd say that 90% of the developers I saw were in a large room with others all around them. It's actually one of the least likely jobs to provide someone with an office, despite the fact that your diagnosis about what the job requires is absolutely right, in my opinion.&lt;br /&gt;
All it takes is a few months in the world of corporations to understand that Dilbert is really a documentary.&lt;/blockquote&gt;
&lt;blockquote class="tr_bq"&gt;
I currently work in an open plan office and absolutely hate it. Not having an office with a door that others need to knock on before disturbing me has led to non-stop disturbances all day long. Not to mention having to listen to nonstop chatter of those around me. It is an incredibly inefficient - and I may add, unprofessional - way to work. Any money saved on rent is surely made up for in lost productivity.&lt;/blockquote&gt;
&lt;blockquote class="tr_bq"&gt;
I've noticed that the person who decides on the open cube layout usually sits in an office.&lt;/blockquote&gt;
&lt;blockquote class="tr_bq"&gt;
Having participated in the design of office environments, where we used low cubicle walls or even no walls to support certain kinds of collaborative work, with the full involvement of the employees in that design, it was always distressing to see the 'open office' faddishly embraced by management everywhere, regardless of actual practices required for the work. Consultancies, as usual, led the way in yet another blind embrace of 'innovation'.&lt;br /&gt;
Why would individual 'entrepreneurs' - to take one example from this story - want or need to be able to see and hear each other whilst working? Are the activities in which they are engaged intimately linked, are the tasks often (and necessarily) performed conjointly, is their own working division of labour a concerted one? What do these entrepreneurs themselves think about such matters? (Well, as the story makes plain, they have very clear answers, voting with their headphones!) Anyway, these are the kinds of very practical questions about work activities and worker needs that should drive office design.&lt;/blockquote&gt;
In my article, I advocated isolating teams instead of individuals, but most commenters seem to be heavy supporters of private offices - including walls and a door. I never experienced that in a professional environment, so I couldn't say if that would work for software development. Can you? Collaboration and communication between developers is a necessity. Fostering that just seems hard when everyone is in a separate place - definitely for young teams. While comparing office layouts, I wondered how my proposed solution would scale; how many people can you put in one team before you encounter the same undesired open plan side-effects?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-1209675754519828376?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/2lUa4JuyOaOoVo_sQ-iaZMg0NZI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/2lUa4JuyOaOoVo_sQ-iaZMg0NZI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/2lUa4JuyOaOoVo_sQ-iaZMg0NZI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/2lUa4JuyOaOoVo_sQ-iaZMg0NZI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/k_p8PVdDPhA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/1209675754519828376/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/05/open-plan-fallacy-testimonials.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/1209675754519828376?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/1209675754519828376?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/k_p8PVdDPhA/open-plan-fallacy-testimonials.html" title="The open plan fallacy testimonials" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/05/open-plan-fallacy-testimonials.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUYFRX49cSp7ImA9WhVUFU8.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-5283065963535596196</id><published>2012-05-20T17:18:00.000+02:00</published><updated>2012-05-20T17:18:34.069+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-20T17:18:34.069+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><category scheme="http://www.blogger.com/atom/ns#" term="CodeSnippets" /><category scheme="http://www.blogger.com/atom/ns#" term="database" /><title>Painless database logging with mongoDB</title><content type="html">While browsing the source code of the&lt;a href="https://github.com/CaptainCodeman/elmah-mongodb"&gt; ELMAH mongoDB provider&lt;/a&gt;, I learned about a special type of collections: capped collections.&lt;br /&gt;
&lt;br /&gt;
From the &lt;a href="http://www.mongodb.org/display/DOCS/Capped+Collections"&gt;mongoDB documentation&lt;/a&gt;:&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
Capped collections are fixed sized collections that have a very high performance auto-FIFO age-out feature (age out is based on insertion order). In addition, capped collections automatically, with high performance, maintain insertion order for the documents in the collection; this is very powerful for certain use cases such as logging.&lt;/blockquote&gt;
This is such a killer feature. Logging to the database can be extremely useful, but also rather expensive. Using this feature, you can turn on database logging without too many worries.&lt;br /&gt;
&lt;br /&gt;
Insertion into a capped collection is ridiculously fast. To get an idea of how fast it really is, I did some measuring on my own humble machine. I managed to insert 10.000 small documents in &lt;i&gt;less than 3.7 seconds&lt;/i&gt;. The headaches of tweaking buffer sizes and rolling asynchronous appenders seem to be miles away.&lt;br /&gt;
&lt;br /&gt;
Something which religiously gets ignored until shit hits the fan, is log table maintenance. With a capped collection there is no need to set up a database job that periodically cleans the logging table. You just set a fixed size, and you're done. No more middle-of-the-night support calls when the logging table is eating up all the disk space.&lt;br /&gt;
&lt;br /&gt;
Creating a capped collection with the C# driver can look like this.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;var&lt;/span&gt; server = MongoServer.Create(&lt;span class="str"&gt;"mongodb://localhost/"&lt;/span&gt;);
&lt;span class="kwrd"&gt;var&lt;/span&gt; db = server.GetDatabase(&lt;span class="str"&gt;"PlayGround"&lt;/span&gt;);

&lt;span class="kwrd"&gt;var&lt;/span&gt; options = CollectionOptions
    .SetCapped(&lt;span class="kwrd"&gt;true&lt;/span&gt;)
    .SetMaxSize(5000)
    .SetMaxDocuments(100);

&lt;span class="kwrd"&gt;if&lt;/span&gt; (!db.CollectionExists(&lt;span class="str"&gt;"Log"&lt;/span&gt;))
    db.CreateCollection(&lt;span class="str"&gt;"Log"&lt;/span&gt;, options);&lt;/pre&gt;
Now that's easy sailing.&lt;br /&gt;
&lt;br /&gt;
Expect to find me posting more on mongoDB in the coming months, I'm starting to really like this little database engine.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-5283065963535596196?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/MuVk_KvnvdwlJMZ1O1fw-6WHiio/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/MuVk_KvnvdwlJMZ1O1fw-6WHiio/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/MuVk_KvnvdwlJMZ1O1fw-6WHiio/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/MuVk_KvnvdwlJMZ1O1fw-6WHiio/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/jPLkWvq4C18" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/5283065963535596196/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/05/painless-database-logging-with-mongodb.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/5283065963535596196?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/5283065963535596196?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/jPLkWvq4C18/painless-database-logging-with-mongodb.html" title="Painless database logging with mongoDB" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/05/painless-database-logging-with-mongodb.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8GQXY8fSp7ImA9WhVVGUw.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-6158041392623434660</id><published>2012-05-13T16:20:00.000+02:00</published><updated>2012-05-13T16:20:20.875+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-13T16:20:20.875+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ramblings" /><title>The open plan fallacy</title><content type="html">I haven't worked in a whole lot of places, somewhere around four, but every single one of them used an &lt;a href="http://en.wikipedia.org/wiki/Open_plan"&gt;open plan&lt;/a&gt; to structure their workplace. From what I hear from others, it's the standard.&lt;br /&gt;
&lt;br /&gt;
There are a few things to say about the advantages of an open office layout. They should stimulate communication, create more opportunities for observing and learning from others and be more cost-effective. I'm afraid it's the latter which is the biggest driver though.&lt;br /&gt;
&lt;br /&gt;
In reality open plans really aren't all that great. The noise alone has proven to reduce productivity by &lt;i&gt;one third&lt;/i&gt;. When I look around, I see plenty of signs that people have a hard time getting their job done: programmers buying $500 noise-cancelling headphones in an attempt to keep the environmental noise out, project managers camping in free meeting rooms trying to focus on their number wizardry or even whole teams occupying a meeting room days before a release.&lt;br /&gt;
&lt;br /&gt;
What bothers me the most is overhearing other teams, those distractions amount to nothing at all. What's the value of me overhearing a discussion on some obscure Sharepoint problem? None. What are the benefits of listening to your team's Friday after lunch bullshitting? Nothing. Team dynamics differ, that's normal, but they shouldn't disturb others.&lt;br /&gt;
&lt;br /&gt;
The extreme alternative would be private offices, but I'm pretty confident that's far from perfect as well.&lt;br /&gt;
&lt;br /&gt;
What I suggest, is installing each team in their own fully isolated area, free from distractions caused by other teams. This would still do right to all the advantages of an open plan, while taking away some of its biggest bottlenecks. By installing each team in their own cocoon, you create an environment where overhearing conversations on the next feature is an added value, where you can quickly short-circuit discussions on an architectural decision or where you might even concentrate on a problem for 45 minutes straight. The only advantage which is partially gone is cost-effectiveness; there needs to be some flexible infrastructure in place, that makes it easy to swiftly adapt the environment when team compositions change.&lt;br /&gt;
&lt;br /&gt;
Instead of always focusing on that short term budget win, we need to start paying attention to the big picture again: offices should enable teams, not sabotage them.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Is there anybody who genuinely loves open plan offices? If not, what alternative would you prefer?&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-6158041392623434660?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/wRPKp9p_wVdKwcW08AQjZ2c4G5Q/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/wRPKp9p_wVdKwcW08AQjZ2c4G5Q/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/wRPKp9p_wVdKwcW08AQjZ2c4G5Q/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/wRPKp9p_wVdKwcW08AQjZ2c4G5Q/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/KIBDF9x_V7E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/6158041392623434660/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/05/open-plan-fallacy.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/6158041392623434660?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/6158041392623434660?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/KIBDF9x_V7E/open-plan-fallacy.html" title="The open plan fallacy" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>5</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/05/open-plan-fallacy.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08HSX0-fCp7ImA9WhVVE0w.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-3758848113211671465</id><published>2012-05-06T16:50:00.000+02:00</published><updated>2012-05-06T16:50:38.354+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-06T16:50:38.354+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="RSS" /><category scheme="http://www.blogger.com/atom/ns#" term="Browsers" /><category scheme="http://www.blogger.com/atom/ns#" term="Ramblings" /><title>Why I will always love RSS</title><content type="html">There has been a lot of noise in the tech community earlier this year about how RSS is&amp;nbsp;supposedly&amp;nbsp;having one foot in the grave. If that would be even remotely true, I hope it dies with its boots on. The herald would be browsers and social networking sites killing or hiding support for RSS. While that may be true, their motives shouldn't rig our opinions.&lt;br /&gt;
&lt;br /&gt;
RSS has never worked out for the regular consumer, not directly anyways. So I get why browsers are dropping support for it, I am not even disappointed. Most popular social networks have enough traction by now so that they can safely start fencing their gardens with the purpose of bringing more money in. Also reasonable.&lt;br /&gt;
&lt;br /&gt;
What startles me is when peers start advertising the death of RSS. The explanations I hear are somewhere along the lines of "Why use RSS? I count on the people I follow on Twitter to share links to good information", or similarly "I just check Hacker News a few times a day to read up on the latest news". I strongly believe the contemporary fetish of liking and sharing cheapens the way we consume our information. Don't get me wrong, I do see value in community driven content, but there's also a lot of dirt and sensationalism. Some days it feels like I'm reading the front page of a cheap tabloid.&lt;br /&gt;
&lt;br /&gt;
I used to subscribe to a bunch of feeds, but today I'm pickier; I subscribe to authors who share things I care about and who inspire me in some way. By subscribing to their feed, I'm not missing a single update. This forces me to absorb it all, even though most of their content doesn't go viral. This liberates me from feeling the urge to be connected all the friggin' time, plus more importantly, there is a gold mine of wisdom to be found in the non-controversial content out there.&lt;br /&gt;
&lt;br /&gt;
I love RSS. It just works, and enables me to learn so much, without having to be connected constantly or having to rely on others to tell me what to consume.&lt;br /&gt;
&lt;br /&gt;
Subscribe to my RSS feed &lt;a href="http://feeds.feedburner.com/DiaryOfAnetDeveloperByJefClaes"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-3758848113211671465?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/FIT2Bxe_q2dIOVINrHqc7SwKPB0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/FIT2Bxe_q2dIOVINrHqc7SwKPB0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/FIT2Bxe_q2dIOVINrHqc7SwKPB0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/FIT2Bxe_q2dIOVINrHqc7SwKPB0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/9zJOpmnvPjk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/3758848113211671465/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/05/why-i-will-always-love-rss.html#comment-form" title="24 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3758848113211671465?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3758848113211671465?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/9zJOpmnvPjk/why-i-will-always-love-rss.html" title="Why I will always love RSS" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>24</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/05/why-i-will-always-love-rss.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYNR3oyeSp7ImA9WhVWGUU.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-3201250658927704870</id><published>2012-05-02T20:09:00.002+02:00</published><updated>2012-05-02T20:09:56.491+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-02T20:09:56.491+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="HTML5" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET MVC" /><title>My InfoQ article on HTML5 offline web applications</title><content type="html">After writing &lt;a href="http://www.jefclaes.be/2012/04/visualizing-offline-application-cache.html"&gt;a&lt;/a&gt; &lt;a href="http://www.jefclaes.be/2012/03/html5-offline-web-applications-as.html"&gt;few&lt;/a&gt; &lt;a href="http://www.jefclaes.be/2012/03/how-web-application-can-download-and.html"&gt;things&lt;/a&gt; on HTML5 offline web applications earlier this year, I got contacted by &lt;a href="http://www.infoq.com/"&gt;InfoQ&lt;/a&gt; to write an in-detail article on the subject for them. I hesitated at first, because I was afraid that it would feel too much like work, taking the fun out of my writing. Turns out it wasn't like that at all. The guys at infoQ were really relaxed to work with, asking interesting questions and giving useful feedback, without forcing me into a certain direction. A pleasant experience.&lt;br /&gt;&lt;br /&gt;You can &lt;a href="http://www.infoq.com/articles/Offline-Web-Apps"&gt;find the article here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-3201250658927704870?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Xzn5wRaafqtD7CXSAALYxhao5LY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Xzn5wRaafqtD7CXSAALYxhao5LY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Xzn5wRaafqtD7CXSAALYxhao5LY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Xzn5wRaafqtD7CXSAALYxhao5LY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/abLE6X_iQ_s" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/3201250658927704870/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/05/my-infoq-article-on-html5-offline-web.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3201250658927704870?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3201250658927704870?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/abLE6X_iQ_s/my-infoq-article-on-html5-offline-web.html" title="My InfoQ article on HTML5 offline web applications" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/05/my-infoq-article-on-html5-offline-web.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUEGRX06fyp7ImA9WhVXFkU.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-3934766696040323677</id><published>2012-04-17T20:33:00.000+02:00</published><updated>2012-04-17T20:33:44.317+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-17T20:33:44.317+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Refactoring" /><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><category scheme="http://www.blogger.com/atom/ns#" term="CodeSnippets" /><category scheme="http://www.blogger.com/atom/ns#" term="Ramblings" /><title>Some Servicelocator pattern stinks</title><content type="html">I have been working on a somewhat legacy codebase which makes use of the &lt;a href="http://martinfowler.com/articles/injection.html#UsingAServiceLocator"&gt;Servicelocator pattern&lt;/a&gt;. Although I always thought of &lt;a href="http://martinfowler.com/articles/injection.html#FormsOfDependencyInjection"&gt;Dependecy Injection&lt;/a&gt; to be the superior pattern, I was pleased to find some &lt;a href="http://martinfowler.com/articles/injection.html#InversionOfControl"&gt;Inversion of Control&lt;/a&gt; implementation in there. Working with the codebase, I discovered first hand how easily, when used without caution and discipline, the Servicelocator pattern can introduce code rot.

&lt;br /&gt;
&lt;br /&gt;
I will walk you through some of the issues I have with the Servicelocator pattern, mostly looking at it from a test perspective. It's interesting how you can often quickly discover friction in a codebase by just looking at, or writing, tests.&lt;br /&gt;
&lt;br /&gt;
The first thing that rubs me the wrong way is that by using the Servicelocator pattern, you make each class dependant on the servicelocator, degrading the purity of the models. Although this is a conceptual consideration, it considerably affects development.&lt;br /&gt;
&lt;br /&gt;
What I really like about Dependency Injection is that you can look at the constructor (or properties) from a class and instantly know its dependencies. The Servicelocator pattern makes it too easy to hide them.&lt;br /&gt;
&lt;br /&gt;
To demonstrate some of the beef I have with the Servicelocator pattern, I wrote a FireStation class which has one public method Alert. I chose an example which is not the de facto OrderService or ShoppingBasket implementation, but which still is small enough to grasp easily.&lt;br /&gt;
&lt;br /&gt;
So let's have a look at this FireStation class. I know it's a ridiculously naïve implementation, but good enough as an example.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; FireStation
{
    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Alert(Incident incident)
    {
        SendPagerMessage();
        SendEmail();

        &lt;span class="kwrd"&gt;if&lt;/span&gt; (incident.Priority == IncidentPriority.High)            
            ActivateSiren();                        
    }

    &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; SendPagerMessage()
    {
        ServiceLocator.Current.GetInstance&amp;lt;IPagingTerminal&amp;gt;().SendMessage();
    }

    &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; SendEmail()
    {
        ServiceLocator.Current.GetInstance&amp;lt;IMailServer&amp;gt;().SendMailMessage();
    }

    &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; ActivateSiren()
    {
        ServiceLocator.Current.GetInstance&amp;lt;IImmoticaServer&amp;gt;().ActivateSiren();
    }
}&lt;/pre&gt;
There is one public Alert method, which takes an Incident instance, and uses three alarmation channels to alert firemen: paging, email and a siren.&lt;br /&gt;
&lt;br /&gt;
So, let's imagine I wanted to test whether the siren is activated when there is a high priority incident. I would just start writing the test, to ultimately find out I have no idea what to assert.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;[TestMethod()]        
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Alert_activates_the_siren_when_the_priority_is_high()
{
    &lt;span class="kwrd"&gt;var&lt;/span&gt; fireStation = &lt;span class="kwrd"&gt;new&lt;/span&gt; FireStation();

    fireStation.Alert(&lt;span class="kwrd"&gt;new&lt;/span&gt; Incident(IncidentPriority.High));

    Assert.Inconclusive(&lt;span class="str"&gt;"How can I verify the siren is activated?"&lt;/span&gt;);
}&lt;/pre&gt;
Looking at the collapsed method definitions, I still don't know. There is no constructor, so the dependencies aren't resolved in there. Nothing left to do but inspect the Alert method content, and look at the ActiviateSiren method implementation. That's where I eventually find out I need to mock the IImmoticaServer interface to assert the interaction.&lt;br /&gt;
&lt;br /&gt;
So I do that, by creating the mock, setting up the container and setting it as the provider for my servicelocator.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;[TestMethod()]    
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Alert_activates_the_siren_when_the_priority_is_high()
{            
    &lt;span class="kwrd"&gt;var&lt;/span&gt; immoticaServer = &lt;span class="kwrd"&gt;new&lt;/span&gt; Mock&amp;lt;IImmoticaServer&amp;gt;();

    &lt;span class="kwrd"&gt;var&lt;/span&gt; kernel = &lt;span class="kwrd"&gt;new&lt;/span&gt; StandardKernel();
    kernel.Bind&amp;lt;IImmoticaServer&amp;gt;().ToConstant(immoticaServer.Object);
    
    ServiceLocator.SetLocatorProvider(() =&amp;gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; NinjectServiceLocator(kernel));    

    &lt;span class="kwrd"&gt;new&lt;/span&gt; FireStation().Alert(&lt;span class="kwrd"&gt;new&lt;/span&gt; Incident(IncidentPriority.High));

    immoticaServer.Verify(i =&amp;gt; i.ActivateSiren(), Times.Once());
}
&lt;/pre&gt;
Now I'm polluting my test with responsibilities it shouldn't really care about. I could probably move that into some infrastructure or the test setup, but still, I'm writing code that could be easily averted. &lt;br /&gt;&lt;br /&gt;Anyways, I should now be able to verify the interaction with the IImotticaServer implementation. Boom, red test. You probably figured this one out already, but if I hadn't expanded the private methods, you would have had no idea that I needed two extra stubs to make the test pass; one for the IPagingTerminal dependency and one for the IMailServer dependency.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;kernel.Bind&amp;lt;IPagingTerminal&amp;gt;().ToConstant(&lt;span class="kwrd"&gt;new&lt;/span&gt; Mock&amp;lt;IPagingTerminal&amp;gt;().Object);
kernel.Bind&amp;lt;IMailServer&amp;gt;().ToConstant(&lt;span class="kwrd"&gt;new&lt;/span&gt; Mock&amp;lt;IMailServer&amp;gt;().Object);&lt;/pre&gt;
After binding these two dependencies the test finally turns up green.&lt;br /&gt;&lt;br /&gt;This development experience was horrible. The Servicelocator pattern fails at making it easy for me to discover dependencies, leading to an interrupted test flow. Another harmful side-effect is that it also becomes harder for me to read code and understand the high level interactions between objects, without getting knees deep into the implementation details.&lt;br /&gt;&lt;br /&gt;By hiding your dependencies, you also promote ignorance to a problematic amount of dependencies. I discovered three in this example, but as this class grows, more and more of them will be buried inside the method implementations.&lt;br /&gt;&lt;br /&gt;To make it easier to avoid and spot these smells earlier on, without dropping the Servicelocator pattern as a whole, you could move service resolution to the constructor. This isn't a completely safe refactoring though. It's possible that some instantiations are expensive, and should be implemented to be lazy initialized.&lt;br /&gt;&lt;br /&gt;There is one more annoyance I would like to spout in this post. The Servicelocator pattern doesn't encourage good OO. I'm not an OO purist, but seeing dozens of static classes resolving their dependencies through a servicelocator sends shivers down my spine. I would rather pick an Inversion of Control technique which basically makes it impossible to do this.&lt;br /&gt;&lt;br /&gt;This post turned out to be longer than I expected it to be, and I even left some annoyances uncovered. I hope I succeeded in explaining my beef with the Servicelocator pattern. I trust that the general consensus will soon dictate that this pattern easily leads to marginal code, and that it actually is an anti-pattern which should be avoided if possible.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;What is your experience with the Servicelocator pattern?&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-3934766696040323677?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/T8QfQupc9t0I0b8jQoUvezFbuEs/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/T8QfQupc9t0I0b8jQoUvezFbuEs/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/T8QfQupc9t0I0b8jQoUvezFbuEs/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/T8QfQupc9t0I0b8jQoUvezFbuEs/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/Iu2WGqcz2j0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/3934766696040323677/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/04/some-servicelocator-pattern-stinks.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3934766696040323677?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3934766696040323677?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/Iu2WGqcz2j0/some-servicelocator-pattern-stinks.html" title="Some Servicelocator pattern stinks" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/04/some-servicelocator-pattern-stinks.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0IBR3g_eSp7ImA9WhVXEUg.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-4221269094875840731</id><published>2012-04-11T16:27:00.001+02:00</published><updated>2012-04-11T16:45:56.641+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-11T16:45:56.641+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="HTML5" /><category scheme="http://www.blogger.com/atom/ns#" term="Browsers" /><title>Visualizing the offline application cache update progress</title><content type="html">I wrote about using the HTML5 application cache &lt;a href="http://jclaes.blogspot.com/2012/03/html5-offline-web-applications-as.html"&gt;earlier&lt;/a&gt;, mostly&amp;nbsp;focusing on generating and serving the manifest file using ASP.NET MVC. I also bitched about how &lt;a href="http://jclaes.blogspot.com/2012/03/how-web-application-can-download-and.html"&gt;not one browser I know of gives an indication of the application cache update progress&lt;/a&gt;. Today I wanted to write something about how you can visualize the application cache update progress yourself.&lt;br /&gt;
&lt;br /&gt;
The applicationCache API has several useful and rather straightforward events we can handle to inform the user of the update progress.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;window.applicationCache.onchecking = function (e) {
    updateCacheStatus(&lt;span class="str"&gt;'Checking for a new version of the application.'&lt;/span&gt;);
};
window.applicationCache.ondownloading = function (e) {
    updateCacheStatus(&lt;span class="str"&gt;'Downloading a new offline version of the application'&lt;/span&gt;);
};
window.applicationCache.oncached = function (e) {
    updateCacheStatus(&lt;span class="str"&gt;'The application is available offline.'&lt;/span&gt;);
};
window.applicationCache.onerror = function (e) {
    updateCacheStatus(&lt;span class="str"&gt;'Something went wrong while updating the offline version 
                        of the application. It will not be available offline.'&lt;/span&gt;);
};
window.applicationCache.onupdateready = function (e) {
    window.applicationCache.swapCache();
    updateCacheStatus(&lt;span class="str"&gt;'The application was updated. Refresh for the changes to take place.'&lt;/span&gt;);
};
window.applicationCache.onnoupdate = function (e) {
    updateCacheStatus(&lt;span class="str"&gt;'The application is also available offline.'&lt;/span&gt;);
};
window.applicationCache.onobsolete = function (e) {
    updateCacheStatus(&lt;span class="str"&gt;'The application can not be updated, no manifest file was found.'&lt;/span&gt;);
};&lt;/pre&gt;
One event that is particularly helpful is the progress event. This event fires every time a resource is downloaded and contains three useful attributes we can use to display the download progress: lengthComputable, loaded and total. These attributes should be fairly self-descriptive, but here is the relevant snippet of &lt;a href="http://www.w3.org/TR/2011/WD-html5-20110525/offline.html"&gt;the W3C specificiations&lt;/a&gt;.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
For each cache host associated with an application cache in cache group, queue a post-load task to fire an event with the name progress, which does not bubble, which is cancelable, and which uses the ProgressEvent interface, at the ApplicationCache singleton of the cache host. The lengthComputable attribute must be set to true, the total attribute must be set to the number of files in file list, and the loaded attribute must be set to the number of number of files in file list that have been either downloaded or skipped so far. The default action of these events must be, if the user agent shows caching progress, the display of some sort of user interface indicating to the user that a file is being downloaded in preparation for updating the application.&amp;nbsp;&lt;/blockquote&gt;
To have a text that updates the percentage of downloaded resources on every download, I came up with this.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;window.applicationCache.onprogress = function (e) {               
    &lt;span class="kwrd"&gt;var&lt;/span&gt; message = &lt;span class="str"&gt;'Downloading offline resources.. '&lt;/span&gt;;

    &lt;span class="kwrd"&gt;if&lt;/span&gt; (e.lengthComputable) {
        updateCacheStatus(message + Math.round(e.loaded / e.total * 100) + &lt;span class="str"&gt;'%'&lt;/span&gt;);
    } &lt;span class="kwrd"&gt;else&lt;/span&gt; {
        updateCacheStatus(message);
    };
};&lt;span style="font-family: 'Times New Roman';"&gt;&lt;span style="white-space: normal;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
These attributes seem to be implemented in WebKit browsers, but not in Firefox. Firefox will fall back to the static message 'Downloading offline resources..'. Internet Explorer doesn't support the offline application cache as a whole.&lt;br /&gt;
&lt;br /&gt;
I'm sure more creative souls have it in them to build a really elegant and visually pleasing progress indication using this technique. I'm curious to hear (or &lt;b&gt;see&lt;/b&gt;!) how you would represent the update process.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-4221269094875840731?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/_QevkCjPR1IVLdTHza1Q4VRKsU0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_QevkCjPR1IVLdTHza1Q4VRKsU0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/_QevkCjPR1IVLdTHza1Q4VRKsU0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_QevkCjPR1IVLdTHza1Q4VRKsU0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/iaEGLJQ9YKg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/4221269094875840731/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/04/visualizing-offline-application-cache.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/4221269094875840731?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/4221269094875840731?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/iaEGLJQ9YKg/visualizing-offline-application-cache.html" title="Visualizing the offline application cache update progress" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/04/visualizing-offline-application-cache.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUENSHo8eSp7ImA9WhVQE0o.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-6881355528047497190</id><published>2012-04-02T15:34:00.000+02:00</published><updated>2012-04-02T15:34:59.471+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-02T15:34:59.471+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="Browsers" /><title>Check for local file browsing with JavaScript</title><content type="html">Because I do most of my research while commuting by train, I often pull entire websites offline using &lt;a href="http://www.httrack.com/"&gt;httrack&lt;/a&gt;. While browsing the &lt;a href="http://jquerymobile.com/demos/1.1.0-rc.1/"&gt;jQuery Mobile documentation&lt;/a&gt; locally this morning, I stumbled upon following gem.
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-WM9mJB8bMwA/T3mcWHlmlNI/AAAAAAAABRA/jbiH2vtVmd8/s1600/jQueryMobileLocal.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="83" src="http://2.bp.blogspot.com/-WM9mJB8bMwA/T3mcWHlmlNI/AAAAAAAABRA/jbiH2vtVmd8/s640/jQueryMobileLocal.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
I was curious to see how they determine whether a page is browsed locally or not. Looking into the source, I was a bit dissapointed to find nothing but plain common sense. The trick is comparing the protocol of the current location with known local protocols.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;if&lt;/span&gt; ( location.protocol.substr(0,4)  === &lt;span class="str"&gt;'file'&lt;/span&gt; ||
     location.protocol.substr(0,11) === &lt;span class="str"&gt;'*-extension'&lt;/span&gt; ||
     location.protocol.substr(0,6)  === &lt;span class="str"&gt;'widget'&lt;/span&gt; ) {
    &lt;span class="rem"&gt;// Disable AJAX support etc&lt;/span&gt;
}&lt;/pre&gt;
If you would want to use that check in multiple locations in your codebase, you might want to extend the &lt;a href="https://developer.mozilla.org/en/DOM/window.location"&gt;location object&lt;/a&gt; with an isLocal function.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;window.location.constructor.prototype.isLocal = function() { 
    &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;this&lt;/span&gt;.protocol.substr(0,4)  === &lt;span class="str"&gt;'file'&lt;/span&gt; || 
            &lt;span class="kwrd"&gt;this&lt;/span&gt;.protocol.substr(0,11) === &lt;span class="str"&gt;'*-extension'&lt;/span&gt; || 
            &lt;span class="kwrd"&gt;this&lt;/span&gt;.protocol.substr(0,6)  === &lt;span class="str"&gt;'widget'&lt;/span&gt;; 
}&lt;/pre&gt;
The function could be used like this.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;if&lt;/span&gt; (window.location.isLocal()) {
    &lt;span class="rem"&gt;// Disable AJAX support etc&lt;/span&gt;
}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-6881355528047497190?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Y0fF4pvWSBTaTeirtsZigXphjn0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Y0fF4pvWSBTaTeirtsZigXphjn0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Y0fF4pvWSBTaTeirtsZigXphjn0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Y0fF4pvWSBTaTeirtsZigXphjn0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/3-zEZm2K7a0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/6881355528047497190/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/04/check-for-local-file-browsing-with.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/6881355528047497190?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/6881355528047497190?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/3-zEZm2K7a0/check-for-local-file-browsing-with.html" title="Check for local file browsing with JavaScript" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-WM9mJB8bMwA/T3mcWHlmlNI/AAAAAAAABRA/jbiH2vtVmd8/s72-c/jQueryMobileLocal.PNG" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/04/check-for-local-file-browsing-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0EGRHk_eip7ImA9WhVQE00.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-8622358011752059404</id><published>2012-04-01T19:32:00.000+02:00</published><updated>2012-04-01T19:33:45.742+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-01T19:33:45.742+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Tools" /><category scheme="http://www.blogger.com/atom/ns#" term="Tips" /><title>Add images to a GitHub readme</title><content type="html">Today I wanted to add some screenshots to a GitHub readme for the sake of documenting. While this wasn't particularly hard, I do had to iterate a few times before I got it right.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Hosting the images&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;You could simply add the images to your repository and reference them using the raw url's, but this isn't very efficient. Using this method, every request needs to go through GitHub's application layer. It's far better to make use of &lt;a href="http://pages.github.com/"&gt;GitHub Pages&lt;/a&gt;, a feature purely designed to publish web content. I also like how you're not polluting the repository this way.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Referencing the images&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;I'm using&amp;nbsp;the&amp;nbsp;- oh so beautiful and simple - &lt;a href="http://daringfireball.net/projects/markdown/syntax"&gt;markdown format&lt;/a&gt; for most of my readme's. The syntax for embedding images looks like this.&lt;br /&gt;
&lt;br /&gt;
![My image](username.github.com/repository/img/image.jpg)&lt;br /&gt;
&lt;br /&gt;
I hope this post fills in the void I stumbled upon when googling for useful pointers earlier today.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-8622358011752059404?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/O5Vkv8Y3fQYrHmMevw7TargBSeY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/O5Vkv8Y3fQYrHmMevw7TargBSeY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/O5Vkv8Y3fQYrHmMevw7TargBSeY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/O5Vkv8Y3fQYrHmMevw7TargBSeY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/Um1GRmOVvgU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/8622358011752059404/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/04/add-images-to-github-readme.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/8622358011752059404?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/8622358011752059404?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/Um1GRmOVvgU/add-images-to-github-readme.html" title="Add images to a GitHub readme" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/04/add-images-to-github-readme.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4EQX8_eip7ImA9WhVRFkQ.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-3802405913099774949</id><published>2012-03-25T13:11:00.000+02:00</published><updated>2012-03-25T18:28:20.142+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-25T18:28:20.142+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="HTML5" /><category scheme="http://www.blogger.com/atom/ns#" term="Browsers" /><title>How a web application can download and store over 2GB without you even knowing it</title><content type="html">I have been experimenting with the &lt;a href="http://www.w3.org/TR/html5/offline.html"&gt;HTML5 offline application cache&lt;/a&gt; some more over the last few days, doing boundary tests in an attempt to learn more about browser behaviour in edge cases.&lt;br /&gt;
&lt;br /&gt;
One of these experiments was testing the cache quota.&lt;br /&gt;
&lt;br /&gt;
Two weeks ago, I blogged about &lt;a href="http://jclaes.blogspot.com/2012/03/html5-offline-web-applications-as.html"&gt;generating and serving an offline application manifest using ASP.NET MVC&lt;/a&gt;. I reused that code to add hundreds of 7MB PDF files to the cache.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; ActionResult Manifest()
{     
    &lt;span class="kwrd"&gt;var&lt;/span&gt; cacheResources = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt;();
    &lt;span class="kwrd"&gt;var&lt;/span&gt; n = 300; &lt;span class="rem"&gt;// Play with this number&lt;/span&gt;

    &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;var&lt;/span&gt; i = 0; i &amp;lt; n; i++)
        cacheResources.Add(&lt;span class="str"&gt;"Content/"&lt;/span&gt; + Url.Content(&lt;span class="str"&gt;"book.pdf?"&lt;/span&gt; + i));

    &lt;span class="kwrd"&gt;var&lt;/span&gt; manifestResult = &lt;span class="kwrd"&gt;new&lt;/span&gt; ManifestResult(&lt;span class="str"&gt;"1"&lt;/span&gt;)
    {
        NetworkResources = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt;[] { &lt;span class="str"&gt;"*"&lt;/span&gt; },
        CacheResources = cacheResources
    };

    &lt;span class="kwrd"&gt;return&lt;/span&gt; manifestResult;
}&lt;span style="font-family: 'Times New Roman';"&gt;&lt;span style="white-space: normal;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
I initially tried adding 1000 PDF files to the cache, but this threw an error: Chrome failed to commit the new cache to the storage, because the quota would be exceeded.&lt;br /&gt;
&lt;br /&gt;
After lowering the number of files several times, I hit the sweet spot. I could add 300 PDF files to the cache without breaking it.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Looking into &lt;i&gt;chrome://appcache-internals/&lt;/i&gt;, I can see the size of the cache being a whopping &lt;b&gt;2.2GB&lt;/b&gt; now for one single web application.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-0Ktx0DtzqRI/T27rKCIB3dI/AAAAAAAABQE/nJZqHiBoMvw/s1600/appcache-internals.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://2.bp.blogspot.com/-0Ktx0DtzqRI/T27rKCIB3dI/AAAAAAAABQE/nJZqHiBoMvw/s400/appcache-internals.PNG" width="330" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
As a user, I had no idea that the website I'm browsing is downloading a suspicious amount of data in the background. Chrome (17.0.963.83), nor any other desktop browser that I know of, warns me. I would expect the browser to ask for my permission when a website wants to download and store such an excessive amount of data on my machine.&lt;br /&gt;
&lt;br /&gt;
Something else I noticed, is that other sites now fail to commit anything to the application cache due to the browser-wide quota being exceeded. I'm pretty sure this 'first browsed, first reserved' approach will be a source of frustration in the future.&lt;br /&gt;
To handle this scenario we could use the applicationCache API to listen for quota errors, and inform the user to browse to &lt;i&gt;chrome://appcache-internals/&lt;/i&gt; and remove other caches in favor of the new one. This feels sketchy though; shouldn't the browser intervene in a more elegant way here?&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-hh-FTCiP658/T27rnNRPGUI/AAAAAAAABQM/ux5lYqLfI40/s1600/exceedquote.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="137" src="http://3.bp.blogspot.com/-hh-FTCiP658/T27rnNRPGUI/AAAAAAAABQM/ux5lYqLfI40/s400/exceedquote.PNG" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;b&gt;What are your thoughts? What would you want your browser to do in these scenarios?&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-3802405913099774949?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/qvHlTq-KP_iSbhZ-MC8QEQJrG4w/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qvHlTq-KP_iSbhZ-MC8QEQJrG4w/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/qvHlTq-KP_iSbhZ-MC8QEQJrG4w/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qvHlTq-KP_iSbhZ-MC8QEQJrG4w/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/TufJ-I2fLhM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/3802405913099774949/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/03/how-web-application-can-download-and.html#comment-form" title="45 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3802405913099774949?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3802405913099774949?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/TufJ-I2fLhM/how-web-application-can-download-and.html" title="How a web application can download and store over 2GB without you even knowing it" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-0Ktx0DtzqRI/T27rKCIB3dI/AAAAAAAABQE/nJZqHiBoMvw/s72-c/appcache-internals.PNG" height="72" width="72" /><thr:total>45</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/03/how-web-application-can-download-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04NR38-fyp7ImA9WhVREE0.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-6430172329219876199</id><published>2012-03-17T19:46:00.000+01:00</published><updated>2012-03-17T19:46:36.157+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-17T19:46:36.157+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ramblings" /><title>Sent from my phone</title><content type="html">According to &lt;a href="http://37signals.com/svn/posts/2500-lets-be-honest-sent-from-my"&gt;Matt from 37signals&lt;/a&gt; the line "Sent from my iPhone" at the bottom of an email means this:&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
Let’s be honest. “Sent from my iPhone” really means “I’m not going to bother to proofread and correct this because it would take me an extra 30 seconds.”&lt;/blockquote&gt;
I agree. I too use this line as an excuse to write a terse message and omit proper salutations.&lt;br /&gt;
&lt;br /&gt;
However, I also think these four simple words greatly helped the viral growth of the mobile phone. Having early adopters brag about how they're sending emails from their fancy new phone must have been an invaluable form of word-of-mouth advertising.&lt;br /&gt;
&lt;br /&gt;
I think it's safe to say that those four words first are a marketing technique and then a convenient way of letting your correspondents know that there might be a typo in your message. If not, the default line wouldn't include the brand name.&lt;br /&gt;
&lt;br /&gt;
I'm turning this thing into a rambling though. What I really wanted to share is a snippet of the &lt;i&gt;much recommended&lt;/i&gt;&amp;nbsp;book &lt;a href="http://www.amazon.com/gp/product/0307887898/ref=as_li_tf_tl?ie=UTF8&amp;amp;tag=diofanedebyje-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0307887898%22"&gt;The Lean Startup&lt;/a&gt;, where &lt;a href="http://www.startuplessonslearned.com/"&gt;Eric Ries&lt;/a&gt; shares a story on how Hotmail used a similar technique in 1996 to fuel their viral engine of growth.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
But everything changed when they made one small tweak to the product. They added to the bottom of every e-mail the message "P.S. Get your free e-mail at Hotmail" along with a clickable link.&lt;br /&gt;
Within weeks, that small product change produced massive results. Within six months, Bhatia and Smith had signed up more than 1 million new customers. Five weeks later, they hit the 2 million mark. Eighteen months after launching the service, with 12 million subscribers, they sold the company to Microsoft for $400 million.&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;
I find it incredibly intriguing how a few words can have such an impact. Maybe you do too.&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-6430172329219876199?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/kUk42eiIo_S7x8xKHbtpBEYzRKg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/kUk42eiIo_S7x8xKHbtpBEYzRKg/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/kUk42eiIo_S7x8xKHbtpBEYzRKg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/kUk42eiIo_S7x8xKHbtpBEYzRKg/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/IdZUGd7G00o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/6430172329219876199/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/03/sent-from-my-phone.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/6430172329219876199?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/6430172329219876199?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/IdZUGd7G00o/sent-from-my-phone.html" title="Sent from my phone" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/03/sent-from-my-phone.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D04ARHs5fip7ImA9WhVSF04.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-4949242923755502734</id><published>2012-03-14T15:39:00.000+01:00</published><updated>2012-03-14T15:39:05.526+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-14T15:39:05.526+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="HTML5" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET MVC" /><title>HTML5 Offline Web applications as an afterthought in ASP.NET MVC</title><content type="html">Recently I prototyped a mobile web application using ASP.NET MVC, &lt;a href="http://jquerymobile.com/"&gt;jQuery Mobile&lt;/a&gt; and some HTML5 features. One of the key goals was to find out how far you can push a web 'application' until the browser starts getting in the way. Working disconnected is one of these things that appear to be a major showstopper at first.&lt;br /&gt;
&lt;br /&gt;
However - to my surprise honestly - the &lt;a href="http://dev.w3.org/html5/spec/offline.html"&gt;HTML5 Offline Web applications API&lt;/a&gt; seems to be &lt;a href="http://caniuse.com/#search=offline"&gt;widely implemented&lt;/a&gt; across modern browsers already.&amp;nbsp;&lt;a href="https://picasaweb.google.com/lh/photo/lVnWvGLepGsFhIc2Z4SKdcdrvCRMWyke7-RbgGXMEiI?feat=directlink"&gt;Not of all of them though&lt;/a&gt;. Looking into the specifics, the API itself is fairly straightforward. At his core, you will find the manifest file, which dictates which files should be cached by the browser. The API provides other useful events and methods for inspecting the status of the cache and swapping the cache for a newer version, but they are out of scope today. A useful resource to read up on the full API can be found &lt;a href="http://dev.opera.com/articles/view/offline-applications-html5-appcache/"&gt;here&lt;/a&gt;, and a working example implementation can be found &lt;a href="http://html5demos.com/offlineapp"&gt;here&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The manifest file&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Back to the manifest file. A manifest file could look like this.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-F5Pd3Ss5aQQ/T1-l_0ZT7kI/AAAAAAAABPE/srprjIgP8pc/s1600/ManifestFile.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="257" src="http://2.bp.blogspot.com/-F5Pd3Ss5aQQ/T1-l_0ZT7kI/AAAAAAAABPE/srprjIgP8pc/s400/ManifestFile.PNG" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
The first line in the file should say CACHE MANIFEST. If you want to write comments, you should prefix the lines with a number sign.&lt;br /&gt;
&lt;br /&gt;
In the CACHE section you declare which files should be cached. An important and interesting note is that these files will be served from the cache, even if you're online.&lt;br /&gt;
&lt;br /&gt;
In the NETWORK section you declare which files the browser should try to download from the server, regardless of whether the user is online or offline.&lt;br /&gt;
&lt;br /&gt;
In the last section, the FALLBACK section, you can define fallback resources to be used when the user is offline.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Serving and generating the manifest file&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Now that we got all this theory out of the way, let's look at generating and serving the manifest file using ASP.NET MVC.&lt;br /&gt;
&lt;br /&gt;
I started by adding a ResourcesController with one action named Manifest.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ResourcesController : Controller
{             
    &lt;span class="kwrd"&gt;public&lt;/span&gt; ActionResult Manifest() { }
}
&lt;/pre&gt;
&lt;/div&gt;
This action should serve a text file, using a specific cache-manifest MIME type. To accommodate this I created a new action result, which inherits from the &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.mvc.fileresult.aspx"&gt;FileResult&lt;/a&gt; class, and overwrites the content type.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ManifestResult : FileResult
{
    &lt;span class="kwrd"&gt;public&lt;/span&gt; ManifestResult(&lt;span class="kwrd"&gt;string&lt;/span&gt; version)
        : &lt;span class="kwrd"&gt;base&lt;/span&gt;(&lt;span class="str"&gt;"text/cache-manifest"&lt;/span&gt;) { }    
}&lt;/pre&gt;
I also made this same class (for the sake of example) responsible for formatting and writing the manifest file to the output stream. That's why I added a few extra properties to the manifest result, one for each section and one for versioning. Versioning the file comes in handy when you want to expire the cache, because it only expires when the manifest file changes.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ManifestResult : FileResult
{
    &lt;span class="kwrd"&gt;public&lt;/span&gt; ManifestResult(&lt;span class="kwrd"&gt;string&lt;/span&gt; version)
        : &lt;span class="kwrd"&gt;base&lt;/span&gt;(&lt;span class="str"&gt;"text/cache-manifest"&lt;/span&gt;)
    {
        CacheResources = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt;();
        NetworkResources = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt;();
        FallbackResources = &lt;span class="kwrd"&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, &lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt;();
        Version = version;
    }

    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt; Version { &lt;span class="kwrd"&gt;get&lt;/span&gt;; &lt;span class="kwrd"&gt;set&lt;/span&gt;; }

    &lt;span class="kwrd"&gt;public&lt;/span&gt; IEnumerable&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt; CacheResources { &lt;span class="kwrd"&gt;get&lt;/span&gt;; &lt;span class="kwrd"&gt;set&lt;/span&gt;; }

    &lt;span class="kwrd"&gt;public&lt;/span&gt; IEnumerable&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt; NetworkResources { &lt;span class="kwrd"&gt;get&lt;/span&gt;; &lt;span class="kwrd"&gt;set&lt;/span&gt;; }       

    &lt;span class="kwrd"&gt;public&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, &lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt; FallbackResources { &lt;span class="kwrd"&gt;get&lt;/span&gt;; &lt;span class="kwrd"&gt;set&lt;/span&gt;; }        
}&lt;span style="font-family: 'Times New Roman';"&gt;&lt;span style="white-space: normal;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
To write the file to the output stream, I had to override the WriteFile method.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; WriteFile(HttpResponseBase response)
{
    WriteManifestHeader(response);            
    WriteCacheResources(response);
    WriteNetwork(response);
    WriteFallback(response);
}

&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; WriteManifestHeader(HttpResponseBase response)
{
    response.Output.WriteLine(&lt;span class="str"&gt;"CACHE MANIFEST"&lt;/span&gt;);
    response.Output.WriteLine(&lt;span class="str"&gt;"#V"&lt;/span&gt; + Version ?? &lt;span class="kwrd"&gt;string&lt;/span&gt;.Empty);            
}

&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; WriteCacheResources(HttpResponseBase response)
{
    response.Output.WriteLine(&lt;span class="str"&gt;"CACHE:"&lt;/span&gt;);           
    &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (&lt;span class="kwrd"&gt;var&lt;/span&gt; cacheResource &lt;span class="kwrd"&gt;in&lt;/span&gt; CacheResources)
        response.Output.WriteLine(cacheResource);
}

&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; WriteNetwork(HttpResponseBase response)
{
    response.Output.WriteLine();
    response.Output.WriteLine(&lt;span class="str"&gt;"NETWORK:"&lt;/span&gt;);            
    &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (&lt;span class="kwrd"&gt;var&lt;/span&gt; networkResource &lt;span class="kwrd"&gt;in&lt;/span&gt; NetworkResources)
        response.Output.WriteLine(networkResource);
}

&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; WriteFallback(HttpResponseBase response)
{
    response.Output.WriteLine();
    response.Output.WriteLine(&lt;span class="str"&gt;"FALLBACK:"&lt;/span&gt;);
    &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (&lt;span class="kwrd"&gt;var&lt;/span&gt; fallbackResource &lt;span class="kwrd"&gt;in&lt;/span&gt; FallbackResources)
        response.Output.WriteLine(fallbackResource.Key + &lt;span class="str"&gt;" "&lt;/span&gt; + fallbackResource.Value);
}&lt;/pre&gt;
In the CACHE section I wanted to include all my static resources, meaning the contents of the Scripts and Content folder. To do this in a simple and low-maintenace fashion I introduced the GetRelativePathsToRoot method. This method takes the path of a virtual folder, recursively scans its content and returns a list of relative paths for each file.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; IEnumerable&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt; GetRelativePathsToRoot(&lt;span class="kwrd"&gt;string&lt;/span&gt; virtualPath)
{
    &lt;span class="kwrd"&gt;var&lt;/span&gt; physicalPath = Server.MapPath(virtualPath);
    &lt;span class="kwrd"&gt;var&lt;/span&gt; absolutePaths = Directory.GetFiles(physicalPath, &lt;span class="str"&gt;"*.*"&lt;/span&gt;,   SearchOption.AllDirectories);

    &lt;span class="kwrd"&gt;return&lt;/span&gt; absolutePaths.Select(
        x =&amp;gt; Url.Content(virtualPath + x.Replace(physicalPath, &lt;span class="str"&gt;""&lt;/span&gt;))
    );
}&lt;span style="font-family: 'Times New Roman';"&gt;&lt;span style="white-space: normal;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
For the Content folder, the result could look something like this.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-kT79KvErvrk/T1-mb26LjaI/AAAAAAAABPM/72SLJwKfbN4/s1600/ContentFolder.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="240" src="http://3.bp.blogspot.com/-kT79KvErvrk/T1-mb26LjaI/AAAAAAAABPM/72SLJwKfbN4/s640/ContentFolder.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
To add pages to the CACHE section, I used the Url.Action method.
&lt;br /&gt;
&lt;br /&gt;
For the NETWORK resources, I added an asterisk, which basically means that the cache shouldn't be used when the user is online. I didn't specify any fallback resources in this example.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; ActionResult Manifest()
{
    &lt;span class="kwrd"&gt;var&lt;/span&gt; pages = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt;();
    pages.Add(Url.Action(&lt;span class="str"&gt;"SomeAction"&lt;/span&gt;, &lt;span class="str"&gt;"ControllerName"&lt;/span&gt;));    

    &lt;span class="kwrd"&gt;var&lt;/span&gt; scriptsPaths = GetRelativePathsToRoot(&lt;span class="str"&gt;"~/Scripts/"&lt;/span&gt;);
    &lt;span class="kwrd"&gt;var&lt;/span&gt; contentPaths = GetRelativePathsToRoot(&lt;span class="str"&gt;"~/Content/"&lt;/span&gt;);

    &lt;span class="kwrd"&gt;var&lt;/span&gt; cacheResources = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt;();
    cacheResources.AddRange(pages);
    cacheResources.AddRange(contentPaths);
    cacheResources.AddRange(scriptsPaths);
    
    &lt;span class="kwrd"&gt;var&lt;/span&gt; manifestResult = &lt;span class="kwrd"&gt;new&lt;/span&gt; ManifestResult(&lt;span class="str"&gt;"1.0"&lt;/span&gt;)
    {
        NetworkResources = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt;[] { &lt;span class="str"&gt;"*"&lt;/span&gt; },
        CacheResources = cacheResources
    };            

    &lt;span class="kwrd"&gt;return&lt;/span&gt; manifestResult;
}&lt;/pre&gt;
&lt;b&gt;Setting up a route and including the manifest&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Now that we are able to generate and serve a manifest file, we should set up a specific route for the manifest file; some browsers aren't very forgiving and expect it to have a specific name and location: /cache.manifest.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;routes.MapRoute(&lt;span class="str"&gt;"cache.manifest"&lt;/span&gt;, &lt;span class="str"&gt;"cache.manifest"&lt;/span&gt;, &lt;span class="kwrd"&gt;new&lt;/span&gt; { controller = &lt;span class="str"&gt;"Resources"&lt;/span&gt;, action = &lt;span class="str"&gt;"Manifest"&lt;/span&gt; });&lt;/pre&gt;
The last step I had to take was include a reference to the manifest file in the html element.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;html&lt;/span&gt; &lt;span class="attr"&gt;manifest&lt;/span&gt;&lt;span class="kwrd"&gt;="@Url.RouteUrl("&lt;/span&gt;&lt;span class="attr"&gt;cache&lt;/span&gt;.&lt;span class="attr"&gt;manifest&lt;/span&gt;&lt;span class="kwrd"&gt;")"&lt;/span&gt;)&lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;b&gt;Poor man's testing&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
To verify if all of this works, you can look at the console of the Chrome developer tools. You should see something like this.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-w-cW_dijbHM/T1-n9orswUI/AAAAAAAABPU/IR_Htbo0BPY/s1600/ManifestDownloading.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="96" src="http://3.bp.blogspot.com/-w-cW_dijbHM/T1-n9orswUI/AAAAAAAABPU/IR_Htbo0BPY/s640/ManifestDownloading.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;br /&gt;&lt;/div&gt;
That console logging has proven to be extremely useful when debugging the manifest file.&lt;br /&gt;
&lt;br /&gt;
You could also just browse to the manifest file to inspect its content. Don't mind this screenshot too much, obviously there's plenty of cleaning up to do in my Scripts folder.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-3MTEom9Kurs/T1-oGpUqN7I/AAAAAAAABPc/XcudJp4lgbk/s1600/BrowsetoManifestFile.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="400" src="http://4.bp.blogspot.com/-3MTEom9Kurs/T1-oGpUqN7I/AAAAAAAABPc/XcudJp4lgbk/s400/BrowsetoManifestFile.PNG" width="373" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;b&gt;Summary&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
In this post I showed you a technique I came up with to take advantage of ASP.NET MVC to easily generate, maintain and serve an HTML5 Offline Webappliction manifest file:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Create a controller and action that can serve the file&lt;/li&gt;
&lt;li&gt;Create a new action result, which returns the correct MIME type and formats the file&lt;/li&gt;
&lt;li&gt;Set up a specific route&lt;/li&gt;
&lt;li&gt;Include a reference to the manifest in the html tag&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Remember, this is a proof of concept, it's not perfect. I look forward to any feedback you might have!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-4949242923755502734?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/5PCekgJBrIW0LO4AwAAKTgI_PBw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/5PCekgJBrIW0LO4AwAAKTgI_PBw/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/5PCekgJBrIW0LO4AwAAKTgI_PBw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/5PCekgJBrIW0LO4AwAAKTgI_PBw/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/CGlwlj0yjaM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/4949242923755502734/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/03/html5-offline-web-applications-as.html#comment-form" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/4949242923755502734?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/4949242923755502734?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/CGlwlj0yjaM/html5-offline-web-applications-as.html" title="HTML5 Offline Web applications as an afterthought in ASP.NET MVC" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-F5Pd3Ss5aQQ/T1-l_0ZT7kI/AAAAAAAABPE/srprjIgP8pc/s72-c/ManifestFile.PNG" height="72" width="72" /><thr:total>12</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/03/html5-offline-web-applications-as.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkIGQHg_cCp7ImA9WhVSFEs.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-5878821973626115371</id><published>2012-03-11T11:08:00.000+01:00</published><updated>2012-03-11T11:08:41.648+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-11T11:08:41.648+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="General" /><title>Learning: the Hacker Way</title><content type="html">I have had a fair amount of discussions on continuous learning and knowledge sharing the past few days. It became rather obvious that a lot of us have developed their own techniques, but also that maybe most of us are still in search of more efficient techniques. Having gone through several phases myself, I would like to share my current way of learning: the Hacker Way.&lt;br /&gt;
&lt;br /&gt;
Here are some snippets taken from a &lt;a href="http://seo-hacker.com/hacker/"&gt;recent letter&lt;/a&gt; from Mark Zuckerberg addressed to the Facebook shareholders.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
Hacking is an inherently hands-on and active discipline. Instead of debating for days whether a new idea is possible or what the best way to build something is, hackers would rather just prototype something and see what works. There's a hacker mantra that you'll hear a lot around Facebook offices: Code wins arguments.&lt;/blockquote&gt;
I like to believe learning should be a hands-on activity as well. Basically, stop consuming, start producing. Don't get me wrong, I do think there is value in reading blog posts (I might be slightly biased on this one), reading books and watching videos, but I find that this value is marginal compared to what you gain by actually doing it.&lt;br /&gt;
&lt;br /&gt;
I remember I wanted to step up my JavaScript game two years ago, and &lt;a href="http://jclaes.blogspot.com/2010/02/book-review-object-oriented-javascript.html"&gt;ordered a book&lt;/a&gt;. A few months after finishing the book, I finally had the chance to implement something in JavaScript, but I couldn't. I remembered some syntax, and most concepts, but I couldn't do it. Eventually I really learned JavaScript by doing it on the job, having to constantly fall back on the book and Stackoverflow, making costly mistakes along the way.&lt;br /&gt;
&lt;br /&gt;
It's not a terrible idea to pick up a new technology on the job, but it very much depends on the environment. If you're in the consultancy business, there often is no room to pick up a brand new technology on the job. Customers expect you to know what you're doing.&lt;br /&gt;
Imagine you really want to get into mobile development, and you were able to get in the race for a new mobile project. Getting interviewed by the customer, you have to admit that you haven't built something for mobile yet, but you did read a book and you find mobile development really interesting. It's really hard to sell that. If you are able to show something you made, and talk about things that work and things that don't work in the real world, the customer can get a far better feel if you would be a good fit for the project.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
The Hacker Way is an approach to building that involves continuous improvement and iteration. Hackers believe that something can always be better, and that nothing is ever complete.&amp;nbsp;&lt;/blockquote&gt;
&lt;blockquote class="tr_bq"&gt;
Hacker culture is also extremely open and meritocratic. Hackers believe that the best idea and implementation should always win - not the person who is best at lobbying for an idea or the person who manages the most people.&amp;nbsp;&lt;/blockquote&gt;
This is where it becomes interesting and where you can really make the difference as a company.&lt;br /&gt;
&lt;br /&gt;
Depending on various parameters, you often don't have the room to experiment on the day job, but if you're building something by yourself or in a small team without these constraints, you can &lt;a href="http://www.youtube.com/watch?v=QckCBwdeCJU"&gt;get wild&lt;/a&gt;: experiment, innovate and fail often. Not only do you build experience faster, but you also (maybe indirectly) challenge existing practices and build a deeper understanding of the used methodologies and technologies. Also, having the freedom to innovate, aspiring to get closer to the Silver Bullet, can bring yourself, the company and the industry to the next level.&lt;br /&gt;
&lt;br /&gt;
This is just the first step of the cycle though. The next step should be to get these little projects and acquired experiences out there. Share them with your peers and the community, educate those who want to listen and ask for feedback from those who care, hopefully fueling the inspiration of others.&lt;br /&gt;
&lt;br /&gt;
A welcome side-effect might be that the results could be used to extend your personal and your company's portfolio. This can help you prove that you can do more than just talk the walk, talk is cheap after all.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;I want to know from you what the flaws are in my way of thinking. Is the described technique naive, too idealistic or unrealistic?&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;Which technique has proven to work best for you?&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-5878821973626115371?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/O2jcF0102lzdZFU-AyhLVoixrKc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/O2jcF0102lzdZFU-AyhLVoixrKc/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/O2jcF0102lzdZFU-AyhLVoixrKc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/O2jcF0102lzdZFU-AyhLVoixrKc/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/PuK5w1p800s" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/5878821973626115371/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/03/learning-hacker-way.html#comment-form" title="13 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/5878821973626115371?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/5878821973626115371?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/PuK5w1p800s/learning-hacker-way.html" title="Learning: the Hacker Way" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>13</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/03/learning-hacker-way.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DU4DQnwzeSp7ImA9WhVTEUQ.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-3977501940000710754</id><published>2012-02-25T20:26:00.000+01:00</published><updated>2012-02-25T20:26:13.281+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-25T20:26:13.281+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MVC" /><category scheme="http://www.blogger.com/atom/ns#" term="CodeSnippets" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET MVC" /><title>ASP.NET MVC4 bundling in ASP.NET MVC3</title><content type="html">One of the new wildly evangelized features of &lt;a href="http://www.asp.net/mvc/mvc4"&gt;ASP.NET MVC4&lt;/a&gt; is the &lt;a href="http://weblogs.asp.net/scottgu/archive/2011/11/27/new-bundling-and-minification-support-asp-net-4-5-series.aspx"&gt;built-in support for bundling and minification&lt;/a&gt; of scripts and stylesheets.&lt;br /&gt;
&lt;br /&gt;
I don't see any reason why this new feature wouldn't work for ASP.NET MVC3 though. If you open the packages config of an ASP.NET MVC4 beta project, you will find that bundling support lives in the Microsoft.Web.Optimization package.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;package&lt;/span&gt; &lt;span class="attr"&gt;id&lt;/span&gt;&lt;span class="kwrd"&gt;="Microsoft.Web.Optimization"&lt;/span&gt; &lt;span class="attr"&gt;version&lt;/span&gt;&lt;span class="kwrd"&gt;="1.0.0-beta"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;&lt;/pre&gt;
So we should just be able to install &lt;a href="http://nuget.org/packages/Microsoft.Web.Optimization/0.1"&gt;this package&lt;/a&gt; for an ASP.NET MVC3 project. To install the package, run following command. Pay attention to the -Pre switch.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;PM&amp;gt; Install-Package Microsoft.Web.Optimization -Pre
Attempting to resolve dependency &lt;span class="str"&gt;'Microsoft.Web.Infrastructure (= 1.0.0)'&lt;/span&gt;.
Successfully installed &lt;span class="str"&gt;'Microsoft.Web.Infrastructure 1.0.0.0'&lt;/span&gt;.
Successfully installed &lt;span class="str"&gt;'Microsoft.Web.Optimization 1.0.0-beta'&lt;/span&gt;.
Successfully added &lt;span class="str"&gt;'Microsoft.Web.Infrastructure 1.0.0.0'&lt;/span&gt; to Optimization.
Successfully added &lt;span class="str"&gt;'Microsoft.Web.Optimization 1.0.0-beta'&lt;/span&gt; to Optimization.&lt;/pre&gt;
Adding bundles happens when the application starts, together with registering areas, adding global filters and registering routes.
&lt;br /&gt;
&lt;br /&gt;
The quickest way to enable bundling is by enabling the default bundles.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;BundleTable.Bundles.EnableDefaultBundles();&lt;/pre&gt;
This method will add two bundles to the bundle table: one bundle for the stylesheets in the Content folder and one bundle for the scripts in the Scripts folder. The default bundles try to take core scripts into account when ordering the scripts in the bundle. For example, jQuery will be included before any of its plug-ins are included. &lt;br /&gt;
&lt;br /&gt;
To reference these bundles you can add following snippet to your view or layout file.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;link&lt;/span&gt; &lt;span class="attr"&gt;href&lt;/span&gt;&lt;span class="kwrd"&gt;="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("&lt;/span&gt;~/&lt;span class="attr"&gt;Content&lt;/span&gt;/&lt;span class="attr"&gt;css&lt;/span&gt;&lt;span class="kwrd"&gt;")"&lt;/span&gt; 
    &lt;span class="attr"&gt;rel&lt;/span&gt;&lt;span class="kwrd"&gt;="stylesheet"&lt;/span&gt; &lt;span class="attr"&gt;type&lt;/span&gt;&lt;span class="kwrd"&gt;="text/css"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;script&lt;/span&gt; &lt;span class="attr"&gt;src&lt;/span&gt;&lt;span class="kwrd"&gt;="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("&lt;/span&gt;~/&lt;span class="attr"&gt;Scripts&lt;/span&gt;/&lt;span class="attr"&gt;js&lt;/span&gt;&lt;span class="kwrd"&gt;")"&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;script&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;    &lt;/pre&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
If you start your application now, and inspect the HTML, you will find two versioned links to a minified version of your CSS and JavaScript.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-geLdnio3dDg/T0kxDIad5PI/AAAAAAAABOg/XF-Au8UkQ8I/s1600/minifiedhead.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="180" src="http://4.bp.blogspot.com/-geLdnio3dDg/T0kxDIad5PI/AAAAAAAABOg/XF-Au8UkQ8I/s640/minifiedhead.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-FZ8MofszPzc/T0kxOUyyJFI/AAAAAAAABOo/t1pL7nfDkHI/s1600/minifiedJS.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="328" src="http://3.bp.blogspot.com/-FZ8MofszPzc/T0kxOUyyJFI/AAAAAAAABOo/t1pL7nfDkHI/s640/minifiedJS.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div style="text-align: left;"&gt;
If you're trying this on a new project, you will probably have no problems. However, if you're trying this on an existing project, chances are that some things are not included how they should be.&lt;br /&gt;
&lt;br /&gt;
To troubleshoot what's going wrong, you can inspect the results of the GetRegisteredBundles method.&lt;/div&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;var&lt;/span&gt; registeredBundles = BundleTable.Bundles.GetRegisteredBundles();&lt;/pre&gt;
If you need more fine-grained control, you can remove the default bundles again and add your own bundles to the bundle table.&lt;br /&gt;
&lt;br /&gt;
For example, this is how you can add a jQuery bundle.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;var&lt;/span&gt; jQueryBundle = &lt;span class="kwrd"&gt;new&lt;/span&gt; Bundle(&lt;span class="str"&gt;"~/Scripts/jquery"&lt;/span&gt;, &lt;span class="kwrd"&gt;new&lt;/span&gt; JsMinify());
jQueryBundle.AddDirectory(&lt;span class="str"&gt;"~/Scripts"&lt;/span&gt;, &lt;span class="str"&gt;"jquery*.js"&lt;/span&gt;, searchSubdirectories: &lt;span class="kwrd"&gt;false&lt;/span&gt;, throwIfNotExist: &lt;span class="kwrd"&gt;true&lt;/span&gt;);

BundleTable.Bundles.Add(jQueryBundle);&lt;/pre&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;script&lt;/span&gt; &lt;span class="attr"&gt;src&lt;/span&gt;&lt;span class="kwrd"&gt;="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("&lt;/span&gt;~/&lt;span class="attr"&gt;Scripts&lt;/span&gt;/&lt;span class="attr"&gt;jQuery&lt;/span&gt;&lt;span class="kwrd"&gt;")"&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;script&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;  &lt;/pre&gt;
When you instantiate a new bundle, specify the relative path of the bundle and pick a bundle transformation. You can add a directory to the bundle, filtered by a search pattern. You can also tell the algorithm to search in the subdirectories or to throw an exception when the directory doesn't exist.&lt;br /&gt;
&lt;br /&gt;
If you don't want to add a whole directory to the bundle, but just one or more files, you can use the AddFile method.&lt;br /&gt;
&lt;br /&gt;
For example, this is a separate bundle for modernizr.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;var&lt;/span&gt; modernizrBundle = &lt;span class="kwrd"&gt;new&lt;/span&gt; Bundle(&lt;span class="str"&gt;"~/Scripts/modernizr"&lt;/span&gt;, &lt;span class="kwrd"&gt;new&lt;/span&gt; JsMinify());
modernizrBundle.AddFile(&lt;span class="str"&gt;"~/Scripts/modernizr-1.7.js"&lt;/span&gt;, throwIfNotExist: &lt;span class="kwrd"&gt;true&lt;/span&gt;);

BundleTable.Bundles.Add(modernizrBundle);&lt;/pre&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;&lt;br/&gt;It's relatively easy to take advantage of bundling in ASP.NET MVC3. Install the NuGet package, set up the bundle table, include the references in your view or layout page and you're done.&lt;br /&gt;
&lt;br /&gt;There are some more interesting things you can do using bundling. I just started experimenting with it, so I wouldn't be surprised if I will be writing a few more things on bundling in the coming weeks.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-3977501940000710754?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/OnHcK_PmT9gxq2WXCDmlv7Gk5XA/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/OnHcK_PmT9gxq2WXCDmlv7Gk5XA/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/OnHcK_PmT9gxq2WXCDmlv7Gk5XA/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/OnHcK_PmT9gxq2WXCDmlv7Gk5XA/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/5oi4_VFpido" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/3977501940000710754/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/02/aspnet-mvc4-bundling-in-aspnet-mvc3.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3977501940000710754?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3977501940000710754?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/5oi4_VFpido/aspnet-mvc4-bundling-in-aspnet-mvc3.html" title="ASP.NET MVC4 bundling in ASP.NET MVC3" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-geLdnio3dDg/T0kxDIad5PI/AAAAAAAABOg/XF-Au8UkQ8I/s72-c/minifiedhead.PNG" height="72" width="72" /><thr:total>5</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/02/aspnet-mvc4-bundling-in-aspnet-mvc3.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUAMQHw-cCp7ImA9WhRaF08.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-1376860285625139130</id><published>2012-02-20T08:43:00.000+01:00</published><updated>2012-02-20T08:43:01.258+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-20T08:43:01.258+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="General" /><title>There's no place for monogamy in technology</title><content type="html">In this post I would like to share some of my thoughts on a &lt;a href="http://prog21.dadgum.com/128.html"&gt;recent post by James Hague&lt;/a&gt; titled 'Don't Fall in Love With Your Technology'. If you haven't read that post yet, please do, it's so short that me summarizing it here would be silly.&lt;br /&gt;&lt;br /&gt;I think there is nothing wrong with falling in love with your technology per se. If you want to have a fair relationship with your technology, you have to invest in her. Don't have a superficial relationship, take her home with you, spend some cosy Sunday afternoons together. Get to know her inside out. Know when it's fun to be with her, but also more importantly when it's not.&lt;br /&gt;&lt;br /&gt;The problem with falling in love with your technology is that a lot of us fall blindly in love. Please don't. Be unfaithful, do - at least - look at other technologies. Take them out on a crazy Friday night date. In the meanwhile you might discover that that new technology does a few things better than the current one and that she's a lot more fun.&lt;br /&gt;&lt;br /&gt;I guarantee that what now seems like the love of your life, will one day be nothing more than a long forgotten memory which might bring a smirk on your face on remembering her, but there will be no regret in leaving her.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-1376860285625139130?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/V3dJ9IC1K0HhkLTcuAyxpLXv5Vw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/V3dJ9IC1K0HhkLTcuAyxpLXv5Vw/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/V3dJ9IC1K0HhkLTcuAyxpLXv5Vw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/V3dJ9IC1K0HhkLTcuAyxpLXv5Vw/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/ei-dyEppebg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/1376860285625139130/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/02/theres-no-place-for-monogamy-in.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/1376860285625139130?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/1376860285625139130?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/ei-dyEppebg/theres-no-place-for-monogamy-in.html" title="There's no place for monogamy in technology" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/02/theres-no-place-for-monogamy-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkYCRnY7fCp7ImA9WhRaGE8.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-5770107763537756641</id><published>2012-02-19T16:52:00.000+01:00</published><updated>2012-02-21T11:29:27.804+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-21T11:29:27.804+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MVC" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET MVC" /><title>ASP.NET MVC4 Installer is incompatible with Microsoft .NET Framework 4.5</title><content type="html">I tried installing &lt;a href="http://www.asp.net/mvc/mvc4"&gt;ASP.NET MVC4 beta&lt;/a&gt; today, but seconds into the installation the WebPI already halted the process.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
ASP.NET MVC4 Installer is incompatible with Microsoft .NET Framework 4.5&lt;/blockquote&gt;
Apparently, this was documented in the ASP.NET MVC4 &lt;a href="http://www.asp.net/whitepapers/mvc4-release-notes#_Toc303253802"&gt;installation notes&lt;/a&gt;.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
This release is not compatible with the .NET Framework 4.5 Developer Preview. You must uninstall the .NET 4.5 Developer Preview before installing the ASP.NET MVC 4 Beta.&lt;/blockquote&gt;
I guess you have to be forgiving if you want to play with the early bits.&lt;br /&gt;
&lt;br /&gt;
Make sure to uninstall &lt;b&gt;all&lt;/b&gt; the .NET Framework 4.5 related stuff though. I had some pain, wasting over an hour, after only partially removing the installation.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-uTEP_jjuoZs/T0ES9EQqafI/AAAAAAAABOU/zprj1ljSOWA/s1600/uninstall.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="213" src="http://3.bp.blogspot.com/-uTEP_jjuoZs/T0ES9EQqafI/AAAAAAAABOU/zprj1ljSOWA/s400/uninstall.PNG" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;b&gt;Update: &lt;/b&gt;&lt;a href="https://twitter.com/#!/bradwilson/status/170237822647808000"&gt;Brad Wilson answers the question why they aren't compatible.&lt;/a&gt;&lt;br /&gt;
&lt;b&gt;Update: &lt;/b&gt;&lt;a href="http://weblogs.asp.net/scottgu/archive/2012/02/19/asp-net-mvc-4-beta.aspx"&gt;Scott Guthrie announced that the next Beta release should be compatible.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-5770107763537756641?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/7scwBJCWLPjDFW2_zz4cgLV6XMs/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/7scwBJCWLPjDFW2_zz4cgLV6XMs/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/7scwBJCWLPjDFW2_zz4cgLV6XMs/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/7scwBJCWLPjDFW2_zz4cgLV6XMs/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/zA-6dZETZ1M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/5770107763537756641/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/02/aspnet-mvc4-installer-is-incompatible.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/5770107763537756641?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/5770107763537756641?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/zA-6dZETZ1M/aspnet-mvc4-installer-is-incompatible.html" title="ASP.NET MVC4 Installer is incompatible with Microsoft .NET Framework 4.5" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-uTEP_jjuoZs/T0ES9EQqafI/AAAAAAAABOU/zprj1ljSOWA/s72-c/uninstall.PNG" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/02/aspnet-mvc4-installer-is-incompatible.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8CSX49fSp7ImA9WhRbF08.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-56079878282826741</id><published>2012-02-08T20:21:00.000+01:00</published><updated>2012-02-08T20:21:08.065+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-08T20:21:08.065+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><category scheme="http://www.blogger.com/atom/ns#" term="Bookreview" /><title>Book review: Working with NHibernate 3.0</title><content type="html">It's been a while since I wrote my last book review, mostly because I'm still trying to figure out when it adds value to write one. For this one it was pretty obvious, there are far too little reviews out there.&lt;br /&gt;
&lt;br /&gt;
Being new to NHibernate, and NHibernate being known as having a steep learning curve, I thought it would be a good idea to do some reading. Searching for books on NHibernate 3.0 on Amazon only yielded three results: &lt;a href="http://www.amazon.com/gp/product/1849516022/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;amp;tag=diofanedebyje-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=1849516022"&gt;NHibernate 3 beginner's guide&lt;/a&gt;, &lt;a href="http://www.amazon.com/gp/product/184951304X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;amp;tag=diofanedebyje-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=184951304X"&gt;NHibernate 3.0 cookbook&lt;/a&gt; and &lt;a href="http://www.amazon.com/gp/product/1118112571/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;amp;tag=diofanedebyje-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=1118112571"&gt;working with NHibernate 3.0&lt;/a&gt;. None of these books have a decent amount of reviews, so I had to pick judging by the cover and summary. I chose the last one.&lt;br /&gt;
&lt;br /&gt;
The book &lt;a href="http://the%20book%20working%20with%20nhibernate%203.0%20by%20benjamin%20delcamp%20perkins%20contains%20six%20chapters%2C%20covered%20over%20213%20pages./"&gt;Working with NHibernate 3.0&lt;/a&gt; by &lt;a href="http://thebestcsharpprogrammerintheworld.com/"&gt;Benjamin Delcamp Perkins&lt;/a&gt; contains six chapters, covered over 213 pages.&lt;br /&gt;
&lt;br /&gt;
The first chapter very briefly explains what an ORM is, and then starts looking at configuring NHibernate. This means setting up the session factory and its configuration, creating entities and mapping them to the database (using XML and code), but also configuring log4net (NHibernate's logging framework), serializing startup and interceptors and events. These last two subjects felt a little misplaced in this chapter though.&lt;br /&gt;
&lt;br /&gt;
In the first chapter the foundation for the Guitar store example application is laid. This example application is written in WPF, which bothered me a bit. Not because I dislike WPF, but because when I'm reading a book on a data access technology I really don't want it to be littered with fragments of XAML and code-behind. To make matters worse, the author suggests using a console application to test your queries. I think it would have been far more valuable to use real unit tests to prove the queries are correct, &lt;a href="https://github.com/davybrion/NHibernateWorkshop/tree/master/NHibernateWorkshop"&gt;more like this&lt;/a&gt;. Another complaint about the example application that I read in another review is that there are no scripts available to set up the database, which might be discouraging if you want to follow along.&lt;br /&gt;
&lt;br /&gt;
Chapter two, three and four cover the various NHibernate query API's: HQL (Hibernate Query Language), ICriteria and LINQ. Every chapter implements the same or at least similar queries. Concepts covered in these chapters are simple queries, complex queries, detached queries, futures and aggregates. I think these chapters succeed in giving a good overview of the ways you can use NHibernate to query data. Also the tips on how to use futures, various fetch modes, the stateless session and aggregates to improve performance will prove useful in the future.&lt;br /&gt;
&lt;br /&gt;
The fifth chapter covers managing state and saving data. In this chapter, the author explains the various ways you can handle database concurrency, listing the advantages and disadvantages of each option. NHibernate caching is also explained, looking at first and second-level caching. Further in this chapter, you can find an example of a custom data type implementation. Finally we arrive at saving data with NHibernate. Next to the standard way of saving data, the author explains the use of Evict, Merge and Persist.&lt;br /&gt;
&lt;br /&gt;
The last chapter, covering only 9 pages, shows how you should set up NHibernate in an MVC3 application.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Although this book doesn't do a great job showing you how to use NHibernate in the real world, it does do a decent job giving you a basic overview of NHibernate's capabilities. Reading this book when you're new to NHibernate will save you from a few costly common NHibernate pitfalls. I don't think I will be able to use this book for reference, but it should be easier now - knowing the correct terminology - to search in the &lt;a href="http://stackoverflow.com/questions/135776/best-place-for-nhibernate-documentation"&gt;NHibernate documentation online&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;My rating: 3/5.&lt;/b&gt; &lt;i&gt;Do you advise other books on NHibernate?&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-56079878282826741?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/atKjO7RcPx_q8XvwQBr7ljELMoY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/atKjO7RcPx_q8XvwQBr7ljELMoY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/atKjO7RcPx_q8XvwQBr7ljELMoY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/atKjO7RcPx_q8XvwQBr7ljELMoY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/wChSGTkvgx4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/56079878282826741/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/02/book-review-working-with-nhibernate-30.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/56079878282826741?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/56079878282826741?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/wChSGTkvgx4/book-review-working-with-nhibernate-30.html" title="Book review: Working with NHibernate 3.0" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/02/book-review-working-with-nhibernate-30.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YAQXcyeCp7ImA9WhRbFUw.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-1695945743076441966</id><published>2012-02-06T08:59:00.000+01:00</published><updated>2012-02-06T08:59:00.990+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-06T08:59:00.990+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET MVC" /><title>Testing DI bootstrappers</title><content type="html">While your &lt;a href="http://martinfowler.com/articles/injection.html"&gt;Dependency Injection&lt;/a&gt; bootstrappers - being responsible for gluing your application together - are a vital part of your application, they are seldom put under test. I don't see any reason why they shouldn't be though. The cost of these tests is negligible, definitely if you compare it to the cost of the often catastrophical outcome of bugs in your bootstrappers.&lt;br /&gt;
&lt;br /&gt;
I encourage you to take a look at the commit history of your DI bootstrappers; I bet they change a lot. Wouldn't it be nice to have a set of tests that proves that the dependency container still behaves like you expect it to at runtime? Next to proving correctness, I think writing these tests also helps you discover various behaviours of your DI container, which is a valuable investment in itself.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;
Let me show you a few tests that I wrote to put my &lt;a href="http://jclaes.blogspot.com/2011/10/ninjecting-mvc3.html"&gt;ASP.NET MVC Ninject&lt;/a&gt; bootstrapper under test.&lt;br /&gt;
&lt;br /&gt;
I started by opening up the Ninject bootstrapper, making the CreateKernel method public.&lt;/div&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; IKernel CreateKernel()
{
    &lt;span class="kwrd"&gt;var&lt;/span&gt; kernel = &lt;span class="kwrd"&gt;new&lt;/span&gt; StandardKernel();
    
    kernel.Bind&amp;lt;IEntryService&amp;gt;().To&amp;lt;EntryService&amp;gt;();

    &lt;span class="kwrd"&gt;return&lt;/span&gt; kernel;
}&lt;/pre&gt;
In the test class, I used the &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.testinitializeattribute(v=vs.80).aspx"&gt;TestInitialize attribute&lt;/a&gt; to initialize a new instance of the kernel before every test. I'm not sure this is really necessary, but I want to avoid that my tests experience side-effects of a previous test.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;[TestInitialize]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Setup()
{         
    _kernel = NinjectMVC3.CreateKernel();
}        &lt;/pre&gt;
Needless to say, the first test should prove that an implementation of the IEntryService interface can be resolved. The behaviour I observed while playing with this case, is that Ninject throws a Ninject.ActivationException when the implementation can't be resolved.&lt;br /&gt;
&lt;blockquote&gt;
Ninject.ActivationException: Error activating IEntryService&lt;br /&gt;
&amp;nbsp; No matching bindings are available, and the type is not self-bindable.&lt;br /&gt;
&amp;nbsp; Activation path:&lt;br /&gt;
&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;Request for IEntryService&lt;br /&gt;
&lt;br /&gt;
Suggestions:&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Ensure that you have defined a binding for IEntryService.&lt;/li&gt;
&lt;li&gt;If the binding was defined in a module, ensure that the module has been loaded into the kernel.&lt;/li&gt;
&lt;li&gt;Ensure you have not accidentally created more than one kernel.&lt;/li&gt;
&lt;li&gt;If you are using constructor arguments, ensure that the parameter name matches the constructors parameter name.&lt;/li&gt;
&lt;li&gt;If you are using automatic module loading, ensure the search path and filters are correct.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
So to test whether an implementation can be resolved, I just make sure no exceptions are thrown on resolving the dependency.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;[TestMethod]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_IEntryService_Can_Be_Resolved()
{
    AssertDoesNotThrowWhenResolved&amp;lt;IEntryService&amp;gt;();
}&lt;/pre&gt;
The AssertDoesNotThrowWhenResolved method is a helper method which tries to resolve a dependency of T and asserts that no exceptions are thrown while doing so. The assertion is borrowed from &lt;a href="http://xunit.codeplex.com/"&gt;Xunit&lt;/a&gt; (&lt;a href="http://nuget.org/packages/xunit"&gt;package available on Nuget&lt;/a&gt;).
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; AssertDoesNotThrowWhenResolved&amp;lt;T&amp;gt;() 
{
    Xunit.Assert.DoesNotThrow(() =&amp;gt; _kernel.Get&amp;lt;T&amp;gt;());
}&lt;/pre&gt;
A second useful test is testing the lifetime of the resolved implementation. For most of my dependencies, I expect a new instance every time they are resolved. This test looks like this.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;[TestMethod]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_IEntryService_Is_New_Instance()
{
    AssertNewInstanceIsResolved&amp;lt;IEntryService&amp;gt;();
}&lt;/pre&gt;
The AssertNewInstanceIsResolved method is another helper method which resolves two instances of T and asserts they are not the same.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; AssertNewInstanceIsResolved&amp;lt;T&amp;gt;()
{
    &lt;span class="kwrd"&gt;var&lt;/span&gt; instance = _kernel.Get&amp;lt;T&amp;gt;();
    &lt;span class="kwrd"&gt;var&lt;/span&gt; secondInstance = _kernel.Get&amp;lt;T&amp;gt;();

    Assert.AreNotSame(instance, secondInstance);
}  &lt;/pre&gt;
That's it. While these tests are very cheap to write, they do provide great value. I can imagine testing more complex bindings, like contextual bindings, taking a bit more time to set up, but the value of these tests increases proportionally.

&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Do you put your DI bootstrappers under test?&lt;/b&gt; If you don't, why not?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-1695945743076441966?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Y1zZk_JEuQUbIYDaar-dGwy_nQY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Y1zZk_JEuQUbIYDaar-dGwy_nQY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Y1zZk_JEuQUbIYDaar-dGwy_nQY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Y1zZk_JEuQUbIYDaar-dGwy_nQY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/3rsX8YtVa80" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/1695945743076441966/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/02/testing-di-bootstrappers.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/1695945743076441966?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/1695945743076441966?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/3rsX8YtVa80/testing-di-bootstrappers.html" title="Testing DI bootstrappers" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/02/testing-di-bootstrappers.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cDSXcyfyp7ImA9WhRbE0s.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-1409944377361701089</id><published>2012-02-04T15:17:00.002+01:00</published><updated>2012-02-04T15:17:58.997+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-04T15:17:58.997+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Tips" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET MVC" /><title>Adding ELMAH to an AppHarbor application</title><content type="html">For those who haven't heard of &lt;a href="http://code.google.com/p/elmah/"&gt;ELMAH&lt;/a&gt; yet, here is the project description.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
ELMAH (Error Logging Modules and Handlers) is an application-wide error logging facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment.&lt;/blockquote&gt;
While ELMAH is completely &lt;b&gt;not&lt;/b&gt; &lt;a href="https://appharbor.com/"&gt;AppHarbor&lt;/a&gt; specific, there do seem to be a &lt;a href="http://feedback.appharbor.com/forums/95687-general/suggestions/1423059-add-support-for-elmah"&gt;fair amount&lt;/a&gt; of &lt;a href="http://support.appharbor.com/discussions/problems/541-getting-elmah-to-work"&gt;questions&lt;/a&gt; on&lt;a href="http://support.appharbor.com/discussions/problems/18-permissions-for-elmahaxd"&gt; the AppHarbor support forums&lt;/a&gt; on how to get ELMAH running. I just installed and configured ELMAH for one of my AppHarbor ASP.NET MVC3 applications, so I thought it would be nice to share and give something back to the AppHarbor support guys.&lt;br /&gt;
&lt;br /&gt;
I think enabling ELMAH shouldn't take more than 10 minutes if you follow this tutorial.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Installation&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
The easiest way to add ELMAH to your web application is by installing &lt;a href="http://www.nuget.org/packages/elmah"&gt;the nuget package&lt;/a&gt;.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;PM&amp;gt; Install-Package elmah
Attempting to resolve dependency &lt;span class="str"&gt;'elmah.corelibrary (= 1.2)'&lt;/span&gt;.
Successfully installed &lt;span class="str"&gt;'elmah 1.2.0.1'&lt;/span&gt;.
Successfully added &lt;span class="str"&gt;'elmah 1.2.0.1'&lt;/span&gt; to Docary.&lt;span style="font-family: 'Times New Roman';"&gt;&lt;span style="white-space: normal;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
Once this is done you will find a newly added Elmah reference and a changed web.config.&lt;br /&gt;
&lt;br /&gt;
I'm not going to list all the changes to the web.config here. If you're interested in what the package does to your web.config, you should just run a &lt;a href="http://book.git-scm.com/3_comparing_commits_-_git_diff.html"&gt;git diff&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Error log storage&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
You have to tell ELMAH where it should store the logs. A list of error log&amp;nbsp;implementations can be found &lt;a href="http://code.google.com/p/elmah/wiki/ErrorLogImplementations"&gt;on the project wiki&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
I choose to use the in-memory error log &lt;i&gt;for now&lt;/i&gt;. Add the elmah section and errorLog element to your web.config.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;elmah&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;    
    &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;errorLog&lt;/span&gt; &lt;span class="attr"&gt;type&lt;/span&gt;&lt;span class="kwrd"&gt;="Elmah.MemoryErrorLog, Elmah"&lt;/span&gt; &lt;span class="attr"&gt;size&lt;/span&gt;&lt;span class="kwrd"&gt;="250"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;elmah&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;&lt;b&gt;
&lt;/b&gt;&lt;/pre&gt;
&lt;b&gt;Remote access&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
By default you can only see the logging on the local machine. To enable remote access, you have to add the security element to the elmah section.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;elmah&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;security&lt;/span&gt; &lt;span class="attr"&gt;allowRemoteAccess&lt;/span&gt;&lt;span class="kwrd"&gt;="1"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;errorLog&lt;/span&gt; &lt;span class="attr"&gt;type&lt;/span&gt;&lt;span class="kwrd"&gt;="Elmah.MemoryErrorLog, Elmah"&lt;/span&gt; &lt;span class="attr"&gt;size&lt;/span&gt;&lt;span class="kwrd"&gt;="250"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;elmah&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;b&gt;Security&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Now that you're allowing remote access, you want to &lt;a href="http://www.troyhunt.com/2012/01/aspnet-session-hijacking-with-google.html"&gt;secure the logging page&lt;/a&gt;. I used ASP.NET authorization to achieve this.&lt;br /&gt;
&lt;br /&gt;
Add the following element to your web.config, and make sure you change the authorization configuration to the &lt;b&gt;relevant values&lt;/b&gt;.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;location&lt;/span&gt; &lt;span class="attr"&gt;path&lt;/span&gt;&lt;span class="kwrd"&gt;="elmah.axd"&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;system.web&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;httpHandlers&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;add&lt;/span&gt; &lt;span class="attr"&gt;verb&lt;/span&gt;&lt;span class="kwrd"&gt;="POST,GET,HEAD"&lt;/span&gt; &lt;span class="attr"&gt;path&lt;/span&gt;&lt;span class="kwrd"&gt;="elmah.axd"&lt;/span&gt; &lt;span class="attr"&gt;type&lt;/span&gt;&lt;span class="kwrd"&gt;="Elmah.ErrorLogPageFactory, Elmah"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;httpHandlers&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
     &lt;b&gt; &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;authorization&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;allow&lt;/span&gt; &lt;span class="attr"&gt;roles&lt;/span&gt;&lt;span class="kwrd"&gt;="SuperUser"&lt;/span&gt;&lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;deny&lt;/span&gt; &lt;span class="attr"&gt;users&lt;/span&gt;&lt;span class="kwrd"&gt;="*"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;authorization&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;&lt;/b&gt;
    &lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;system.web&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;system.webServer&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;handlers&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;add&lt;/span&gt; &lt;span class="attr"&gt;name&lt;/span&gt;&lt;span class="kwrd"&gt;="ELMAH"&lt;/span&gt; &lt;span class="attr"&gt;verb&lt;/span&gt;&lt;span class="kwrd"&gt;="POST,GET,HEAD"&lt;/span&gt; &lt;span class="attr"&gt;path&lt;/span&gt;&lt;span class="kwrd"&gt;="elmah.axd"&lt;/span&gt; &lt;span class="attr"&gt;type&lt;/span&gt;&lt;span class="kwrd"&gt;="Elmah.ErrorLogPageFactory, Elmah"&lt;/span&gt; &lt;span class="attr"&gt;preCondition&lt;/span&gt;&lt;span class="kwrd"&gt;="integratedMode"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;handlers&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;system.webServer&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;location&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;b&gt;Finished&lt;/b&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: -webkit-auto;"&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;
Push these changes, and you should be done. Navigate to elmah.axd in the root of your web application and you should see something like this.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://4.bp.blogspot.com/-quBMPRBOSY8/Ty0zxNNJSMI/AAAAAAAABOM/PgQRM78iJfs/s1600/Elmah.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="144" src="http://4.bp.blogspot.com/-quBMPRBOSY8/Ty0zxNNJSMI/AAAAAAAABOM/PgQRM78iJfs/s640/Elmah.PNG" width="640" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
It should be easier now to start playing with &lt;a href="http://code.google.com/p/elmah/wiki"&gt;more advanced ELMAH options&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-1409944377361701089?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/vdLv57y5MLx8_dnuDVOFuxXgniM/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/vdLv57y5MLx8_dnuDVOFuxXgniM/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/vdLv57y5MLx8_dnuDVOFuxXgniM/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/vdLv57y5MLx8_dnuDVOFuxXgniM/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/C_l3w_E5Kro" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/1409944377361701089/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/02/adding-elmah-to-appharbor-application.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/1409944377361701089?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/1409944377361701089?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/C_l3w_E5Kro/adding-elmah-to-appharbor-application.html" title="Adding ELMAH to an AppHarbor application" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-quBMPRBOSY8/Ty0zxNNJSMI/AAAAAAAABOM/PgQRM78iJfs/s72-c/Elmah.PNG" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/02/adding-elmah-to-appharbor-application.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEUEQXw6eSp7ImA9WhRbEEU.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-7338523233107605643</id><published>2012-02-01T08:43:00.001+01:00</published><updated>2012-02-01T08:43:20.211+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-01T08:43:20.211+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="LOL" /><category scheme="http://www.blogger.com/atom/ns#" term="Ramblings" /><title>A solar storm anecdote</title><content type="html">Last week, several news channels reported on the strongest &lt;a href="http://en.wikipedia.org/wiki/Solar_flare"&gt;solar storm&lt;/a&gt; since 2005. &lt;a href="http://www.bbc.co.uk/news/science-environment-16701407"&gt;This news item&lt;/a&gt; reminded me of a peculiar support ticket we received one gray Monday morning a few years ago, when I was still writing software for fire departments.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
*********************************************************************************&lt;br /&gt;
&lt;b&gt;Ticket 7238&lt;/b&gt;&lt;br /&gt;
Subject: &lt;b&gt;AVL broken&lt;/b&gt;&lt;br /&gt;
Status: New &lt;br /&gt;
Description&lt;br /&gt;
06:22 Vehicles stay mostly stationary on the map, even when we are&lt;br /&gt;
positive they&amp;nbsp;are en route.&lt;br /&gt;
*********************************************************************************&lt;/blockquote&gt;
Fire departments that have to cover a large area - and are wealthy enough - often use &lt;a href="http://en.wikipedia.org/wiki/Automatic_vehicle_location"&gt;AVL (Automatic Vehicle Location)&lt;/a&gt; to track their vehicles and visualize them on a map. This is extremely valuable, because you always want to dispatch the vehicles with the smallest response time to a high priority intervention. Also being able to advise drivers of possible blockages and toxic gas clouds can save lives. To be able to track a vehicle, an AVL module is installed into each vehicle's cockpit. This module uses GPS to determine the location and sends the location data over GPRS to a central server.&lt;br /&gt;
&lt;br /&gt;
On our end, we had a third party service listening for those location packets and translating them into a more understandable format. This service, not being mission critical, wasn't being monitored, so we had to look into the logs to see what was going on. Scrolling through megabytes of debug logs, we couldn't find anything suspicious.&lt;br /&gt;
&lt;br /&gt;
While I was investigating this, a co-worker had come in and was reading through his mails while sipping on his morning coffee. After reading support ticket 7238, he nonchalantly said he knew what was going on with AVL and he would take over from there.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
*********************************************************************************&lt;br /&gt;
Ticket 7238&lt;br /&gt;
Subject: AVL broken&lt;br /&gt;
Status: Pending&lt;br /&gt;
Description&lt;br /&gt;
&lt;b&gt;08:45 I heard on the radio that there is a solar storm going on at&lt;br /&gt; &lt;/b&gt;&lt;b&gt;the moment,&amp;nbsp;which affects sattelites. The AVL module might have&lt;br /&gt; &lt;/b&gt;&lt;b&gt;a hard time getting a GPS&amp;nbsp;fix. This issue should solve itself over&lt;br /&gt; &lt;/b&gt;&lt;b&gt;time. We will keep an eye on this issue.&lt;/b&gt;&lt;br /&gt;
---------------------------------------------------------------------------------&lt;br /&gt;
06:22 Vehicles stay mostly stationary on the map, even when we are&lt;br /&gt;
positive they&amp;nbsp;are en route.&lt;br /&gt;
*********************************************************************************&lt;/blockquote&gt;
A few days passed and although the solar storm was over, we were still seeing signifcant packet loss. After spending a few hours working the ticket, in which we restarted the service, monitored network traffic on the machine and conctacted the telephony provider, we were getting a bit desperate. We were discussing other potential paths to investigate, when one of our more seasoned co-workers asked "You guys did try restarting the server, right?".&lt;br /&gt;
&lt;br /&gt;
Good enough, after restarting the server, we were seeing no more packet loss and the vehicles started moving on the map again.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://2.bp.blogspot.com/-r8DZJ7Vi71k/TyWKSHbI_2I/AAAAAAAABOE/0_RldNsqvxk/s1600/PokerFace.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="232" src="http://2.bp.blogspot.com/-r8DZJ7Vi71k/TyWKSHbI_2I/AAAAAAAABOE/0_RldNsqvxk/s320/PokerFace.png" width="320" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
*********************************************************************************&lt;br /&gt;
Ticket 7238&lt;br /&gt;
Subject: AVL broken&lt;br /&gt;
&lt;b&gt;Status: Resolved&lt;/b&gt;&lt;br /&gt;
Description&lt;br /&gt;
&lt;b&gt;15:47 The solar storm must be over. The vehicle locations are&lt;br /&gt; &lt;/b&gt;&lt;b&gt;being updated in a&amp;nbsp;&lt;/b&gt;&lt;b&gt;timely fashion again.&lt;/b&gt;&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
08:45 I heard on the radio that there is a solar storm going on at&lt;br /&gt;
the moment,&amp;nbsp;which affects sattelites. The AVL module might have&lt;br /&gt;
a hard time getting a GPS fix.&amp;nbsp;This issue should solve itself over&lt;br /&gt;
time. We will keep an eye on this issue.&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
06:22 Vehicles stay mostly stationary on the map, even when we are&lt;br /&gt;
positive they&amp;nbsp;are en route.&lt;br /&gt;
*********************************************************************************&lt;/blockquote&gt;
Until today, we never talked about ticket 7238 again.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-7338523233107605643?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/KBVKhgTRm7sO999uP0JvxNjmv40/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/KBVKhgTRm7sO999uP0JvxNjmv40/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/KBVKhgTRm7sO999uP0JvxNjmv40/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/KBVKhgTRm7sO999uP0JvxNjmv40/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/Md-MB5kYSuI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/7338523233107605643/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/02/solar-storm-anecdote.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/7338523233107605643?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/7338523233107605643?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/Md-MB5kYSuI/solar-storm-anecdote.html" title="A solar storm anecdote" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-r8DZJ7Vi71k/TyWKSHbI_2I/AAAAAAAABOE/0_RldNsqvxk/s72-c/PokerFace.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/02/solar-storm-anecdote.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkMFSXY4fip7ImA9WhRUEEs.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-3358719362814109771</id><published>2012-01-19T20:56:00.000+01:00</published><updated>2012-01-20T15:06:58.836+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-20T15:06:58.836+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="HTML5" /><category scheme="http://www.blogger.com/atom/ns#" term="Browsers" /><title>How Wikipedia uses HTML5 to save bandwidth</title><content type="html">Something I hadn't noticed until recently is that Wikipedia tries to use the browser's native &lt;a href="http://www.w3.org/TR/SVG/"&gt;SVG&lt;/a&gt; support to render certain images. For example, if you search for &lt;a href="http://upload.wikimedia.org/wikipedia/commons/9/92/Flag_of_Belgium_%28civil%29.svg"&gt;a high resolution image of your country's flag&lt;/a&gt;, you will probably end up viewing an SVG. Wikipedia also offers downloads to the image rendered as a PNG though. &lt;br /&gt;
&lt;br /&gt;
Next to being able to scale to an arbitrary size without suffering data loss, the SVG data format allows images to be far more compact. Basically, SVG is just XML, which also means it can be easily compressed to make its size even smaller.

For example, this is the (uncompressed) SVG for the flag of the &lt;a href="http://en.wikipedia.org/wiki/Belgium"&gt;Kingdom of Belgium&lt;/a&gt;.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;svg&lt;/span&gt; &lt;span class="attr"&gt;xmlns&lt;/span&gt;&lt;span class="kwrd"&gt;="http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="attr"&gt;width&lt;/span&gt;&lt;span class="kwrd"&gt;="450"&lt;/span&gt; &lt;span class="attr"&gt;height&lt;/span&gt;&lt;span class="kwrd"&gt;="300"&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;rect&lt;/span&gt; &lt;span class="attr"&gt;width&lt;/span&gt;&lt;span class="kwrd"&gt;="450"&lt;/span&gt; &lt;span class="attr"&gt;height&lt;/span&gt;&lt;span class="kwrd"&gt;="300"&lt;/span&gt;&lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;rect&lt;/span&gt; &lt;span class="attr"&gt;x&lt;/span&gt;&lt;span class="kwrd"&gt;="150"&lt;/span&gt; &lt;span class="attr"&gt;width&lt;/span&gt;&lt;span class="kwrd"&gt;="150"&lt;/span&gt; &lt;span class="attr"&gt;height&lt;/span&gt;&lt;span class="kwrd"&gt;="300"&lt;/span&gt; &lt;span class="attr"&gt;fill&lt;/span&gt;&lt;span class="kwrd"&gt;="#FAE042"&lt;/span&gt;&lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;rect&lt;/span&gt; &lt;span class="attr"&gt;x&lt;/span&gt;&lt;span class="kwrd"&gt;="300"&lt;/span&gt; &lt;span class="attr"&gt;width&lt;/span&gt;&lt;span class="kwrd"&gt;="150"&lt;/span&gt; &lt;span class="attr"&gt;height&lt;/span&gt;&lt;span class="kwrd"&gt;="300"&lt;/span&gt; &lt;span class="attr"&gt;fill&lt;/span&gt;&lt;span class="kwrd"&gt;="#ED2939"&lt;/span&gt;&lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;svg&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
This svg node only weighs as much as 224 bytes, while the image rendered as a high resolution PNG weighs 13.402 bytes. Stuff like that makes a significant difference when you're &lt;a href="http://stats.wikimedia.org/EN/TablesPageViewsMonthlyCombined.htm"&gt;serving millions of page views on a daily basis&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
The first time I touched SVG was a few years ago when I was still working on fire department projects. We were working with a third party that used SVG to draw maps in the browser. Being already in the post-Google Maps era, I thought it was terrible I had to download &lt;a href="http://www.adobe.com/svg/viewer/install/"&gt;Adobe's SVG viewer&lt;/a&gt;. While the Google Maps technology already works great, there are still things SVG can do better and cleaner, especially for more specialized GIS applications. There is an interesting paper on that subject &lt;a href="http://svgopen.org/2008/papers/82-Web_Mapping_and_WebGIS_do_we_actually_need_to_use_SVG/"&gt;here&lt;/a&gt;, it's a bit outdated though. &lt;br /&gt;
&lt;br /&gt;
I can only applaud making the browser more capable, and losing &lt;i&gt;yet another plug-in&lt;/i&gt;. I'm curious to see how other applications will start taking advantage of the opportunities native SVG support &lt;b&gt;across all modern browsers&lt;/b&gt; (even Internet Explorer) presents.&lt;br /&gt;
&lt;br /&gt;
Have you already built things using SVG? Or even considered it?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-3358719362814109771?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/JIQPUGLjQnAPpaFzy_UjKJsK7Fk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/JIQPUGLjQnAPpaFzy_UjKJsK7Fk/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/JIQPUGLjQnAPpaFzy_UjKJsK7Fk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/JIQPUGLjQnAPpaFzy_UjKJsK7Fk/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/0EVuLLNdYuM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/3358719362814109771/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/01/how-wikipedia-uses-html5-to-save.html#comment-form" title="23 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3358719362814109771?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3358719362814109771?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/0EVuLLNdYuM/how-wikipedia-uses-html5-to-save.html" title="How Wikipedia uses HTML5 to save bandwidth" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><thr:total>23</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/01/how-wikipedia-uses-html5-to-save.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkAERnk7cCp7ImA9WhRVGEU.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-7046745319855346358</id><published>2012-01-18T10:29:00.001+01:00</published><updated>2012-01-18T10:58:27.708+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-18T10:58:27.708+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Hack" /><category scheme="http://www.blogger.com/atom/ns#" term="jQuery" /><title>Undoing the Wikipedia black-out</title><content type="html">&lt;div class="separator" style="clear: both; text-align: left;"&gt;
&lt;b&gt;Update: &lt;/b&gt;A comprehensive collection of ways to undo the black-out can be found in &lt;a href="https://gist.github.com/1631355"&gt;this Gist&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Five minutes into work this morning, I already ran against the &lt;a href="http://en.wikipedia.org/wiki/Wikipedia:SOPA_initiative/Learn_more"&gt;Wikipedia black-out&lt;/a&gt;. If you &lt;a href="http://en.wikipedia.org/wiki/Computer_science"&gt;try it yourself&lt;/a&gt;, you will see the article's content being briefly visible and then disappearing.&lt;/div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-9r98Lx_4mxE/TxaI8Vw1wYI/AAAAAAAABNk/I-CZMNOFeu8/s1600/wiki_blackout.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="302" src="http://1.bp.blogspot.com/-9r98Lx_4mxE/TxaI8Vw1wYI/AAAAAAAABNk/I-CZMNOFeu8/s640/wiki_blackout.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;br /&gt;&lt;/div&gt;
Curious as I am, I looked at what they were doing in the browser. Upon inspecting the source, you instantly notice that the content is&amp;nbsp;still&amp;nbsp;&amp;nbsp;present in the DOM, and that the black-out is nothing more than an overlay.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://3.bp.blogspot.com/-0l31KxU203Q/TxaJKPCX3VI/AAAAAAAABNs/_ZVeSQIavgA/s1600/sopa_elements.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="282" src="http://3.bp.blogspot.com/-0l31KxU203Q/TxaJKPCX3VI/AAAAAAAABNs/_ZVeSQIavgA/s640/sopa_elements.PNG" width="640" /&gt;&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
If you run following script in the console, you can undo the black-out. This script removes the overlay from the DOM, and makes all other elements visible again (which might not be completely correct).
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;jQuery("#mw-sopaOverlay").remove(); 
jQuery("*").show(); &lt;/pre&gt;
This was mostly just a fun exercise, I will not be using this script. &lt;b&gt;I fully support &lt;a href="http://en.wikipedia.org/wiki/Wikipedia:SOPA_initiative/Learn_more"&gt;this protest&lt;/a&gt;.&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-7046745319855346358?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/nCjFeS6WMdUhRWs3HHFsQhH5Fi0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/nCjFeS6WMdUhRWs3HHFsQhH5Fi0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/nCjFeS6WMdUhRWs3HHFsQhH5Fi0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/nCjFeS6WMdUhRWs3HHFsQhH5Fi0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/-6HoV7NNSx4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/7046745319855346358/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/01/undoing-wikipedia-black-out.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/7046745319855346358?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/7046745319855346358?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/-6HoV7NNSx4/undoing-wikipedia-black-out.html" title="Undoing the Wikipedia black-out" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-9r98Lx_4mxE/TxaI8Vw1wYI/AAAAAAAABNk/I-CZMNOFeu8/s72-c/wiki_blackout.PNG" height="72" width="72" /><thr:total>5</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/01/undoing-wikipedia-black-out.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08ASH4zeyp7ImA9WhRVFk4.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-6210725874781025616</id><published>2012-01-15T16:04:00.000+01:00</published><updated>2012-01-15T16:04:09.083+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-15T16:04:09.083+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Hack" /><category scheme="http://www.blogger.com/atom/ns#" term="CodeSnippets" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET MVC" /><title>Autocorrecting unknown actions using the Levenshtein distance</title><content type="html">This weekend I prototyped an idea I had earlier this week: autocorrecting unknown actions in ASP.NET MVC.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Handling unknown actions&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;
To give you an example, let's say I have a Home controller with an action named Kitten on it. If there is an incoming route for the Home controller with Kitty (instead of Kitten) as the action name, the controller will not be able to invoke any action method and instead will call the HandleUnknownAction method. &lt;br /&gt;&lt;br /&gt;Here is the snippet from the ASP.NET MVC source.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; ExecuteCore() {
    PossiblyLoadTempData();
    &lt;span class="kwrd"&gt;try&lt;/span&gt; {
        &lt;span class="kwrd"&gt;string&lt;/span&gt; actionName = RouteData.GetRequiredString(&lt;span class="str"&gt;"action"&lt;/span&gt;);
        &lt;span class="kwrd"&gt;if&lt;/span&gt; (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
            HandleUnknownAction(actionName);
        }
    }
    &lt;span class="kwrd"&gt;finally&lt;/span&gt; {
        PossiblySaveTempData();
    }
}&lt;/pre&gt;
The HandleUnknownAction is virtual, meaning we can override it in our derived controller. The base implementation of the HandleUnknownAction method does nothing more than throwing a 404 HttpException.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;virtual&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; HandleUnknownAction(&lt;span class="kwrd"&gt;string&lt;/span&gt; actionName) {
    &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; HttpException(404, String.Format(CultureInfo.CurrentCulture,
        MvcResources.Controller_UnknownAction, actionName, GetType().FullName));
}&lt;/pre&gt;
So let's override the HandleUnknownAction method and try to autocorrect the unknown action name. To be safe, we will only attempt to autocorrect the action name when it's a GET HTTP request.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; HandleUnknownAction(&lt;span class="kwrd"&gt;string&lt;/span&gt; actionName)
{
    &lt;span class="kwrd"&gt;if&lt;/span&gt; (!HttpContext.Request.HttpMethod.Equals(&lt;span class="str"&gt;"GET"&lt;/span&gt;, StringComparison.OrdinalIgnoreCase))
        Throw404HttpException(actionName);
    
    TryToRedirectToAnActionNearby(actionName);           
}&lt;/pre&gt;
&lt;b&gt;Listing all actions&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
First we need a list of all available action names. I reflect on the methods of the current controller and select the methods which are public, can be invoked and are an instance method. Also the method should return an ActionResult, not be decorated with the HttpPost attribute and not have a special name. I'm pretty sure I'm missing a few things here, but there seems to be no generic way to extract this metadata from a controller. Places where these type of things are used in the framework seem to be internal or non-public.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; IEnumerable&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt; GetAllHttpGetActionNames()
{
    &lt;span class="kwrd"&gt;return&lt;/span&gt; GetType()
            .GetMethods(BindingFlags.InvokeMethod | 
                        BindingFlags.Public | 
                        BindingFlags.Instance)
            .Where(m =&amp;gt; m.ReturnType == &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(ActionResult) &amp;amp;&amp;amp;
                        !m.IsSpecialName &amp;amp;&amp;amp;
                        !m.GetCustomAttributes(&lt;span class="kwrd"&gt;true&lt;/span&gt;)
                            .Contains(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(HttpPostAttribute)))
            .Select(m =&amp;gt; m.Name)
            .Distinct();
}&lt;/pre&gt;
Once we have all these action names, we want to see how distant they are from the unknown action name we are trying to autocorrect here. To calculate this we can use the Levenshtein distance algorithm. &lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The Levenshtein distance&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
The &lt;a href="http://en.wikipedia.org/wiki/Levenshtein_distance"&gt;Levenshtein distance&lt;/a&gt; is defined by Wikipedia like this.&lt;br /&gt;
&lt;blockquote class="tr_bq"&gt;
In information theory and computer science, the Levenshtein distance is a string metric for measuring the amount of difference between two sequences. The Levenshtein distance between two strings is defined as the minimum number of edits needed to transform one string into the other, with the allowable edit operations being insertion, deletion, or substitution of a single character. It is named after Vladimir Levenshtein, who considered this distance in 1965.&lt;/blockquote&gt;
An implementation of this algorithm in C# could look like this.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;int&lt;/span&gt; CalculateDistance(&lt;span class="kwrd"&gt;string&lt;/span&gt; str1, &lt;span class="kwrd"&gt;string&lt;/span&gt; str2) 
{
    &lt;span class="kwrd"&gt;var&lt;/span&gt; matrix = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;int&lt;/span&gt;[str1.Length + 1, str2.Length + 1];

    &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;var&lt;/span&gt; i = 0; i &amp;lt;= str1.Length; i++)
        matrix[i, 0] = i;
    &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;var&lt;/span&gt; j = 0; j &amp;lt;= str2.Length; j++)
        matrix[0, j] = j;

    &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;var&lt;/span&gt; i = 1; i &amp;lt;= str1.Length; i++)
    {
        &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;var&lt;/span&gt; j = 1; j &amp;lt;= str2.Length; j++)
        {
            &lt;span class="kwrd"&gt;var&lt;/span&gt; cost = str1[i - 1] == str2[j - 1] ? 0 : 1;

            matrix[i, j] = (&lt;span class="kwrd"&gt;new&lt;/span&gt;[]
            {
                matrix[i - 1, j] + 1, matrix[i, j - 1] + 1, matrix[i - 1, j - 1] + cost
            }).Min();

            &lt;span class="kwrd"&gt;if&lt;/span&gt; ((i &amp;gt; 1) &amp;amp;&amp;amp; 
                (j &amp;gt; 1) &amp;amp;&amp;amp; 
                (str1[i - 1] == str2[j - 2]) &amp;amp;&amp;amp;
                (str1[i - 2] == str2[j - 1]))
            {
                matrix[i, j] = Math.Min(matrix[i, j], matrix[i - 2, j - 2] + cost);
            }
        }
    }

    &lt;span class="kwrd"&gt;return&lt;/span&gt; matrix[str1.Length, str2.Length];
}        &lt;/pre&gt;
This is a direct port from the &lt;a href="http://en.wikipedia.org/wiki/Levenshtein_distance#Computing_Levenshtein_distance"&gt;pseudocode found on Wikipedia&lt;/a&gt;. These tests might, probably a lot more than the implementation, help you understand what the Levenshtein algorithm calculates.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;[TestMethod()]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_CalculateDistance_With_Two_Empty_String()
{           
    Assert.AreEqual(0, Levenshtein.CalculateDistance(&lt;span class="kwrd"&gt;string&lt;/span&gt;.Empty, &lt;span class="kwrd"&gt;string&lt;/span&gt;.Empty));
}   

[TestMethod()]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_CalculateDistance_With_Empty_First_String()
{         
    Assert.AreEqual(6, Levenshtein.CalculateDistance(&lt;span class="kwrd"&gt;string&lt;/span&gt;.Empty, &lt;span class="str"&gt;"kitten"&lt;/span&gt;));
}

[TestMethod()]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_CalculateDistance_With_Empty_Second_String()
{           
    Assert.AreEqual(6, Levenshtein.CalculateDistance(&lt;span class="str"&gt;"kitten"&lt;/span&gt;, &lt;span class="kwrd"&gt;string&lt;/span&gt;.Empty));
}

[TestMethod()]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_CalculateDistance_With_Missing_Characters()
{           
    Assert.AreEqual(2, Levenshtein.CalculateDistance(&lt;span class="str"&gt;"kitten"&lt;/span&gt;, &lt;span class="str"&gt;"kitt"&lt;/span&gt;));
}

[TestMethod()]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_CalculateDistance_With_Wrong_Characters()
{           
    Assert.AreEqual(1, Levenshtein.CalculateDistance(&lt;span class="str"&gt;"kitten"&lt;/span&gt;, &lt;span class="str"&gt;"kittyn"&lt;/span&gt;));
}

[TestMethod()]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_CalculateDistance_With_Too_Much_Characters()
{           
    Assert.AreEqual(5, Levenshtein.CalculateDistance(&lt;span class="str"&gt;"kitten"&lt;/span&gt;, &lt;span class="str"&gt;"kittenkitty"&lt;/span&gt;));
}

[TestMethod()]
&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Test_CalculateDistance_With_Equal_Strings()
{          
    Assert.AreEqual(0, Levenshtein.CalculateDistance(&lt;span class="str"&gt;"kitten"&lt;/span&gt;, &lt;span class="str"&gt;"kitten"&lt;/span&gt;));
}&lt;span style="font-family: 'Times New Roman';"&gt;&lt;span style="white-space: normal;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
Now that we have implemented the Levenshtein distance algorithm, we can calculate the distances between the unknown action name and all the available action names.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, &lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt; CalculateLevenshteinDistance(IEnumerable&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt; actionList, &lt;span class="kwrd"&gt;string&lt;/span&gt; actionName)
{
    &lt;span class="kwrd"&gt;return&lt;/span&gt; actionList
            .Select(a =&amp;gt; &lt;span class="kwrd"&gt;new&lt;/span&gt;
            {
                Action = a.ToLower(),
                Distance = Levenshtein.CalculateDistance(a.ToLower(), actionName.ToLower())
            })                    
            .ToDictionary(k =&amp;gt; k.Action, v =&amp;gt; v.Distance);
}&lt;/pre&gt;
For the unknown action name 'Kitty', when the action names 'Kitten', 'Index' and 'Dog' are available, this method would return a dictionary that looks like this.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="str"&gt;'kitten'&lt;/span&gt; : 2
&lt;span class="str"&gt;'index'&lt;/span&gt; : 6
&lt;span class="str"&gt;'dog'&lt;/span&gt; : 6&lt;/pre&gt;
&lt;b&gt;Putting it all together&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;
Now we have this dictionary, we want to filter on a certain distance threshold. I picked three, given that when a word is three characters off, the chance of it being a typo is rather small.&lt;br /&gt;
&lt;br /&gt;
If the dictionary still contains some items after filtering, we want to take the action with the shortest distance, this action is the nearest to the unknown action. Only thing left to do is change the action in the &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.routedata(v=vs.90).aspx"&gt;RouteData&lt;/a&gt; and execute a &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.mvc.redirectresult.aspx"&gt;RedirectResult&lt;/a&gt;. The easiest way to generate a url to redirect to, is to use the controller's UrlHelper to let it generate the url based on the RouteData.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TryToRedirectToAnActionNearby(&lt;span class="kwrd"&gt;string&lt;/span&gt; actionName)
{
    &lt;span class="kwrd"&gt;var&lt;/span&gt; httpGetActionNames = GetAllHttpGetActionNames();
    &lt;span class="kwrd"&gt;if&lt;/span&gt; (!httpGetActionNames.Any())
        Throw404HttpException(actionName);

    &lt;span class="kwrd"&gt;var&lt;/span&gt; actionDistanceMap = CalculateLevenshteinDistance(httpGetActionNames, actionName)
                                .Where(i =&amp;gt; i.Value &amp;lt;= 3);
    &lt;span class="kwrd"&gt;if&lt;/span&gt; (!actionDistanceMap.Any())
        Throw404HttpException(actionName);

    &lt;span class="kwrd"&gt;var&lt;/span&gt; shortestDistance = actionDistanceMap.Select(v =&amp;gt; v.Value).Min();
    &lt;span class="kwrd"&gt;var&lt;/span&gt; nearestAction = actionDistanceMap.Where(i =&amp;gt; i.Value == shortestDistance).First().Key;

    ControllerContext.RouteData.Values[&lt;span class="str"&gt;"action"&lt;/span&gt;] = nearestAction;

    &lt;span class="kwrd"&gt;new&lt;/span&gt; RedirectResult(Url.RouteUrl(RouteData.Values), permanent: &lt;span class="kwrd"&gt;true&lt;/span&gt;)
        .ExecuteResult(ControllerContext);
}&lt;/pre&gt;
&lt;b&gt;The outcome&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Now when I, the user, type http://somesite/someController/kitty, I will be redirected to http://somesite/someController/kitten without me even noticing.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://4.bp.blogspot.com/-HfDQZJsCVrc/TxLkaKp-g1I/AAAAAAAABNc/A5VpLUrKE20/s1600/LevenshteinDistanceRedirect.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="68" src="http://4.bp.blogspot.com/-HfDQZJsCVrc/TxLkaKp-g1I/AAAAAAAABNc/A5VpLUrKE20/s640/LevenshteinDistanceRedirect.PNG" width="640" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Feedback&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
This implementation definitely is &lt;b&gt;not production ready&lt;/b&gt;. It's a prototype, not even under test. I wonder if this &amp;nbsp;is even something you would want to do. Or is this &lt;b&gt;breaking the Web&lt;/b&gt; in one way or the other? Would it bother search engines?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-6210725874781025616?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/W1SSQaUtClUzHV4CoPQXvrIHPTY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/W1SSQaUtClUzHV4CoPQXvrIHPTY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/W1SSQaUtClUzHV4CoPQXvrIHPTY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/W1SSQaUtClUzHV4CoPQXvrIHPTY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/Cd1OlhrfHAs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/6210725874781025616/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/01/autocorrecting-unknown-actions-using.html#comment-form" title="15 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/6210725874781025616?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/6210725874781025616?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/Cd1OlhrfHAs/autocorrecting-unknown-actions-using.html" title="Autocorrecting unknown actions using the Levenshtein distance" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-HfDQZJsCVrc/TxLkaKp-g1I/AAAAAAAABNc/A5VpLUrKE20/s72-c/LevenshteinDistanceRedirect.PNG" height="72" width="72" /><thr:total>15</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/01/autocorrecting-unknown-actions-using.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08BRHY6fip7ImA9WhRUF0Q.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-8593083699823823119</id><published>2012-01-12T21:16:00.000+01:00</published><updated>2012-01-29T00:04:15.816+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-29T00:04:15.816+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="CodeSnippets" /><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET MVC" /><title>An approach to mobile support in ASP.NET MVC</title><content type="html">&lt;div class="separator" style="clear: both; text-align: left;"&gt;
I have been spending a decent amount of time working on a side project in ASP.NET MVC lately. From the start, I planned on supporting mobile. There are lots of ways you can use or extend ASP.NET MVC to support mobile. Having tried several, I can say they all have their merits, and the solution that will work for you very much depends on your requirements. In this post I will run you over the implementation that worked for me, for my specific requirements.&lt;/div&gt;
&lt;br /&gt;
I &lt;i&gt;looked&lt;/i&gt; at dynamically changing layouts, but I doubt if there is anyone who has gotten a satisfying result with that. I also considered rolling my own &lt;a href="https://bitbucket.org/shanselman/mobileviewengines/changeset/48310997a453"&gt;view engine&lt;/a&gt;, but after going with that for a little while, it became obvious that this wasn't going to work out either. The behavior and functionalities of my mobile version were quickly drifting away of those of the desktop version. I could only reuse so few existing actions or viewmodels that I was close to seeing the two versions as two completely different sites. Let me elaborate a bit further. While it was often possible to use the same viewmodels for both versions, it wasn't as clean as I wanted it to be. I strive for lean views, and doing projections in the views just to get the viewmodels in a workable format felt not right. Also, I had a few cases where I had to split one action in multiple smaller ones to make the flow work for the mobile version.&lt;br /&gt;
&lt;br /&gt;
Eventually I ended up dividing the desktop and mobile version in two separate areas: desktop and mobile. Because some things do overlap, I share a few abstract base controllers between the areas.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-p6ESdW-A2Qo/Tw855wXEtKI/AAAAAAAABNU/fkJ-sO8Ok_w/s1600/Areas_Mobile_Solution.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-p6ESdW-A2Qo/Tw855wXEtKI/AAAAAAAABNU/fkJ-sO8Ok_w/s320/Areas_Mobile_Solution.PNG" width="160" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
So when I look at the root of my application now, I see no more views. Two things are left though: the HomeController and the AccountController.&lt;br /&gt;
&lt;br /&gt;
I was able to pinpoint three points in the application where I need to take special care of detecting the device or rerouting to the correct area.&lt;br /&gt;
&lt;br /&gt;
The default route when the user types http://MySite in the address bar still is Home/Default. In the default action of the HomeController I want to detect the area I want to redirect to, based on the user agent. Notice I'm using a permanent redirect, not wasting a HTTP request.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; HomeController : Controller
{
    &lt;span class="kwrd"&gt;public&lt;/span&gt; ActionResult Index()
    {           
        &lt;span class="kwrd"&gt;var&lt;/span&gt; view = Request.IsAuthenticated ? &lt;span class="str"&gt;"Index"&lt;/span&gt; : &lt;span class="str"&gt;"Welcome"&lt;/span&gt;;
        &lt;span class="kwrd"&gt;var&lt;/span&gt; area = Request.ResolveDestinationArea();             

        &lt;span class="kwrd"&gt;return&lt;/span&gt; RedirectToActionPermanent(view, &lt;span class="str"&gt;"Home"&lt;/span&gt;, &lt;span class="kwrd"&gt;new&lt;/span&gt; { Area = area });            
    }
}
&lt;/pre&gt;
This is the first scenario. When a user opens the homepage, I redirect him to a specific area based on the user agent. If the user is on a desktop he will be redirected to http://MySite/desktop/home and when he's on a mobile device he will be redirected to http://MySite/mobile/home. If this somewhat well-informed guess turns out to be wrong, the user can still use a link to take him to the correct area.

&lt;br /&gt;
&lt;br /&gt;
There are two places left where I have to add some logic assuring the user stays in the correct area. When the session is expired, I have to redirect the user to the correct login page. Since you can only define one loginUrl and there is no way of plugging into that, I let ASP.NET redirect to the Account controller in my root.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;authentication&lt;/span&gt; &lt;span class="attr"&gt;mode&lt;/span&gt;&lt;span class="kwrd"&gt;="Forms"&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;forms&lt;/span&gt; &lt;span class="attr"&gt;loginUrl&lt;/span&gt;&lt;span class="kwrd"&gt;="~/Account/LogOn"&lt;/span&gt; &lt;span class="attr"&gt;timeout&lt;/span&gt;&lt;span class="kwrd"&gt;="2880"&lt;/span&gt;&lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;authentication&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
In the AccountController I can use the loginUrl in the querystring to decide which area I want to redirect to. If it contains mobile, I redirect to the mobile area. If it contains desktop, I redirect to the desktop area.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; AccountController : Controller
{       
    &lt;span class="kwrd"&gt;public&lt;/span&gt; ActionResult LogOn()
    {
        &lt;span class="kwrd"&gt;return&lt;/span&gt; RedirectToAction(
            &lt;span class="str"&gt;"LogOn"&lt;/span&gt;, &lt;span class="str"&gt;"Account"&lt;/span&gt;, &lt;span class="kwrd"&gt;new&lt;/span&gt; { Area = Request.ResolveDestinationArea() });      
    }
}&lt;/pre&gt;
If the url were http://MySite/Account/LogOn?ReturnUrl=%2fmobile%2fentry%2fadd, the 'mobile' substring in the ReturnUrl querystring would suffice to make this work.&lt;br /&gt;
&lt;br /&gt;
Now there is one more place left where we want to take care of redirecting to the correct area: in the error handling. Like always, there are several ways of handling errors in ASP.NET MVC. I chose for the one where I could handle exceptions outside the MVC pipeline and add custom redirect logic easily. I'm talking about the Application_Error event in the Global.asax. When I'm handling this event, I can also use the request url to resolve the correct area.
&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Application_Error()
{
    Response.TrySkipIisCustomErrors = &lt;span class="kwrd"&gt;true&lt;/span&gt;;

    &lt;span class="kwrd"&gt;var&lt;/span&gt; exception = Server.GetLastError();
    &lt;span class="kwrd"&gt;var&lt;/span&gt; httpException = exception &lt;span class="kwrd"&gt;as&lt;/span&gt; HttpException;

    Response.Clear();
    Server.ClearError();           
    
    &lt;span class="kwrd"&gt;var&lt;/span&gt; routeData = &lt;span class="kwrd"&gt;new&lt;/span&gt; RouteData();
    routeData.DataTokens[&lt;span class="str"&gt;"area"&lt;/span&gt;] = HttpContext.Current.Request.ResolveDestinationArea();
    routeData.Values[&lt;span class="str"&gt;"controller"&lt;/span&gt;] = &lt;span class="str"&gt;"Errors"&lt;/span&gt;;
    routeData.Values[&lt;span class="str"&gt;"action"&lt;/span&gt;] = &lt;span class="str"&gt;"General"&lt;/span&gt;;
    routeData.Values[&lt;span class="str"&gt;"exception"&lt;/span&gt;] = exception;
    
    Response.StatusCode = 500;
    
    &lt;span class="kwrd"&gt;if&lt;/span&gt; (httpException != &lt;span class="kwrd"&gt;null&lt;/span&gt;)
    {
        Response.StatusCode = httpException.GetHttpCode();
        &lt;span class="kwrd"&gt;if&lt;/span&gt; (Response.StatusCode == 403)
            routeData.Values[&lt;span class="str"&gt;"action"&lt;/span&gt;] = &lt;span class="str"&gt;"Forbidden"&lt;/span&gt;;
        &lt;span class="kwrd"&gt;if&lt;/span&gt; (Response.StatusCode == 404)
            routeData.Values[&lt;span class="str"&gt;"action"&lt;/span&gt;] = &lt;span class="str"&gt;"NotFound"&lt;/span&gt;;
    }

    &lt;span class="kwrd"&gt;var&lt;/span&gt; errorsController = (IController)&lt;span class="kwrd"&gt;new&lt;/span&gt; ErrorsController();
    &lt;span class="kwrd"&gt;var&lt;/span&gt; requestContext = &lt;span class="kwrd"&gt;new&lt;/span&gt; RequestContext(&lt;span class="kwrd"&gt;new&lt;/span&gt; HttpContextWrapper(Context), routeData);
    errorsController.Execute(requestContext);
}&lt;/pre&gt;
&lt;br /&gt;
The url in this case would look like http://MySite/mobile/home, so the 'mobile' substring in the url should suffice to make the correct decision.&lt;br /&gt;
&lt;br /&gt;
Pay attention, when you assemble a &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.routing.routedata.aspx"&gt;routedata&lt;/a&gt; object, you want to store the area value in the DataTokens property.&lt;br /&gt;
&lt;br /&gt;
So far we have identified three things we want to hold in account when resolving the correct area: the url, the querystring of the url and the user agent. These decisions are encapsulated into a ResolveDestinationArea extension method on the HttpRequest class.&lt;br /&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt; ResolveDestinationArea(&lt;span class="kwrd"&gt;this&lt;/span&gt; HttpRequest request)
{
    &lt;span class="kwrd"&gt;var&lt;/span&gt; mobileArea = &lt;span class="str"&gt;"mobile"&lt;/span&gt;;
    &lt;span class="kwrd"&gt;var&lt;/span&gt; desktopArea = &lt;span class="str"&gt;"desktop"&lt;/span&gt;;

    &lt;span class="kwrd"&gt;var&lt;/span&gt; uri = &lt;span class="kwrd"&gt;new&lt;/span&gt; Uri(request.Url.AbsoluteUri);
    &lt;span class="rem"&gt;// We want to search in the left part of the absolute uri&lt;/span&gt;
    &lt;span class="kwrd"&gt;var&lt;/span&gt; valueToSearchIn = uri.GetLeftPart(UriPartial.Path);
    
    &lt;span class="rem"&gt;// However if there is a returnUrl querystring, we want to search in that value&lt;/span&gt;
    &lt;span class="kwrd"&gt;var&lt;/span&gt; returnUrl = HttpUtility.ParseQueryString(uri.Query).Get(&lt;span class="str"&gt;"returnUrl"&lt;/span&gt;);
    &lt;span class="kwrd"&gt;if&lt;/span&gt; (!&lt;span class="kwrd"&gt;string&lt;/span&gt;.IsNullOrEmpty(returnUrl))
        valueToSearchIn = returnUrl;
        
    &lt;span class="rem"&gt;// If the url contains 'mobile', redirect to the mobile area    &lt;/span&gt;
    &lt;span class="kwrd"&gt;var&lt;/span&gt; urlContainsMobile = 
    valueToSearchIn.IndexOf(&lt;span class="str"&gt;"mobile"&lt;/span&gt;, StringComparison.OrdinalIgnoreCase) &amp;gt; -1;
    &lt;span class="kwrd"&gt;if&lt;/span&gt; (urlContainsMobile)
        &lt;span class="kwrd"&gt;return&lt;/span&gt; mobileArea;

    &lt;span class="rem"&gt;// If the url contains 'desktop', redirect to the desktop area&lt;/span&gt;
    &lt;span class="kwrd"&gt;var&lt;/span&gt; urlContainsDesktop = 
    valueToSearchIn.IndexOf(&lt;span class="str"&gt;"/desktop/"&lt;/span&gt;, StringComparison.OrdinalIgnoreCase) &amp;gt; -1;
    &lt;span class="kwrd"&gt;if&lt;/span&gt; (urlContainsDesktop)
        &lt;span class="kwrd"&gt;return&lt;/span&gt; desktopArea;

    &lt;span class="rem"&gt;// If the url does not contain 'mobile' nor 'desktop', we have to look at the user agent&lt;/span&gt;
    &lt;span class="kwrd"&gt;return&lt;/span&gt; request.Browser.IsMobileDevice ? mobileArea : desktopArea;
}&lt;/pre&gt;
The implementation of the ResolveDestinationArea method is a bit simplistic. Most sites would want to be a bit more careful about the way the url is parsed. Only the second part of the url really matters (/desktop/ or /mobile/), unless the returnUrl is present in the querystring.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Like I said in the introduction, there are several ways you can use ASP.NET MVC to support mobile devices. I think this pragmatic, maybe naïve solution, works for me. There are only three scenarios where I need to think about mobile devices: when the user enters the homepage, when the user needs to authenticate again and when there is an unhandled exception. All the logic is encapsulated in one little extension method on the request object, making it easily changeable in the future.&lt;br /&gt;
&lt;br /&gt;
There is one extra scenario which I'm thinking of supporting in the future. When the user explicitly chooses a different version, I can save that preference to a cookie, so that I can use that as well to resolve the area in the future.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;b&gt;If you already implemented mobile support in some way, what worked for you?&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-8593083699823823119?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/5Q0XHGJHU36oUDtJ2FRkCQF3D88/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/5Q0XHGJHU36oUDtJ2FRkCQF3D88/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/5Q0XHGJHU36oUDtJ2FRkCQF3D88/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/5Q0XHGJHU36oUDtJ2FRkCQF3D88/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/rp8eJfhB98A" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/8593083699823823119/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2012/01/approach-to-mobile-support-in-aspnet.html#comment-form" title="11 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/8593083699823823119?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/8593083699823823119?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/rp8eJfhB98A/approach-to-mobile-support-in-aspnet.html" title="An approach to mobile support in ASP.NET MVC" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-p6ESdW-A2Qo/Tw855wXEtKI/AAAAAAAABNU/fkJ-sO8Ok_w/s72-c/Areas_Mobile_Solution.PNG" height="72" width="72" /><thr:total>11</thr:total><feedburner:origLink>http://www.jefclaes.be/2012/01/approach-to-mobile-support-in-aspnet.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0AFSX8yfSp7ImA9WhRXGU4.&quot;"><id>tag:blogger.com,1999:blog-1165127106246535168.post-3536457332066964215</id><published>2011-12-26T21:35:00.000+01:00</published><updated>2011-12-26T21:35:18.195+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-26T21:35:18.195+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="General" /><title>2011 Annual Review</title><content type="html">&lt;br /&gt;
Since the end of the year is approaching, I would like to look back on 2011 and take a peek at 2012. Unlike the years before, I'm hardly setting any goals this year. Interests change so quickly, and new opportunities present themselves so regularly, I feel like it might be ignorant to set these things in stone.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;My career&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
The year started out interesting, working on a brand new product for fire departments at Ferranti Computer Systems. Although my role in this project should have been fairly satisfying, I still felt like my position back then failed to fill a certain void. That's why I decided to take the leap into &lt;a href="http://jclaes.blogspot.com/2011/08/high-hopes.html"&gt;the Great Unknown&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
In September, I started working for &lt;a href="http://www.euri.com/"&gt;Euricom&lt;/a&gt; as a consultant. So far, I haven't regretted making that decision at all, I ended up in a group of intelligent, progressive and enjoyable people.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Blog&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
This year I managed to write 69 posts (70 including this one), which is a bit more than the year before.&lt;br /&gt;
&lt;br /&gt;
Plenty of posts have gotten &lt;a href="http://jclaes.blogspot.com/2011/12/2011s-most-read-posts.html"&gt;decent attention&lt;/a&gt; this year, making the traffic tripple compared to 2010. And no matter how other bloggers claim you shouldn't care about your blog statistics, this does mean a bunch to me.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-o1rP8xmqDDg/TvjXTkXH9FI/AAAAAAAABNA/brC-7WrDNSM/s1600/BlogTraffic.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="65" src="http://2.bp.blogspot.com/-o1rP8xmqDDg/TvjXTkXH9FI/AAAAAAAABNA/brC-7WrDNSM/s640/BlogTraffic.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
The main topics I wrote about this year were mostly web related (HTML5, JavaScript and ASP.NET MVC). I even wrote some opinionated posts this year, which I found harder, but also more rewarding.&lt;br /&gt;
&lt;br /&gt;
Last year, I set a goal to get better at MVC and to at least look at WebMatrix. I spent a large part of the year building things in ASP.NET MVC at the day job, so that goal was easily achieved. I built &lt;a href="http://jclaes.blogspot.com/2011/09/real-developer-knows-when-to-pull-plug.html"&gt;one small thing&lt;/a&gt; in &lt;a href="http://jclaes.blogspot.com/2011/05/my-thoughts-on-webmatrix.html"&gt;WebMatrix&lt;/a&gt;, which was enough to give me a feel of what WebMatrix is all about.&lt;br /&gt;
&lt;br /&gt;
My guess is that I will cover similar topics next year. My faith in the web has only grown stronger. I think the rise and rise of the browser and JavaScript provides us with unseen opportunities, but also with an enormous amount of challenges. I am really eager to start looking more into &lt;i&gt;serious&lt;/i&gt; JavaScript development.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Community&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
I think I did fairly well this year. I attended several local community meetings and got to to know a lot of nice people in the process.&lt;br /&gt;
&lt;br /&gt;
The highlight probably was &lt;a href="http://jclaes.blogspot.com/2011/04/video-slides-and-source-from-my.html"&gt;my own talk on WebSockets at HTML5 Webcamps&lt;/a&gt;. I really enjoyed that experience. I do not aspire to be a speaker though, but should I have interesting things to talk about, I probably won't reject invitations.&lt;br /&gt;
&lt;br /&gt;
I'm not sure if I will be doing a lot of community events in 2012. I often don't seem to get out of them what I hoped for. Also, Euricom regularly organizes meetups to talk about all things software development. These will already consume plenty of time.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Travelling&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
This year my girlfriend and I did &lt;a href="http://jclaes.blogspot.com/2011/09/once-upon-time-in-west.html"&gt;a three-week roadtrip along the West Coast&lt;/a&gt;. This trip was by far the best time of our lives. We are determined to undertake similar projects in the future again.&lt;br /&gt;
&lt;br /&gt;
Next to that, we revisited the Czech Republic, taking plenty of time to &lt;a href="http://jclaes.blogspot.com/2011/04/prague-impressions.html"&gt;visit Prague&lt;/a&gt; this time.&lt;br /&gt;
&lt;br /&gt;
We're not sure what 2012 will bring. Right now, we are looking into something more nearby, Italy.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Sport/Health&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Last year I picked up boxing, but I had to give that up due to scheduling conflicts. I have been working a tweaked &lt;a href="http://en.wikipedia.org/wiki/P90X"&gt;P90X routine&lt;/a&gt; twice a week instead, which is very much recommended.&lt;br /&gt;
&lt;br /&gt;
I reasonably succeeded in achieving my running ambitions. Altogether, I ran somewhere around 800 kilometers (= 500 miles) this year. This summer, I probably was in the best shape of my life, running 15 km pretty (= 9 miles) comfortably. Although the graph below suggests that I'm slacking, it's only partially true. I have been doing shorter (and higher paced) runs lately, increasing the intensity. Running is probably one of the sports that give me the most energy in return, so I intend to stay at it.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-QBhq3CQa_LA/TvjYBFyVsWI/AAAAAAAABNM/qZC7-JWoEY4/s1600/nikeplus.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="186" src="http://1.bp.blogspot.com/-QBhq3CQa_LA/TvjYBFyVsWI/AAAAAAAABNM/qZC7-JWoEY4/s400/nikeplus.PNG" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
2011 has been an eventful year for me. In 2012, I hope to be able to keep doing the things I love, surrounded by the people I love. Trying to live life as much as I can.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;I wish you and your family the best for 2012. That the new year may bring you health, love and plenty of hacking.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1165127106246535168-3536457332066964215?l=www.jefclaes.be' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/0qJu84FY-K1EjqMm438ZaxgcpwQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0qJu84FY-K1EjqMm438ZaxgcpwQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/0qJu84FY-K1EjqMm438ZaxgcpwQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0qJu84FY-K1EjqMm438ZaxgcpwQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/DiaryOfAnetDeveloperByJefClaes/~4/dsJWwzM_prg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.jefclaes.be/feeds/3536457332066964215/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.jefclaes.be/2011/12/2011-annual-review.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3536457332066964215?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1165127106246535168/posts/default/3536457332066964215?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/DiaryOfAnetDeveloperByJefClaes/~3/dsJWwzM_prg/2011-annual-review.html" title="2011 Annual Review" /><author><name>Jef Claes</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-ZzXnkFyDsxU/AAAAAAAAAAI/AAAAAAAABEI/mQdWG3NJm2A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-o1rP8xmqDDg/TvjXTkXH9FI/AAAAAAAABNA/brC-7WrDNSM/s72-c/BlogTraffic.PNG" height="72" width="72" /><thr:total>5</thr:total><feedburner:origLink>http://www.jefclaes.be/2011/12/2011-annual-review.html</feedburner:origLink></entry></feed>

