<?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.dantup.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0"> <id>tag:blog.dantup.com,2009:/</id> <updated>2010-03-19T18:11:00Z</updated> <title type="text">Danny Tuppeny</title> <subtitle type="text">.NET developer by day, Google App Engine developer by night</subtitle>  <link rel="alternate" type="text/html" href="http://blog.dantup.com/" /> <author> <name>Danny Tuppeny</name> <uri>http://blog.dantup.com/</uri> </author> <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.dantup.com/DanTup" /><feedburner:info uri="dantup" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry> <id>tag:blog.dantup.com,2010-03-19:/2010/03/is-google-code-readying-git-support</id> <published>2010-03-19T18:11:00Z</published> <updated>2010-03-19T18:11:00Z</updated> <category term="Google Code" /> <title type="text">Is Google Code Readying Git Support?</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;It looks like Google Code could be working on adding Git support (at last)! I've been following the &lt;a href="http://code.google.com/p/support/issues/detail?id=2454"&gt;native Git support issue&lt;/a&gt; in the issue tracker and some interesting comments have appeared today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(8) &lt;strong&gt;sussman&lt;/strong&gt;: Tell you what... as soon as git can do HTTP reasonably, let us know, and we can look 
into the issue of git on googlecode.  :-)&lt;/li&gt;
&lt;li&gt;(12) &lt;strong&gt;rickvug&lt;/strong&gt;: Git over HTTP is now more efficient as of version 1.6.6. For more information see &lt;a href="http://progit.org/2010/03/04/smart-http.html"&gt;http://progit.org/2010/03/04/smart-http.html&lt;/a&gt;. I guess this is now something to look into? :) &lt;/li&gt;
&lt;li&gt;(13) &lt;strong&gt;sussman&lt;/strong&gt;: We're very aware of this, since it was a googler who wrote the new HTTP protocol. :-)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fact that Google are "very aware" (not just aware, but &lt;em&gt;very&lt;/em&gt; aware!) and one of their own staff has had a hand in this, I wouldn't be at all surprised if Git support showed up soon.&lt;/p&gt;

&lt;p&gt;Watch this space! (or, the issue tracker!)&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/Z_69qvy1DFQ" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/Z_69qvy1DFQ/is-google-code-readying-git-support" title="Is Google Code Readying Git Support?" /> <feedburner:origLink>http://blog.dantup.com/2010/03/is-google-code-readying-git-support</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-03-14:/2010/03/intermittent-server-createobject-failed-asp-0177-8000ffff-error-creating-net-com-components</id> <published>2010-03-14T11:32:00Z</published> <updated>2010-03-14T11:32:00Z</updated> <category term=".NET" /> <title type="text">(Solved) Intermittent Server."CreateObject Failed" 'ASP 0177 : 8000ffff' Error Creating .NET COM Components</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Over the past year or so we've been wrestling with an intermittent error from classic ASP when trying to instantiate .NET components with Server.CreateObject. Everything works fine 90% of the time, and now and then we'll start seeing this error:&lt;/p&gt;

&lt;pre&gt;
Server object error 'ASP 0177 : 8000ffff' 
Server.CreateObject Failed 
&amp;lt;FileName&amp;gt;.asp, line &amp;lt;LineNumber&amp;gt;
8000ffff
&lt;/pre&gt;

&lt;p&gt;Once this error starts happening, it generally persists until we move the application to another application pool or restart IIS. &lt;strong&gt;Recycling the app pool does not fix the problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, the error message and number are fairly generic, so it's been pretty tricky to track down. There are lots of people with very similar issues posting all over the web that have solved them in various ways. &lt;strong&gt;None of the solutions we found have ever worked for us&lt;/strong&gt;. Most of them were people seeing the error all the time (eg. permissions errors), but we found very few people seeing our behaviour where the code would just randomly stop working.&lt;/p&gt;

&lt;p&gt;After many, many months of searching, we found some reports of similar behaviour being caused by installing an Internet Explorer 7 patch. Many people were rolling the patch back with some success. Rolling back patches doesn't seem like the best thing in the world, so we've always avoided the complications that go with it.&lt;/p&gt;

&lt;h2&gt;The Solution&lt;/h2&gt;

&lt;p&gt;Eventually, we found the solution. It's in &lt;a href="http://support.microsoft.com/kb/945701"&gt;this Microsoft knowledgebase article &lt;strong&gt;KB945701&lt;/strong&gt;&lt;/a&gt;. The problem appears to be a failure to read some IE-related values from the registry. The hotfix on the page above (which is included in the latest service pack, so &lt;strong&gt;you probably do not need the hotfix&lt;/strong&gt;) adds the ability to ignore these errors by setting a registry key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Locate and then click the following registry subkey:
&lt;blockquote&gt;&lt;strong&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\&lt;br /&gt;FeatureControl\FEATURE_IGNORE_ZONES_INITIALIZATION_FAILURE_KB945701&lt;/strong&gt;&lt;/blockquote&gt;
&lt;strong&gt;Note&lt;/strong&gt; If the FEATURE_IGNORE_ZONES_INITIALIZATION_FAILURE_KB945701 subkey does not exist, you must manually create it.&lt;/li&gt;
&lt;li&gt;Right-click &lt;strong&gt;FEATURE_IGNORE_ZONES_INITIALIZATION_FAILURE_KB945701&lt;/strong&gt;, point to &lt;strong&gt;New&lt;/strong&gt;, and then click &lt;strong&gt;DWORD Value&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Type &lt;strong&gt;w3wp.exe&lt;/strong&gt; to name the new registry entry, and then press ENTER.&lt;/li&gt;
&lt;li&gt;Right-click &lt;strong&gt;w3wp.exe&lt;/strong&gt;, and then click &lt;strong&gt;Modify&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Value data&lt;/strong&gt; box, type &lt;strong&gt;1&lt;/strong&gt;, and then click &lt;strong&gt;OK&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After setting this registry key, a simple app pool restart will apply the change. No longer will your .NET COM components randomly stop working with no real solution except shuffling application pools!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/04iJYivyYCY" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/04iJYivyYCY/intermittent-server-createobject-failed-asp-0177-8000ffff-error-creating-net-com-components" title="(Solved) Intermittent Server.&quot;CreateObject Failed&quot; 'ASP 0177 : 8000ffff' Error Creating .NET COM Components" /> <feedburner:origLink>http://blog.dantup.com/2010/03/intermittent-server-createobject-failed-asp-0177-8000ffff-error-creating-net-com-components</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-03-09:/2010/03/wanted-artist-for-iphone-game</id> <published>2010-03-09T22:30:00Z</published> <updated>2010-03-14T12:16:00Z</updated> <category term="iPhone" /> <title type="text">Wanted: Artist for iPhone Game</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;I posted this &lt;a href="http://www.google.com/buzz/danny.tuppeny/QQg6LZJvPWX/Working-on-an-iPhone-game-but-cant-draw-for-toffee"&gt;on Buzz&lt;/a&gt;, but thought it worth also repeating here. I'm working on a few iPhone games, but I'm no good when it comes to graphics. I'm looking for someone that might be able to do the graphics for me. One of the games involves a penguin with an umbrella, falling from the sky :-)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Got a designer signed up. Time to start coding!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/HiyO8Qqj5RA" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/HiyO8Qqj5RA/wanted-artist-for-iphone-game" title="Wanted: Artist for iPhone Game" /> <feedburner:origLink>http://blog.dantup.com/2010/03/wanted-artist-for-iphone-game</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-02-11:/2010/02/google-buzz-whats-it-all-about</id> <published>2010-02-11T22:09:00Z</published> <updated>2010-02-11T22:09:00Z</updated> <category term="Google Buzz" /> <title type="text">Google Buzz - What's it all About?</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Like every man and his dog, I've been &lt;a href="http://www.google.com/profiles/danny.tuppeny#buzz"&gt;playing around with Google Buzz&lt;/a&gt; the last few days. So far it's looking quite good. It's not entirely accurate to call it a Twitter clone, but it certainly works in a very similar way.&lt;/p&gt;

&lt;p&gt;If you'd like to have your blog posts (or other external content) posted on Twitter, you'd need to find a service that uses the Twitter API to post messages. Google Buzz works the opposite way, and Google will pull updates from other services (Twitter, Flickr, Blogs, Youtube, Google Chat, Picasa, Google Reader) and show them in your Buzz feed.&lt;/p&gt;

&lt;p&gt;Currently, there's no built-in way to send your Buzz updates back to these services, though there are a few 3rd party services and workarounds to make this work.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.google.com/buzz/dclinton/XxER6oP4WGe/The-best-way-to-get-a-sense-of-where-the-Buzz-API"&gt;DeWitt Clinton posted an interesting Buzz&lt;/a&gt; (?) about where Google Buzz is heading. It focuses on being open and interoperable. Rather than a closed system like Twitter, where you can only share with other Twitter users, Google are promoting an open system where different hosts can exchange messages with each other, like how other systems like Email and Google Wave work.&lt;/p&gt;

&lt;p&gt;I have high hopes for Google Buzz. For too long have we put up with Twitter's fail whale and erratic API. It's time for their monopoly to end and everyone to play nicely together.&lt;/p&gt;

&lt;p&gt;If you're giving Google Buzz a try, you can follow me using the button in the right column of &lt;a href="http://www.google.com/profiles/danny.tuppeny#buzz"&gt;my Google Profile page&lt;/a&gt;. If not, you can still find &lt;a href="http://twitter.com/DanTup"&gt;my ramblings on Twitter&lt;/a&gt;.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/8DrfSHCuGGU" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/8DrfSHCuGGU/google-buzz-whats-it-all-about" title="Google Buzz - What's it all About?" /> <feedburner:origLink>http://blog.dantup.com/2010/02/google-buzz-whats-it-all-about</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-01-28:/2010/01/world-of-warcraft-addon-updaters</id> <published>2010-01-28T21:21:00Z</published> <updated>2010-01-28T21:21:00Z</updated> <category term="Games" /> <category term="MMOs" /> <title type="text">World of Warcraft Addon Updaters - What Do You Use and Why?</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;This post is for the World of Warcraft players among you. More specifically, those that use Addons. I'm interested in whether you use an addon updater (that is, software to keep your addons up-to-date). If so, which one, and what you do and don't like about it.&lt;/p&gt;

&lt;p&gt;I don't want to get into a debate about Curse, WoWInterface and WoWMatrix (that ship has sailed, and it's been "discussed" more than enough), so please try to avoid mentioning that ;-)&lt;/p&gt;

&lt;p&gt;Feedback is encouraged in the comments, the &lt;a href="http://forums.wow-europe.com/thread.html?sid=1&amp;topicId=12304323555&amp;postId=123028971748"&gt;World of Warcraft Forums&lt;/a&gt;, or &lt;a href="http://twitter.com/?status=@NewsWoW+I+use+..."&gt;tweeted&lt;/a&gt; &lt;a href="http://twitter.com/NewsWoW"&gt;@NewsWoW&lt;/a&gt;.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/I2dOIpxmj60" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/I2dOIpxmj60/world-of-warcraft-addon-updaters" title="World of Warcraft Addon Updaters - What Do You Use and Why?" /> <feedburner:origLink>http://blog.dantup.com/2010/01/world-of-warcraft-addon-updaters</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-01-22:/2010/01/open-sourcing-google-wave-notifier</id> <published>2010-01-22T17:44:00Z</published> <updated>2010-01-22T17:44:00Z</updated> <category term=".NET" /> <category term="Google Wave" /> <category term="Google Wave Notifier" /> <title type="text">Open Sourcing Google Wave Notifier</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Yesterday I finally got around to do something I've been planning on doing for a number of weeks. I uploaded Google Wave Notifier to Google Code. From today, &lt;a href="http://code.google.com/p/google-wave-notifier/"&gt;Google Wave Notifier is Open Source&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;In hindsight, I should have done this much sooner. The app is now very stable and contains all the features that I planned to implement (and more!). I'm still getting lots of feature requests, and I really wish I could implement them all. However, in reality, I just don't have time. By sharing the code with the world, all of these features need not go unimplemented!&lt;/p&gt;

&lt;p&gt;I still intend to work through bugs and some feature requests myself, though it's unlikely to be at the rate of previous releases. With help from the community, hopefully we'll still see regular releases and new functionality.&lt;/p&gt;

&lt;p&gt;For more info, or to download the source, please visit the &lt;a href="http://code.google.com/p/google-wave-notifier/"&gt;Google Wave Notifier page on Google Code&lt;/a&gt;.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/ZOUB1QAiIkM" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/ZOUB1QAiIkM/open-sourcing-google-wave-notifier" title="Open Sourcing Google Wave Notifier" /> <feedburner:origLink>http://blog.dantup.com/2010/01/open-sourcing-google-wave-notifier</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-01-17:/2010/01/generic-redirection-script-for-google-app-engine</id> <published>2010-01-17T11:02:00Z</published> <updated>2010-01-17T11:02:00Z</updated> <category term="Google App Engine" /> <title type="text">Generic 301 Redirection Script for Google App Engine</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;&lt;small&gt;Although this post is about writing a redirect script for App Engine, it doesn't require that any of the sites are hosted on App Engine, so it could be useful to you even if you're hosting .NET websites elsewhere, but need to handle redirecting old domains.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;If you believe what &lt;a href="http://derek-says.blogspot.com/"&gt;Derek Says&lt;/a&gt;, I &lt;a href="/2010/01/new-domain-dantup-com-please-update-your-links"&gt;change my domain&lt;/a&gt; every 5 minutes. While this might be a slight exaggeration, I have moved many domains recently and needed to deal with the usual problems this brings: search engine rankings and existing inbound links.&lt;/p&gt;

&lt;h2&gt;301 vs 302 Redirects&lt;/h2&gt;

&lt;p&gt;The most common type of redirect you'll see on the web is a "302 Temporary Redirect". This is what most frameworks will output when you redirect (eg. Response.Redirect() in ASP/.NET or self.redirect() in App Engine). The "Temporary" part of this redirect means the redirect is a one-off and will not always be served. This is handy for example, for redirecting a user back to a page after logging in to your site. Since this page may be different each time, the redirect is not fixed.&lt;/p&gt;

&lt;p&gt;The other type of redirect, 301, is a "Permanent Redirect". Most frameworks support these redirects without outputting your own headers. Eg. in App Engine you can use self.redirect(url, permanent=True). This type of redirect means the redirect will &lt;em&gt;always&lt;/em&gt; occur to the same link, and that clients are free to bypass your script and assume it will always go to the same place. This is the redirect that we want to use when moving domains. It tells search engines that this page has moved, permanently, and they may associate any rankings for the old page, with the new page.&lt;/p&gt;

&lt;h2&gt;Generic App Engine Redirect Script&lt;/h2&gt;

&lt;p&gt;So, now we know what type of redirect to use, it's time to build a script to handle our redirects. If you have multiple domains like me, it makes sense to write a script that can handle them all in one go. I've decided to set up a new App Engine app with the sole purpose of redirects for all my domains from this point forward.&lt;/p&gt;

&lt;p&gt;Since I recently decided to move everything from dantup.me.uk to dantup.com, I had a few different domains to redirect. blog.dantup.me.uk needs to map to blog.dantup.com, wavenotifier.dantup.me.uk needs to map to wavenotifier.dantup.com and all of the unused domains (eg. dantup.com, www.dantup.com, tuppeny.com, etc.) need to map to the root of my blog.&lt;/p&gt;

&lt;h2&gt;App.yaml Setup&lt;/h2&gt;

&lt;p&gt;If we're gong to be redirecting all incoming requests, we need to route all requests through our script. This is why it's important to use a new App Engine app rather than piggy-back onto an existing one. We'll see up our App.yaml file to route all requests into a script called main.py.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
application: myapp-redir
version: 1
runtime: python
api_version: 1

handlers:

- url: /.*
 script: main.py
&lt;/pre&gt;

&lt;p&gt;Next we need to define a way to hold all of the data we'll need to perform our redirects. As well as the old domain and the new domain, we need to know whether to map urls from the request onto the new domain, or just redirect to the root. Eg., when I moved my blog from blog.dantup.me.uk, I wanted blog.dantup.me.uk/mypost to redirect to blog.dantup.com/mypost. However I want tuppeny.com/anything to just redirect to the root, blog.dantup.com.&lt;/p&gt;

&lt;p&gt;A dictionary seems to be a good way to store this data because we can perform lookups on the domain quickly, and we can store the new domain and a boolean for the url mapping as a tuple.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
# Old Domain: New Domain, Map urls (else redirects to root)
urls = {
    'www.dantup.com': ('blog.dantup.com', False),
    'www.dantup.me.uk': ('blog.dantup.com', False),
    'www.tuppeny.com': ('blog.dantup.com', False),
    'dantup-redir.appspot.com': ('blog.dantup.com', False),
    'blog.dantup.me.uk': ('blog.dantup.com', True),
    'feeds.dantup.me.uk': ('feeds.dantup.com', True),
    'wavenotifier.dantup.me.uk': ('wavenotifier.dantup.com', True),
    'wavenotifier.tuppeny.com': ('wavenotifier.dantup.com', True),
    'go.dantup.me.uk': ('go.dantup.com', True),
    'go.tuppeny.com': ('go.dantup.com', True),
}
&lt;/pre&gt;

&lt;p&gt;In addition to this mapping, we should declare a default domain, so if any requests make it to our script that don't have a mapping, we can redirect there. We'll use a 302 and also log and email this, since it's probably a mistake.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
DEFAULT_URL = 'http://blog.dantup.com/'
&lt;/pre&gt;

&lt;p&gt;This is all looking a little complicated, so it makes sense to build in a way to test our mappings without having to set up lots of entries in the hosts file. I've decided to declare a boolean that enables/disables testing. When testing is enabled, if you navigate to /test then it will output a bunch of URLs and the locations they'll redirect to. We'll keep a list of URLs to test in the code:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
ALLOW_TEST = True

test_urls = [
    'http://www.dantup.me.uk',
    'http://www.dantup.me.uk/',
    'http://www.dantup.me.uk/blah',
    'http://www.dantup.com',
    'http://www.dantup.com/',
    'http://www.dantup.com/blah',
    'http://www.tuppeny.com',
    'http://www.tuppeny.com/',
    'http://www.tuppeny.com/blah',
    'http://blog.dantup.me.uk',
    'http://blog.dantup.me.uk/',
    'http://blog.dantup.me.uk/2010/mytest',
    'http://feeds.dantup.me.uk',
    'http://feeds.dantup.me.uk/',
    'http://feeds.dantup.me.uk/2010/mytest',
    'http://wavenotifier.dantup.me.uk',
    'http://wavenotifier.dantup.me.uk/',
    'http://wavenotifier.dantup.me.uk/2010/mytest',
    'http://wavenotifier.tuppeny.com',
    'http://wavenotifier.tuppeny.com/',
    'http://wavenotifier.tuppeny.com/2010/mytest',
    'http://go.dantup.me.uk',
    'http://go.dantup.me.uk/',
    'http://go.dantup.me.uk/mytest',
    'http://go.tuppeny.com',
    'http://go.tuppeny.com/',
    'http://go.tuppeny.com/mytest',
]
&lt;/pre&gt;

&lt;p&gt;Now we've set the data up, it's time to write the code to handle the redirects. To allow for easy testing, we'll first create a method that takes a URL and returns where it should map to. This will be called by both the tests and the real redirects.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
def get_redirect_url(url):
    scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
	
    # Discard any port number from the hostname
    netloc = netloc.split(':', 1)[0]
	
    # Fix empty paths to be just '/' for consistency
    if path == '':
        path = '/'
	
    # Check if we have a mapping for this domain
    if netloc in urls:
        # Grab the redirect info tuple
        redirect_info = urls[netloc]
        # Root redirects
        if not redirect_info[1]:
            return 'http://' + redirect_info[0] + '/'
        # Redirects with paths
        else:
            return urlparse.urlunsplit(['http', redirect_info[0], path, query, fragment])
    # No mapping, so return None
    else:
        return None
&lt;/pre&gt;

