<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://weblogs.asp.net/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Jeff and .NET</title><link>http://weblogs.asp.net/jeff/default.aspx</link><description>The .NET musings of Jeff Putz</description><dc:language>en</dc:language><generator>CommunityServer 2007 SP1 (Build: 20510.895)</generator><item><title>When the Google beats on your SignalR</title><link>http://weblogs.asp.net/jeff/archive/2013/06/10/when-the-google-beats-on-your-signalr.aspx</link><pubDate>Tue, 11 Jun 2013 03:56:00 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:10377628</guid><dc:creator>Jeff</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=10377628</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/06/10/when-the-google-beats-on-your-signalr.aspx#comments</comments><description>&lt;p&gt;Around the end of April, I put v11 of &lt;a href="https://popforums.codeplex.com/" target="_blank"&gt;POP Forums&lt;/a&gt; into production on &lt;a href="http://coasterbuzz.com/" target="_blank"&gt;CoasterBuzz&lt;/a&gt;. Probably the biggest feature of that release was all of the new real-time stuff in the forum, with new posts appearing before your eyes and in the topic lists and such. This was all enabled in part by &lt;a href="http://www.asp.net/signalr" target="_blank"&gt;SignalR&lt;/a&gt;, the framework that allows for bidirectional communication between the browser and the server over an open connection (or simulated open connection, depending on the browser).&lt;/p&gt;
&lt;p&gt;It didn't take long before I noticed some odd exceptions being thrown in the error logs around the SignalR endpoints. They were all coming from Googlebot, which apparently scans Javascript and looks for ways to scan content that's ordinarily loaded dynamically into your site's pages. Yay for Google trying to find content on your site, but in this case, there are two big problems.&lt;/p&gt;
&lt;p&gt;The first problem is that Googlebot appears to be somewhat stupid. While it identifies the endpoint, the actual URL that SignalR uses, it seems to have no regard as to what data has to be posted to it. That's where the exceptions come from, because SignalR doesn't understand the request.&lt;/p&gt;
&lt;p&gt;The second problem is that Googlebot understandably expects to get a response and move along. But SignalR likes to keep an open connection so that the client and server bits can talk to each other. That's kind of the whole point. I didn't catch this issue until I used Google Webmaster Tools to see what my load times were looking like. You can very plainly see where I started using SignalR, and when I fixed the problem. Google was hanging on for as long as two seconds.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;img src="http://jeffputz.com/Post/Image/3772" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;The reason I looked is because Google was being relentless at one point, banging on the thing hard enough to generate hundreds of exceptions every hour. The fix was easy enough, just put a few lines in your robots.txt file that tell the Google to back off:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;User-agent: *&lt;br /&gt;Disallow: /signalr/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The more you know, the smarter you grow.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=10377628" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/POP+Forums/default.aspx">POP Forums</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category><category domain="http://weblogs.asp.net/jeff/archive/tags/MVC/default.aspx">MVC</category></item><item><title>Everyone else is doing it (incorrectly)!</title><link>http://weblogs.asp.net/jeff/archive/2013/05/18/everyone-else-is-doing-it-incorrectly.aspx</link><pubDate>Sat, 18 May 2013 20:51:12 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:10307075</guid><dc:creator>Jeff</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=10307075</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/05/18/everyone-else-is-doing-it-incorrectly.aspx#comments</comments><description>&lt;p&gt;&lt;em&gt;[This is actually a &lt;a href="http://jeffputz.com/blog/everyone-else-is-doing-it-incorrectly" target="_blank"&gt;repost&lt;/a&gt; from my personal blog, but I think the technical audience might “dig” it as well.]&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Innovation is hard. You can definitely foster it, but you can't really force it. It's completely fascinating when people innovate in a massively disruptive way. While you can't make innovation happen, it's something I try to strive for. There are certain ways that I've had a great deal of success innovating, and others where I haven't. Professionally, it's easy to get into the rut of doing things a certain way, because everyone else does it that way. The first step to doing it in a better way often requires questioning the establishment. While my inner rebel is all about that, it's also an exhausting practice.&lt;/p&gt;  &lt;p&gt;Coaching volleyball is one of those scenarios where the questioning comes easy. For example, before a match, you're given several minutes of court time to warm up (the actual time depends on the governing organization). Since I was in high school, that time was always used by coaches to send perfectly tossed balls into the air for hitting, while your one or two short defensive specialists tried to dig those hit balls. This results in a lot of &amp;quot;whoo-hoo's&amp;quot; and pleasure on the part of your athletes, but I wasn't sure if it was constructive.&lt;/p&gt;  &lt;p&gt;Attacking the ball is always step three in volleyball. Someone has to expertly pass the ball first, then someone has to set it for the hitter. Without those two things, there is no hitting. So after a season or two, I thought, why am I wasting time on this, especially when my kids can't pass to save their lives? So despite the protest of the kids (and parents, who always have the answers), I ditched the hitting lines. I put six kids on the other side of the net, and tossed balls in for them to pass, set and hit. I rotated them around. This exercised all of the skills necessary to score, including the ever important transition on and off the net. It was &lt;em&gt;real&lt;/em&gt;, core to the game, and made a huge difference. It also happened to be noisy and menacing in appearance, which freaked out the other team, so that was a plus.&lt;/p&gt;  &lt;p&gt;I tossed out what everyone else was doing, and tried something that seemed to better serve the scenario. I try to do this with all things in life. And yes, it can be exhausting questioning everything, especially if you end up where you started, and &amp;quot;everyone&amp;quot; had it right.&lt;/p&gt;  &lt;p&gt;It's a lot harder to innovate your way out of the norm in my line of work. In terms of the actual computer science, sure, there are a lot of things that have been thought to death and they're good ideas. It tends to be the process and the associated people issues that are harder to change. There is an important parallel though to the volleyball warm-up. It turns out that process is almost always wrought with wasted time for things that don't matter, that don't get to something real and valuable. Even in celebrated (capital &amp;quot;A&amp;quot;) Agile practices, teams have a hard time identifying the things they do that aren't adding value, let alone innovating.&lt;/p&gt;  &lt;p&gt;Innovation isn't easy, but you can get practice at it. It starts when you stop accepting sheep behavior and ask if there's a better way.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=10307075" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/agile/default.aspx">agile</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Career/default.aspx">Career</category><category domain="http://weblogs.asp.net/jeff/archive/tags/culture/default.aspx">culture</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category></item><item><title>Lessons from live blogging with Azure (nothing bad happened)</title><link>http://weblogs.asp.net/jeff/archive/2013/05/12/lessons-from-live-blogging-with-azure-nothing-bad-happened.aspx</link><pubDate>Sun, 12 May 2013 18:09:46 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:10278393</guid><dc:creator>Jeff</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=10278393</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/05/12/lessons-from-live-blogging-with-azure-nothing-bad-happened.aspx#comments</comments><description>&lt;p&gt;I &lt;a href="http://weblogs.asp.net/jeff/archive/2013/04/28/building-a-live-blog-app-in-windows-azure.aspx"&gt;wrote previously&lt;/a&gt; about how I built a &lt;a href="http://live.pointbuzz.com/"&gt;&amp;quot;live blog&amp;quot; app&lt;/a&gt; in Azure, so we could use it for &lt;a href="http://pointbuzz.com/"&gt;PointBuzz&lt;/a&gt; during last week's festivities at Cedar Point. Not surprisingly, it worked just fine. As I expected, the whole thing was kind of overkill. Sweet, over-provisioned overkill.&lt;/p&gt;  &lt;p&gt;The traffic loads we encountered were not a big deal. At one point, we were close to 300 simultaneous connections. We didn't really need Azure to handle that, but the truth is that I wasn't entirely sure what to expect in terms of SignalR and its effect on the servers. What better reason to spin up virtual machines in Azure? I still think that's the biggest magic about cloud services, that you can turn on stuff just when you need it, and pay just for that. It sure beats having to buy or rent a rack of equipment.&lt;/p&gt;  &lt;p&gt;The performance was stellar. Average CPU usage never went over 1.5%. I ran two instances of the MVC app as a Web role. I chose this over straight Web sites because of the distributed cache that comes free with it. Of course I didn't really need it, but why not? I didn't do any page rendering timing at the server, because grabbing a few objects out of the cache and making them into a simple page was probably stupid fast, but testing from the park's WiFi, the time from request to last byte (not counting images) was generally under 200 ms. The AJAX calls on scroll for more content were just slightly faster.&lt;/p&gt;  &lt;p&gt;The CDN performance was similarly pretty solid. I did a few unscientific tests on that prior to our big day, comparing the latency of the CDN to direct calls to blob storage. Predictably, the first hit to the CDN was always slower as it grabbed the data from storgage, but after that, they were about the same. Again, this was not scientific, and I also can't control which point of presence I was hitting on the CDN. This was another feature I certainly didn't need, but figured I would try it since it was there.&lt;/p&gt;  &lt;p&gt;We moved a total of 6 gigs of photos that day, which was a lot more than I expected. This isn't a big deal for a few days of activity, but if I were using this (or any of the cloud services) long-term, bandwidth costs would be a concern. They're still a lot higher than the &amp;quot;free&amp;quot; terabytes of transfer you typically get when you rent a box in some giant data center.&lt;/p&gt;  &lt;p&gt;At the end of the day, the app proved two things. The first was that SignalR imposes very little overhead, even with hundreds of open connections. The service bus functionality, still in beta, works great, to shuttle messages between running instances of the app. The other thing that it proves is that I bet you could throw this simple thing up for a big live blog event like an Apple product announcement and it would work just fine. I need to find someone willing to take that chance now. :)&lt;/p&gt;  &lt;p&gt;So what does all of this overkill cost?&lt;/p&gt;  &lt;p&gt;166 compute hours (2 small instances): $13.28    &lt;br /&gt;6 gigs out of the CDN: $0.60     &lt;br /&gt;18,000 CDN transactions: $0 (it's a dime for a million)     &lt;br /&gt;11,000 service bus message: $0 (it's a buck for a million)     &lt;br /&gt;1.2 gigs out from app: $0.10     &lt;br /&gt;200 MB storage: &amp;lt; $1     &lt;br /&gt;SQL database: &amp;lt; $5 for a month&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=10278393" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Internet/default.aspx">Internet</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Azure/default.aspx">Azure</category><category domain="http://weblogs.asp.net/jeff/archive/tags/cloud/default.aspx">cloud</category></item><item><title>Building for Web scale is a different skill</title><link>http://weblogs.asp.net/jeff/archive/2013/05/06/building-for-web-scale-is-a-different-skill.aspx</link><pubDate>Tue, 07 May 2013 03:32:00 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:10255185</guid><dc:creator>Jeff</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=10255185</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/05/06/building-for-web-scale-is-a-different-skill.aspx#comments</comments><description>&lt;p&gt;There are a lot of things that one can find satisfying about building stuff for the Web. For a lot of people, it's probably just the act of building something cool, pretty and useful. These are certainly things to strive for, but for me, the interesting thing has always been to build something that can scale.&lt;/p&gt;  &lt;p&gt;Like so many things in life, this particular desire grew out of experience. Very early on, before I was technically getting paid to be a software developer, I learned about scale problems. In the wild west of 2000, I launched CoasterBuzz and did some advertising for it. I was on a shared hosting plan, and the site started to get slow in a hurry. There were a number of things I did poorly, including some recursive database queries, and worse, fetching more data than I needed. You live and learn, as they say, and I got better at it over time.&lt;/p&gt;  &lt;p&gt;Many years later, I would have the chance to work on the MSDN/TechNet forums, which served well over 45 million pages per &lt;em&gt;month&lt;/em&gt;. It's not lost on me how rare it is for anyone to get to work on a Web app that has to scale to that size. My team was actually there to try and rope it in a little, because it required a huge number of servers to run. There was a lot of low-hanging fruit, and some really hard things to do as well. I didn't directly do a lot of the performance enhancing stuff (though I did pair for it), but I still took a lot away from that experience.&lt;/p&gt;  &lt;p&gt;With my own sites, they collectively do 12 to 15 million pages per year, depending on what's going on that year. Respectable, but under any normal circumstances, not a lot. At peak times, that works out to be between 6 to 10 pages per second, and less than 1 in off-peak times. It's very rare that my server ever gets pushed beyond 25% CPU usage (it doesn't hurt that it's total overkill, with four fast cores).&lt;/p&gt;  &lt;p&gt;Still, I've noticed that people who work on Web applications don't always think in Web terms. By that, I mean it's not uncommon for them to think in &amp;quot;offline&amp;quot; terms, where time is not nearly as critical. For example, someone who works a typical job doing line-of-business applications doesn't care if they build a SQL query that has a ten-way join over two views. It might take a few seconds (or minutes) to get results, but it doesn't matter for the report it's going to generate. For the Web, that timing matters.&lt;/p&gt;  &lt;p&gt;So here are a few of the things that I think people building apps for the Web need to think about. If there are others you can think of, I'd love to hear them! This is not an exhaustive list...&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Denormalize! Disk space is cheap, and disks are huge. Really, it's OK to duplicate data if it means you don't have to do a bunch of expensive database joins. This is even more important now, in an age where we might use different kinds of data storage, like the various flavors of table and blob storage. &lt;/li&gt;    &lt;li&gt;Calculate once. This is perhaps the biggest sin I've seen. You might have a large set of rules on whether or not you should display some piece of data. You have two choices: You can make those calculations every time the data is requested, or you can do it once and store the outcome of that decision. Which is going to be faster? Calculating once, probably in an infrequent data writing situation, or calculating every time, in a frequent read-only situation? I think the answer is pretty obvious. &lt;/li&gt;    &lt;li&gt;Use caching, but only when it makes sense. Slapping an extra box with a bunch of memory on your network to store data is a pretty quick way to boost performance. There are some pitfalls to avoid, however. If the data changes frequently, make sure your code to invalidate the cache is well tested. Beware giant object graphs that serialize into gigantic objects that are many times larger than their binary counterparts. If you're caching because of expensive data querying or composition, fix that problem first. &lt;/li&gt;    &lt;li&gt;Don't wait until the end to understand performance. I'll be honest, premature optimization annoys the crap out of me. Developers who waste time on what-ifs and try to code for them drive me nuts. That said, you can't pretend that performance is a last mile consideration. Fortunately, most shops these days are working with continuous integration environments at least as far as staging or testing, so problems should become apparent early on. &lt;/li&gt;    &lt;li&gt;Use appropriate instrumentation. I worked with one company that had a hard time finding the weak spots in its system, because it wasn't obvious where the problems were. Big distributed systems can have a lot of moving parts, and you need insight into how each part talks to the other parts. For that company, I insisted that we had a dashboard to show the average times and failure rates for calls to an external system. (I also wanted complete logging of every interaction, but didn't get it.) Sure enough, one system was choking on requests at one point every day, and we could address it. &lt;/li&gt;    &lt;li&gt;Remember your HTTP basics. I'm being intentionally broad here. Think about the size and shape of scripts and CSS (minification and compression), the limits in the number of connections a browser has to any one host, cookies and headers, the very statelessness of what you're doing. The Web is not VB6, regardless of the layers of abstraction you pile on top of it. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;These are mostly off the top of my head, but I'd love to hear more suggestions.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=10255185" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/Career/default.aspx">Career</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category></item><item><title>Building a live blog app in Windows Azure</title><link>http://weblogs.asp.net/jeff/archive/2013/04/28/building-a-live-blog-app-in-windows-azure.aspx</link><pubDate>Mon, 29 Apr 2013 03:28:13 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:10227008</guid><dc:creator>Jeff</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=10227008</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/04/28/building-a-live-blog-app-in-windows-azure.aspx#comments</comments><description>&lt;p&gt;If you're a technology nerd, then you've probably seen one technology news site or another do a &amp;quot;live blog&amp;quot; at some product announcement. This is basically a page on the Web where text and photo updates stream into the page as you sit there and soak it in. I don't remember which year these started to appear, but you may recall how frequently they failed. The traffic would overwhelm the site, and down it would go.&lt;/p&gt;  &lt;p&gt;So I got to thinking, how would I build something like this? We've got a pretty big media day at Cedar Point coming up for &lt;a href="http://pointbuzz.com/c/gatekeeper.aspx" target="_blank"&gt;GateKeeper&lt;/a&gt;, and it would be fun to post updates in real time. Ars Technica posted &lt;a href="http://arstechnica.com/staff/2013/03/how-ars-slayed-the-moonshark-on-building-and-running-liveblogs/" target="_blank"&gt;an article about how they tackled the problem&lt;/a&gt; a couple of months ago, and while elegant, it wasn't how I would do it.&lt;/p&gt;  &lt;p&gt;My traffic expectations are lower. I don't expect to get tens of thousands of simultaneous visitors, but a couple thousand is possible. The last time we even had the chance to publish real-time from an event was 2006, for Skyhawk. Maverick got delayed the next year, and that event was scaled back to a few hours in the morning. Still, the server got stressed enough back in 2006 with a lot of open connections, in this case because I was serving video myself, and I didn't write the code that I write today. Regardless, I still wanted to build this using cloud services, as if I was expecting insane traffic. The resulting story, from a development standpoint, is wholly unremarkable, but I'll get to why that's important.&lt;/p&gt;  &lt;p&gt;So the design criteria went something like this:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Be able to add instances on the fly to address rising traffic conditions. &lt;/li&gt;    &lt;li&gt;Update in real-time with open connections, not a browser polling mechanism. &lt;/li&gt;    &lt;li&gt;Keep as much stuff in memory as possible. &lt;/li&gt;    &lt;li&gt;Serve media from a CDN or something not going through the Web site itself. &lt;/li&gt;    &lt;li&gt;Not spend a ton of time on it. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The first thing I did was wire up the bits on the server and client to fire up a SignalR connection, and have an admin push content to the browsers. I won't go deeper into that, because there are plenty of examples around the Internets showing how to do it with a few lines of code. Later in the process, I added the extra line of code and downloaded the package to make SignalR work through Azure Service Bus. This means that if I ran three instance of the app, the admin pushing content out from one instance, will have the content go via the service bus to the other instances, where other users are connected via SignalR. It's stupid easy. Adding instances on the fly and make it real-timey, check.&lt;/p&gt;  &lt;p&gt;Next, I needed a way to persist the content. Originally I toyed with using table storage for this, because it's cheaper than SQL. However, ordering in a range becomes a problem, because while you can take a number of entities in a time stamp range, and then order them in code, there's no guarantee you'll get that number of entities. After thinking about it, SQL is $5/month for 100 MB, and I was only going to be using it for a few days. Performance differences would likely be negligible, and since I was going to cache the heck out of everything, that was even less important. I used SQL Azure instead.&lt;/p&gt;  &lt;p&gt;Instead of using the Azure Web Sites, I used Web roles, or &amp;quot;cloud services&amp;quot; as they're labeled in the Azure portal. These are the original PaaS things that I was originally drawn to. Sure, they're technically full blown VM's, you can even RDP into them, but I like the way they're intended to exist for a single purpose, maintained and updated for you. More to the point, they have Azure Cache available, which is essentially AppFabric spun up across every instance you have. So if you have two instances up, and they use 30% of the memory of these small instances, that adds up to around a gigabyte of distributed cache, for free. Yes, please! I had my data repositories use this cache liberally. The infinite scroll functionality takes n content items after a certain date, which means different people starting up the page at different times will have different &amp;quot;pages&amp;quot; of data, but I cache those pages despite the overlap. Why not? It's cheap! Keep stuff in memory, check.&lt;/p&gt;  &lt;p&gt;The CDN functionality is pretty easy too. I probably didn't need this at all, but why not? Again, for a day or two, given the amount of content, it's not an expensive endeavor. The Azure CDN is simply an extension of your blob storage, so there's little more to do beyond turning it on, adding a CNAME to my DNS, and off we go. CDN, check.&lt;/p&gt;  &lt;p&gt;I stole a bunch of stuff from &lt;a href="http://popforums.codeplex.com/" target="_blank"&gt;POP Forums&lt;/a&gt;, too. Image resizing was already there, the infinite scrolling, the date formatting, the time updating... all copy and paste with a few tweaks. I didn't do the page design either. Granted, most of it wasn't used, but my PointBuzz partner Walt did that. Total time into this endeavor was around 10 hours. Not spend a lot of time, check.&lt;/p&gt;  &lt;p&gt;Here's the Visio diagram:&lt;/p&gt;  &lt;p&gt;&lt;img title="Live Blog diagram" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="Live Blog diagram" src="http://weblogs.asp.net/blogs/jeff/Live-Blog-diagram_56190198.png" width="640" height="638" /&gt;&lt;/p&gt;  &lt;p&gt;As I said, if this sounds unremarkable from a development standpoint, it is, and that's really the point. I'm whipping up and provisioning a long list of technologies without having to buy a rack full of equipment. That's awesome. Think about what this app is using:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Two Web servers &lt;/li&gt;    &lt;li&gt;Distributed cache (on the Web servers, in this case) &lt;/li&gt;    &lt;li&gt;Database server &lt;/li&gt;    &lt;li&gt;A service bus &lt;/li&gt;    &lt;li&gt;External storage &lt;/li&gt;    &lt;li&gt;A CDN &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;For the four days or so that I'll use this stuff, it's going to cost me less than twenty bucks. This, my friends, is why cloud infrastructure and platform services get me so excited. We can build these enterprisey things with practically no money at all. Compare this to 2000, when the most cost effective way to run a couple of quasi-popular Web sites was to get a T-1 to my house, where I ran my own box, and paid $1,200 a month for 1.5 mbits. Things are so awesome now.&lt;/p&gt;  &lt;p&gt;I'll let you know how it goes after the live event on May 9!&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=10227008" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category><category domain="http://weblogs.asp.net/jeff/archive/tags/MVC/default.aspx">MVC</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Azure/default.aspx">Azure</category><category domain="http://weblogs.asp.net/jeff/archive/tags/cloud/default.aspx">cloud</category></item><item><title>One interface to rule them all</title><link>http://weblogs.asp.net/jeff/archive/2013/04/19/one-interface-to-rule-them-all.aspx</link><pubDate>Sat, 20 Apr 2013 00:31:23 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:10185786</guid><dc:creator>Jeff</dc:creator><slash:comments>3</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=10185786</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/04/19/one-interface-to-rule-them-all.aspx#comments</comments><description>&lt;p&gt;I'm not shy about telling people that I'm not much of a computer science kind of guy. It's not that I don't respect computer science or understand it, I'm just not one to get academic over it to the point of not building anything. And while I can't always remember what the hell SOLID stands for, I do remember that the &amp;quot;I&amp;quot; stands for the &amp;quot;interface segregation principle.&amp;quot; It says, &amp;quot;Thou shalt not force everything to use one interface, because specific interfaces are better.&amp;quot;&lt;/p&gt;  &lt;p&gt;I've seen this principle violated many times, but twice in the last six months I've seen projects that just abuse it to death. The big problem for me is that trying to force everything into a particular interface leads to pain and suffering whenever you want to change something. While developers are in the general sense understanding what dependency injection is, I find that they're often doing it a way that violates the interface segregation principle.&lt;/p&gt;  &lt;p&gt;For example, I've seen several instances where people are passing in the dependency resolver itself (for MVC, this is one of the IDependencyResolver interfaces) to various classes, and then those classes new up instances of whatever they need. This is bad for two reasons. For one, you're then coding on One Interface to rule them all, and for another, the overhead of mocking and verifying in testing gets bigger. That's no fun.&lt;/p&gt;  &lt;p&gt;Another anti-pattern, related to the ISP, is the master do-it-all abstract class. These drive me nuts, too. While an abstract class isn't exactly an interface per se, it ends up being used as one. In an effort to keep the interface concise (as if it's easy to change when it's used in a thousand places), it ends up having one or two methods in order to conform to the base type. This is suboptimal because it keeps you from grouping similar functionality together, it abuses generics (which are fun when you have to bounce between value and reference types), and more to the point, that single interface isn't single for any really good reason. I would rather see you inject whatever functionality you need by way of a specific interface than force One Interface.&lt;/p&gt;  &lt;p&gt;Think about it in terms of the mature frameworks that you've used. There aren't a lot of interfaces to be had that are widely used, because they would be hard to change, and force-feed members that don't need to be widely used. When there are widely used interfaces, they're pretty sparse (think IDisposable).&lt;/p&gt;  &lt;p&gt;Specific is better. Don't try to cure cancer with an interface.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=10185786" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/unit+testing/default.aspx">unit testing</category><category domain="http://weblogs.asp.net/jeff/archive/tags/culture/default.aspx">culture</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category></item><item><title>POP Forums v11 for ASP.NET MVC posted, with SingalR goodness</title><link>http://weblogs.asp.net/jeff/archive/2013/04/17/pop-forums-v11-for-asp-net-mvc-posted-with-singalr-goodness.aspx</link><pubDate>Thu, 18 Apr 2013 02:03:29 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:10176967</guid><dc:creator>Jeff</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=10176967</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/04/17/pop-forums-v11-for-asp-net-mvc-posted-with-singalr-goodness.aspx#comments</comments><description>&lt;p&gt;&lt;a href="https://popforums.codeplex.com/releases/view/97420" target="_blank"&gt;Get the juicy bits on CodePlex!&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;This is a significant upgrade that adds lots of real-time features (using SignalR), as well as a number of style improvements. I’m really excited about this version. I have to give a big shout out to the members of &lt;a href="http://coasterbuzz.com/Forums" target="_blank"&gt;the forum on CoasterBuzz&lt;/a&gt; who have been so excellent in providing feedback.&lt;/p&gt;  &lt;h2&gt;Upgrading?&lt;/h2&gt;  &lt;p&gt;This release has no data changes, and is consistent with v10. Views, scripts, content and of course the core library have all been updated.&lt;/p&gt;  &lt;h2&gt;What's new?&lt;/h2&gt;  &lt;ul&gt;   &lt;li&gt;Updated to use v4.5 of .NET.&lt;/li&gt;    &lt;li&gt;External references now use NuGet.&lt;/li&gt;    &lt;li&gt;Adding an award definition in admin now bounces you to its edit page.&lt;/li&gt;    &lt;li&gt;Fixed: Show more posts updates topic context with updated page counts.&lt;/li&gt;    &lt;li&gt;Activities and awards restyled.&lt;/li&gt;    &lt;li&gt;User profiles are tabbed.&lt;/li&gt;    &lt;li&gt;Activity feed shows real-time view of activity sent via the scoring game API&lt;/li&gt;    &lt;li&gt;Times are updated every minute, formatted to current culture.&lt;/li&gt;    &lt;li&gt;More posts are loaded on scroll (a la Facebook), but pager links are maintained for search engine discoverability.&lt;/li&gt;    &lt;li&gt;New posts appear inline at end of post stream as they're made.&lt;/li&gt;    &lt;li&gt;Forum home and individual topic lists updated in real time.&lt;/li&gt;    &lt;li&gt;Breadcrumb/navigation floats at top of browser.&lt;/li&gt;    &lt;li&gt;.forumGrid CSS removes outline, so it's more Metro-y.&lt;/li&gt; &lt;/ul&gt;  &lt;h2&gt;Known Issues&lt;/h2&gt;  &lt;p&gt;None to report yet.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=10176967" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/POP+Forums/default.aspx">POP Forums</category><category domain="http://weblogs.asp.net/jeff/archive/tags/open+source/default.aspx">open source</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Community+News/default.aspx">Community News</category><category domain="http://weblogs.asp.net/jeff/archive/tags/MVC/default.aspx">MVC</category></item><item><title>POP Forums v11 Beta for MVC posted on CodePlex</title><link>http://weblogs.asp.net/jeff/archive/2013/03/25/pop-forums-v11-beta-for-mvc-posted-on-codeplex.aspx</link><pubDate>Mon, 25 Mar 2013 16:35:09 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:10049342</guid><dc:creator>Jeff</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=10049342</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/03/25/pop-forums-v11-beta-for-mvc-posted-on-codeplex.aspx#comments</comments><description>&lt;p&gt;I won’t repost all of the changes here, but this is the version of the app that gets all real-time and stuff (thank you, SignalR!). I also spent some time refining the UI. You can get these naughty bits, and the overall change log, here:&lt;/p&gt;  &lt;p&gt;&lt;a title="http://popforums.codeplex.com/releases/view/103956" href="http://popforums.codeplex.com/releases/view/103956"&gt;http://popforums.codeplex.com/releases/view/103956&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;I’ve got the app running in production on &lt;a href="http://coasterbuzz.com/Forums" target="_blank"&gt;CoasterBuzz&lt;/a&gt;, and I have to give that community a shout out for being my guinea pigs and an excellent source of feedback.&lt;/p&gt;  &lt;p&gt;A few weeks to track down any final issues, and I’ll release the final version.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=10049342" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/POP+Forums/default.aspx">POP Forums</category><category domain="http://weblogs.asp.net/jeff/archive/tags/open+source/default.aspx">open source</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Community+News/default.aspx">Community News</category></item><item><title>The burden of hiring software developers</title><link>http://weblogs.asp.net/jeff/archive/2013/02/20/the-burden-of-hiring-software-developers.aspx</link><pubDate>Thu, 21 Feb 2013 03:49:08 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:9896207</guid><dc:creator>Jeff</dc:creator><slash:comments>8</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=9896207</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/02/20/the-burden-of-hiring-software-developers.aspx#comments</comments><description>&lt;p&gt;&lt;em&gt;(I wrote this for &lt;a href="http://jeffputz.com/blog/the-burden-of-hiring-software-developers" target="_blank"&gt;my personal blog&lt;/a&gt;, but it’s obviously an important topic here.)&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;I've had the unusual opportunity to hire and manage people on and off starting with my first real job after college. I think it's one of the hardest things to do because it's time consuming and expensive to make mistakes. When I first had to assemble a team in a consulting gig (I think it was 2005, for context), I found out it's even harder to hire software developers.&lt;/p&gt;  &lt;p&gt;First off, check out my former boss, Jonathan, and &lt;a href="http://channel9.msdn.com/Events/ALM-Summit/ALM-Summit-3/Technical-Interviewing-You-re-Doing-it-Wrong" target="_blank"&gt;the talk he did with another guy about how not to do technical interviewing&lt;/a&gt;. The irony to people who have had bad experiences interviewing at Microsoft is not lost on me, but Jonathan gets it. Obviously, since he hired me. :) Go rate up his video!&lt;/p&gt;  &lt;p&gt;The problem in hiring starts with the fact that resumes don't mean much. You look for the key word matches for what you're looking for, and from there look at the depth and breadth of the experience. If it doesn't smell like bullshit, you move on to a phone screen. From there you further dismiss the fakers. By the time you bring someone in, I would guess that 90% of the time you can already be pretty sure they would be a good fit, and you can have your choice of candidates provided they like you and your offer.&lt;/p&gt;  &lt;p&gt;But it's the screening part that is such a huge burden. The resume part isn't that big of a deal. I can get through a stack of resumes pretty fast and figure out who I want to follow up with. It's the next stage of the screening that takes too damn long and sucks the life out of you.&lt;/p&gt;  &lt;p&gt;My typical phone screen is more about the gauging the person's knowledge. I don't ask them to identify acronyms like SOLID or DRY (I can never remember what the former stands for), but I can walk them through questions about language and object-oriented patterns and get a pretty good feel for where they are. But even if you're trying to get a faker off of the phone, these still take a half-hour at least, and that's not counting the overhead in agreeing to a time to talk.&lt;/p&gt;  &lt;p&gt;If I bring someone in for real interviews, that's going to take at least three hours, including some time working on a real software problem with, you know a computer. I don't complain about that part, it's the screening process that is a huge burden.&lt;/p&gt;  &lt;p&gt;Hiring people, even for something as technical as a software development position, is still largely a problem with human beings. Expectations are set, social contracts have to be followed and of course people have to get along. It just doesn't feel like it should be so inefficient.&lt;/p&gt;  &lt;p&gt;First off, job boards are nearly useless. They're just keyword matching devices. The quality of candidates varies by board, but it's still not a great value prop. Staffing agencies add even less value, especially now that it's common for a person sitting in India or China to just roll through a database, making keyword matches, spamming people.&lt;/p&gt;  &lt;p&gt;I've been talking with people a lot lately about how to make the discovery and vetting process more efficient. The use cases for smaller to medium sized businesses in particular interest me, since they might not even have someone who is technically proficient enough to make that first critical hire.&lt;/p&gt;  &lt;p&gt;I'm open to suggestions. How do you make the discovery and vetting easier? Is there a technical process that could help?&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=9896207" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/Career/default.aspx">Career</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Community+News/default.aspx">Community News</category></item><item><title>SignalR really changes everything</title><link>http://weblogs.asp.net/jeff/archive/2013/02/14/signalr-really-changes-everything.aspx</link><pubDate>Fri, 15 Feb 2013 03:52:34 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:9874095</guid><dc:creator>Jeff</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=9874095</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2013/02/14/signalr-really-changes-everything.aspx#comments</comments><description>&lt;p&gt;I think I started to mess with HTML in 1995, and the Internets became the focus of my profession in 1999. The fun thing about this is that I’ve watched the tools and development technology evolve most of the way, and it has been an awesome ride.&lt;/p&gt;  &lt;p&gt;That said, the Web has only had what I would consider a small number of “wow” moments in terms of development technology. AJAX as a concept was a game changer, but it wasn’t really until jQuery came along that it became stupid easy to perform AJAX tasks. The ASP.NET MVC framework was another great moment, but as it was clearly inspired by other platforms, I don’t know that it’s a big deal outside of my little world. Beyond that, dev tech has been slow and evolutionary.&lt;/p&gt;  &lt;p&gt;Until now. &lt;a href="http://signalr.net/" target="_blank"&gt;SignalR&lt;/a&gt;, as far as I’m concerned, is a huge deal. It really does change everything. It lifts the constraint inherent to standard HTTP exchanges, in that we call the server, get something back, and we’re done. Now the client and server can talk to each other, and do so on a potentially massive scale.&lt;/p&gt;  &lt;p&gt;At first it sounds like we should credit WebSockets for this, but by itself, that technology is only slightly useful. I say that because browser implementation is not always consistent, and SignalR compensates by gracefully falling back to long “open” connections, or polling, if necessary. It’s also not entirely useful without the backend portion, which handles the registration of clients and the ability to remotely call code at the client end.&lt;/p&gt;  &lt;p&gt;There are a great many things that I’m thinking about using it for, not the least of which is POP Forums, my open source project. I’m wrapping up &lt;a href="http://popforums.codeplex.com/workitem/107" target="_blank"&gt;real-time updating&lt;/a&gt; right now, in fact, for various forum and topic lists. The amount of code to do it has been trivial. It’s a big deal.&lt;/p&gt;  &lt;p&gt;Go try it. You won’t regret it.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=9874095" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/POP+Forums/default.aspx">POP Forums</category><category domain="http://weblogs.asp.net/jeff/archive/tags/open+source/default.aspx">open source</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category></item><item><title>A year of remote working</title><link>http://weblogs.asp.net/jeff/archive/2012/12/28/a-year-of-remote-working.aspx</link><pubDate>Fri, 28 Dec 2012 19:13:45 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:9667537</guid><dc:creator>Jeff</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=9667537</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2012/12/28/a-year-of-remote-working.aspx#comments</comments><description>&lt;p&gt;&lt;em&gt;[Note: I originally posted this on &lt;a href="http://jeffputz.com/blog/a-year-of-remote-working" target="_blank"&gt;my personal blog&lt;/a&gt;, but it occurs to me that it’s likely something of general interest to software developers. -J]&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Today is my last day working for Humana, after about a year. One of the initial considerations in taking the job was that it was remote, as the home office is in Louisville, Kentucky, and I'm not. I had worked from home from time to time while at Microsoft, but it wasn't on a schedule or anything, just when it made sense. I didn't see any reason why it wasn't possible to do it on a full-time basis.&lt;/p&gt;  &lt;p&gt;My conclusions after a year are that it totally makes sense to work remotely in this line of work. The only negative consideration that I can think of is that you don't build the same kind of social relationships with your coworkers. You obviously can't go out for a beer with them after work. Beyond that, the benefits are huge.&lt;/p&gt;  &lt;p&gt;The most obvious benefit to me is increased productivity. I didn't expect this at all, but it makes sense. There are fewer distractions when you're not in the same physical environment as everyone else. When you don't have to worry about the commute, I would also argue that you're a lot more willing to spend more time on actual work. In on-site jobs, I've always been quick to make sure I'm out by 4:30 to get a jump on traffic. That concern goes away when your commute involves going downstairs to your kitchen.&lt;/p&gt;  &lt;p&gt;The benefit applies to companies as well, because remote workers don't require real estate, where they take up space. If it costs $10 per square foot (double that in a lot of prime markets), and you plop someone in a 5x5 cube, you're spending $3,000 a year for that office space, for one person.&lt;/p&gt;  &lt;p&gt;Technology is in a state where it's not hard to collaborate with people in different places. Ideally it means you have more real-time, ad hoc collaboration and less meetings, but certainly big old companies have a hard time with this. IP telephony, Web cams, screen sharing, wikis, Sharepoint... these all reduce barriers to remote work.&lt;/p&gt;  &lt;p&gt;There are cultural problems that I'm sure can get in the way. This is especially true with what I call the &amp;quot;Midwest factory culture&amp;quot; in the workplace. These are businesses that have a strict working hour policy, where face time is associated with productivity. They're the same kinds of businesses that promote people for working later instead of working smarter, oblivious to the actual results of work. Those are places not equipped to handle remote workers because they don't know how to evaluate their effectiveness.&lt;/p&gt;  &lt;p&gt;The personal benefits are many, not the least of which is not having to get into a car. I estimate that I saved almost two weeks of my life this year by not having to drive anywhere. The best part of that is the fact that most of that time was directly translated into more time with my 2-year-old, and that's priceless. Not seeing him for pre-nap roughhousing is something I really struggled with when I considered changing jobs.&lt;/p&gt;  &lt;p&gt;Overall, I see a future world where many business, especially in those related to software development or other primarily electronic endeavors, will tend to work in a hybrid mode of sorts. Formal and structured work spaces, with cubes and offices, will go away. It's rarely necessary to meet in-person, but there are certainly times when it adds certain intangibles to the work experience and the output of the business.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=9667537" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/Misc_2E00_/default.aspx">Misc.</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Career/default.aspx">Career</category><category domain="http://weblogs.asp.net/jeff/archive/tags/culture/default.aspx">culture</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Internet/default.aspx">Internet</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category></item><item><title>Real-world SignalR example, ditching ghetto long polling</title><link>http://weblogs.asp.net/jeff/archive/2012/11/08/real-world-signalr-example-ditching-ghetto-long-polling.aspx</link><pubDate>Thu, 08 Nov 2012 22:10:14 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:9342303</guid><dc:creator>Jeff</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=9342303</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2012/11/08/real-world-signalr-example-ditching-ghetto-long-polling.aspx#comments</comments><description>&lt;p&gt;One of the highlights of BUILD last week was the announcement that SignalR, a framework for real-time client to server (or cloud, if you will) communication, would be a real supported thing now with the weight of Microsoft behind it. Love the open source flava!&lt;/p&gt;  &lt;p&gt;If you aren’t familiar with SignalR, &lt;a href="http://channel9.msdn.com/Events/Build/2012/3-034" target="_blank"&gt;watch this BUILD session with PM Damian Edwards and dev David Fowler&lt;/a&gt;. Go ahead, I’ll wait. You’ll be in a happy place within the first ten minutes. If you skip to the end, you’ll see that they plan to ship this as a real first version by the end of the year. Insert slow clap here.&lt;/p&gt;  &lt;p&gt;Writing a few lines of code to move around a box from one browser to the next is a way cool demo, but how about something real-world? When learning new things, I find it difficult to be abstract, and I like real stuff. So I thought about what was in my tool box and the decided to port my crappy long-polling “there are new posts” feature of &lt;a href="http://popforums.codeplex.com/" target="_blank"&gt;POP Forums&lt;/a&gt; to use SignalR.&lt;/p&gt;  &lt;p&gt;A few versions back, I added a feature where a button would light up while you were pecking out a reply if someone else made a post in the interim. It kind of saves you from that awkward moment where someone else posts some snark before you.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/jeff/image_30A71B24.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://weblogs.asp.net/blogs/jeff/image_thumb_79CCC6DD.png" width="554" height="385" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;While I was proud of the feature, I hated the implementation. When you clicked the reply button, it started polling an MVC URL asking if the last post you had matched the last one the server, and it did it every second and a half until you either replied or the server told you there was a new post, at which point it would display that button. The code was not glam:&lt;/p&gt;  &lt;pre&gt;// in the reply setup
PopForums.replyInterval = setInterval(&amp;quot;PopForums.pollForNewPosts(&amp;quot; + topicID + &amp;quot;)&amp;quot;, 1500);

// called from the reply setup and the handler that fetches more posts
PopForums.pollForNewPosts = function (topicID) {
	$.ajax({
		url: PopForums.areaPath + &amp;quot;/Forum/IsLastPostInTopic/&amp;quot; + topicID,
		type: &amp;quot;GET&amp;quot;,
		dataType: &amp;quot;text&amp;quot;,
		data: &amp;quot;lastPostID=&amp;quot; + PopForums.currentTopicState.lastVisiblePost,
		success: function (result) {
			var lastPostLoaded = result.toLowerCase() == &amp;quot;true&amp;quot;;
			if (lastPostLoaded) {
				$(&amp;quot;#MorePostsBeforeReplyButton&amp;quot;).css(&amp;quot;visibility&amp;quot;, &amp;quot;hidden&amp;quot;);
			} else {
				$(&amp;quot;#MorePostsBeforeReplyButton&amp;quot;).css(&amp;quot;visibility&amp;quot;, &amp;quot;visible&amp;quot;);
				clearInterval(PopForums.replyInterval);
			}
		},
		error: function () {
		}
	});
};&lt;/pre&gt;

&lt;p&gt;What’s going on here is the creation of an interval timer to keep calling the server and bugging it about new posts, and setting the visibility of a button appropriately. It looks like this if you’re monitoring requests in FireBug:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/jeff/image_7B9D1CA4.png"&gt;&lt;img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://weblogs.asp.net/blogs/jeff/image_thumb_2F67A3A7.png" width="669" height="282" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gross.&lt;/p&gt;

&lt;p&gt;The SignalR approach was to call a message broker when a reply was made, and have that broker call back to the listening clients, via a SingalR hub, to let them know about the new post. It seemed weird at first, but the server-side hub’s only method is to add the caller to a group, so new post notifications only go to callers viewing the topic where a new post was made. Beyond that, it’s important to remember that the hub is also the means to calling methods at the client end.&lt;/p&gt;

&lt;p&gt;Starting at the server side, here’s the hub:&lt;/p&gt;

&lt;pre&gt;using Microsoft.AspNet.SignalR.Hubs;

namespace PopForums.Messaging
{
	public class Topics : Hub
	{
		public void &lt;strong&gt;ListenTo&lt;/strong&gt;(int topicID)
		{
			Groups.Add(Context.ConnectionId, topicID.ToString());
		}
	}
}&lt;/pre&gt;

&lt;p&gt;Have I mentioned how awesomely not complicated this is? The hub acts as the channel between the server and the client, and you’ll see how JavaScript calls the above method in a moment. Next, the broker class and its associated interface:&lt;/p&gt;

&lt;pre&gt;using Microsoft.AspNet.SignalR;
using Topic = PopForums.Models.Topic;

namespace PopForums.Messaging
{
	public interface IBroker
	{
		void NotifyNewPosts(Topic topic, int lasPostID);
	}

	public class Broker : IBroker
	{
		public void NotifyNewPosts(Topic topic, int lasPostID)
		{
			var context = GlobalHost.ConnectionManager.GetHubContext&amp;lt;Topics&amp;gt;&lt;topics&gt;();
			context.Clients.Group(topic.TopicID.ToString()).&lt;strong&gt;notifyNewPosts&lt;/strong&gt;(lasPostID);
		}
	}
}&lt;/pre&gt;

&lt;p&gt;The NotifyNewPosts method uses the static GlobalHost.ConnectionManager.GetHubContext&amp;lt;Topics&amp;gt;&lt;topics&gt;() method to get a reference to the hub, and then makes a call to clients in the group matched by the topic ID. It’s calling the notifyNewPosts method on the client. The TopicService class, which handles the reply data from the MVC controller, has an instance of the broker new’d up by dependency injection, so it took literally one line of code in the reply action method to get things moving.&lt;/p&gt;

&lt;pre&gt;_broker.NotifyNewPosts(topic, post.PostID);&lt;/pre&gt;

&lt;p&gt;The JavaScript side of things wasn’t much harder. When you click the reply button (or quote button), the reply window opens up and fires up a connection to the hub:&lt;/p&gt;

&lt;pre&gt;var hub = $.connection.topics;
hub.client.&lt;strong&gt;notifyNewPosts&lt;/strong&gt; = function (lastPostID) {
	PopForums.setReplyMorePosts(lastPostID);
};
$.connection.hub.start().done(function () {
	hub.server.&lt;strong&gt;listenTo&lt;/strong&gt;(topicID);
});&lt;/pre&gt;

&lt;p&gt;The important part to look at here is the creation of the notifyNewPosts function. That’s the method that is called from the server in the Broker class above. Conversely, once the connection is done, the script calls the listenTo method on the server, letting it know that this particular connection is listening for new posts on this specific topic ID.&lt;/p&gt;



&lt;p&gt;This whole experiment enables a lot of ideas that would make the forum more Facebook-like, letting you know when stuff is going on around you.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=9342303" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/POP+Forums/default.aspx">POP Forums</category><category domain="http://weblogs.asp.net/jeff/archive/tags/open+source/default.aspx">open source</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category><category domain="http://weblogs.asp.net/jeff/archive/tags/AJAX/default.aspx">AJAX</category><category domain="http://weblogs.asp.net/jeff/archive/tags/MVC/default.aspx">MVC</category></item><item><title>Portable class libraries and fetching JSON</title><link>http://weblogs.asp.net/jeff/archive/2012/10/31/portable-class-libraries-and-fetching-json.aspx</link><pubDate>Wed, 31 Oct 2012 21:41:00 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:9268198</guid><dc:creator>Jeff</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=9268198</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2012/10/31/portable-class-libraries-and-fetching-json.aspx#comments</comments><description>&lt;p&gt;After much delay, we finally have the Windows Phone 8 SDK to go along with the Windows 8 Store SDK, or whatever ridiculous name they’re giving it these days. (Seriously… that no one could come up with a suitable replacement for “metro” is disappointing in an otherwise exciting set of product launches.) One of the neat-o things is the potential for code reuse, particularly across Windows 8 and Windows Phone 8 apps.&lt;/p&gt;  &lt;p&gt;This is accomplished in part with &lt;a href="http://msdn.microsoft.com/en-us/library/gg597391.aspx" target="_blank"&gt;portable class libraries&lt;/a&gt;, which allow you to share code between different types of projects. With some other techniques and quasi-hacks, you can share some amount of code, and I saw it mentioned in one of the &lt;a href="http://www.buildwindows.com/" target="_blank"&gt;Build&lt;/a&gt; videos that they’re seeing as much as 70% code reuse. Not bad.&lt;/p&gt;  &lt;p&gt;However, I’ve already hit a super annoying snag. It appears that the HttpClient class, with its idiot-proof async goodness, is not included in the Windows Phone 8 class libraries. Shock, gasp, horror, disappointment, etc. The delay in releasing it already caused dismay among developers, and I’m sure this won’t help.&lt;/p&gt;  &lt;p&gt;So I started refactoring some code I already had for a Windows 8 Store app (ugh) to accommodate the use of HttpWebRequest instead. I haven’t tried it in a Windows Phone 8 project beyond compiling, but it appears to work.&lt;/p&gt;  &lt;p&gt;I used &lt;a href="http://stackoverflow.com/a/10565229/99897" target="_blank"&gt;this StackOverflow answer&lt;/a&gt; as a starting point since it’s been a long time since I used HttpWebRequest, and keep in mind that it has no exception handling. It needs refinement. The goal here is to new up the client, and call a method that returns some deserialized JSON objects from the Intertubes. Adding facilities for headers or cookies is probably a good next step. You need to use NuGet for a Json.NET reference. So here’s the start:&lt;/p&gt; &lt;code&gt;   &lt;p&gt;using System.Net;      &lt;br /&gt;using System.Threading.Tasks;       &lt;br /&gt;using Newtonsoft.Json;       &lt;br /&gt;using System.IO;&lt;/p&gt;    &lt;p&gt;namespace MahProject      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public class ServiceClient&amp;lt;T&amp;gt; where T : class       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; public ServiceClient(string url)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; _url = url;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; private readonly string _url;&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; public async Task&amp;lt;T&amp;gt; GetResult()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var response = await MakeAsyncRequest(_url);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var result = JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(response);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return result;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; public static Task&amp;lt;string&amp;gt; MakeAsyncRequest(string url)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var request = (HttpWebRequest)WebRequest.Create(url);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; request.ContentType = &amp;quot;application/json&amp;quot;;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Task&amp;lt;WebResponse&amp;gt; task = Task.Factory.FromAsync(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; request.BeginGetResponse,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; asyncResult =&amp;gt; request.EndGetResponse(asyncResult),       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return task.ContinueWith(t =&amp;gt; ReadStreamFromResponse(t.Result));       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; private static string ReadStreamFromResponse(WebResponse response)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; using (var responseStream = response.GetResponseStream())       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; using (var reader = new StreamReader(responseStream))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var content = reader.ReadToEnd();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return content;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;}&lt;/p&gt; &lt;/code&gt;  &lt;p&gt;Calling it in some kind of repository class may look like this, if you wanted to return an array of Park objects (Park model class omitted because it doesn’t matter):&lt;/p&gt; &lt;code&gt;   &lt;p&gt;public class ParkRepo      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public async Task&amp;lt;Park[]&amp;gt; GetAllParks()       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var client = new ServiceClient&amp;lt;Park[]&amp;gt;(http://superfoo/endpoint);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return await client.GetResult();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;}&lt;/p&gt; &lt;/code&gt;  &lt;p&gt;And then from inside your WP8 or W8S app (see what I did there?), when you load state or do some kind of UI event handler (making sure the method uses the async keyword):&lt;/p&gt; &lt;code&gt;   &lt;p&gt;var parkRepo = new ParkRepo();      &lt;br /&gt;var results = await parkRepo.GetAllParks();       &lt;br /&gt;// bind results to some UI or observable collection or something&lt;/p&gt; &lt;/code&gt;  &lt;p&gt;Hopefully this saves you a little time.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=9268198" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Visual+Studio/default.aspx">Visual Studio</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Windows+Phone/default.aspx">Windows Phone</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Windows+RT/default.aspx">Windows RT</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Surface/default.aspx">Surface</category></item><item><title>Review: Microsoft Surface RT</title><link>http://weblogs.asp.net/jeff/archive/2012/10/29/review-microsoft-surface-rt.aspx</link><pubDate>Mon, 29 Oct 2012 14:41:21 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:9249691</guid><dc:creator>Jeff</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=9249691</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2012/10/29/review-microsoft-surface-rt.aspx#comments</comments><description>&lt;p&gt;Being the ever cautious fan of technology, I ordered a Surface RT within minutes of it going live on Microsoft’s store. I received it Friday, and spent the weekend with it, and wrote a review. &lt;a href="http://jeffputz.com/blog/review-microsoft-surface-rt" target="_blank"&gt;I posted that review on my personal blog.&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;TL;DR: It’s a pretty cool device, but has spots of weirdness that need to be addressed.&lt;/p&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=9249691" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/Microsoft/default.aspx">Microsoft</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Community+News/default.aspx">Community News</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Mobile/default.aspx">Mobile</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Windows+RT/default.aspx">Windows RT</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Surface/default.aspx">Surface</category></item><item><title>.NET development on Macs</title><link>http://weblogs.asp.net/jeff/archive/2012/09/21/net-development-on-macs.aspx</link><pubDate>Fri, 21 Sep 2012 15:04:52 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:8959736</guid><dc:creator>Jeff</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/jeff/rsscomments.aspx?PostID=8959736</wfw:commentRss><comments>http://weblogs.asp.net/jeff/archive/2012/09/21/net-development-on-macs.aspx#comments</comments><description>&lt;p&gt;I posted the “exciting” conclusion of my laptop trade-ins and issues on my personal blog. The links, in chronological order, are posted below. While those posts have all of the details about performance and software used, I wanted to comment on why I like using Macs in the first place.&lt;/p&gt;  &lt;p&gt;It started in 2006 when Apple released the first Intel-based Mac. As someone with a professional video past, I had been using Macs on and off since college (1995 graduate), so I was never terribly religious about any particular platform. I’m still not, but until recently, it was staggering how crappy PC’s were. They were all plastic, disposable, commodity crap. I could never justify buying a PowerBook because I was a Microsoft stack guy. When Apple went Intel, they removed that barrier. They also didn’t screw around with selling to the low end (though the plastic MacBooks bordered on that), so even the base machines were pretty well equipped. Every Mac I’ve had, I’ve used for three years. Other than that first one, I’ve also sold each one, for quite a bit of money.&lt;/p&gt;  &lt;p&gt;Things have changed quite a bit, mostly within the last year. I’m actually relieved, because Apple needs competition at the high end. Other manufacturers are finally understanding the importance of industrial design. For me, I’ll stick with Macs for now, because I’m invested in OS X apps like Aperture and the Mac versions of Adobe products. As a Microsoft developer, it doesn’t even matter though… with Parallels, I Cmd-Tab and I’m in Windows.&lt;/p&gt;  &lt;p&gt;So after three and a half years with a wonderful 17” MBP and upgraded SSD, it was time to get something lighter and smaller (traveling light is critical with a toddler), and I eventually ended up with a 13” MacBook Air, with the i7 and 8 gig upgrades, and I love it. At home I “dock” it to a Thunderbolt Display.&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://jeffputz.com/blog/a-new-laptop" target="_blank"&gt;A new laptop&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://jeffputz.com/blog/net-development-on-a-retina-macbook-pro-with-windows-8" target="_blank"&gt;.NET development on a Retina MacBook Pro with Windows 8&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://jeffputz.com/blog/returning-my-macbook-pro-with-retina-display" target="_blank"&gt;Returning my MacBook Pro with Retina display&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://jeffputz.com/blog/net-development-on-a-macbook-air-with-windows-8" target="_blank"&gt;.NET development on a MacBook Air with Windows 8&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=8959736" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/jeff/archive/tags/.NET/default.aspx">.NET</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Visual+Studio/default.aspx">Visual Studio</category><category domain="http://weblogs.asp.net/jeff/archive/tags/Apple/default.aspx">Apple</category><category domain="http://weblogs.asp.net/jeff/archive/tags/General+Software+Development/default.aspx">General Software Development</category></item></channel></rss>