&lt;p&gt;This code is fairly straight forward. It uses our mappings dictionary to look up the domain to redirect to, and whether to include the path information. Next we need to write the code that actually handles incoming requests. This will check whether test mode is enabled, and if the request is '/test'. If so, it will output a table using out list of test URLs above. Otherwise it will call the same method, but actually perform a redirect. If we couldn't match a domain, we'll use a 302 redirect to the default URL, and send an email/log.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
    def get(self):
        # If we're allowed to test (eg. local), and requested /test, then output the test
        if ALLOW_TEST and self.request.path == '/test':
            self.response.out.write('&amp;lt;h1&amp;gt;testing&amp;lt;/h1&amp;gt;')
            self.response.out.write('&amp;lt;table&amp;gt;')
            for test_url in test_urls:
                self.response.out.write('&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;' + test_url + '&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;nbsp;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;' + get_redirect_url(test_url) + '&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;')
            self.response.out.write('&amp;lt;/table&amp;gt;')

        # Otherwise, just go ahead and redirect
        else:
            # Perform redirect
            url = get_redirect_url(self.request.url)
			
            if url:
                logging.info('Redirecting ' + self.request.url + ' to ' + url);
                self.redirect(url, permanent=True)

            else:
                # Log that we didn't know what this was, and redirect to a good default
                logging.error('Unable to redirect this url: ' + self.request.url);
                mail.send_mail_to_admins(
                    sender='"DanTup Redirect" &amp;lt;myemail@mydomain.com&amp;gt;',
                    subject='Redirect Script Error',
                    body='Unable to redirect this url: ' + self.request.url
                )

                # Don't do permanent (301), since we don't know what this is.
                # Move it into the dictionary above if needed
                self.redirect(DEFAULT_URL)
&lt;/pre&gt;

&lt;p&gt;There's a lot of code there, but it should be fairly simple to understand. We handle the test mode by just spitting out a table of our test URLs and the redirects. We can then look over this manually to make sure everything looks correct before going live. Otherwise we work out the redirect for the current request and redirect. If no URL was found, we log and email the attempt, and redirect to the default URL. When the email comes through, we can then add the domain we missed to the mappings dictionary and specify how it should be handled.&lt;/p&gt;

&lt;h2&gt;Naked Domains on App Engine&lt;/h2&gt;

&lt;p&gt;You'll notice that "naked" versions of my domains are missing from the script. This is because App Engine doesn't support naked domains, so these are all set up as redirects in my registrars control panel. They support 301 redirects with the same URL mapping options (eg. redirect all to root, or copy the path).&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;It didn't take much to write a simple generic redirect script, and now we can handle redirects for all domains in the future. This simply needs setting up on App Engine and any number of domains pointing at it. It's worth noting that you can point multiple domains from different Google Apps accounts at the same App Engine app. There is no requirement to use App Engine for hosting your sites in order for this script to be used. The fact that blog.dantup.com is hosted on App Engine doesn't change anything. You could redirect to an Azure site if you wished! Though you &lt;a href="/2009/12/microsoft-windows-azure-vs-google-app.html"&gt;probably wouldn't&lt;/a&gt; &lt;a href="http://jacksonshaw.blogspot.com/2010/01/is-azure-priced-too-high.html"&gt;want to&lt;/a&gt; ;-)&lt;/p&gt;

&lt;h2&gt;Full Listing&lt;/h2&gt;

&lt;p&gt;For convenience, you can get &lt;a href="/files/generic_app_engine_redirect_script.txt" title="A full copy of the generic App Engine redirect script"&gt;a copy of the full script&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218312"&gt;Developing with Google App Engine by Eugene Ciurana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/059680069X"&gt;Using Google App Engine by Charles Severance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/fGuV4dpyXIM" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/fGuV4dpyXIM/generic-redirection-script-for-google-app-engine" title="Generic 301 Redirection Script for Google App Engine" /> <feedburner:origLink>http://blog.dantup.com/2010/01/generic-redirection-script-for-google-app-engine</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-01-16:/2010/01/google-app-engine-benchmarks-db-put-performance</id> <published>2010-01-16T20:23:00Z</published> <updated>2010-01-16T20:23:00Z</updated> <category term="Google App Engine" /> <title type="text">Google App Engine Benchmarks - db.put() Performance</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Over the past few weeks as I've been using Google App Engine, I've come across people requesting benchmarks so they can compare App Engine performance to other solutions before they try it out. I don't really think comparing Google App Engine and it's Datastore to something like Azure and SQL Server is all that useful (because you'd generally structure things very different on each platform), but either way, it's interesting to see how things perform.&lt;/p&gt;

&lt;p&gt;As well as comparing numbers with other platforms, I think it's worthwhile for App Engine developers to know how the different APIs perform (eg. the difference between fetching something from Memcache and the Datastore). Over the next few blog posts, I'm hoping to provide some numbers I gathered on the production App Engine servers. Please bear in mind that App Engine is still considered a "preview" and as things evolve, performance may change (hopefully for the better!).&lt;/p&gt;

&lt;p&gt;The first set of data I gathered was on the performance of db.put(), and more specifically, the difference between calling db.put() multiple times with single entities vs calling it with a whole bunch of entities at once. It's very easy to call db.put() multiple times in a single request, but it's usually trivial to change your code to save all the entities in a single call. I thought that illustrating this difference with some pretty graphs might encourage people to use batch operations.&lt;/p&gt;

&lt;p&gt;As always, the size and shape of your data will affect your timings. In my sample I used a small entity with only three string properties (and a key_name). You can grab a copy of the data I gathered in CSV format: &lt;a href="/files/app_engine_benchmark_put.csv"&gt;App Engine db.Put() Benchmarks&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;db.put() Performance&lt;/h2&gt;

&lt;p&gt;Each test was run 10 times, and both the mean and median values plotted on a chart. I did this for varying numbers of entities from 10 to 500 (since db.put() has a limit of 500 entities).&lt;/p&gt;

&lt;a href="/pi/bm_put_perf.png"&gt;&lt;img src="/pi/bm_put_perf_thumb.png" alt="Datastore db.put() performance comparison between individual and batch calls" /&gt;&lt;/a&gt;

&lt;p&gt;As expected, in all cases, the batch method out-performs calling db.put() many times. Both operations scale very linearly (note the first 3 points are not increments of 100, which is why the line doesn't appear straight). For very small numbers (eg. 10 or less) the results are very similar, but as the number of entities increases, it becomes more important to batch up your requests.&lt;/p&gt;

&lt;p&gt;It's worth noting that when you call db.put() with multiple entities, they are not combined into a transaction. If one of the writes fails, an error is raised, but any entities that have already been saved are not rolled back. If you want to update multiple entities are part of a transaction, you must do this the usual way by giving them the same parent and using run_in_transaction.&lt;/p&gt;

&lt;h2&gt;db.put() Performance Consistency&lt;/h2&gt;

&lt;p&gt;Since each test was run 10 times, I had enough data to draw a chat showing the consistency of the db.put() performance. The closer a line is to being completely horizontal, the more consistent the write performance is.&lt;/p&gt;

&lt;h2&gt;Individual db.put() Performance&lt;/h2&gt;

&lt;a href="/pi/bm_put_ind.png"&gt;&lt;img src="/pi/bm_put_ind_thumb.png" alt="Consistency of individual datastore db.put() calls" /&gt;&lt;/a&gt;

&lt;h2&gt;Batch db.put() Performance&lt;/h2&gt;

&lt;a href="/pi/bm_put_batch.png"&gt;&lt;img src="/pi/bm_put_batch_thumb.png" alt="Consistency of batch datastore db.put() calls" /&gt;&lt;/a&gt;

&lt;p&gt;As you can see, the calls are fairly consistent, though the more entities you're saving, the bigger the variance. Although from the graph it looks like the batch calls are less consistent, they graph is drawn at a different scale. The batch calls vary my up to 1.5 seconds, whereas the individual calls vary by up to 5 seconds!&lt;/p&gt;

&lt;p&gt;I hope to run some more benchmarks over the coming weeks showing the difference between other APIs such as using Memcache to avoid going to the Datastore on every request.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218312"&gt;Developing with Google App Engine by Eugene Ciurana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/059680069X"&gt;Using Google App Engine by Charles Severance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/7Kz-BGX1Cws" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/7Kz-BGX1Cws/google-app-engine-benchmarks-db-put-performance" title="Google App Engine Benchmarks - db.put() Performance" /> <feedburner:origLink>http://blog.dantup.com/2010/01/google-app-engine-benchmarks-db-put-performance</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-01-09:/2010/01/new-domain-dantup-com-please-update-your-links</id> <published>2010-01-09T12:24:00Z</published> <updated>2010-01-09T12:24:00Z</updated> <category term="Google App Engine" /> <category term="Google Wave Notifier" /> <title type="text">New Domain - DanTup.com - Please Update Your Links!</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;After much discussion and debate, I've decided to move all of my sites from the dantup.me.uk domain to dantup.com. The .me.uk domain came free with some hosting and was never really intended to become my "main" domain, but it did. It looked like Google were ranking me lower in Google.com vs Google.co.uk because they believed my content to be more relevant to the UK, which isn't really the case.&lt;/p&gt;

&lt;p&gt;Over the last few hours I've been slowly migrating things and setting up redirects. An unfortunate side effect is that you may get duplicate posts in my feed (again). Apologies for that, but once everything is moved over, hopefully it's now here to stay :-)&lt;/p&gt;

&lt;p&gt;Although not necessary (old URLs will 301-redirect to new ones), if you have any links to any of my sites/feed, it'd be nice if you could update them to the .com to avoid additional redirects. Here's the new URLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Blog:&lt;/strong&gt; &lt;a href="http://blog.dantup.com/"&gt;http://blog.dantup.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feed:&lt;/strong&gt; &lt;a href="http://feeds.dantup.com/DanTup"&gt;http://feeds.dantup.com/DanTup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wave Notifier:&lt;/strong&gt; &lt;a href="http://wavenotifier.dantup.com/"&gt;http://wavenotifier.dantup.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It looks like some things that I've moved from one Google service to another (eg. Feedburner to App Engine) are taking some time to make it through the Google network, so the old domains may behave strangle until that's done. Please let me know if you notice anything strange.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/sX-QZGFqKqc" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/sX-QZGFqKqc/new-domain-dantup-com-please-update-your-links" title="New Domain - DanTup.com - Please Update Your Links!" /> <feedburner:origLink>http://blog.dantup.com/2010/01/new-domain-dantup-com-please-update-your-links</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-01-06:/2010/01/app-engine-entity-groups-contention-and-transactions</id> <published>2010-01-06T17:16:00Z</published> <updated>2010-01-06T17:16:00Z</updated> <category term="Google App Engine" /> <title type="text">Entity Groups, Contention and Transactions in Google App Engine</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;When I started learning Google App Engine, I misunderstood a fairly fundamental part of the datastore - Entity Groups. The documentation is not very clear, and over the weeks I've seen many questions asked in videos and forums that suggest I'm not the only one that misunderstood. I thought it was worth a blog post to explain.&lt;/p&gt;

&lt;h2&gt;Entity Groups are not Tables!&lt;/h2&gt;

&lt;p&gt;The misunderstanding is that people think of entity groups like table in SQL. This is not the case. If you create two entities that are of the same kind, by default, they &lt;em&gt;do not&lt;/em&gt; belong to the same entity group. Unless you specifically choose to put them in the same group, all of your entities will be in a seperate entity group. This means if you have 1,000 entities of the same kind, you have 1,000 entity groups containing one entity each! This is not a bad thing, you shouldn't put things into the same entity groups unless you need to, since updates to an entity lock the whole entity group.&lt;/p&gt;

&lt;h2&gt;Putting Entities in the Same Entity Group&lt;/h2&gt;

&lt;p&gt;All entity groups have a root entity. You can't put two entities into the same entity group without one of them being the root entity (parent). To put two entities into the same entity group, you set the parent property, like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
# Create the company
my_company = Company(name='Danny\'s Company')
my_company.put()

# Create the employee
me = Employee(
    parent=my_company,
    name='Danny Tuppeny'
)
me.put()
&lt;/pre&gt;

&lt;p&gt;Because updates lock an entire entity group, you should only use them if you need to. If you're not going to need to update an employee and its company in the same transaction, you don't need to put them in the same entity group.&lt;/p&gt;

&lt;h2&gt;Entity Keys Include Their Parent Entity Keys&lt;/h2&gt;

&lt;p&gt;Something worth noting, is that the key for an entity includes the key of it's parent. That means you can do some clever things for performance purposes, such as "Relation Index Entities" as described by &lt;a href="http://www.onebigfluke.com/"&gt;Brett Slatkin&lt;/a&gt; in &lt;a href="http://code.google.com/events/io/2009/sessions/BuildingScalableComplexApps.html"&gt;this video&lt;/a&gt;. Brett creates child entities for querying and converts their keys to their parents keys using the &lt;a href="http://code.google.com/appengine/docs/python/datastore/keyclass.html#Key_parent"&gt;parent()&lt;/a&gt; method to then batch fetch the entities themselves.&lt;/p&gt;

&lt;p&gt;Hopefully this clears things up a little. If it still leaves questions (or I've missed anything), please leave a comment below and I'll try to update the post.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a href="http://code.google.com/appengine/docs/python/datastore/keysandentitygroups.html"&gt;App Engine docs - Keys and Entity Groups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/events/io/2009/sessions/BuildingScalableComplexApps.html"&gt;Brett Slatkin at Google I/O - Building Scalable, Complex Apps on App Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218312"&gt;Developing with Google App Engine by Eugene Ciurana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/059680069X"&gt;Using Google App Engine by Charles Severance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/8PjvXgTmmpo" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/8PjvXgTmmpo/app-engine-entity-groups-contention-and-transactions" title="Entity Groups, Contention and Transactions in Google App Engine" /> <feedburner:origLink>http://blog.dantup.com/2010/01/app-engine-entity-groups-contention-and-transactions</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-01-03:/2010/01/profiling-google-app-engine-with-appstats</id> <published>2010-01-03T17:49:00Z</published> <updated>2010-01-09T12:46:00Z</updated> <category term="Google App Engine" /> <title type="text">Profiling Google App Engine with Appstats</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;As mentioned in my &lt;a href="/2010/01/caching-app-engine-datastore-results-in-memcache"&gt;previous post&lt;/a&gt;, I've been keeping an eye on my logs after putting this blog live on App Engine. One thing that wasn't so easy to do with App Engine was monitor the datastore calls being made (like I would with Microsoft's SQL Server Profiler).&lt;/p&gt;

&lt;p&gt;Luckily for us, Google added some API Hooks into App Engine and with some help from &lt;a href="http://blog.notdot.net/2009/11/API-call-hooks-for-fun-and-profit"&gt;Nick Johnson&lt;/a&gt; and &lt;a href="http://blog.appenginefan.com/2009/01/hacking-google-app-engine-part-1.html"&gt;Jens Scheffler&lt;/a&gt; I managed to cobble together some scripts that allowed me to identify and solve my problems.&lt;/p&gt;

&lt;p&gt;Today, by accident, I came across a library that completely blew my hacked scripts out of the water...&lt;/p&gt;

&lt;h2&gt;Guido van Rossum's Appstats&lt;/h2&gt;

&lt;p&gt;Guido van Rossum (a Software Engineer at Google, and the author of the Python programming language!) has released &lt;a href="https://sites.google.com/site/appengineappstats/"&gt;a library called Appstats&lt;/a&gt; that hooks and monitors the API calls your app makes and presents them in tables and graphs to aid profiling. Setting it up in your app is a breeze (especially if you already use util.run_wsgi_app to run your app).&lt;/p&gt;

&lt;p&gt;I got it up and running on my blog via the dev server (though it works equally well on the production servers) to see how well it works. After a few clicks around the app, I navigated to /stats to see what it came up with.&lt;/p&gt;

&lt;h3&gt;The Appstats Dashboard&lt;/h3&gt;

&lt;a href="/pi/appstats_1.png"&gt;&lt;img src="/pi/appstats_1_thumb.png" alt="The Appstats dashboard"&gt;&lt;/a&gt;

&lt;p&gt;The Appstats dashboard is broken into three sections. The first shows stats for each API method you've called. The second shows stats by URL. The third shows recent requests. Each line in these tables can be expanded for a breakdown of the numbers, as shown below.&lt;/p&gt;

&lt;a href="/pi/appstats_2.png"&gt;&lt;img src="/pi/appstats_2_thumb.png" alt="Appstats request breakdown"&gt;&lt;/a&gt;

&lt;h3&gt;Request Statistics&lt;/h3&gt;

&lt;p&gt;Once you click on a request, you'll get a lot of information about the API calls made.&lt;/p&gt;

&lt;a href="/pi/appstats_3.png"&gt;&lt;img src="/pi/appstats_3_thumb.png" alt="Appstats request statistics"&gt;&lt;/a&gt;

&lt;p&gt;The first thing shown is a timeline showing not only how long each call took, but also when it started and finished (the will help identify large amounts of time being spent outside of the API calls). In the above example you can see that the most expensive part of this request was a RunQuery call. This call fetches comments for a given post when the posts HTML is not in memcache. All other lookups performed are either memcache Gets or db get_by_key_name calls, which are very fast in comparison.&lt;/p&gt;

&lt;a href="/pi/appstats_4.png"&gt;&lt;img src="/pi/appstats_4_thumb.png" alt="Appstats API call statistics"&gt;&lt;/a&gt;

&lt;p&gt;If you expand one of the API calls you'll see a breakdown of what the call involved (both the request and response). You can specify how much text is included in these tables (such as the request/response) by changing the Appstats options.&lt;/p&gt;

&lt;p&gt;Finally there's a summary table showing all API calls for the given request, much the same as on the dashboard, though this one includes timings.&lt;/p&gt;

&lt;a href="/pi/appstats_5.png"&gt;&lt;img src="/pi/appstats_5_thumb.png" alt="Appstats API call summary"&gt;&lt;/a&gt;

&lt;p&gt;Appstats looks to be a very valuable tool for any App Engine developer, and I look forward to seeing what comes out of the team in the future!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218312"&gt;Developing with Google App Engine by Eugene Ciurana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/059680069X"&gt;Using Google App Engine by Charles Severance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/nVEIHH6KoP8" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/nVEIHH6KoP8/profiling-google-app-engine-with-appstats" title="Profiling Google App Engine with Appstats" /> <feedburner:origLink>http://blog.dantup.com/2010/01/profiling-google-app-engine-with-appstats</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2010-01-01:/2010/01/caching-app-engine-datastore-results-in-memcache</id> <published>2010-01-01T20:58:00Z</published> <updated>2010-01-01T22:10:00Z</updated> <category term="Google App Engine" /> <title type="text">Caching App Engine Datastore Results in Memcache</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Since moving my blog to Google App Engine a few days ago, I've been keeping a close eye on the logs. This is my first app engine project that's using the datastore, so I wanted to make sure I hadn't done anything silly and I wasn't getting a large number of timeouts. Although it's probably overkill for my blog, to learn the APIs I use memcache to avoid hitting the datastore lots for the same data.&lt;/p&gt;

&lt;p&gt;The caching I've implemented is fairly basic. When it generates a page using Django templates, the entire page contents are put into memcache, using a key that includes the page URL. If somebody else requests the same page, I can serve the entire thing from memcache without any processing overhead. This works well because there is no dynamic (different per user) content on any pages (unless you're an admin, but then memcache is bypassed entirely). When a comment is posted or a post is modified, the entire cache is wiped out. This is done to ensure comment counts on list pages are always up-to-date. Since posts and comments happen very infrequently on this blog (a handful per day at most) this shouldn't be an issue.&lt;/p&gt;

&lt;p&gt;So, after putting things live, I noticed that some of my page hits were still quite expensive (in terms of CPU) with the caching. It turned out, that when someone visited a page that wasn't in the cache, I had to execute 3 or 4 datastore queries. One to get the Tag or Archive you were viewing (this step wasn't needed for homepage of direct post pages), one for the post(s), one for the tag list and one for the archive list. Since the tags and archive list don't change much, these could be cached across the application, rather than just per-page, which would result in much faster loading of uncached pages (since they would now be only 1-2 datastore hits).&lt;/p&gt;

&lt;p&gt;So, I implemented this extra caching as follows, and all was good.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
# TAGS: Check cache first
tags = memcache.get('tags')

# TAGS: Get and cache if not there
if not tags:
    logging.info('Saving tags to cache')
    tags = Tag.all().order('name_lower')
    memcache.set('tags', tags, CACHE_TIME)
else:
    logging.info('Got tags from cache')
	
# ARCHIVE: Check cache first
archive = memcache.get('archive')

# ARCHIVE: Get and cache if not there
if not archive:				
    logging.info('Saving archive to cache')
    archive = Archive.all().order('-date')
    memcache.set('archive', archive, CACHE_TIME)
else:
    logging.info('Got archive from cache')
&lt;/pre&gt;

&lt;p&gt;Or so I thought. Although I tested the code worked with no errors, there's no nice "Datastore Profiler" like the SQL Server Profile I'm used to in Microsoft-land, so I assumed everything was good.&lt;/p&gt;

&lt;p&gt;Today, it occurred to me that the CPU values for my uncached page views was still pretty high. I was seeing up to half a second CPU time for what should be a single datastore query. This seemed pretty high even for my inefficient coding!&lt;/p&gt;

&lt;p&gt;It turns out, I made a rookie mistake. The problem is with this code right here:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
archive = Archive.all().order('-date')
memcache.set('archive', archive, CACHE_TIME)
&lt;/pre&gt;

&lt;p&gt;This code does not cache the entities returned by the query. Rather, it caches the query. That means every time I grabbed it from cache and gave it into Django, the query was being executed as the template being parsed!&lt;/p&gt;

&lt;p&gt;A quick change to call fetch() forces the query to be executed and now the entities are cached instead.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
archive = Archive.all().order('-date').fetch(1000)
memcache.set('archive', archive, CACHE_TIME)
&lt;/pre&gt;

&lt;p&gt;I'm not so keen on the hard-coded 1000, but until I can find a better way to force the query to execute, it'll do the job. My next job is to write some better hooks to allow better profiling of the datastore so I can better test for this kind of issue in future!&lt;/p&gt;

&lt;h2&gt;Update&lt;/h2&gt;

&lt;p&gt;After posting in the App Engine group, I was direct to the article &lt;a href="http://blog.notdot.net/2009/9/Efficient-model-memcaching"&gt;Efficient Model Memcaching&lt;/a&gt; on Nick Johnson's blog, which shows a better way, avoiding some of the overhead of caching Model instances directly.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218312"&gt;Developing with Google App Engine by Eugene Ciurana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/059680069X"&gt;Using Google App Engine by Charles Severance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.notdot.net/"&gt;Nick Johnson's Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/9QLpsEhNgOY" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/9QLpsEhNgOY/caching-app-engine-datastore-results-in-memcache" title="Caching App Engine Datastore Results in Memcache" /> <feedburner:origLink>http://blog.dantup.com/2010/01/caching-app-engine-datastore-results-in-memcache</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-31:/2009/12/serving-different-content-based-on-a-users-location-geo-targeting</id> <published>2009-12-31T14:55:00Z</published> <updated>2009-12-31T14:55:00Z</updated> <category term=".NET" /> <category term="ASP.NET MVC" /> <category term="Google App Engine" /> <category term="iPhone" /> <title type="text">Serving Different Ads/Content Based on a Users Location (Geo-Targeting)</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;You probably haven't noticed, but this blog serves up different ads depending on where you're visiting from. Or at least, it'll serve Amazon UK ads if you're near the UK, and Amazon US ads otherwise. Serving up US ads to UK visitors (and vice versa) is pretty pointless, and I've always tried to avoid showing any ads unless they're relevant and at least targeted to the right country.&lt;/p&gt;

&lt;p&gt;There are a number of ways to determine where your visitors are coming from, so I spent some time yesterday trying to find the most reliable way (and preferably one that didn't involve having a huge IP database sat alongside my site!). After much hacking and testing, I found what I believe to be the best way. Google.&lt;/p&gt;

&lt;p&gt;Google has a JavaScript loader API, which allows developers to load JavaScript libraries from Google with various benefits. That's not really what we're interested in though, it has something more exciting:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://code.google.com/apis/ajax/documentation/#ClientLocation"&gt;google.loader.ClientLocation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It appears that you do not need an API key to use the JavaScript loader, you can simply reference it at &lt;a href="http://www.google.com/jsapi"&gt;http://www.google.com/jsapi&lt;/a&gt;. If you look at the JavaScript served up (which is incredibly fast), you'll see something like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
google.loader.ClientLocation = { "latitude":50.123, "longitude":-2.876, "address": { "city":"Liverpool", "region":"Merseyside", "country":"United Kingdom", "country_code":"GB" } };
&lt;/pre&gt;

&lt;p&gt;Not only do you get the country, but you get the county, city and even lat/lon pair. For me, the location given was within 2-3 miles of where I live, so if you wanted, you could &lt;em&gt;really&lt;/em&gt; localise your ads!&lt;/p&gt;

&lt;p&gt;On this site, the country is just sent to a script that will serve up some ads based on keywords I've tagged against a post. You might wish to be a bit more exciting and show your users places or people nearby. This could be especially useful for mobile applications/sites, though be sure to read any associated terms and conditions before using it!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/78CvttmXhus" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/78CvttmXhus/serving-different-content-based-on-a-users-location-geo-targeting" title="Serving Different Ads/Content Based on a Users Location (Geo-Targeting)" /> <feedburner:origLink>http://blog.dantup.com/2009/12/serving-different-content-based-on-a-users-location-geo-targeting</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-31:/2009/12/google-wave-notifier-now-in-15-languages</id> <published>2009-12-31T14:50:00Z</published> <updated>2009-12-31T14:50:00Z</updated> <category term="Google Wave" /> <category term="Google Wave Notifier" /> <title type="text">Google Wave Notifier Now in 15 Languages!</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;When I started writing &lt;a href="http://wavenotifier.dantup.com/"&gt;Google Wave Notifier&lt;/a&gt; last month, it was really only for my own use. I wouldn't use Google Wave if I had no way of knowing when I had messages (and keeping a browser open is not an acceptable solution!) so I hacked something together to login to Wave and parse the JavaScript objects.&lt;/p&gt;

&lt;a href="http://wavenotifier.dantup.com/"&gt;&lt;img src="http://wavenotifier.dantup.com/images/gb_screenshot.png" alt="A screenshot of Google Wave Notifier in action!"&gt;&lt;/a&gt;

&lt;p&gt;I decided others might benefit from the app, so I cleaned it up a little, gave it an icon and published it on the web. I had no idea that it would get close to 10,000 downloads in little over a month! Nor did I expect &lt;a href="http://wavenotifier.dantup.com/translate"&gt;so many people to send in translations&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;So here we are 6 weeks down the line, and the app has been translated into 15 different languages!&lt;/p&gt;

&lt;ul&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/gb.png');"&gt;English&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/cz.png');"&gt;Czech&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/dk.png');"&gt;Danish&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/nl.png');"&gt;Dutch&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/fr.png');"&gt;French&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/de.png');"&gt;German&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/hu.png');"&gt;Hungarian&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/it.png');"&gt;Italian&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/pl.png');"&gt;Polish&lt;/li&gt;		
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/br.png');"&gt;Portuguese&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/ro.png');"&gt;Romanian&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/ru.png');"&gt;Russian&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/es.png');"&gt;Spanish&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/se.png');"&gt;Swedish&lt;/li&gt; 
&lt;li style="list-style-image: url('http://wavenotifier.dantup.com/images/tr.png');"&gt;Turkish&lt;/li&gt; 
&lt;/ul&gt;

&lt;p&gt;So I'd like to say thanks to everyone that's helped; using, translating and finding bugs in the app. What was a small script written for personal use has turned into something quite useful!&lt;/p&gt;

&lt;p&gt;For more information or to download, see the &lt;a href="http://wavenotifier.dantup.com/"&gt;Google Wave Notifier website&lt;/a&gt;!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/axGXK1F54No" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/axGXK1F54No/google-wave-notifier-now-in-15-languages" title="Google Wave Notifier Now in 15 Languages!" /> <feedburner:origLink>http://blog.dantup.com/2009/12/google-wave-notifier-now-in-15-languages</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-30:/2009/12/redirecting-requests-from-appid-appspot-com-to-a-custom-domain</id> <published>2009-12-30T12:31:00Z</published> <updated>2009-12-30T12:31:00Z</updated> <category term="Google App Engine" /> <title type="text">Redirecting Requests from appid.appspot.com to a Custom Domain</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;If you're running your app engine project on a custom domain (like this blog), you're probably not so happy that people can still access your app at http://&lt;em&gt;appid&lt;/em&gt;.appspot.com.&lt;/p&gt;

&lt;p&gt;When I started working on my blog, I realised this might be an issue, and did some investigating into how I could stop it. I found &lt;a href="http://stackoverflow.com/questions/1364733/block-requests-from-appspot-com-and-force-custom-domain-in-google-app-engine"&gt;solution on SackOverflow&lt;/a&gt; that seemed to do what I needed, so I set it up and got it working.&lt;/p&gt;

&lt;p&gt;Not long after implementing this code, I found a few problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSL is not supported on custom domains&lt;/li&gt;
&lt;li&gt;Cron jobs fail when Google invokes them with an appspot.com address and you serve a 301&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So with some tweaks, I've managed to get this working as required. The only annoyance is the hard-coded '/admin' check. This is to support cron jobs, task queues etc., which are all protected ("login: admin" in app.yaml). They must work with an appspot.com address, because Google doesn't seem to follow the redirect when invoking them. It's possible you could do an IP address check here, but I'm not sure how consistent cron/task queue IP addresses are.&lt;/p&gt;

&lt;p&gt;The code is called like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
def main():
    dantup.run_app([
        ("/\d*", RootHandler),
        ("/feed", FeedHandler),
        ("/tags/.*", TagHandler),
        ("/archive/.*", ArchiveHandler),
        ("/.*", PostHandler)
    ])
&lt;/pre&gt;

&lt;p&gt;And dantup.py looks like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

def run_app(url_mapping):
    application = webapp.WSGIApplication(url_mapping, debug=True)
    application = redirect_from_appspot(application)
    run_wsgi_app(application)

def redirect_from_appspot(wsgi_app):
    """Handle redirect to my domain if called from appspot (and not SSL)"""
    from_server = "dantup-blog.appspot.com"
    to_server = "blog.dantup.me.uk"

    def redirect_if_needed(env, start_response):
	
        # If we're calling on the appspot address, and we're not SSL (SSL only works on appspot)
        if env["HTTP_HOST"].endswith(from_server) and env["HTTPS"] == "off":
		
            # Parse the URL
            import webob, urlparse
            request = webob.Request(env)
            scheme, netloc, path, query, fragment = urlparse.urlsplit(request.url)
            url = urlparse.urlunsplit([scheme, to_server, path, query, fragment])
			
            # Exclude /admin calls, since they're used by Cron, TaskQueues and will fail if they return a redirect
            if not path.startswith('/admin'):
                # Send redirect
                start_response("301 Moved Permanently", [("Location", url)])
                return ["301 Moved Peramanently", "Click Here %s" % url]
	
        # Else, we return normally
        return wsgi_app(env, start_response)

    return redirect_if_needed
&lt;/pre&gt;

&lt;p&gt;Hopefully you may find this useful. If you encounter any problems with it, please let me know!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218312"&gt;Developing with Google App Engine by Eugene Ciurana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/059680069X"&gt;Using Google App Engine by Charles Severance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://appengine-cookbook.appspot.com/"&gt;Google App Engine Cookbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/tauGrpC-8NM" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/tauGrpC-8NM/redirecting-requests-from-appid-appspot-com-to-a-custom-domain" title="Redirecting Requests from appid.appspot.com to a Custom Domain" /> <feedburner:origLink>http://blog.dantup.com/2009/12/redirecting-requests-from-appid-appspot-com-to-a-custom-domain</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-29:/2009/12/1-2-3-testing-is-this-thing-on-new-blog-engine</id> <published>2009-12-29T17:54:00Z</published> <updated>2009-12-29T17:54:00Z</updated> <category term="Google App Engine" /> <title type="text">1-2-3-Testing. Is This Thing On? New Blog!</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;There's nothing like testing a system &lt;em&gt;after&lt;/em&gt; it's gone live!&lt;/p&gt;

&lt;p&gt;After lots of frantic hacking over the last few weeks, the blog engine I've been writing for Google App Engine is up and running. It's not complete, but the important things (frontend, feed, posting, etc.) are done so it's at least usable.&lt;/p&gt;

&lt;p&gt;Apologies if you just got a flood of old messages in your feed reader. I did investigate keeping the Blogger IDs to avoid this, but it seemed more hassle than it was worth, so I'll forgive you for clicking "Mark All as Read" this one time ;-)&lt;/p&gt;

&lt;p&gt;I'll post more info (and code) on the blog as I get time, but now I'm off to sort some food out after a hard day's hacking!&lt;/p&gt;

&lt;p&gt;If you notice anything messed up in the transition, please do let me know!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/ahvCtIGgxns" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/ahvCtIGgxns/1-2-3-testing-is-this-thing-on-new-blog-engine" title="1-2-3-Testing. Is This Thing On? New Blog!" /> <feedburner:origLink>http://blog.dantup.com/2009/12/1-2-3-testing-is-this-thing-on-new-blog-engine</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-24:/2009/12/from-net-to-python-on-app-engine.html</id> <published>2009-12-24T21:50:00Z</published> <updated>2009-12-24T22:05:00Z</updated> <category term="Google App Engine" /> <category term=".NET" /> <category term="ASP.NET MVC" /> <title type="text">From .NET to Python on App Engine: Building a Blog on Google App Engine</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Over the past few weeks I've been playing with Google App Engine. I find the best way to learn a new language/framework/platform is to just jump in and write something in/on it. So that's what I'm doing. I've decided to write my blog in Python for Google App Engine.&lt;/p&gt;

&lt;p&gt;As I may have blogged about in the past, I wrote a blog engine in &lt;a href="/tags/ASP.NET%20MVC"&gt;Microsoft ASP.NET MVC&lt;/a&gt; not so long ago with the aim of moving away from Blogger. It was around 90% complete when I abandoned it for a variety of reasons (one being &lt;a href="/2009/12/microsoft-windows-azure-vs-google-app.html"&gt;Azure pricing&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It's entirely possible the Google App Engine blog engine will also be abandoned, but since the &lt;a href="/2009/12/microsoft-windows-azure-vs-google-app.html"&gt;hosting is free&lt;/a&gt; it at least stands a good chance of seeing the light of day! It'll also make an interesting comparison to the ASP.NET MVC version.&lt;/p&gt;

&lt;p&gt;I started writing code a few nights ago, and currently the blog stands at 159 non-blank lines. I'm actually quite impressed with how little code I've had to write to get up and running. Currently there's no back-end, but the displaying of posts, comments, tags and archives are all working. Here's a quick screenshot to prove it exists! :)&lt;/p&gt;

&lt;img src="/pi/gae_blog.png" alt="A screenshot of how the App Engine blog currently looks"&gt;

&lt;p&gt;Over the coming weeks I'll blog about how I've built it (including code), the pitfalls and the the experience of moving from .NET and C# to Python and App Engine!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218312"&gt;Developing with Google App Engine by Eugene Ciurana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/059680069X"&gt;Using Google App Engine by Charles Severance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/cJU05Om9_8k" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/cJU05Om9_8k/from-net-to-python-on-app-engine.html" title="From .NET to Python on App Engine: Building a Blog on Google App Engine" /> <feedburner:origLink>http://blog.dantup.com/2009/12/from-net-to-python-on-app-engine.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-20:/2009/12/downloadingexporting-app-engine-logs.html</id> <published>2009-12-20T16:53:00Z</published> <updated>2009-12-20T17:18:00Z</updated> <category term="Google App Engine" /> <title type="text">Downloading/Exporting App Engine Logs</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Although I've been playing with App Engine for quite a few weeks now, I only found out yesterday how I can download the logs from App Engine for parsing locally. There's no export option in the dashboard, nor any option in the Windows launcher. However, you can do this yourself with appcfg.py.&lt;/p&gt;

&lt;p&gt;If, like me, you've only used the launcher up until now, and never done anything with Python outside of GAE, you might find the thought of running python scripts a little scary. Don't worry, it actually turns out to be very easy. I'm hoping when you installed the Launcher you ticked the "Add to PATH" option... :o)&lt;/p&gt;

&lt;p&gt;The command you want to run looks something like this:&lt;/p&gt;

&lt;pre&gt;appcfg.py request_logs appname/ output.txt&lt;/pre&gt;

&lt;p&gt;You should replace "appname" with the name of your app. This is the folder the contains the app.yaml file for the app you wish to get logs for. That means you need to be in the correct directory (or provide a full path). Output.txt is obviously where the logs should be written.&lt;/p&gt;

&lt;p&gt;This command will retrieve everything from the last day. This might not be what you want, so you can increase this with the num_days argument.&lt;/p&gt;

&lt;pre&gt;appcfg.py --num_days=5 request_logs appname/ output.txt&lt;/pre&gt;

&lt;p&gt;Note that you can also specify 0 to get all logs:&lt;/p&gt;

&lt;pre&gt;appcfg.py --num_days=0 request_logs appname/ output.txt&lt;/pre&gt;

&lt;p&gt;And finally, if you want all logs that you haven't already downloaded, you can use --append, which will scan the last line of the existing file, and download anything since:&lt;/p&gt;

&lt;pre&gt;appcfg.py --append request_logs appname/ output.txt&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, you'll find this doesn't work on Windows, and you'll end up with a load of duplicate entries. This is in the bugtracker, but I don't know if/when it'll be fixed. For now, I'm having to just download everything (and I'm not sure if this is counting towards my bandwidth quota!).&lt;/p&gt;

&lt;p&gt;Another option worth noting, is include_vhost, which will include the hostname used, so you can seperate requests for different versions of your app (or different custom domains). You can use this like so:&lt;/p&gt;

&lt;pre&gt;appcfg.py --num_days=0 --include_vhost request_logs appname/ "Logs/Logs.txt"&lt;/pre&gt;

&lt;p&gt;And that's all there is to it. Now you can create nice pretty charts in Microsoft Excel (since Google Spreadsheets sucks!) showing how well (or badly, in my case) your app is doing!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218312"&gt;Developing with Google App Engine by Eugene Ciurana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/059680069X"&gt;Using Google App Engine by Charles Severance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/xypVQRQIMD0" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/xypVQRQIMD0/downloadingexporting-app-engine-logs.html" title="Downloading/Exporting App Engine Logs" /> <feedburner:origLink>http://blog.dantup.com/2009/12/downloadingexporting-app-engine-logs.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-19:/2009/12/increased-app-engine-quotas-for-free.html</id> <published>2009-12-19T13:45:00Z</published> <updated>2009-12-19T13:53:00Z</updated> <category term="Google App Engine" /> <title type="text">Increased App Engine Quotas, for Free?</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;While writing my &lt;a href="/2009/12/microsoft-windows-azure-vs-google-app.html"&gt;comparison of Azure and App Engine pricing&lt;/a&gt; yesterday, I had a thought about how to increase some of your quotas without actually paying anything. I'm not sure whether Google would consider this "gaming the system" and stamp on you (and I certainly have no need to do it with my traffic levels), but I thought I'd post it in the interest of sharing.&lt;/p&gt;

&lt;p&gt;If you check the &lt;a href="http://code.google.com/appengine/docs/quotas.html"&gt;App Engine Quotas page&lt;/a&gt;, you'll see the differences between the free quota, and the billing enabled quota. For example, if you don't have billing enabled, you may serve 1,300,000 requests per day. If you have billing enabled, you get 43,000,000 requests per day (other limits, such as CPU/bandwidth still apply).&lt;/p&gt;

&lt;p&gt;So, if you've taken a look at how the billing works, you'll know that you get to decide exactly how your budget is split between resources. The minimum budget you can set is $1/day.&lt;/p&gt;

&lt;p&gt;Consider what would happen if you enabled billing, but assigned the whole budget to something you know you won't exceed (eg., if you don't use memcache, assign it to memcache). Your app will be given the higher "billing enabled" quotas, but it won't cost you a penny!&lt;/p&gt;

&lt;p&gt;As I said earlier - I've no idea whether Google will frown upon this behaviour, so if you do it, it's at your own risk!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/hoz8AFawwLY" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/hoz8AFawwLY/increased-app-engine-quotas-for-free.html" title="Increased App Engine Quotas, for Free?" /> <feedburner:origLink>http://blog.dantup.com/2009/12/increased-app-engine-quotas-for-free.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-18:/2009/12/microsoft-windows-azure-vs-google-app.html</id> <published>2009-12-18T14:40:00Z</published> <updated>2009-12-18T15:29:00Z</updated> <category term="Google App Engine" /> <category term=".NET" /> <category term="Azure" /> <title type="text">Microsoft Windows Azure vs Google App Engine: Pricing</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;As a .NET developer, I was quite excited to hear about Windows Azure. It sounded like a less painful version of Amazon's EC2, supporting .NET (less painful in terms of server management!). When I saw the pricing, it didn't look too bad either. That was, until I realised that their "compute hour" referred to an hour of your app running, not an hour of actual CPU time. Wow. This changes things. To keep a single web role running, you're looking at $0.12/hour = $2.88/day = $20.16/week = $86.40/month.&lt;/p&gt;

&lt;p&gt;Anyone that's bought hosting for a small site/app recently will know that this is not particularly cheap!&lt;/p&gt;

&lt;p&gt;So, recently I've been playing around with Google App Engine. It has this massive problem called Python (and an even bigger one called Java ;o)), but it's such a nice framework/engine to work with that I've somehow overlooked this and started coding with it. There's so much to like about it. Everything is so simple to deploy, and it scales "out of the box". Want Cron jobs? No worries, specify them in a file in your app, and when you deploy, App Engine will pick them up and schedule them. Want to queue up work to process later so that your pages return faster? Task queues do just that. What's more, you get a ridiculous free quota every day. It may be Python, but this sounds tempting, no?&lt;/p&gt;

&lt;p&gt;So, I thought it'd be interesting to compare the costs of App Engine vs Azure. I understand this isn't really a like-for-like comparison, but both can achieve the same sort of things, and while all programmers will have a preferred language/framework (I'm no exception), many can be swayed by a cool framework or hosting.&lt;/p&gt;

&lt;p&gt;First off, let's compare what you get for free. Bear in mind that Azure is free until the end of January, but since this is a CTP and won't end soon, I'm going to exclude it. Google's free quota currently has no time restrictions.&lt;/p&gt;

&lt;table&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;&lt;/th&gt;
			&lt;th&gt;Windows Azure&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/th&gt;
			&lt;th&gt;Google App Engine&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;CPU Hours&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;-&lt;/td&gt;
			&lt;td&gt;6.5hrs/day&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Bandwidth (out)&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;-&lt;/td&gt;
			&lt;td&gt;1GB/day&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Bandwidth (in)&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;-&lt;/td&gt;
			&lt;td&gt;1GB/day&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Storage (DB)&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;-&lt;/td&gt;
			&lt;td&gt;1GB&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Storage Transactions&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;-&lt;/td&gt;
			&lt;td&gt;10,368,000/day&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Well, for free, I think we have a clear winner. If you can run your website/app within the limits above, then you can do it for free with Google. It's worth mentioning that Google let you have 10 apps per account (though you may not balance a single site/app across app instances - they are specifically for separate projects).&lt;/p&gt;

&lt;p&gt;But what if you're bigger than that? What if you get Slashdotted or Dugg a lot? You might find you quickly break out of the free limits. How do prices compare?&lt;/p&gt;

&lt;table&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;&lt;/th&gt;
			&lt;th&gt;Windows Azure&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/th&gt;
			&lt;th&gt;Google App Engine&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;CPU&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;$0.12/hour&lt;/td&gt;
			&lt;td&gt;$0.10/hour&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td colspan="3"&gt;&lt;small&gt;Note that MS bill "per hour app is running" whereas Google bill "per CPU hour consumed"&lt;/small&gt;&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Bandwidth (out)&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;$0.15/GB&lt;/td&gt;
			&lt;td&gt;$0.12/GB&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Bandwidth (in)&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;$0.10/GB&lt;/td&gt;
			&lt;td&gt;$0.10/GB&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Storage (Files)&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;$0.15/GB/month&lt;/td&gt;
			&lt;td&gt;Pricing unavailable (Blobstore)&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Storage (DB)&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;$9.99/month (SQL Server up to 1GB)&lt;/td&gt;
			&lt;td&gt;$0.15/GB/month ($0.005/GB/day)&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;strong&gt;Storage Transactions&lt;/strong&gt;&lt;/td&gt;
			&lt;td&gt;$0.01/10,000&lt;/td&gt;
			&lt;td&gt;?&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td colspan="3"&gt;&lt;small&gt;I can't find any prices for Google App Engine storage transactions, so it's possible there is no charge (though a limit of 140,000,000/day applies)&lt;/small&gt;&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Well, that's interesting. I was going to try and calculate at what point Azure would become cheaper, but looking at those prices, it just isn't going to happen. Now it's worth pointing out that not all of the comparisons are fair. Google bill per actual CPU hour (so if nobody visits your site, it's not costing you), whereas Microsoft are billing for each hour your app is live and able to respond. There's also a significant difference between SQL Server and App Engine Datastore (and depending on what you're doing, one will have advantages over the other).&lt;/p&gt;

&lt;p&gt;I really hope Microsoft re-evaluate their pricing for small apps. It's too expensive to play around with small prototypes at those prices, whereas Google's offering will let me get started completely free, until my app is churning a considerable amount of traffic, and even then, it'll work our cheaper for the same processing/transfer.&lt;/p&gt;

&lt;p&gt;Sorry Microsoft. I love .NET and Visual Studio, but Google App Engine is just so easy and cheap that it's going to be my "toy of choice" for my hobby coding for the immediate future!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1439806802"&gt;Cloud Computing by John W. Rittinghouse, James F. Ransome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0071626948"&gt;Cloud Computing, A Practical Approach by Toby Velte, Anthony Velte, Robert C. Elsenpeter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470484705"&gt;Cloud Computing for Dummies by Judith Hurwitz, Robin Bloor, Marcia Kaufman, Fern Halper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/KOEX3TQcU0o" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/KOEX3TQcU0o/microsoft-windows-azure-vs-google-app.html" title="Microsoft Windows Azure vs Google App Engine: Pricing" /> <feedburner:origLink>http://blog.dantup.com/2009/12/microsoft-windows-azure-vs-google-app.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-13:/2009/12/should-i-move-my-blog-to-google-app.html</id> <published>2009-12-13T17:10:00Z</published> <updated>2009-12-13T17:39:00Z</updated> <category term="Google App Engine" /> <category term=".NET" /> <category term="ASP.NET MVC" /> <title type="text">Should I move my Blog to Google App Engine?</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Over the years, I've written (and re-written) many blog engines with the intention of hosting my blog on my own code. I'm a programmer, that's what we do. We don't like the thought of other people writing our HTML!&lt;/p&gt;

&lt;p&gt;If you've been following my blog over the last twelve months, you'll probably know what my language/framework of choice would be when writing a web app, based on my previous posts...&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href="/2009/04/aspnet-mvc-handleerror-attribute-custom.html"&gt;ASP.NET MVC HandleError Attribute, Custom Error Pages and Logging Exceptions&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="/2009/04/using-openid-in-your-aspnet-mvc.html"&gt;Using OpenID in your ASP.NET MVC Application/Blog&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="/2009/04/reducing-duplicate-content-with-aspnet.html"&gt;Reducing Duplicate Content with ASP.NET MVC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a .NET developer by trade, I'm experienced with C#, .NET, ASP.NET MVC etc. and it's always been the logical choice. There's nothing I like to write more than C#/.NET&lt;/p&gt;

&lt;h2&gt;So why are you hosted on Blogger?&lt;/h2&gt;

&lt;p&gt;An interesting question :) Unfortuantely the answer is not interesting. The answer is simply: Hosting. Windows hosting is a pain in the ass. I've had many bad Windows hosts that have dodgy control panels, poor performance or a lack of required features. Most of this is addressed with IIS 7 because we can now do most things via XML config files, but it still leaves one issue: Price. Prices seem to vary wildly, and it's such a pain to set up that I'm often put off signing up in case it turns out to be a lemon. This is the reason I have a fully working .NET MVC blog engine ready to roll in my Documents folder that's never made it to the big cloud in the sky.&lt;/p&gt;

&lt;h2&gt;What about Azure?&lt;/h2&gt;

&lt;p&gt;A few months ago I was really excited about Azure. It looked ideal for what I wanted, and looking at how EC2 is priced, I suspected it would be cheap if you're only getting a few hundred visitors per day. Then Microsoft announced the prices... To run a no-to-low traffic blog would cost more than getting a virtual server! On top of that, it seemed quite hacky to get MVC working, and it's still only in preview.&lt;/p&gt;

&lt;h2&gt;Enter, Google App Engine&lt;/h2&gt;

&lt;p&gt;While playing around in Google Wave, I downloaded the Google App Engine SDK and had a play. While Python isn't exactly my language of choice (I've never written it before!), it struck me as quite a good deal. Five million page views per month... for free! It's hosted on Google's infrastructure and scales well, and there's no way I'd hit those limits anytime soon.&lt;/p&gt;

&lt;p&gt;So, now I'm having thoughts... Do I become a hacky Python programmer, putting aside all the .NET experience I have, for free hosting with Microsoft's biggest competitor?&lt;/p&gt;

&lt;p&gt;If anyone has any experience with running blogs on Google App Engine, I'd be very interested in hearing from them. Should I run with it, or should I stop being tight-fisted and just pay for some Windows hosting?&lt;/p&gt;

&lt;p&gt;To be continued!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/ItErDfjW_CE" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/ItErDfjW_CE/should-i-move-my-blog-to-google-app.html" title="Should I move my Blog to Google App Engine?" /> <feedburner:origLink>http://blog.dantup.com/2009/12/should-i-move-my-blog-to-google-app.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-12-05:/2009/12/google-wave-notifier-now-in-spanish.html</id> <published>2009-12-05T11:47:00Z</published> <updated>2009-12-05T11:50:00Z</updated> <category term="Google Wave Notifier" /> <category term="Google Wave" /> <title type="text">Google Wave Notifier now in Spanish, Russian, Swedish</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;With the help of some users, yesterday I added support for Spanish, Russian and Swedish languages to &lt;a href="http://wavenotifier.dantup.me.uk/"&gt;Google Wave Notifier&lt;/a&gt;. All parts of the interface are translated and I'll keep adding languages if people are prepared to help with the translations :)&lt;/p&gt;

&lt;p&gt;If you'd like to help translate into your native language, please visit the &lt;a href="http://wavenotifier.dantup.me.uk/translate"&gt;translation page&lt;/a&gt;.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/J5t2fzz-AaU" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/J5t2fzz-AaU/google-wave-notifier-now-in-spanish.html" title="Google Wave Notifier now in Spanish, Russian, Swedish" /> <feedburner:origLink>http://blog.dantup.com/2009/12/google-wave-notifier-now-in-spanish.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-11-28:/2009/11/google-wave-notifier-update.html</id> <published>2009-11-28T23:45:00Z</published> <updated>2009-11-28T23:48:00Z</updated> <category term="Google Wave Notifier" /> <category term=".NET" /> <category term="Google Wave" /> <title type="text">Google Wave Notifier Update</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Apologies if this is all I seem to be blogging about lately, but it's the only interesting thing I've been doing!&lt;/p&gt;

&lt;p&gt;I've just pushed out a small update to my Google Wave Notifier application (v0.6). &lt;a href="http://wavenotifier.dantup.me.uk/"&gt;You can read about it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;New features include the ability to play the Windows "New Mail" sound when you get new messages, the ability to change the interval it polls for new messages and some general tidying up and bug fixes!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/uUBuJ1pmR8c" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/uUBuJ1pmR8c/google-wave-notifier-update.html" title="Google Wave Notifier Update" /> <feedburner:origLink>http://blog.dantup.com/2009/11/google-wave-notifier-update.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-11-20:/2009/11/google-wave-notifier-update-automatic.html</id> <published>2009-11-20T13:46:00Z</published> <updated>2009-11-25T20:09:00Z</updated> <category term="Google Wave Notifier" /> <category term="Google Wave" /> <title type="text">Google Wave Notifier Update - Automatic Update Checking</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;I wasn't planning on pushing out an update to my &lt;a href="http://wavenotifier.dantup.me.uk/"&gt;Google Wave Notifier&lt;/a&gt; application so soon, but I realised that without automatic update notification, people will just get stuck on the old version and never update.&lt;/p&gt;

&lt;p&gt;So, I've pushed out v0.4 which will notify you when an update to the app is available. Other than that it just has some minor tweaks (such as a busy icon while it's checking).&lt;/p&gt;

&lt;p&gt;If you find it useful, let your friends on Wave know about it!&lt;/p&gt;

&lt;p&gt;&lt;a href="http://wavenotifier.dantup.me.uk/"&gt;Click here for the Google Wave Notifier website&lt;/a&gt;.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/5QIAh2dso0I" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/5QIAh2dso0I/google-wave-notifier-update-automatic.html" title="Google Wave Notifier Update - Automatic Update Checking" /> <feedburner:origLink>http://blog.dantup.com/2009/11/google-wave-notifier-update-automatic.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-11-20:/2009/11/google-wave-notifier-v03-public-release.html</id> <published>2009-11-20T11:09:00Z</published> <updated>2009-11-25T20:08:00Z</updated> <category term="Google Wave Notifier" /> <category term="Google Wave" /> <title type="text">Google Wave Notifier v0.3 Public Release</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;&lt;strong&gt;Edit: Google Wave Notifier has been released now. &lt;a href="/tags/Google%20Wave%20Notifier"&gt;You can find the latest posts here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After some thorough testing, I've decided to publicly release the Google Wave Notifier I've been working on recently. Thanks to everyone that helped test it over the last few days.&lt;/p&gt;

&lt;p&gt;Because Blogger doesn't support attachments, I've created a &lt;a href="http://wavenotifier.dantup.me.uk/"&gt;download page&lt;/a&gt; on Google Sites.&lt;/p&gt;

&lt;p&gt;As always, please post your suggestions or bugs to the &lt;a href="http://s.dantup.me.uk/wavenotifierbugs"&gt;bug tracker&lt;/a&gt;. I hope you might find this useful!&lt;/p&gt;

&lt;p&gt;&lt;a href="http://wavenotifier.dantup.me.uk/"&gt;Click here for the Google Wave Notifier Download Page&lt;/a&gt;&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/_Dc16o6o0TU" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/_Dc16o6o0TU/google-wave-notifier-v03-public-release.html" title="Google Wave Notifier v0.3 Public Release" /> <feedburner:origLink>http://blog.dantup.com/2009/11/google-wave-notifier-v03-public-release.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-11-19:/2009/11/balloons-world-just-got-smaller.html</id> <published>2009-11-19T20:57:00Z</published> <updated>2009-11-19T21:45:00Z</updated> <category term="iPhone" /> <title type="text">Balloons! - The World Just Got Smaller!</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As I post this, &lt;a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=335941458&amp;amp;mt=8"&gt;Balloons!&lt;/a&gt; has been reduced to £0.59 for the paid version. Get it while it lasts!&lt;/p&gt;

&lt;p&gt;A few weeks ago I was lucky enough to participate in the beta of &lt;a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=335941458&amp;amp;mt=8"&gt;Balloons! for iPhone&lt;/a&gt;. Since it's now available to buy, I thought I'd share my thoughts on the app here.&lt;/p&gt;

&lt;p&gt;Balloons! is purely for entertainment, though it doesn't really fall into the category of games. The basic idea is that you pick a balloon and attach a message and/or photo to the label, and launch it into the air. The balloon will travel around the globe (electronically, of course) to be caught by other Balloons! users. When someone catches your balloon, they may attach their own message and photo and relaunch it into the sky for someone else to catch.&lt;/p&gt;

&lt;p&gt;I've included a few screenshots below, and although you can see the level of polish Balloons! has, unfortunately you can't see the subtle animations that really bring the app to life. The &lt;a href="http://balloonsapp.com/"&gt;official website&lt;/a&gt; has a video that might give you a better idea of how it looks.&lt;/p&gt;

&lt;div style="width: 400px; float: left;"&gt;
&lt;img src="/pi/balloons_1.png" style="float: left; margin: 0 20px 20px 0;" alt="The Balloons! menu screen"&gt;
&lt;p&gt;The Balloons! menu screen.&lt;/p&gt;
&lt;/div&gt;

&lt;div style="width: 400px; float: left;"&gt;
&lt;img src="/pi/balloons_2.png" style="float: left; margin: 0 20px 20px 0;" alt="The balloon selection screen"&gt;
&lt;p&gt;When you launch a balloon, you get to choose how it looks from a selection ranging from normal round balloons to heart and bear-shaped balloons.&lt;/p&gt;
&lt;/div&gt;

&lt;div style="width: 400px; float: left;"&gt;
&lt;img src="/pi/balloons_3.png" style="float: left; margin: 0 20px 20px 0;" alt="The catch balloon screen"&gt;
&lt;p&gt;Catching a balloon is easy. You can catch balloons from all over the world, though realistically, they have to have been travelling for some time to get from one side of the globe to the other!&lt;/p&gt;
&lt;/div&gt;

&lt;div style="width: 400px; float: left;"&gt;
&lt;img src="/pi/balloons_4.png" style="float: left; margin: 0 20px 20px 0;" alt="The balloon tracker"&gt;
&lt;p&gt;The Balloon Tracker allows you to see all balloons you've launched, along with the messages and photos that have been attached to them. This is only available in the paid version.&lt;/p&gt;
&lt;/div&gt;

&lt;div style="width: 400px; float: left;"&gt;
&lt;img src="/pi/balloons_5.png" style="float: left; margin: 0 20px 20px 0;" alt="The balloon screen"&gt;
&lt;p&gt;If you click on a balloon in the Balloon Tracker, you get to see the original message/photo, along with any other messages added to it.&lt;/p&gt;
&lt;/div&gt;

&lt;div style="clear: left;"&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Paid vs Free&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Like most apps on the App Store, there's a free version and a paid version of Balloons! With the exception of ads, the only difference is the "Balloon Tracker", which lets you see all balloons you've launched, and the messages that have been added to them. It's well worth paying for this additional feature, or you'll be left wondering whether anyone has caught and added messages to your balloons!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/DMbeubE1IcQ" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/DMbeubE1IcQ/balloons-world-just-got-smaller.html" title="Balloons! - The World Just Got Smaller!" /> <feedburner:origLink>http://blog.dantup.com/2009/11/balloons-world-just-got-smaller.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-11-19:/2009/11/wanted-beta-testers-for-google-wave.html</id> <published>2009-11-19T18:31:00Z</published> <updated>2009-11-25T20:06:00Z</updated> <category term="Google Wave Notifier" /> <category term="Google Wave" /> <title type="text">Wanted: Beta Testers for Google Wave Notifier Application</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;&lt;strong&gt;Edit: Google Wave Notifier has been released now. &lt;a href="/tags/Google%20Wave%20Notifier"&gt;You can find the latest posts here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those of you lucky enough to have a &lt;a href="http://wave.google.com/"&gt;Google Wave&lt;/a&gt; account have probably noticed there's no official app to notify you of new waves. Google Talk doesn't do it, GMail Notifier doesn't do it, and there's no official Google Wave Notifier.&lt;/p&gt;

&lt;p&gt;Yesterday I put together a quick app that will check for new messages in waves and light up a system tray icon (along with showing a notification balloon) when new messages are found. Before I release it into the wild, I'm trying to find a few people to help me test it. If you've got a Google Wave account and would be happy to try it, please send me a wave: danny.tuppeny @ googlewave.com.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/5uq9ZjPeUlI" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/5uq9ZjPeUlI/wanted-beta-testers-for-google-wave.html" title="Wanted: Beta Testers for Google Wave Notifier Application" /> <feedburner:origLink>http://blog.dantup.com/2009/11/wanted-beta-testers-for-google-wave.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-10-21:/2009/10/derren-brown-how-not-to-beat-fake.html</id> <published>2009-10-21T19:19:00Z</published> <updated>2009-10-21T19:44:00Z</updated> <category term="Derren Brown" /> <title type="text">Derren Brown - How (not) to Beat a (fake) Casino</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;I've just finished watching a recorded "Derren Brown - How to Beat a Casino". Yet again, I've been thoroughly disappointed :( It's another episode where he's moved away from the mind tricks for which he became famous, and turned to magic and camera tricks.&lt;/p&gt;

&lt;p&gt;For those who didn't watch it, Derren "stole" £5,000 from a viewer and aimed to enter a casino and bet the whole lot on a roulette wheel, with odds of 1 in 37, to win £180,000. By calculating the speed of the wheel and ball, he was going to work out where the ball would land in time to place a bet. He had a hidden camera up his sleeve so we could watch, and the whole thing went out "live". Unfortunately Derren guessed the wrong number, and lost the £5,000. He promised he'd give the £5,000 back to the viewer.&lt;/p&gt;

&lt;h2&gt;The whole thing was a scam. It was not broadcast live.&lt;/h2&gt;

&lt;p&gt;Early on in the show, Derren pointed out people have electronic devices to calculate the speeds and guess where the ball will land. Unfortunately they're easily detected, so he would have to do it in his head.&lt;/p&gt;

&lt;h2&gt;Hold on a minute - he had a camera up his sleeve!&lt;/h2&gt;

&lt;p&gt;It's quite clear that if you can't get away with a small electronic device, you won't get away with a camera! So what really happened? Well, I don't think it's hard to figure out...&lt;/p&gt;

&lt;h2&gt;The casino was fake.&lt;/h2&gt;

&lt;p&gt;There's no way he'd get in with a camera, and the shots were so bad there's no way we would be able to tell whether it's a real casino. &lt;strong&gt;The film was not live.&lt;/strong&gt; Derren pre-recorded the bet, taking multiple shots until the ball landed on the number next to his prediction. They just cut the shots in to make it look live, but it was actually all pre-recorded. &lt;strong&gt;He got the wrong number on purpose.&lt;/strong&gt; If he'd got it right, he'd have to pay out £180,000 to the viewer whose money he used. By getting "the next one" he simply gives the £5,000 back and everyone thinks he's genius because he was "only one number out".&lt;/p&gt;

&lt;p&gt;Another promising show, ruined by camera and technology trickery rather than the clever mind tricks we all like to see from Derren Brown.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1905026358"&gt;Tricks of the Mind by Derren Brown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/K00vJ6jpqII" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/K00vJ6jpqII/derren-brown-how-not-to-beat-fake.html" title="Derren Brown - How (not) to Beat a (fake) Casino" /> <feedburner:origLink>http://blog.dantup.com/2009/10/derren-brown-how-not-to-beat-fake.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-10-10:/2009/10/aion-how-not-to-launch-mmo.html</id> <published>2009-10-10T19:12:00Z</published> <updated>2009-10-10T20:14:00Z</updated> <category term="Games" /> <category term="Aion" /> <category term="MMOs" /> <title type="text">Aion - How Not to Launch an MMO</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;As is becoming tradition when a new MMO launch gets lots of hype, &lt;a href="http://bootblock.co.uk/"&gt;BootBlock&lt;/a&gt; and I recently started playing &lt;a href="http://uk.aiononline.com/"&gt;Aion&lt;/a&gt;. Aion had already been out in Korea for almost a year, so the game should be stable and the launch should be smooth. The gameplay videos looked cool - a lot like &lt;a href="http://www.playonline.com/ff11eu/"&gt;Final Fantasy XI&lt;/a&gt;, the first MMO I ever played (on a console!).&lt;/p&gt;

&lt;p&gt;Although the game hasn't really lived up to the hype, and has had more bugs and missing features than NHibernate, it's been an interested change from playing &lt;a href="http://www.wow-europe.com/"&gt;World of Warcraft&lt;/a&gt;. Unfortunately, the launch hasn't been anywhere near as smooth as it should've been.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Note: I've tried to use the word "realm" in place of "server" to refer to a named world that you select to create your character on when playing an MMO to avoid confusion with physical (or virtual) servers.&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;Realm Queues&lt;/h2&gt;

&lt;p&gt;The first problem Aion has experience is massive (2 hours plus) realm queues caused by too many people trying to log onto the same realm at the same time. Queues of this length are unacceptable for a game that many will only want to play for a few hours after work/school. This may sound like NCSoft just didn't anticipate the demand for their game, but I disagree. There were over 400,000 pre-orders. NCSoft knew this. They also knew exactly how much stock they'd shipped to retailers for launch week. They'd also already launched in several countries - there's no excuse for getting the capacity so wrong.&lt;/p&gt;

&lt;h2&gt;Scaling in the Wrong Direction&lt;/h2&gt;

&lt;p&gt;If there's something to be learned from previous MMOs, it's that the population on a realm is a critical factor in how well people can play the game on that realm. Too few, and people won't be able to find groups. Too many, and the realm will be too crowded and people will have to fight over mobs.&lt;/p&gt;

&lt;p&gt;Aion came up with a &lt;em&gt;perfect&lt;/em&gt; solution. They call them "Channels", and it allows multiple "instances" of a given zone to split the players. While there are some issues with the implementation, this drastically simplifies the problem of managing population. The number of "Channels" can be changed per zone, on the fly, by NCSoft. This means instead of having lots of realms at launch and potentially having the population fall after the first few weeks, NCSoft could simply load a lot of channels on a small number of realms and reduce them over time as the number of players falls (along with the hardware and player caps associated with the realm).&lt;/p&gt;

&lt;p&gt;The problem in Aion, is that it looks like their realms are restricted to a number of players (and an amount of hardware), full stop. It doesn't look like the hardware assigned to a realm can be scaled at all. This means when the queues became very long, they were seemingly unable to increase the hardware assigned to some realms (and therefore the player caps on those realms), and instead had to set up some new realms. When the population falls (which it will, after this launch!) they may end up with many underpopulated realms tying up lots of hardware. This may result in some realms being merged - a complicated and messy process.&lt;/p&gt;

&lt;h2&gt;Character/Realm Transfers&lt;/h2&gt;

&lt;p&gt;Ok, let's forgive NCSoft for not being able to anticipate demand for their game. They rolled out some new realms pretty quickly, so at least now everyone can play, right? &lt;strong&gt;Wrong!&lt;/strong&gt; Since most of the people playing have created (and levelled) characters on the original (crowded) realms, they'll have to start all over again if they move realms. This means the new realms are great for new players, but not so great for existing players (specifically those that pre-ordered and had already played for over a week when the new realms arrived). Since there's currently no way to transfer your character from one realm to another, you really are stuck between starting again, or waiting many hours every time you want to play.&lt;/p&gt;

&lt;p&gt;NCSoft do say that they're working on a character transfer feature for the coming months, but it's really too late. It's an important part of balancing the population of your MMO and should've been there from the start.&lt;/p&gt;

&lt;h2&gt;Bots, RMT and Spamming&lt;/h2&gt;

&lt;p&gt;Most MMOs have been at some point plagued by bots and spammers. RMT stands for "Real Money Trading" which refers to trading real money for in-game items. In most pay-to-play MMOs this is strictly against the rules. RMT websites take advantage of players that are happy to spend some real money in order to have an advantage in the game. They will write bots to login and play the game, killing mobs and looting money and items. They will also spam logged in players with messages asking them to visit their websites to purchase gold.&lt;/p&gt;

&lt;p&gt;Not so long ago, this was a huge problem in World of Warcraft. However, if you login today you'll probably see very few bots or spammers. Players shouted about this a lot in the WoW forums and Blizzard (eventually) reacted. There are now very easy methods in game for reporting players as bots and spammers. When someone is reported a number of times, they will automatically be silenced and flagged for a GM to investigate. There are also restrictions stopping trial (free) accounts from speaking in public channels or sending whispers. This has had a significant impact on the RMT business and it's rare to see bots or receive spam these days.&lt;/p&gt;

&lt;p&gt;Aion launched without &lt;em&gt;any&lt;/em&gt; protection against this. The forums are now full of complaints about bots and spammers. NCSoft have patched in a method for blocking users that send you spam messages, but whether enough of these blocks will silence an account is unknown. If not, then it's useless. No spammer sends multiple messages to the same person from the same character. Never mind, NCSoft is on the case with some bans...&lt;/p&gt;

&lt;h2&gt;Mass Account Banning&lt;/h2&gt;

&lt;p&gt;In order to quickly reduce the number of bots and spammers in game, NCSoft have started mass banned accounts and IP ranges. It looks like they're hitting a lot of innocent people with the ban hammer as they go. Whilst I don't believe everyone crying in the forum is innocent, there are far too many of them for me to believe they're all botters/gold buyers.&lt;/p&gt;

&lt;p&gt;The mass banning has only increased the amount of complains in the forums. The bans happened on a Friday evening, right before the weekend when most people will want to play (and presumably when they have reduced staff to resolve these incorrect bannings).&lt;/p&gt;

&lt;p&gt;Aion had a &lt;em&gt;lot&lt;/em&gt; of potential to take a huge chunk of the MMO market from Blizzard. Unfortunately a lot of the issues during launch will have had a detrimental impact on the game. Just like Age of Conan, a lot of players will leave after the included 30 days, never to return. Hopefully other companies working on future MMOs have been watching this launch (and others) carefully to avoid making the same mistakes.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/3940643793"&gt;Aion : The Official Strategy Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/RERgjUapRRA" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/RERgjUapRRA/aion-how-not-to-launch-mmo.html" title="Aion - How Not to Launch an MMO" /> <feedburner:origLink>http://blog.dantup.com/2009/10/aion-how-not-to-launch-mmo.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-10-09:/2009/10/importing-iphone-dev-keys-on-new-mac.html</id> <published>2009-10-09T12:45:00Z</published> <updated>2009-10-09T12:57:00Z</updated> <category term="iPhone" /> <category term="Mac" /> <title type="text">Importing iPhone Dev Keys on a new Mac</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;When I set my Mac Mini for iPhone development, I was told to backup a key file (.p12) because if I lost it and needed to reinstall, I wouldn't be able to deploy to my iPhone (yikes!). I backed it up as suggested, and made sure I had copies of it all over my Mac, PC and the interwebs.&lt;/p&gt;

&lt;p&gt;With a clean install of Snow Leopard, it was time to import this keyfile and make sure everything still worked. As instructed in the docs, I double-clicked on the .p12 file and was asked for the password. I entered it and got the following message:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An error has occurred.&lt;/strong&gt; Unable to import an item.  The contents of an item cannot be retrieved.&lt;/p&gt;

&lt;h2&gt;Oh, shit.&lt;/h2&gt;

&lt;p&gt;I tried multiple times to import, and even imported other keys I'd exported before it. I'm 100% sure I was putting the password in correctly. No joy, just lots of errors. Although the key appeared in the list (after apparently failing), Xcode refused to recognise it and I'm unable to deploy to my iPhone. This was looking pretty bad...&lt;/p&gt;

&lt;h2&gt;Google to the rescue!&lt;/h2&gt;

&lt;p&gt;After a bit of searching, I came across a post from someone having the same issue, and &lt;a href="http://www.openradar.me/7092640"&gt;a workaround&lt;/a&gt;. Importing via the terminal apparently works. I gave this a shot, and all is good. I can now deploy to my iPhone again. Thanks Dave K!&lt;/p&gt;

&lt;p&gt;Just in case you're reading this with the same problem and the link above is now broken, here's a copy of the solution:&lt;/p&gt;

&lt;h2&gt;Work-around provided by Dave K.&lt;/h2&gt;
&lt;p&gt;You can use the 'security' command line tool:&lt;br&gt;
security import priv_key.p12 -k ~/Library/Keychains/login.keychain&lt;br&gt;
security import pub_key.pem -k ~/Library/Keychains/login.keychain&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/UQ5-0-OeYjk" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/UQ5-0-OeYjk/importing-iphone-dev-keys-on-new-mac.html" title="Importing iPhone Dev Keys on a new Mac" /> <feedburner:origLink>http://blog.dantup.com/2009/10/importing-iphone-dev-keys-on-new-mac.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-08-07:/2009/08/im-blogging-from-mac.html</id> <published>2009-08-07T19:42:00Z</published> <updated>2009-10-09T12:54:00Z</updated> <category term="iPhone" /> <category term="Mac" /> <title type="text">I'm Blogging... From a Mac!</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;I'm shaking my head with shame. I never thought this day would come. I'm writing a blog post... from a Mac!&lt;/p&gt;

&lt;p&gt;Those that know me will probably know I'm a massive Microsoft fanboy. I love .NET, XNA, WPF, Visual Studio, Vista and all the other things that come from Redmond. I don't like Apple.&lt;/p&gt;

&lt;p&gt;Last year I bought an iPhone. I just couldn't help myself. I've had many Windows Mobile devices from iPaqs to XDAs and they suck. Big time. The iPhone came along and it just wiped the floor with anything out there, so I had to get one. I love it!&lt;/p&gt;

&lt;p&gt;Months on, and I've released an Xbox game called Jungle Blocks using XNA. The whole process was pretty awesome. I got to write it in Visual Studio using C# and for very little money my game was out there being played by other people. They were paying money to play &lt;em&gt;my game&lt;/em&gt;. That was a cool feeling!&lt;/p&gt;

&lt;p&gt;Then my mind started to wonder... Maybe I should dabble in iPhone development? I could release &lt;strong&gt;iJungle Blocks&lt;/strong&gt;! (Ok, maybe I won't call it that). In a lot of ways the App Store seems to work very similar to XNA and the Indie Games and since I'd already filled in all the forms and got an ITIN I figured I didn't have much to lose trying.&lt;/p&gt;

&lt;p&gt;Well, except my pride. It turns out that Mr Jobs doesn't want you making money from his iPhone if you don't have his computer. You can only develop for the iPhone on a Mac (legally). This was a bummer. Macs stink. They won't do .NET and they certainly don't do Visual Studio. It was time to do some research!&lt;/p&gt;

&lt;p&gt;I started browsing the web and speaking to a few people to find out what coding on a Mac would be. This is what I found out:&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;The cheapest new Intel Mac is £500&lt;/li&gt;
	&lt;li&gt;Xcode (the Mac IDE) is crap. It does not have all the features of Visual Studio 2008 :(&lt;/li&gt;
	&lt;li&gt;Objective-C is verbose and long-winded. There is no Garbage Collection!&lt;/li&gt;
	&lt;li&gt;All my friends will laugh and point if I buy a Mac&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things were looking grim, but I decided to go nuts. This week, I bought a Mac Mini. After a few hours I discovered a few more things:&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;Mac keyboards suck. Thankfully my Microsoft Keyboard/mouse works :)&lt;/li&gt;
	&lt;li&gt;I can't type " @ or #. They're just mapped wrong.&lt;/li&gt;
	&lt;li&gt;The Mouse on a Mac starts slow and then accelerates &lt;em&gt;very&lt;/em&gt;quickly. It's a pain in the ass.&lt;/li&gt;
	&lt;li&gt;Alt+Tab is Windows+Tab. Ctrl+C is Windows+C. etc. This is madness!&lt;/li&gt;
	&lt;li&gt;The Home and End keys don't work as expected. Neither do Ctrl+Left or Ctrl+Right.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I got Xcode installed and started playing around to see how bad this thing was. After a few nights of wrestling with Xcode, Objective-C, Cocoa-Touch and Mac OSX here's what I've found out:&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;Xcode is not crap. There's still time, but Xcode is not frustrating the hell out of me like I thought it would. It's not all that bad. Nowhere near as bad as some of the crap I've had to use (I'm looking at you, FlexBuilder). I might just get on with it!&lt;/li&gt;
	&lt;li&gt;Microsoft have Mac drivers for my Microsoft Keyboard and mouse. They not only fix the crazy mouse acceleration, it includes a proper mapping to fix " @ and #!&lt;/li&gt;
	&lt;li&gt;Even on the highest setting, the mouse still moves too slow on a Mac. The max setting on a Mac doesn't come close to the Max setting on a PC.&lt;/li&gt;
	&lt;li&gt;Cocoa-Touch is actually really nice :)&lt;/li&gt;
	&lt;li&gt;There is no nice fix for Alt+Tab. I can swap Windows/Alt keys, but then Copy/Paste becomes Alt+C/Alt+V instead of Windows+C/Windows+V.&lt;/li&gt;
	&lt;li&gt;These is seemingly no fix for Home/End/Ctrl+Left/Ctrl+Right&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far, things are not as bad as I imagined. And the Mac &lt;em&gt;does&lt;/em&gt; have some nice stuff. But will it tempt me away from the lovely world of Visual Studio and C#? Unlikely. It's not as bad as I expected it to be, but it's just not the same experience as developing on a PC.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/EhCzCegXx9o" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/EhCzCegXx9o/im-blogging-from-mac.html" title="I'm Blogging... From a Mac!" /> <feedburner:origLink>http://blog.dantup.com/2009/08/im-blogging-from-mac.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-04-17:/2009/04/aspnet-mvc-handleerror-attribute-custom.html</id> <published>2009-04-17T18:24:00Z</published> <updated>2009-10-06T20:24:00Z</updated> <category term=".NET" /> <category term="ASP.NET MVC" /> <title type="text">ASP.NET MVC HandleError Attribute, Custom Error Pages and Logging Exceptions</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;I'm sure I don't need to tell you how bad serving a &lt;a href="http://en.wikipedia.org/wiki/Screen_of_Death#ASP.NET"&gt;Yellow Screen of Death&lt;/a&gt; to your users is. Nonetheless, it seems to be pretty common practice across the web. One of the first things I do when setting up a new ASP.NET project is set up custom error pages and ensure all exceptions are logged (who wants to find out about their errors from their clients?). Since things work slightly differently in ASP.NET MVC I thought I'd dig in and find the best way to do the same thing.&lt;/p&gt;

&lt;h2&gt;The HandleError Attribute&lt;/h2&gt;

&lt;p&gt;The HandleError attribute (which appears on the default controllers in an MVC project) tells the framework that if an unhandled exception occurs in your controller that rather than showing the default Yellow Screen of Death it should instead serve up a view called Error. The controller-specific View folder will be checked first (eg. Views/Home/Error.aspx) and if it's not found, the Shared folder (Views/Home/Error.aspx) will be used.&lt;/p&gt;

&lt;h2&gt;But How Do I Log Exceptions?&lt;/h2&gt;

&lt;p&gt;You might've spotted the problem with HandleError. It just outputs a view, and doesn't let you run any code. This might be fine if you don't want users to see errors but don't really care for fixing them. Hopefully you think this isn't acceptable and you want to investigate all exceptions!&lt;/p&gt;

&lt;h2&gt;The OnException Method&lt;/h2&gt;

&lt;p&gt;The System.Web.Mvc.Controller class contains a method called OnException which is called whenever an exception occuts within an action. This does not rely on the HandleError attribute being set. If you're being a good coder and have your own base Controller class you can override this method in one place to handle/log all errors for your site. You might choose to send emails and/or detect duplicate exceptions and discard them. For now, I'm just going to write them all to a text file in my App_Data folder.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
protected override void OnException(ExceptionContext filterContext)
{
	WriteLog(Settings.LogErrorFile, filterContext.Exception.ToString());
}

/// &amp;lt;summary&amp;gt;
/// Logs a message to the given log file
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name="logFile"&amp;gt;The filename to log to&amp;lt;/param&amp;gt;
/// &amp;lt;param name="text"&amp;gt;The message to log&amp;lt;/param&amp;gt;
static void WriteLog(string logFile, string text)
{
	//TODO: Format nicer
	StringBuilder message = new StringBuilder();
	message.AppendLine(DateTime.Now.ToString());
	message.AppendLine(text);
	message.AppendLine("=========================================");

	System.IO.File.AppendAllText(logFile, message.ToString());
}
&lt;/pre&gt;

&lt;p&gt;This works great, but it still shows our user an unhandled exception message, &lt;em&gt;even if we use the HandleError attribute&lt;/em&gt;. This makes the HandleError attribute look rather useless, so I've removed it. We can easily show the friendly error ourselves with the following code:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
filterContext.ExceptionHandled = true;
this.View("Error").ExecuteResult(this.ControllerContext);
&lt;/pre&gt;

&lt;p&gt;It's important to set ExceptionHandled to true, otherwise you'll still see the default unhandled exception message. The OnException method returns void so we must Execute the view and pass in the ControllerContext ourselves.&lt;/p&gt;

&lt;h2&gt;How Do I see my own Errors During Development?&lt;/h2&gt;

&lt;p&gt;It's a little inconvenient to open log files or keep commenting out your error handling code while developing to see exceptions and stack traces. You might remember ASP.NET has a nice web.config setting that configures custom errors. This property is exposed via MVC, so we can set up our config to show friendly errors to remote users only:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
&amp;lt;customErrors mode="RemoteOnly" /&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Then all we need to do in our OnException method is check this value and serve up the custom error view only if it returns true.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
protected override void OnException(ExceptionContext filterContext)
{
	WriteLog(Settings.LogErrorFile, filterContext.Exception.ToString());

	// Output a nice error page
	if (filterContext.HttpContext.IsCustomErrorEnabled)
	{
		filterContext.ExceptionHandled = true;
		this.View("Error").ExecuteResult(this.ControllerContext);
	}
}
&lt;/pre&gt;

&lt;p&gt;It's worth noting that IsCustomErrorEnabled will resolve the RemoteOnly option for you, you don't need to check where the user is coming from. Now out site serves up friendly errors to users and logs all exceptions without us losing the ability to see stack traces during development.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470384611"&gt;Professional ASP.NET MVC 1.0 by Rob Conery, Scott Hanselman, Phil Haack, Scott Guthrie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430210079"&gt;Pro ASP.NET MVC Framework by Steven Sanderson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430228865"&gt;Pro ASP.NET MVC V2 Framework by Steven Sanderson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://weblogs.asp.net/scottgu/archive/2009/03/10/free-asp-net-mvc-ebook-tutorial.aspx"&gt;ScottGu: Free ASP.NET MVC eBook Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/D30QQd4Vklw" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/D30QQd4Vklw/aspnet-mvc-handleerror-attribute-custom.html" title="ASP.NET MVC HandleError Attribute, Custom Error Pages and Logging Exceptions" /> <feedburner:origLink>http://blog.dantup.com/2009/04/aspnet-mvc-handleerror-attribute-custom.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-04-14:/2009/04/using-openid-in-your-aspnet-mvc.html</id> <published>2009-04-14T19:23:00Z</published> <updated>2009-10-06T20:29:00Z</updated> <category term=".NET" /> <category term="ASP.NET MVC" /> <title type="text">Using OpenID in your ASP.NET MVC Application/Blog</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Over the last few days I've been rewriting this blog in &lt;a href="http://www.asp.net/mvc/"&gt;ASP.NET MVC&lt;/a&gt;. As it gets closer to a state where I can upload it, I found myself needing to implement security for the administration section (adding, editing posts, etc.). I don't want yet another username/password to remember, and I don't want to IP-restrict it because that's not very flexible (and I don't know how static my IP is!), so what are my options?&lt;/p&gt;

&lt;h2&gt;OpenID&lt;/h2&gt;

&lt;p&gt;OpenID is nothing new, it's been around since late 2005. I've been aware of what it did and how it worked, but never really played with it. I did, however, get the impression it might solve my problem. Especially having seen that you can use your Google account as an OpenID!&lt;/p&gt;

&lt;h2&gt;What's OpenID? Why is it Cool?&lt;/h2&gt;

&lt;p&gt;OpenID is a standard for authentication, allowing you to use the same identitiy/login for multiple services. It is not the same as using the same username/password at multiple websites (that's a &lt;em&gt;very&lt;/em&gt; bad idea). Let's see an example.&lt;/p&gt;

&lt;p&gt;I want to be able to login to my blog to edit posts. I don't want another username/password. As Google now works as an identity provider, my blog can redirect me to Google and let them authenticate me. Google will then return me to my blog saying "Yes, this is definitely Danny Tuppeny". This means I don't need any user tables, login forms, or anything else on my blog!&lt;/p&gt;

&lt;p&gt;This might sounds complicated, but as with most things, there's a nice .NET library called &lt;a href="http://code.google.com/p/dotnetopenid/"&gt;dotnetopenid&lt;/a&gt; to hide the complexity. Let's see some code!&lt;/p&gt;

&lt;p&gt;On the first request, dotnetopenid will return a null response. After logging in at the identity providers website, the user will be redirected back (to the same page by default, but this can be changed) with a token on the query string. This will cause dotnetopenid to return a response. The basic code looks like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
var openId = new OpenIdRelyingParty();

if (openId.Response == null)
{
	// No response means this is the first page load
}
else
{
	// This means we're been redirected back after authentication
	if (openId.Response.Status == AuthenticationStatus.Authenticated)
		// User was logged in (as someone!)
}
&lt;/pre&gt;

&lt;p&gt;On the first page load, we would usually ask the user for their OpenID Identifier/URL, however since in my case it's always going to be Google, I'm going to hard-code this as a single value.&lt;/p&gt;

&lt;p&gt;dotnetopenid supports adding claim requests so that you can request (or even demand) specific pieces of information. In my case I only care about authenticating me, I don't need to request my name or email address. As such, I'm just going to fire a simple request off without any claim requests.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
openId.CreateRequest("https://www.google.com/accounts/o8/id").RedirectToProvider();
&lt;/pre&gt;

&lt;p&gt;In the else block we need to check the response. We want to make sure that the status is Authenticated and the ClaimedIdentifier matches the known identifier for my own login.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
// We got a response - check it's valid
if (openId.Response.Status == AuthenticationStatus.Authenticated
	&amp;&amp; openId.Response.ClaimedIdentifier.ToString() == "http://google.com/blah/blah/blah")
{
	Session["Admin"] = true;
	return Redirect("/posts/edit");
}
else
	return Content("Go away, you're not me.");
&lt;/pre&gt;

&lt;p&gt;The ClaimedIdentifier will be unique to each Google account. You can run the code once and examine the returned value to find out your own, and then you can check against it.&lt;/p&gt;

&lt;p&gt;If we put all this together into a controller action, it'll look something like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
public ActionResult Login()
{
	var openId = new OpenIdRelyingParty();

	// If we have no response, start
	if (openId.Response == null)
	{
		// Create a request and redirect the user
		openId.CreateRequest(Settings.AdminOpenIDIdentifier).RedirectToProvider();

		return null;
	}
	else
	{
		// We got a response - check it's valid and that it's me
		if (openId.Response.Status == AuthenticationStatus.Authenticated
			&amp;&amp; openId.Response.ClaimedIdentifier.ToString() == Settings.AdminClaimedIdentifier)
		{
			Session["Admin"] = true;
			return Redirect("/posts/edit");
		}
		else
			return Content("Go away, you're not me.");
	}
}
&lt;/pre&gt;

&lt;p&gt;That's really all there is to it. Now when I hit the Login action I'll be redirected to Google's login page. After logging in, I end up back at /posts/edit on my blog with the correct session variable set. Of course, you could instead call the built-in ASP.NET authentication methods, or look up a user from your database based on their ClaimedIdentifier. There are a lot of ways you can extend this, and I'll cover using OpenID for blog comments in a future article!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0596153767"&gt;OpenID: The Definitive Guide by David Recordon, Laurie Rae, Chris Messina&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470384611"&gt;Professional ASP.NET MVC 1.0 by Rob Conery, Scott Hanselman, Phil Haack, Scott Guthrie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430210079"&gt;Pro ASP.NET MVC Framework by Steven Sanderson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430228865"&gt;Pro ASP.NET MVC V2 Framework by Steven Sanderson&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/C6qYmiDzFqo" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/C6qYmiDzFqo/using-openid-in-your-aspnet-mvc.html" title="Using OpenID in your ASP.NET MVC Application/Blog" /> <feedburner:origLink>http://blog.dantup.com/2009/04/using-openid-in-your-aspnet-mvc.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-04-14:/2009/04/ie8-hanging-with-connecting-when.html</id> <published>2009-04-14T18:09:00Z</published> <updated>2009-04-14T18:24:00Z</updated> <title type="text">IE8: Hanging with "Connecting..." when opening tabs, unable to hide Favourites bar and other bugs</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;I'm not the only person having these problems, so I thought I'd post the solution here for all...&lt;/p&gt;

&lt;p&gt;After hearing that IE8 will be offered via Windows Update next week, I decided to install it on my home PC running Windows Vista. I've been using it since it RTM'd at work with some major stability issues, but I put them down to my machine rather than IE. Oh, how I was wrong!&lt;/p&gt;

&lt;p&gt;After the usual install and reboot cycle, I opened IE8. I turned off the usual trash (Accelerators, Web Slices, Compatibility View for Intranet sites, etc.) and went to hide the nasty favourites bar. Only, I couldn't. Right-clicking on the favourites bar didn't give a context menu. So I tried View &amp;raquo; Toolbars &amp;raquo; Favourites - the option was &lt;strong&gt;disabled&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;I proceeded to Google, doing the usual middle-click on results to open them in new tabs. Every new tab I opened just sat with "Connecting..." in the tab title. The content was blank.&lt;/p&gt;

&lt;h2&gt;This isn't looking good...&lt;/h2&gt;

&lt;p&gt;I swiftly disabled all the non-MS addons (and Fiddler, thinking that could potentially break connections). No change.&lt;/p&gt;

&lt;p&gt;I fired up Event Viewer and found an "Internet Explorer" event log. This would be an interesting find if the entire log wasn't blank. Great!&lt;/p&gt;

&lt;p&gt;I opened up My Computer and navigated to my system drive. WTF - it opened in a new window. I checked folder options - it's still set to "open each folder in the same window". I don't like that :(&lt;/p&gt;

&lt;p&gt;Rolling back to IE7 is now a serious option. This isn't what I've come to expect from Microsoft!&lt;/p&gt;

&lt;p&gt;After a little more Googling (using &lt;a href="http://www.google.co.uk/chrome"&gt;Chrome&lt;/a&gt;, ofcourse) I found I wasn't the only one having these issues. Fixes varied from broken addons to GoogleUpdater. Nothing seemed to apply to my problem.&lt;/p&gt;

&lt;p&gt;Then I found a magic post. Someoe had run IE as Administrator and discovered his problems disappeared. Even better, when he ran as a normal user afterwards, things still worked! Worth a shot, eh?&lt;/p&gt;

&lt;p&gt;Well, it worked. Running as Administrator worked fine. Running again as a normal user and everything continued to work. Specifically, I could now:&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;Hide the IE8 favourites toolbar&lt;/li&gt;
	&lt;li&gt;Open new tabs without the "Connecting..." message/hanging&lt;/li&gt;
	&lt;li&gt;Open Windows explorer folders in the same window&lt;/li&gt;
	&lt;li&gt;Access toolbar context-menus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An interesting bug! I can't explain how it could happen, or why any errors/failures aren't written to the event log. All I can say is I'm glad it's fixed and I don't need to roll back to IE7!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/LjlKg0_4fJA" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/LjlKg0_4fJA/ie8-hanging-with-connecting-when.html" title="IE8: Hanging with &quot;Connecting...&quot; when opening tabs, unable to hide Favourites bar and other bugs" /> <feedburner:origLink>http://blog.dantup.com/2009/04/ie8-hanging-with-connecting-when.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-04-13:/2009/04/eager-fetching-of-relationships-with.html</id> <published>2009-04-13T11:34:00Z</published> <updated>2009-04-13T11:51:00Z</updated> <category term="LINQ to SQL" /> <category term=".NET" /> <title type="text">Eager-Fetching of Relationships with LINQ to SQL</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;By default, LINQ to SQL lazy-loads its relationships. This means it won't go fetching entire trees of objects when you're only using the top ones. However, this might not always be desirable. Imagine if every time you output a post for your blog, you include tagsa. You might write something like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
foreach(var post in db.Posts)
{
	Response.Write("&amp;lt;h1&amp;gt;{0}&amp;lt;/h1&amp;gt;", post.Title);
	foreach(var Tag in post.Tags)
	{
		Response.Write("&amp;lt;a href=\"{0}\"&amp;gt;{1}&amp;lt;/a&amp;gt;",
			Html.Encode(tag.FullUrl),
			Html.Encode(tag.Name)
		);
	}
}
&lt;/pre&gt;

&lt;p&gt;This code will work fine, but if you were to examine the SQL being generated, you'd see n+1 SELECT queries. One for fetching the posts, and one for fetching the tags for each posts - individually.&lt;/p&gt;

&lt;h2&gt;What can we do about it?&lt;/h2&gt;

&lt;p&gt;LINQ to SQL allows us to specify a set of DataLoadOptions that dictate this behaviour. One of the methods of the DataLoadOptions is LoadWith&amp;lt;T&amp;gt; which allows us to say "whenever you load x, always include y". E.g.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
// Create DataLoadOptions
DataLoadOptions dlo = new DataLoadOptions();

// Always fetch tags when we get posts
dlo.LoadWith&amp;lt;Post&amp;gt;(p =&gt; p.Tags);

// Set these options on the DataContext
db.LoadOptions = dlo;
&lt;/pre&gt;

&lt;p&gt;If we re-run the original query, we'll now find a JOIN to the Tags table and just a single query to fetch all the data we require.&lt;/p&gt;

&lt;p&gt;Simple! However, bear in mind that LINQ to SQL might not always generate joins. I've got a few cases where the LoadWith seems to be ignored. As soon as I figure out why, I'll be sure to update this post!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470041811"&gt;Professional LINQ by Scott Klein&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1590597893"&gt;Pro LINQ: Language Integrated Query in C# 2008 by Joseph Rattz Jr&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/ZZ5v6APHqA8" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/ZZ5v6APHqA8/eager-fetching-of-relationships-with.html" title="Eager-Fetching of Relationships with LINQ to SQL" /> <feedburner:origLink>http://blog.dantup.com/2009/04/eager-fetching-of-relationships-with.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-04-09:/2009/04/reducing-duplicate-content-with-aspnet.html</id> <published>2009-04-09T18:54:00Z</published> <updated>2009-04-10T11:28:00Z</updated> <category term=".NET" /> <category term="ASP.NET MVC" /> <title type="text">Reducing Duplicate Content with ASP.NET MVC</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;As you're all no doubt aware, &lt;a href="http://www.asp.net/mvc/"&gt;ASP.NET MVC&lt;/a&gt; &lt;a href="http://weblogs.asp.net/scottgu/archive/2009/04/01/asp-net-mvc-1-0.aspx"&gt;recently went RTM&lt;/a&gt;. This brings the MVC-style of coding, made very popular by Ruby-on-Rails to the ASP.NET world. I've been eager to start using MVC for months, but I've been holding off until I knew the API was locked down so I don't have to change anything.&lt;/p&gt;

&lt;p&gt;Unfortunately, like WebForms, MVC has some "issues" with regards to duplicate content, making it not all that SEO-friendly.&lt;/p&gt;

&lt;h2&gt;What do you mean, Duplicate Content?&lt;/h2&gt;

&lt;p&gt;Duplicate content is just that - the same content repeated on multiple pages/sites. This might not sound like a big deal, but it's not something search engines like. They don't want the search results to show the same content multiple times across different websites so they often penalise or hide duplicate content. Additionally, if you have two pages with the same content, your inbound links might become split between the two - reducing the pagerank passed to either.&lt;/p&gt;

&lt;h2&gt;What's this got to do with ASP.NET MVC?&lt;/h2&gt;

&lt;p&gt;Unfortunately ASP.NET MVC makes it easy to have the same content indexed multiple times. I've listed the main problems below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case-Sensitivity&lt;/strong&gt;. In ASP.NET (or rather IIS and Windows), URLs are not case sensitive. That means you can write Default.asp, default.asp or even DeFalT.aSp and still get the same page. While you'll probably stick to the same case within your website, it wouldn't be hard for someone to create links to your site with different casing (e.g. they might have CAPS LOCK turned on).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default Documents&lt;/strong&gt;. Most websites have a default document set up to serve when a filename is not provided in the request. E.g. http://mydomain.com/ might actually serve up http://mydomain.com/default.asp, but it won't tell the browser that's what it did. It will serve it up as if the two are different URLs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trailing Slashes&lt;/strong&gt;. While the above problems are general ASP.NET/IIS issues, trailing slashes are something that only really become a problem with MVC or other URL rewriting/routing. In ASP.NET if you requested http://mydomain.com/files and you had a folder named files, IIS would issue a redirect to mydomain.com/files/. However, in ASP.NET MVC the URL routing will treat trailing slashes the same as requests without. So http://mydomain.com/controller/action is exactly the same as http://mydomain.com/controller/action/ and therefore results in duplicate content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query Strings&lt;/strong&gt;. Query strings can be a big problem for duplicate content. Imagine if you can add ?sort=field to the end of your page to have a table re-ordered. To a search engine this looks like another page, but the content is mostly the same. Fortunately, ASP.NET MVC doesn't really use query strings thanks to the excellent URL routing.&lt;/p&gt;

&lt;h2&gt;So, what can we do?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lowercase URLs&lt;/strong&gt;. We can force all requests to our application to be lowercase by catching them in BeginRequest in Global.asax and redirecting to the lowercase version if they contain any uppercase characters.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
protected void Application_BeginRequest(Object sender, EventArgs e)
{
	// Get the requested URL so we can do some validation on it.
	// We exclude the query string, and add that later, so it's not included
	// in the validation
	string url = (Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Authority + HttpContext.Current.Request.Url.AbsolutePath);

	// If we've got uppercase characters, fix
	if (Regex.IsMatch(url, @"[A-Z]"))
		PermanentRedirect(url.ToLower() + HttpContext.Current.Request.Url.Query);
}

/// &amp;lt;summary&amp;gt;
/// Redirects with a 301 header to pass along any incoming
/// PageRank/link value.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name="url"&amp;gt;The URL to redirect to&amp;lt;/param&amp;gt;
private void PermanentRedirect(string url)
{
	Response.Clear();
	Response.Status = "301 Moved Permanently";
	Response.AddHeader("Location", url);
	Response.End();
}
&lt;/pre&gt;

&lt;p&gt;Now if anyone requests a URL with uppercase characters, they'll be redirected with a 301 redirect. This works great, but we have a problem. All URLs generated internally by MVC will continue to use Action and Controller names in Pascal case (assuming that's how your classes are named). This means every link within our site will cause two requests (the first being a redirect). To fix this, we can override the default behaviour for creating URLs. We'll create a new extension method for the RouteCollection class called MapRouteLowercase which instead of creating a Route will create an instance of a new class, called LowercaseRoute. This class will override the GetVirtualPath method to lowercase the URL before passing it back. I can't take credit for this code, I pretty much just copied it from &lt;a href="http://goneale.wordpress.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc/"&gt;Graham O'Neale's blog&lt;/a&gt;.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
public class LowercaseRoute : System.Web.Routing.Route
{
	public LowercaseRoute(string url, IRouteHandler routeHandler)
		: base(url, routeHandler) { }
	public LowercaseRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
		: base(url, defaults, routeHandler) { }
	public LowercaseRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
		: base(url, defaults, constraints, routeHandler) { }
	public LowercaseRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
		: base(url, defaults, constraints, dataTokens, routeHandler) { }

	public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
	{
		VirtualPathData path = base.GetVirtualPath(requestContext, values);

		if (path != null)
			path.VirtualPath = path.VirtualPath.ToLowerInvariant();

		return path;
	}
}

public static class RouteCollectionExtensions
{
	public static void MapRouteLowercase(this RouteCollection routes, string name, string url, object defaults)
	{
		routes.MapRouteLowercase(name, url, defaults, null);
	}

	public static void MapRouteLowercase(this RouteCollection routes, string name, string url, object defaults, object constraints)
	{
		if (routes == null)
			throw new ArgumentNullException("routes");

		if (url == null)
			throw new ArgumentNullException("url");

		var route = new LowercaseRoute(url, new MvcRouteHandler())
		{
			Defaults = new RouteValueDictionary(defaults),
			Constraints = new RouteValueDictionary(constraints)
		};

		if (String.IsNullOrEmpty(name))
			routes.Add(route);
		else
		routes.Add(name, route);
	}
}
&lt;/pre&gt;

&lt;p&gt;You can put these classes anywhere. Because MapRouteLowercase is an extension method, you can just call it on the RouteCollection class in place of the existing MapRoute call in your Global.asax.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
// Home stuff
routes.MapRouteLowercase(
	"Default",
	"{page}",
	new { controller = "Home", action = "Index", page = 1 },
	new { page = @"\d+" }
);
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Default Documents&lt;/strong&gt;. While this issue doesn't affect MVC in the same way, there's a very similar problem. In ASP.NET MVC the default routing is {controller}/{action} but it sets a default action of Index. That means on a newly-created project, both /Home/Index and /Home will serve up the same content.&lt;/p&gt;

&lt;p&gt;To work around this, and provide some nicer URLs, I changed the routing a little so that my default actions where mapped to the root and a seperate route dealt with the homepage (which accepts pages, to allow browsing to older posts).&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
public static void RegisterRoutes(RouteCollection routes)
{
	routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

	// Posts
	routes.MapRouteLowercase(
		"Posts",
		"posts/{url}",
		new { controller = "Post", action = "Display" }
	);

	// Tags
	routes.MapRouteLowercase(
		"Tags",
		"tags/{url}/{page}",
		new { controller = "Tag", action = "Display", page = 1 },
		new { page = @"\d+" }
	);

	// Home stuff
	routes.MapRouteLowercase(
		"Default",
		"{page}",
		new { controller = "Home", action = "Index", page = 1 },
		new { page = @"\d+" }
	);

	// Home stuff
	routes.MapRouteLowercase(
		"Home",
		"{action}",
		new { controller = "Home", action = "" }
	);

	// Catch-all for any unmatched URL
	routes.MapRouteLowercase(
		"Error Catch-All",
		"{*path}",
		new { controller = "Home", action = "NotFound" } // NotFound doesn't exist, so HandleUnknownAction will be fired
	);
}
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Trailing Slashes&lt;/strong&gt;. To avoid trailing slashes and a few other minor issues (such as people adding /1 to a URL to get page 1, which is served up without the /1) I added some additional rules to my Global.asax as below.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
protected void Application_BeginRequest(Object sender, EventArgs e)
{
	// Get the requested URL so we can do some validation on it.
	// We exclude the query string, and add that later, so it's not included
	// in the validation
	string url = (Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Authority + HttpContext.Current.Request.Url.AbsolutePath);

	// If we're not a request for the root, and end with a slash, strip it off
	if (HttpContext.Current.Request.Url.AbsolutePath != "/" &amp;&amp; HttpContext.Current.Request.Url.AbsolutePath.EndsWith("/"))
		PermanentRedirect(url.Substring(0, url.Length - 1) + HttpContext.Current.Request.Url.Query);

	// If we end with /1 we're a page 1, and don't need (shouldn't have) the page number
	if (HttpContext.Current.Request.Url.AbsolutePath.EndsWith("/1"))
		PermanentRedirect(url.Substring(0, url.Length - 2) + HttpContext.Current.Request.Url.Query);

	// If we have double-slashes, strip them out
	else if (HttpContext.Current.Request.Url.AbsolutePath.Contains("//"))
		PermanentRedirect(url.Replace("//", "/") + HttpContext.Current.Request.Url.Query);

	// If we've got uppercase characters, fix
	else if (Regex.IsMatch(url, @"[A-Z]"))
		PermanentRedirect(url.ToLower() + HttpContext.Current.Request.Url.Query);
}
&lt;/pre&gt;

&lt;p&gt;This seems to stop many of the issues I came up with, however the double-slash seems to be passed through (in AbsolutePath) as a single slash here (Vista/IIS7) so doesn't work. I've left it in just in case this behaves differently on other web servers.&lt;/p&gt;

&lt;h2&gt;Is there anything else I should do?&lt;/h2&gt;

&lt;p&gt;As of February, &lt;a href="http://googlewebmastercentral.blogspot.com/2009/02/specify-your-canonical.html"&gt;Google&lt;/a&gt;, &lt;a href="http://ysearchblog.com/2009/02/12/fighting-duplication-adding-more-arrows-to-your-quiver/"&gt;Yahoo&lt;/a&gt;, &lt;a href="http://blog.ask.com/2009/02/ask-is-going-canonical.html"&gt;ASK&lt;/a&gt; and &lt;a href="http://blogs.msdn.com/webmaster/archive/2009/02/12/partnering-to-help-solve-duplicate-content-issues.aspx"&gt;Microsoft Live Search&lt;/a&gt; support a new Canonical meta-tag. This allows you to specify on a page that this page is duplicate content and any incoming links should instead be attributed to another page. If your site has query strings or other potential for multiple requests to serve up the same content I would recommend inserting this tag to make sure the search engines choose your prefered page.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470384611"&gt;Professional ASP.NET MVC 1.0 by Rob Conery, Scott Hanselman, Phil Haack, Scott Guthrie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430210079"&gt;Pro ASP.NET MVC Framework by Steven Sanderson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430228865"&gt;Pro ASP.NET MVC V2 Framework by Steven Sanderson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://weblogs.asp.net/scottgu/archive/2009/03/10/free-asp-net-mvc-ebook-tutorial.aspx"&gt;ScottGu: Free ASP.NET MVC eBook Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/12_f9PmH3C4" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/12_f9PmH3C4/reducing-duplicate-content-with-aspnet.html" title="Reducing Duplicate Content with ASP.NET MVC" /> <feedburner:origLink>http://blog.dantup.com/2009/04/reducing-duplicate-content-with-aspnet.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2009-04-06:/2009/04/generic-collections-and-thread-safety.html</id> <published>2009-04-06T18:39:00Z</published> <updated>2009-10-06T20:30:00Z</updated> <category term=".NET" /> <title type="text">Generic Collections and Thread Safety</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Recently I was involved in a discussion about generic collections and thread-safety. I was under the impression the framework had thread-safe versions of the generic collections, but I was unable to locate them and resorted to Google. I was wrong. There aren't any.&lt;/p&gt;

&lt;p&gt;I thought the subject would make an interesting post, since there seems to be a lot of bad information on the subject.&lt;/p&gt;

&lt;h2&gt;Why do we need thread-safe collections?&lt;/h2&gt;

&lt;p&gt;Imagine you're running multiple threads that take an item from a queue and perform some processing. Your code might look like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
// Get the next request from the queue
var request = infoPackRequests.Dequeue();

// Perform some processing
SendInfoPack(request);
&lt;/pre&gt;

&lt;p&gt;At first glance, you might think this code is pretty thread-safe. The first thread will get the first item, and the next thread will get the following one, right? Wrong. Sometimes you might be lucky and it will work out that way. Other times you might find your application does strange things, such as processing the same item twice.&lt;/p&gt;

&lt;p&gt;If you take a peek at the Dequeue method, you'll find some code that looks like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
Object removed = _array[_head];
_array[_head] = null;
_head = (_head + 1) % _array.Length;
_size--;
_version++;
return removed;
&lt;/pre&gt;

&lt;p&gt;This seems fairly straightforward. It grabs the item, nulls it out from the array and decreases the size. Now consider what would happen if both threads executed the first line at the same time. They'll both return the same item. And that's not the only thing that could go wrong.&lt;/p&gt;

&lt;h2&gt;So, how do we fix it?&lt;/h2&gt;

&lt;p&gt;Fortunately, this problem has been considered, and Microsoft provided the &lt;a href="http://msdn.microsoft.com/en-us/library/system.collections.icollection.syncroot.aspx"&gt;SyncRoot&lt;/a&gt; property on ICollection to help us synchronise access. Usage is simple - we take a lock on SyncRoot before doing anything that's not thread-safe:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
// Get the next request from the queue
var request;
lock(infoPackRequests.SyncRoot)
{
	request = infoPackRequests.Dequeue();
}

// Perform some processing
SendInfoPack(request);
&lt;/pre&gt;

&lt;p&gt;Now we can be sure our Dequeue methods won't be called at the same time. Note that we don't need to (and shouldn't) include the SendInfoPack call inside the lock, because it does not touch the collection. Including it in the lock would have a negative impact on performance, because other threads would block for the entire duration of SendInfoPack on any other thread.&lt;/p&gt;

&lt;h2&gt;Problem solved! Why the long face?&lt;/h2&gt;

&lt;p&gt;Well, this all works great when you're using a Queue or ICollection. Most of us however are now using Queue&amp;lt;T&amp;gt; or ICollection&amp;lt;T&amp;gt;. These classes don't expose a SyncRoot property. The BCL Team Blog &lt;a href="http://blogs.msdn.com/bclteam/archive/2005/03/15/396399.aspx"&gt;explains why&lt;/a&gt; this decision was taken. Developers were using "thread-safe" wrappers as if they didn't have to care about the threading issues, eg:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
if (myQueue.Count &gt; 0)
{
	DoSomething(myQueue.Dequeue());
}
&lt;/pre&gt;

&lt;p&gt;The problem here is that the locks provided by the wrapper within each method/property are released between the calls to Count and Dequeue, so there's no guarantee an item still exists when the Dequeue method is called.&lt;/p&gt;

&lt;h2&gt;So what's the preferred method?&lt;/h2&gt;

&lt;p&gt;Like most things, that depends on exactly what you're doing. The reason for taking the SyncRoot property away was to encourage developers to think about the synchronisation between threads rather than just blindly using synchronised wrappers and assuming it'll all be fine. You'll want to hold a lock for the duration of any operation that could cause issues if another thread was doing something at the same time. Using the above example, you'd want to ensure nothing was removed from the queue after you checked the Count property, so you would probably have something like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
var item = null;

lock (lockObject)
{
	if (myQueue.Count &gt; 0)
	{
		item = myQueue.Dequeue();
	}
}

if (item != null)
	DoSomething(item);
&lt;/pre&gt;

&lt;p&gt;This method makes it easier to synchronise multiple objects (imagine you were moving items to a "processed items" collection after processing) since you have a central lock object, rather than two independent SyncRoots.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470191376"&gt;Professional C# 2008 by Christian Nagel, Bill Evjen, Jay Glynn, Morgan Skinner, Karli Watson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1590598849"&gt;Pro C# 2008 and the .NET 3.5 Platform by Andrew Troelsen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0596527438"&gt;Programming C# 3.0 by Jesse Liberty, Donald Xie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/1c9txz50.aspx"&gt;MSDN: Managed Threading Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blogs.msdn.com/jaredpar/archive/2009/02/16/a-more-usable-thread-safe-collection.aspx"&gt;Jared Parsons: Usable API for a mutable thread-safe collection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/pzUpmdGcN3E" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/pzUpmdGcN3E/generic-collections-and-thread-safety.html" title="Generic Collections and Thread Safety" /> <feedburner:origLink>http://blog.dantup.com/2009/04/generic-collections-and-thread-safety.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2007-06-21:/2007/08/vista-obscene-disk-activity-solved.html</id> <published>2007-06-21T08:05:00Z</published> <updated>2007-09-02T11:09:00Z</updated> <category term="Vista" /> <title type="text">Vista Disk Thrashing - Solved?</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;I previously blogged about my &lt;a href="/2007/08/vista-lots-of-hard-disk-activity.html"&gt;crazy Vista disk activity&lt;/a&gt; and had a few suggestions in the comments. Judging by the number of Google referrals I'm getting for "&lt;span class="shop"&gt;vista&lt;/span&gt; disk activity" and like, I'm guessing these are very common issues.&lt;/p&gt;

&lt;p&gt;Since posting, I've actually had very little disk thrashing, and I'm wondering if it's related to the &lt;a href="http://support.microsoft.com/kb/938194/en-us" target="_blank"&gt;Compatibility&lt;/a&gt; and &lt;a href="http://support.microsoft.com/kb/938979/en-us" target="_blank"&gt;Performance&lt;/a&gt; packs I installed around the same time.&lt;/p&gt;

&lt;p&gt;I came across &lt;a href="http://jkontherun.blogs.com/jkontherun/2007/05/disk_thrashing_.html" target="_blank"&gt;this blog&lt;/a&gt; saying that turning off paging and prefetch solved his problem. Additionally, the comments in my &lt;span class="shop"&gt;blog&lt;/span&gt; suggest it could be the indexing service. It's worth giving this things a try if you're still having the same problems (after installing the above packs) and see how you get on. Let us know in a comment how you get on.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470045752"&gt;Microsoft Windows Vista Simplified by Paul McFedries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0735622701"&gt;Windows Vista Inside Out by Carl Siechert, Craig Stinson, Ed Bott&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/iLKtJfymiR8" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/iLKtJfymiR8/vista-obscene-disk-activity-solved.html" title="Vista Disk Thrashing - Solved?" /> <feedburner:origLink>http://blog.dantup.com/2007/08/vista-obscene-disk-activity-solved.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2007-06-18:/2007/08/vista-lots-of-hard-disk-activity.html</id> <published>2007-06-18T11:40:00Z</published> <updated>2009-10-06T20:22:00Z</updated> <category term="Vista" /> <title type="text">Vista - Lots of Hard Disk Activity</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Every time I boot my &lt;span class="shop"&gt;Vista&lt;/span&gt; machine up, I hear my hard disks grinding for quite a while after the machine finishes booting and services finish starting up. I also find that if I leave my machine for a while, I'll come back to more hard disk thrashing. With noisy RAID Raptors, and a case made of mesh, it's not a particularly enjoyable sound!&lt;/p&gt;

&lt;p&gt;Today I'd had enough, and decided to check on the web if anyone else is having this sort of behaviour. It seems many are. Lots of people are pointing at System Restore, so I fired up regedit to find it runs every 24 hours. This explains every day when I boot it up, the hard disk activity. I don't really want to turn this off, but maybe I could increase the frequency. After all, I'm not often doing anything that should upset the system!&lt;/p&gt;

&lt;p&gt;This doesn't address the "idle" noise. I guarantee if I leave my machine for 5 minutes, &lt;span class="shop"&gt;Vista&lt;/span&gt; will begin doing something behind my back, and if I move the mouse, it'll stop within a few seconds. This isn't an accident - &lt;span class="shop"&gt;Vista&lt;/span&gt; is doing something when it thinks I'm not using the machine - &lt;em&gt;but what&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Has anyone else seen this behaviour? Any ideas on what it could be, or how I could track it down? I'm more curious than annoyed - I just want to know what it is!!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470045752"&gt;Microsoft Windows Vista Simplified by Paul McFedries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0735622701"&gt;Windows Vista Inside Out by Carl Siechert, Craig Stinson, Ed Bott&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/DGMCUaB2xf8" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/DGMCUaB2xf8/vista-lots-of-hard-disk-activity.html" title="Vista - Lots of Hard Disk Activity" /> <feedburner:origLink>http://blog.dantup.com/2007/08/vista-lots-of-hard-disk-activity.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2007-06-12:/2007/08/vs-2008-testing-tools-to-be-included-in.html</id> <published>2007-06-12T18:35:00Z</published> <updated>2009-10-06T20:23:00Z</updated> <category term=".NET" /> <title type="text">VS 2008: Testing Tools to be included in Professional edition of Visual Studio 2008</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;In case you hadn't already heard, the testing tools that were previously only available in the Team System edition of &lt;span class="shop"&gt;Visual Studio&lt;/span&gt; are included in Visual Studio 2008! This is great news for those using Nunit because they couldn't afford the &lt;span class="shop"&gt;Team System&lt;/span&gt; editions. Unlike Nunit, these tools full integrate with the IDE!&lt;/p&gt;

&lt;p&gt;So, let's have a look. I opened up one of my existing projects, and in the context menu there's a new option for "Create Unit Tests".&lt;/p&gt;

&lt;img src="/pi/vs_test_1.png"&gt;

&lt;p&gt;If you click this option, you'll be presented with a list of your classes and their methods. You can tick those you wish to generate unit tests for.&lt;/p&gt;

&lt;img src="/pi/vs_test_2.png"&gt;

&lt;p&gt;The settings dialog allows you to set how the class and method names are generated.&lt;/p&gt;

&lt;img src="/pi/vs_test_3.png"&gt;

&lt;p&gt;Tests will automatically be generated in a new project, so when you click Ok, you'll be prompted for a name. I'm not entirely sure why this wasn't just included on the previous dialog, but it's not a problem.&lt;/p&gt;

&lt;img src="/pi/vs_test_4.png"&gt;

&lt;p&gt;Now your tests will be generated. It may take a few minutes - it took a while here, and I only had one test to generate! When finished, you'll see a new project in your solution explorer, and some config files in a new "Solution Items" folder.&lt;/p&gt;

&lt;img src="/pi/vs_test_5.png"&gt;
		
&lt;p&gt;In the generated tests is a property of type TestContext. I've never used anything similar in Nunit, but it looks like this allows data-driven tests, where you can have a single test run for every row in a set of data you provide. I can see this being very powerful for testing lots of different conditions without duplicating tests!&lt;/p&gt;

&lt;img src="/pi/vs_test_6.png"&gt;

&lt;p&gt;To run your tests, you can use the new Tests menu at the top of the window. You'll get build-like progress, and failing tests show similar to compile errors. Double-clicking a failed test will give details, as shown below.&lt;/p&gt;

&lt;img src="/pi/vs_test_7.png"&gt;

&lt;p&gt;While I love Nunit, I've been annoyed there's been no real (free) integration of tests into &lt;span class="shop"&gt;Visual Studio&lt;/span&gt;, so I'm made up to finally have these testing tools at my fingertips. Move over Nunit, Microsoft are taking over!&lt;/p&gt;

&lt;p&gt;The more I use &lt;span class="shop"&gt;Visual Studio&lt;/span&gt; 2008, the more I wish I was using it at work! It's packed with new features that make coding much quicker and less error-prone!&lt;/p&gt;

&lt;p&gt;Don't forget, there are also free versions of &lt;span class="shop"&gt;Visual Studio&lt;/span&gt; 2008 (sadly without testing tools!) for the home-coder on a budget!&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470191376"&gt;Professional C# 2008 by Christian Nagel, Bill Evjen, Jay Glynn, Morgan Skinner, Karli Watson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1590598849"&gt;Pro C# 2008 and the .NET 3.5 Platform by Andrew Troelsen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0596527438"&gt;Programming C# 3.0 by Jesse Liberty, Donald Xie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/B000WM04HU"&gt;Visual Studio 2008 Professional&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/ixYeJ1UnSlg" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/ixYeJ1UnSlg/vs-2008-testing-tools-to-be-included-in.html" title="VS 2008: Testing Tools to be included in Professional edition of Visual Studio 2008" /> <feedburner:origLink>http://blog.dantup.com/2007/08/vs-2008-testing-tools-to-be-included-in.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2007-06-08:/2007/08/wpf-designer-cider-part-2.html</id> <published>2007-06-08T21:21:00Z</published> <updated>2009-10-06T20:28:00Z</updated> <category term="WPF" /> <category term=".NET" /> <title type="text">The WPF Designer (Cider) part 2...</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;After Mark Wilson-Thomas, the Program Manager on the &lt;span class="shop"&gt;WPF&lt;/span&gt; Designer team &lt;a href="/2007/08/visual-studio-2008-beta-2-first.html#2446177544441461195"&gt;responded&lt;/a&gt; to &lt;a href="/2007/08/visual-studio-2008-beta-2-first.html"&gt;my post&lt;/a&gt; about my WPF issues, I thought I owed it a second chance having read his comments.&lt;/p&gt;

&lt;h2&gt;Locating Controls&lt;/h2&gt;
&lt;p&gt;My first issue was with the control drop-down being removed. Mark pointed out a few alternatives like the Document Outline window and the &lt;span class="shop"&gt;XAML&lt;/span&gt; Path control. The Document Outline seems to work pretty well, though I'm still not covinced I'll get along with it. Usually when I'm heading for the old drop-down at the top of the property grid, it's because I need to select something I can't easily select in the designer (menu items, controls that are dynamically hidden/shown etc.), and I usually have the name of them in my head. That makes an alphabetical list much quicker than a tree. I know what the item is likely to be called, but not necessarily how it's embedded in a huge tree of SplitPanels. I've also noticed a few minor issues as you can see in my screenshot.&lt;/p&gt;

&lt;img src="/pi/wpf_1.png" /&gt;

&lt;p&gt;The tree is a bit flat, and not very tree-like, so it's not easy at a glance to see the hierarchy. While this might not seem like a big deal since the drop-down I love was flat, but since the items aren't sorted alphabetically now, it can be a little tricky to find your item. Though this might get easier the more I use it! The other issue is that the small preview appears squashed sometime. I'm not sure if this is a bug, or if it's by design, but the button shown in mine doesn't quite look like the actual button on the form.&lt;/p&gt;

&lt;img src="/pi/wpf_2.png" /&gt;

&lt;p&gt;The XAML tree is pretty nifty, though a little fiddly to use. You can very easily select a parent of the currently selected item, and an arrow (" /&gt; ") list children. While this is pretty powerful, since it only deals with parents/children, it's probably more likely you'll get into the habit of using the Document Outline instead.&lt;/p&gt;

&lt;h2&gt;Adding Non-Default Event Handlers&lt;/h2&gt;
&lt;p&gt;Mark pointed me at &lt;a href="http://blogs.msdn.com/charles_sterling/archive/2007/07/24/wpf-event-handlers-visual-studio-2008-beta2.aspx"&gt;Chuck Sterling's blog&lt;/a&gt; showing new intellisense that can add event handlers. I'm still not convinced this is as nice as being able to easily see a filtered list of events, it's a nice addition. This would be much more friendly if there was a quick way to filter the intellisense to just list events (though I suspect there won't be).&lt;/p&gt;

&lt;h2&gt;Finding Properties&lt;/h2&gt;
&lt;p&gt;Mark hinted at "a better way of locating the properties in the property grid before we ship" which I suspect might be live filtering of properties like in some of the Expression tools (a search box, that hides properties that don't match what you're typing). Withing seeing it, I can't say whether it'll be as natural as looking for properties based on what letter they start with, but I'll post my thoughts when if and when I see it.&lt;/p&gt;

&lt;h2&gt;Rendering Issues&lt;/h2&gt;
&lt;p&gt;As for the rending issues, I managed to get a screenshot of the kind of thing I'm seeing. This particular issue shows up when I hit "Swap Panes" to replace the XAML with the Design view. It always renders the hosted WebBrowser control, but nothing around it. When I click anywhere on the surface, everything else appears.&lt;/p&gt;

&lt;p&gt;Here's screenshots showing what's displayed after clicking the "Swap Panes" button and the expected view (which happens when I switch to XAML then back to Designer normally).&lt;/p&gt;


&lt;img src="/pi/wpf_3.png" /&gt;
&lt;img src="/pi/wpf_4.png" /&gt;

&lt;h2&gt;Update:&lt;/h2&gt;
&lt;p&gt;I've &lt;a href="http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1983498&amp;SiteID=1"&gt;posted to the Cider Forums&lt;/a&gt; as requested.&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/MbwiZ5tfB8Y" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/MbwiZ5tfB8Y/wpf-designer-cider-part-2.html" title="The WPF Designer (Cider) part 2..." /> <feedburner:origLink>http://blog.dantup.com/2007/08/wpf-designer-cider-part-2.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2007-05-18:/2007/08/net-using-dictionaries-for-performance.html</id> <published>2007-05-18T14:27:00Z</published> <updated>2007-09-02T11:20:00Z</updated> <category term=".NET" /> <title type="text">.NET Using Dictionaries for a Performance Boost</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;I often see code that loops through List&amp;lt;T&amp;gt;s or uses List&amp;lt;T&amp;gt;.ForEach() to repeatedly find specific items in a list. Take the below example. Rather than hitting the database every time we need to check permissions, they're all loaded upon login and cached into the session. As the user moves around the application, the permissions are checked with code similar to the below.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
/// &amp;lt;summary&amp;gt;   
/// A cached list of posts with their access levels   
/// &amp;lt;/summary&amp;gt;   
List&amp;lt;Post&amp;gt; posts;   

/// &amp;lt;summary&amp;gt;   
/// Preload the posts for testing permissions   
/// &amp;lt;/summary&amp;gt;   
private void preloadPosts()   
{   
	posts = DataAccess.Post.GetAll();   
}   

/// &amp;lt;summary&amp;gt;   
/// Returns the access level for a given post   
/// &amp;lt;/summary&amp;gt;   
/// &amp;lt;param name="postID"&amp;gt;&amp;lt;/param&amp;gt;   
/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;   
public AccessLevel GetAccessLevel(int postID)   
{   
	// Loop through each post looking for the access level   
	foreach (Post p in posts)   
		if (p.ID == postID)   
			return p.AccessLevel;   

	// If we didn't find a post, we don't have access to it   
	return AccessLevel.NoAccess;   
} 
&lt;/pre&gt;

&lt;p&gt;While this isn't a big deal if the list is small, or the method isn't called much, what started off as simple code could quickly grow.&lt;/p&gt;

&lt;p&gt;I was asked to look at the performance of one our applications, and requested the company invest in &lt;a href="http://www.red-gate.com/products/ants_profiler/index.htm" target="_blank"&gt;RedGate Ants Profiler&lt;/a&gt; (which I'd highly recommend for anyone doing .NET development - shame there isn't a cheaper Home Edition!). When I ran the profiler it became very obvious very quickly where the profile was. The problem was that our application was taking &lt;em&gt;several minutes&lt;/em&gt; to return a single list/table of data. The database had two tables: Person and Answer. The Answer table had a PersonID, and each Person could have hundreds of Answers. There were hundreds of People.&lt;/p&gt;

&lt;p&gt;The offending code looped through all People, and then did a .FindAll() on a List&amp;lt;Answer&amp;gt; containing &lt;em&gt;every answer for every positions&lt;/em&gt;. This mean with 10,000 positions, each having 1,000 answers, the List&amp;lt;Answer&amp;gt; had 10,000,000 items. This meant we were calling .FindAll() 10,000 times on 10,000,000 items. That's 100,000,000,000 iterations.&lt;/p&gt;

&lt;p&gt;Since I wasn't able to modify any of the data access code at the time, I had to work with the two lists (List&amp;lt;Person&amp;gt; = 10,000 items, List&amp;lt;Answer&amp;gt; = 10,000,000 items).&lt;/p&gt;

&lt;p&gt;Rather than loop through the 10,000,000 items for every position, I decided to loop through them &lt;em&gt;once&lt;/em&gt;. By adding them to a dictionary based on their PersonID, I could quickly retrieve them while looping over the people.&lt;/p&gt;

&lt;p&gt;The new code looked like this:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
public void OutputData(List&amp;lt;Person&amp;gt; people, List&amp;lt;Answer&amp;gt; answers)   
{   
	// Create a dictionary to looup answers in   
	Dictionary&amp;lt;int, List&amp;lt;Answer&amp;gt;&amp;gt; answerLookup = new Dictionary&amp;lt;int, List&amp;lt;Answer&amp;gt;&amp;gt;();   
  
	// Loop through every answer   
	foreach (Answer a in answers)   
	{   
		// If we haven't hit this Person before, create a lookup for them   
		List&amp;lt;Answer&amp;gt; myAnswers;   
		if (!answerLookup.ContainsKey(a.PersonID))   
		{   
			myAnswers = new List&amp;lt;Answer&amp;gt;();   
			answerLookup.Add(a.PersonID, myAnswers);   
		}   
		else // Else get the existing list   
			myAnswers = answerLookup[a.PersonID];   
  
		// Add this answer to our lookup   
		myAnswers.Add(a);   
	}   
  
	// Now output our data   
	foreach (Person p in people)   
	{   
		// Get a list of ONLY the answers for this position   
		List&amp;lt;Answer&amp;gt; myAnswers = answers[p.PersonID];   
		Console.WriteLine(p.Name);   
  
		// Loop through our answers   
		foreach (Answer a in myAnswers)   
			Console.WriteLine("	{0}", a.AnswerValue);   
  
	}   
}  
&lt;/pre&gt;

&lt;p&gt;The resulting code took seconds to run instead of minutes, and the time spent on those loops was now only a small fraction of the total time (the database calls took most if it).&lt;/p&gt;

&lt;p&gt;So, next time you find yourself writing a .Find() call or a .ForEach() to just find a particular element, consider the performance implications, and maybe add a &lt;span class="shop"&gt;Dictionary&lt;/span&gt; to make things faster.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0470191376"&gt;Professional C# 2008 by Christian Nagel, Bill Evjen, Jay Glynn, Morgan Skinner, Karli Watson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1590598849"&gt;Pro C# 2008 and the .NET 3.5 Platform by Andrew Troelsen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0596527438"&gt;Programming C# 3.0 by Jesse Liberty, Donald Xie&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/Xh6wQTqVDec" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/Xh6wQTqVDec/net-using-dictionaries-for-performance.html" title=".NET Using Dictionaries for a Performance Boost" /> <feedburner:origLink>http://blog.dantup.com/2007/08/net-using-dictionaries-for-performance.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2007-05-17:/2007/07/loading-models-in-xna-with-content.html</id> <published>2007-05-17T07:43:00Z</published> <updated>2008-12-09T06:32:00Z</updated> <category term=".NET" /> <category term="XNA" /> <title type="text">Loading Models in XNA with the Content Pipeline</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;The last time I played with &lt;span class="shop"&gt;XNA&lt;/span&gt; was when it was in in beta. There was no "Content Pipeline", and loading models was so tedious, I just decided to stop using XNA until it RTM'd. Since now is that time, I thought I'd best see how things have changed...&lt;/p&gt;

&lt;p&gt;I bought the &lt;a href="http://www.garagegames.com/"&gt;GarageGames&lt;/a&gt; &lt;a href="http://www.garagegames.com/products/154/"&gt;Orcs RTS Buildings Pack&lt;/a&gt;, since it looked fitting for an &lt;span class="shop"&gt;MMO&lt;/span&gt;, was pretty cheap and had .x versions already included (which I know the Content Pipeline should handle).&lt;/p&gt;

&lt;p&gt;I fired up GSE and added the .X files to my project and clicked Build....&lt;/p&gt;

&lt;p&gt;I was greeted to some compile errors - and I hadn't even started coding!!&lt;/p&gt;

&lt;p&gt;Thankfully, these errors were trivial. The content pack included .x and .jpg files with the same name (orcs1.jpg and orcs1.x). The content pipeline likes to strip the extensions off for the asset names, so there was a clash. I ran through and added ".jpg" to the asset name of each jpeg (though now I'm typing, I'm thinking I could've just Excluded them from the project, since the reference in the .x file would cause them to be added!).&lt;/p&gt;

&lt;p&gt;With them all building, it was time to see if I could add them to my world. I was starting with the &lt;a href="http://creators.xna.com/Headlines/developmentaspx/archive/2007/01/01/Chase-Camera-Sample.aspx"&gt;ChaseCamera sample from creators.xna.com&lt;/a&gt;, since I knew I'd be able to move the &lt;span class="shop"&gt;camera&lt;/span&gt; around easily to see the models.&lt;/p&gt;

&lt;p&gt;At the top of Game.cs, I added a new variable to hold my models:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;List&amp;lt;Model&amp;gt; buildingModels = new List&amp;lt;Model&amp;gt;();&lt;/pre&gt;

&lt;p&gt;And in the LoadGraphicsContent() method, I loaded each model. Since the filenames weren't sequential, I just hard-coded them for now:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs1"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs11"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs12"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs13"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs14"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs15"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs2"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs3"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs4"));
buildingModels.Add(content.Load&amp;lt;Model&amp;gt;("Content/OrcPack/snow/orcs5"));
&lt;/pre&gt;

&lt;p&gt;And in my Draw() method, I called the existing DrawModel() method (which came from the ChaseCamera sample, to draw the ship/ground). Since the models are in a much smaller scale than the ship, I had to scale them up. Really this should be done per-asset at compile time - I'll see about creating a custom importer for this later (since all content packs will likely have different settings, and I want my units to be metres).&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
DrawModel(buildingModels[0], Matrix.Identity * Matrix.CreateTranslation(new Vector3(0, 0, 0)) * Matrix.CreateScale(100));
DrawModel(buildingModels[1], Matrix.Identity * Matrix.CreateTranslation(new Vector3(300, 0, 0)) * Matrix.CreateScale(100));
DrawModel(buildingModels[2], Matrix.Identity * Matrix.CreateTranslation(new Vector3(600, 0, 0)) * Matrix.CreateScale(100));
DrawModel(buildingModels[3], Matrix.Identity * Matrix.CreateTranslation(new Vector3(600, 0, -300)) * Matrix.CreateScale(100));
DrawModel(buildingModels[4], Matrix.Identity * Matrix.CreateTranslation(new Vector3(600, 0, -600)) * Matrix.CreateScale(100));
DrawModel(buildingModels[5], Matrix.Identity * Matrix.CreateTranslation(new Vector3(300, 0, -600)) * Matrix.CreateScale(100));
DrawModel(buildingModels[6], Matrix.Identity * Matrix.CreateTranslation(new Vector3(0, 0, -600)) * Matrix.CreateScale(100));
DrawModel(buildingModels[7], Matrix.Identity * Matrix.CreateTranslation(new Vector3(0, 0, -300)) * Matrix.CreateScale(100));
DrawModel(buildingModels[8], Matrix.Identity * Matrix.CreateTranslation(new Vector3(200, 0, -200)) * Matrix.CreateScale(100));
DrawModel(buildingModels[9], Matrix.Identity * Matrix.CreateTranslation(new Vector3(400, 0, -400)) * Matrix.CreateScale(100));
&lt;/pre&gt;

&lt;p&gt;I also made a slight change to the ground.x model to reference the snowy ground.jpg from the content pack. I ran the project again, and there are the models!&lt;/p&gt;

&lt;img src="/pi/xna_pipeline_1.png" /&gt;&lt;/a&gt;

&lt;p&gt;So, in conclusion, the Content Pipeline makes loading models trivial. The next few things to look at are heightmaps, loading models in from some sort of "level file" rather than hard-coding, and animated models that react to user input (eg. breathing/running animations).&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/0672330229"&gt;Microsoft XNA Game Studio 3.0 Unleashed by Chad Carter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/143021855X"&gt;XNA 3.0 Game Programming Recipes: A Problem-Solving Approach by Riemer Grootjans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430218177"&gt;Beginning XNA 3.0 Game Programming: From Novice to Professional by Alexandre Santos, Bruno Evangelista, José Antonio Leal de Farias, Riemer Grootjans&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/osp3WvbszQw" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/osp3WvbszQw/loading-models-in-xna-with-content.html" title="Loading Models in XNA with the Content Pipeline" /> <feedburner:origLink>http://blog.dantup.com/2007/07/loading-models-in-xna-with-content.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2007-05-12:/2007/08/using-ipod-on-windows-without-itunes.html</id> <published>2007-05-12T14:27:00Z</published> <updated>2009-12-27T15:38:00Z</updated> <category term="iPhone" /> <title type="text">Using iPod with Windows Media Player instead of iTunes</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Up until recently, I've never had an &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt;. It wasn't that I didn't like them - I think they're the best looking &lt;span class="shop"&gt;MP3 players&lt;/span&gt; out there. There was one reason, and one reason only that I hadn't bought one - &lt;strong&gt;&lt;span class="shop"&gt;iTunes&lt;/span&gt;&lt;/strong&gt;. On &lt;span class="shop"&gt;Windows&lt;/span&gt;, and it has to be amongst the clunkiest applications from a major software company I've ever used. I'm not sure if it's because of the desire to make it look like a &lt;span class="shop"&gt;Mac&lt;/span&gt; and custom-draw &lt;em&gt;everything&lt;/em&gt;, or just sloppy coding. It just always seems clunky and unresponsive, and hogs machine resources.&lt;/p&gt;

&lt;div class="banner mp3"&gt;&lt;/div&gt;

&lt;p&gt;To make things worse, &lt;a href="http://www.itwire.com.au/content/view/9174/53/"&gt;Apple started telling people not to upgrade&lt;/a&gt; to &lt;span class="shop"&gt;Vista&lt;/span&gt; because &lt;span class="shop"&gt;iTunes&lt;/span&gt; didn't work properly on it. WTF! Apple had &lt;em&gt;how long&lt;/em&gt; to sort this out? You can't just tell people not to upgrade their &lt;em&gt;operating system&lt;/em&gt; because you failed at meeting the deadline for your music player! (I do see &lt;a href="http://docs.info.apple.com/article.html?artnum=305042"&gt;this has since been fixed&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Anyway, back to the point. What stopped me buying an &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt; was iTunes. The software is nasty, and I already have my music collection in Windows Media Player, which plays nice with &lt;span class="shop"&gt;Windows Media Center&lt;/span&gt; and my &lt;span class="shop"&gt;Xbox 360&lt;/span&gt;. If Apple made the &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt; work with Windows Media Player, I'd have bought one. Assuming there are other people like me, &lt;em&gt;Apple are losing potential business by trying to force people on to iTunes&lt;/em&gt;. I found there were a few ways to get Windows Media Player to work with the &lt;span class="shop"&gt;iPod&lt;/span&gt;, but it required 3rd party plugins (and cost money), and I wasn't about to buy an &lt;span class="shop"&gt;iPod&lt;/span&gt; to find these solutions aren't stable.&lt;/p&gt;

&lt;div class="banner musicbig1"&gt;&lt;/div&gt;

&lt;p&gt;As fate would have it, I received a &lt;span class="shop"&gt;Blue 4GB iPod nano&lt;/span&gt; from my auntie when she was visiting from the states. The whole idea of not wanting to buy one in case it didn't work was squashed. I had an &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt;, and I was going to use it, with or without Windows Media Player support!&lt;/p&gt;

&lt;p&gt;When I got home, I downloaded a trial of &lt;a href="http://www.mgtek.com/dopisp/"&gt;MGTEK dopisp&lt;/a&gt; - one of the plugins claiming to get the &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt; working with Windows Media Player. There are a few other plugins to do this, but a quick Google revealed less unhappy people using this one! I installed the plugin, connected my &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt;, and fired up Windows Media Player.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error!&lt;/strong&gt; I was greeted with a dialog telling my my &lt;span class="shop"&gt;iPod&lt;/span&gt; had never been set up (via iTunes) and couldn't be used. I had a feeling this would happen, and luckily I had a laptop running Windows XP I was about to flatten! I installed iTunes and set up the iPod, then tried again. The iPod now appeared (with the name I assigned in iTunes) in Windows Media Player as a mobile device. I grabbed a few songs and tried to sync. It worked. It worked exactly like I wanted it to. It was &lt;em&gt;that&lt;/em&gt; easy.&lt;/p&gt;

&lt;p&gt;That makes me wonder &lt;em&gt;why Apple didn't write a similar plugin&lt;/em&gt;? I understand they &lt;em&gt;really&lt;/em&gt; want people to use iTunes, but is it really worth losing &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt; sales over? Forcing people to use your software is &lt;em&gt;not&lt;/em&gt; the way to do business. Sell your &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt; on what it is. Sell iTunes on what it is. If people just want one, let them have it. You're lucky I received an &lt;a href="http://www.amazon.co.uk/gp/product/B002MRRRPA?ie=UTF8&amp;tag=dantupblo-21&amp;linkCode=as2&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B002MRRRPA" target="_blank"&gt;iPod&lt;/a&gt; as a present, because you'd have missed out on this sale without native Windows Media Player support.&lt;/p&gt;

&lt;h2&gt;Related Reading&lt;/h2&gt;
&lt;ul class="related"&gt;
&lt;li&gt;&lt;a class="go" href="/go/1430229349"&gt;Learn iPod Touch and iTunes by Erica Sadun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0321569350"&gt;The iPod Book: Doing Cool Stuff with the iPod and the iTunes Store by Scott Kelby, Terry White&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0071630244"&gt;How to Do Everything iPod, iPhone &amp; iTunes by Guy Hart-Davis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="go" href="/go/0764569848"&gt;Hacking iPod and iTunes (ExtremeTech) by Scott Knaster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="banner musicbig2"&gt;&lt;/div&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/z5JI3CNn20g" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/z5JI3CNn20g/using-ipod-on-windows-without-itunes.html" title="Using iPod with Windows Media Player instead of iTunes" /> <feedburner:origLink>http://blog.dantup.com/2007/08/using-ipod-on-windows-without-itunes.html</feedburner:origLink></entry> <entry> <id>tag:blog.dantup.com,2007-05-04:/2007/08/visual-studio-2008-beta-2-first.html</id> <published>2007-05-04T09:12:00Z</published> <updated>2009-10-06T20:19:00Z</updated> <category term="WPF" /> <category term=".NET" /> <title type="text">Visual Studio 2008 Beta 2 First Impressions: WPF Applications</title> <content type="html" xml:base="http://blog.dantup.com/">
			&lt;p&gt;Visual Studio 2008 Beta 2 is &lt;a href="http://msdn2.microsoft.com/en-us/vstudio/aa700831.aspx"&gt;now available&lt;/a&gt;. That means all of the cool things Microsoft have been tempted us with for the last few years are available to use: &lt;a href="http://msdn2.microsoft.com/en-us/netframework/aa904594.aspx"&gt;Linq&lt;/a&gt;, &lt;a href="http://msdn2.microsoft.com/en-us/library/bb425822.aspx"&gt;Linq to SQL&lt;/a&gt;, Lambda Expressions, (more stable) WPF Designer!&lt;/p&gt;

&lt;p&gt;Something I didn't realise until now, is that the "WPF Application" option hasn't replaced the old "Windows Forms Application" option. You can still create .NET 3.5-targeted, good old Windows Forms. As it turns out, it's a good job, because the WPF designer just doesn't cut it. Here's some things I noticed within 5 minutes of trying to build a simple WPF App:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The property grid doesn't have an option to show Events&lt;/li&gt;
&lt;li&gt;The property grid doesn't have an option to sort alphabetically&lt;/li&gt;
&lt;li&gt;There's no drop-down above the property grid showing all the objects on your forms&lt;/li&gt;
&lt;li&gt;There are massive rendering issues in the designer on my machine (Vista)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Being the final beta and with a Go-Live licence, I was hoping this was all going to be pretty stable, but it really isn't good enough. Sure, the property grid looks like a complete rewrite in WPF, and I admire them for that, but unless it's going to ship working at least as well as the old one, is it really worth it?&lt;/p&gt;

&lt;p&gt;The final point, about rending issues is quite a bug. Sometimes controls just don't appear on the form until I click where they should be. If I run my application and then close it (so VS gets focus back), the form is completely blank. Waving the mouse around clicking reveals all of the controls.&lt;/p&gt;

&lt;p&gt;All in all, I'm not impressed with the WPF Designer, and I won't be building my next application in WPF!&lt;/p&gt;

&lt;p&gt;I don't know how Microsoft choose to categories their properties, so I always use A-Z view. When I've got controls obscuring other controls, I used to use that nice drop-down. Why must I know switch to XAML just to find my control? Why must I go via XAML to see my event handlers?&lt;/p&gt;

&lt;p&gt;Let's hope this changes before VS2008 ships!!&lt;/p&gt;
		&lt;img src="http://feeds.feedburner.com/~r/DanTup/~4/Z11DOQSfMuk" height="1" width="1"/&gt;</content> <link rel="alternate" type="text/html" href="http://feeds.dantup.com/~r/DanTup/~3/Z11DOQSfMuk/visual-studio-2008-beta-2-first.html" title="Visual Studio 2008 Beta 2 First Impressions: WPF Applications" /> <feedburner:origLink>http://blog.dantup.com/2007/08/visual-studio-2008-beta-2-first.html</feedburner:origLink></entry> </feed>
