May 2010 - Posts - Jon Galloway

May 2010 - Posts

Spending the summer at camp… Web Camp, that is

Microsoft is sponsoring a series of Web Camps this summer. They’re a series of free two day events being held worldwide, and I’m really excited about being taking part.

The camp is targeted at a broad range of developer background and experience. Content builds from 101 level introductory material to 200-300 level coverage, but we hit some advanced bits (e.g. MVC 2 features, jQuery templating, IIS 7 features, etc.) that advanced developers may not yet have seen. We start with a lap around ASP.NET & Web Forms, then move on to building and application with ASP.NET MVC 2, jQuery, and Entity Framework 4, and finally deploy to IIS. I got to spend some time working with James before the first Web Camp refining the content, and I think he’s packed about as much goodness into the time available as is scientifically possible. The content is really code focused – we start with File/New Project and spend the day building a real, working application.

The second day of the Web Camp provides attendees an opportunity to get hands on. There are two options:

  • Join a team and build an application of your choice
  • Work on a lab or tutorial

James Senior and I kicked off the fun with the first Web Camp in Toronto a few weeks ago. It was sold out, lots of fun, and by all accounts a great way to spend two days.

I’m really enthusiastic about the format. Rather than just listening to speakers and then forgetting everything in a few days, attendees actually build something of their choice. They get an opportunity to pitch projects they’re interested in, form teams, and build it – getting experience with “real world” problems, with all the help they need from experienced developers.

James got help on the second day practical part from the good folks that run Startup Weekend. Startup Weekend is a fantastic program that gathers developers together to build cool apps in a weekend, so their input on how to organize successful teams for weekend projects was invaluable. Nick Seguin joined us in Toronto, and in addition to making sure that everything flowed smoothly, he just added a lot of fun and excitement to the event, reminding us all about how much fun it is to come up with a cool idea and just build it.

In addition to the Toronto camp, I’ll be at the Mountain View, London, Munich, and New York camps over the next month. London is sold out, but the rest still have space available, so come join us!

Here’s the full list, with the ones I’ll be at bolded because - you know - it’s my blog. The the whole speaker list is great, including Scott Guthrie, Scott Hanselman, James Senior, Rachel Appel, Dan Wahlin, and Christian Wenz.

  • Toronto May 7-8 (James Senior and I were thrown out on our collective ears)
  • Moscow May 19
  • Beijing May 21-22
  • Shanghai May 24-25
  • Mountain View May 27-28 (I’m speaking with Rachel Appel)
  • Sydney May 28-29
  • Singapore June 04-05
  • London June 04-05 (I’m speaking with Christian Wenz – SOLD OUT)
  • Munich June 07-08 (I’m speaking with Christian Wenz)
  • Chicago June 11-12
  • Redmond, WA June 18-19
  • New York June 25-26 (I’m speaking with Dan Wahlin)

    Come say hi!

  • Posted by Jon Galloway | 10 comment(s)
    Filed under:

    Extending NerdDinner: Adding Geolocated Flair

    NerdDinner is a website with the audacious goal of “Organizing the world’s nerds and helping them eat in packs.” Because nerds aren’t likely to socialize with others unless a website tells them to do it.

    Scott Hanselman showed off a lot of the cool features we’ve added to NerdDinner lately during his popular talk at MIX10, Beyond File | New Company: From Cheesy Sample to Social Platform. Did you miss it? Go ahead and watch it, I’ll wait.

    Get Microsoft Silverlight

    One of the features we wanted to add was flair. You know about flair, right? It’s a way to let folks who like your site show it off in their own site. For example, here’s my StackOverflow flair:

    Great! So how could we add some of this flair stuff to NerdDinner?

    What do we want to show?

    If we’re going to encourage our users to give up a bit of their beautiful website to show off a bit of ours, we need to think about what they’ll want to show. For instance, my StackOverflow flair is all about me, not StackOverflow.

    So how will this apply to NerdDinner?

    Since NerdDinner is all about organizing local dinners, in order for the flair to be useful it needs to make sense for the person viewing the web page. If someone visits from Egypt visits my blog, they should see information about NerdDinners in Egypt. That’s geolocation – localizing site content based on where the browser’s sitting, and it makes sense for flair as well as entire websites.

    So we’ll set up a simple little callout that prompts them to host a dinner in their area:

    Nerd Dinner - Flair

    Hopefully our flair works and there is a dinner near your viewers, so they’ll see another view which lists upcoming dinners near them:

    NerdDinner - Flair - Dinners Near You

    The Geolocation Part

    Generally website geolocation is done by mapping the requestor’s IP address to a geographic area. It’s not an exact science, but I’ve always found it to be pretty accurate. There are (at least) three ways to handle it:

    • You pay somebody like MaxMind for a database (with regular updates) that sits on your server, and you use their API to do lookups. I used this on a pretty big project a few years ago and it worked well.
    • You use HTML 5 Geolocation API or Google Gears or some other browser based solution. I think those are cool (I use Google Gears a lot), but they’re both in flux right now and I don’t think either has a wide enough of an install base yet to rely on them. You might want to, but I’ve heard you do all kinds of crazy stuff, and sometimes it gets you in trouble. I don’t mean talk out of line, but we all laugh behind your back a bit. But, hey, it’s up to you. It’s your flair or whatever.
    • There are some free webservices out there that will take an IP address and give you location information. Easy, and works for everyone. That’s what we’re doing.

    I looked at a few different services and settled on IPInfoDB. It’s free, has a great API, and even returns JSON, which is handy for Javascript use.

    The IP query is pretty simple. We hit a URL like this: http://ipinfodb.com/ip_query.php?ip=74.125.45.100&timezone=false

    … and we get an XML response back like this…

    <?xml version="1.0" encoding="UTF-8"?>
    <Response>
      <Ip>74.125.45.100</Ip>
      <Status>OK</Status>
      <CountryCode>US</CountryCode>
      <CountryName>United States</CountryName>
      <RegionCode>06</RegionCode>
      <RegionName>California</RegionName>
      <City>Mountain View</City>
      <ZipPostalCode>94043</ZipPostalCode>
      <Latitude>37.4192</Latitude>
      <Longitude>-122.057</Longitude>
    </Response>

    So we’ll build some data transfer classes to hold the location information, like this:

    public class LocationInfo
    {
        public string Country { get; set; }
        public string RegionName { get; set; }
        public string City { get; set; }
        public string ZipPostalCode { get; set; }
        public LatLong Position { get; set; }
    }
    
    public class LatLong
    {
        public float Lat { get; set; }
        public float Long { get; set; }
    }

    And now hitting the service is pretty simple:

    public static LocationInfo HostIpToPlaceName(string ip)
    {
        string url = "http://ipinfodb.com/ip_query.php?ip={0}&timezone=false";
        url = String.Format(url, ip);
    
        var result = XDocument.Load(url);
    
        var location = (from x in result.Descendants("Response")
                        select new LocationInfo
                        {
                            City = (string)x.Element("City"),
                            RegionName = (string)x.Element("RegionName"),
                            Country = (string)x.Element("CountryName"),
                            ZipPostalCode = (string)x.Element("CountryName"),
                            Position = new LatLong
                            {
                                Lat = (float)x.Element("Latitude"),
                                Long = (float)x.Element("Longitude")
                            }
                        }).First();
    
        return location;
    }

    Getting The User’s IP

    Okay, but first we need the end user’s IP, and you’d think it would be as simple as reading the value from HttpContext:

    HttpContext.Current.Request.UserHostAddress

    But you’d be wrong. Sorry. UserHostAddress just wraps HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"], but that doesn’t get you the IP for users behind a proxy. That’s in another header, “HTTP_X_FORWARDED_FOR". So you can either hit a wrapper and then check a header, or just check two headers. I went for uniformity:

    string SourceIP = string.IsNullOrEmpty(Request.ServerVariables["HTTP_X_FORWARDED_FOR"]) ?
        Request.ServerVariables["REMOTE_ADDR"] :
        Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

    We’re almost set to wrap this up, but first let’s talk about our views. Yes, views, because we’ll have two.

    Selecting the View

    We wanted to make it easy for people to include the flair in their sites, so we looked around at how other people were doing this. The StackOverflow folks have a pretty good flair system, which allows you to include the flair in your site as either an IFRAME reference or a Javascript include. We’ll do both.

    We have a ServicesController to handle use of the site information outside of NerdDinner.com, so this fits in pretty well there. We’ll be displaying the same information for both HTML and Javascript flair, so we can use one Flair controller action which will return a different view depending on the requested format.

    Here’s our general flow for our controller action:

    1. Get the user’s IP
    2. Translate it to a location
    3. Grab the top three upcoming dinners that are near that location
    4. Select the view based on the format (defaulted to “html”)
    5. Return a FlairViewModel which contains the list of dinners and the location information
    public ActionResult Flair(string format = "html")
    {
        string SourceIP = string.IsNullOrEmpty(
            Request.ServerVariables["HTTP_X_FORWARDED_FOR"]) ?
            Request.ServerVariables["REMOTE_ADDR"] :
            Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
    
        var location = GeolocationService.HostIpToPlaceName(SourceIP);
        var dinners = dinnerRepository.
            FindByLocation(location.Position.Lat, location.Position.Long).
            OrderByDescending(p => p.EventDate).Take(3);
    
        // Select the view we'll return. 
        // Using a switch because we'll add in JSON and other formats later.
        string view;
        switch (format.ToLower())
        {
            case "javascript":
                view = "JavascriptFlair";
                break;
            default:
                view = "Flair";
                break;
        }
    
        return View(
            view,
            new FlairViewModel 
            {
                Dinners = dinners.ToList(),
                LocationName = string.IsNullOrEmpty(location.City) ? "you" :  
                    String.Format("{0}, {1}", location.City, location.RegionName)
            }
        );
    }

    Note: I’m not in love with the logic here, but it seems like overkill to extract the switch statement away when we’ll probably just have two or three views. What do you think?

    The HTML View

    The HTML version of the view is pretty simple – the only thing of any real interest here is the use of an extension method to truncate strings that are would cause the titles to wrap.

    public static string Truncate(this string s, int maxLength)
    {
        if (string.IsNullOrEmpty(s) || maxLength <= 0)
            return string.Empty;
        else if (s.Length > maxLength)
            return s.Substring(0, maxLength) + "...";
        else
            return s;
    }

     

    So here’s how the HTML view ends up looking:

    <%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<FlairViewModel>" %>
    <%@ Import Namespace="NerdDinner.Helpers" %>
    <%@ Import Namespace="NerdDinner.Models" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title>Nerd Dinner</title>
            <link href="/Content/Flair.css" rel="stylesheet" type="text/css" />
        </head>
        <body>
            <div id="nd-wrapper">
                <h2 id="nd-header">NerdDinner.com</h2>
                <div id="nd-outer">
                    <% if (Model.Dinners.Count == 0) { %>
                    <div id="nd-bummer">
                        Looks like there's no Nerd Dinners near
                        <%:Model.LocationName %>
                        in the near future. Why not <a target="_blank" href="http://www.nerddinner.com/Dinners/Create">host one</a>?</div>
                    <% } else { %>
                    <h3>
                        Dinners Near You</h3>
                    <ul>
                        <% foreach (var item in Model.Dinners) { %>
                        <li>
                            <%: Html.ActionLink(String.Format("{0} with {1} on {2}", item.Title.Truncate(20), item.HostedBy, item.EventDate.ToShortDateString()), "Details", "Dinners", new { id = item.DinnerID }, new { target = "_blank" })%></li>
                        <% } %>
                    </ul>
                    <% } %>
                    <div id="nd-footer">
                        More dinners and fun at <a target="_blank" href="http://nrddnr.com">http://nrddnr.com</a></div>
                </div>
            </div>
        </body>
    </html>

    You’d include this in a page using an IFRAME, like this:

    <IFRAME height=230 marginHeight=0 src="http://nerddinner.com/services/flair" frameBorder=0 width=160 marginWidth=0 scrolling=no></IFRAME>

    The Javascript view

    The Javascript flair is written so you can include it in a webpage with a simple script include, like this:

    <script type="text/javascript" src="http://nerddinner.com/services/flair?format=javascript"></script>

    The goal of this view is very similar to the HTML embed view, with a few exceptions:

    • We’re creating a script element and adding it to the head of the document, which will then document.write out the content. Note that you have to consider if your users will actually have a <head> element in their documents, but for website flair use cases I think that’s a safe bet.
    • Since the content is being added to the existing page rather than shown in an IFRAME, all links need to be absolute. That means we can’t use Html.ActionLink, since it generates relative routes.
    • We need to escape everything since it’s being written out as strings.
    • We need to set the content type to application/x-javascript. The easiest way to do that is to use the <%@ Page ContentType%> directive.
    <%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<NerdDinner.Models.FlairViewModel>" ContentType="application/x-javascript" %>
    <%@ Import Namespace="NerdDinner.Helpers" %>
    <%@ Import Namespace="NerdDinner.Models" %>
    document.write('<script>var link = document.createElement(\"link\");link.href = \"http://nerddinner.com/content/Flair.css\";link.rel = \"stylesheet\";link.type = \"text/css\";var head = document.getElementsByTagName(\"head\")[0];head.appendChild(link);</script>');
    document.write('<div id=\"nd-wrapper\"><h2 id=\"nd-header\">NerdDinner.com</h2><div id=\"nd-outer\">');
    <% if (Model.Dinners.Count == 0) { %>
      document.write('<div id=\"nd-bummer\">Looks like there\'s no Nerd Dinners near <%:Model.LocationName %> in the near future. Why not <a target=\"_blank\" href=\"http://www.nerddinner.com/Dinners/Create\">host one</a>?</div>');
    <% } else { %>
    document.write('<h3>  Dinners Near You</h3><ul>');
      <% foreach (var item in Model.Dinners) { %>
    document.write('<li><a target=\"_blank\" href=\"http://nrddnr.com/<%: item.DinnerID %>\"><%: item.Title.Truncate(20) %> with <%: item.HostedBy %> on <%: item.EventDate.ToShortDateString() %></a></li>');
      <% } %>
    document.write('</ul>');
    <% } %>
    document.write('<div id=\"nd-footer\">  More dinners and fun at <a target=\"_blank\" href=\"http://nrddnr.com\">http://nrddnr.com</a></div></div></div>'); 

    Getting IP’s for Testing

    There are a variety of online services that will translate a location to an IP, which were handy for testing these out. I found http://www.itouchmap.com/latlong.html to be most useful, but I’m open to suggestions if you know of something better.

    Next steps

    I think the next step here is to minimize load – you know, in case people start actually using this flair. There are two places to think about – the NerdDinner.com servers, and the services we’re using for Geolocation.

    I usually think about caching as a first attack on server load, but that’s less helpful here since every user will have a different IP. Instead, I’d look at taking advantage of Asynchronous Controller Actions, a cool new feature in ASP.NET MVC 2. Async Actions let you call a potentially long-running webservice without tying up a thread on the server while waiting for the response. There’s some good info on that in the MSDN documentation, and Dino Esposito wrote a great article on Asynchronous ASP.NET Pages in the April 2010 issue of MSDN Magazine.

    But let’s think of the children, shall we? What about ipinfodb.com? Well, they don’t have specific daily limits, but they do throttle you if you put a lot of traffic on them. From their FAQ:

    We do not have a specific daily limit but queries that are at a rate faster than 2 per second will be put in "queue". If you stay below 2 queries/second everything will be normal. If you go over the limit, you will still get an answer for all queries but they will be slowed down to about 1 per second. This should not affect most users but for high volume websites, you can either use our IP database on your server or we can whitelist your IP for 5$/month (simply use the donate form and leave a comment with your server IP). Good programming practices such as not querying our API for all page views (you can store the data in a cookie or a database) will also help not reaching the limit.

    So the first step there is to save the geolocalization information in a time-limited cookie, which will allow us to look up the local dinners immediately without having to hit the geolocation service.

    Posted by Jon Galloway | 5 comment(s)
    Filed under:

    Introducing the MVC Music Store - MVC 2 Sample Application and Tutorial

    A couple weeks ago we did a soft release of a new ASP.NET MVC 2 Tutorial and Sample Application I’ve been working on over the past few months, the MVC Music Store. The source code and an 80 page tutorial are available on CodePlex. I’m also working on a video tutorial series for the ASP.NET website which will walk through building the application. After that, it’s time to talk about a feature length film and a worldwide MVC Music Store On Ice tour, but the plans aren’t completely set just yet.

    MVC Music Store

    MVC Music Store - Shopping Cart

    MVC Music Store - Admin

    Everything’s under friendly licenses - the source is available under Ms-PL license, and the tutorial is under Creative Commons Attribution license. The data for the application is based on the Chinook database, which is also under Ms-PL license.

    Who is this for?

    This tutorial is mostly written for beginners to ASP.NET MVC, including folks who are kind of new to web development. We already have a few tutorials out there at the intermediate to advanced developer level (Nerd Dinner, MVC Storefront) if you’re looking for something more advanced, but if you want to start with the basics of how ASP.NET MVC works before diving into repositories and IoC and TDD, this is a great place to get started.

    However, I think it’s also pretty useful if you’ve got some experience with ASP.NET MVC 1.0 but want to get a look at using some of the new features in ASP.NET MVC 2, especially model validation.

    What’s it cover?

    Store features:

    • Store browse
    • Shopping cart
    • Checkout
    • Membership and registration
    • Basic administration (add and edit albums)

    Tutorial covers:

    • ASP.NET MVC Basics
      • Controllers
      • Views
      • Models and ViewModels
    • Data access using Entity Framework 4
    • Model Validation
    • Editor and Display Templates
    • Use of ASP.NET Membership system
    • Client-side enhancements
      • Client-side validation
      • AJAX update showing both Ajax.ActionLink and jQuery

    How’s the code?

    I think this is a great tutorial sample, and I’m really proud of it. I spent a lot of time on it, and I had several reviews with Scott Guthrie and Scott Hanselman, as well as Phil Haack and Brad Wilson from the ASP.NET team. I’m hugely thankful to all of them for their input and patience, but of course I’ll take responsibility for any errors or killed kittens.

    This is a beginner tutorial, so the code focuses on communicating how ASP.NET MVC works. There are no dependencies, and things are written using a minimum of code and complexity. If you do see something that makes you angry or sad please let me know, but keep in mind that we’re not trying to build an enterprise store system here. As it is, the tutorial is 80 pages long, so I really tried to keep as much out as possible and focus on the basics.

    Where next?

    I’ve already gotten some good feedback on the discussion forum and at the Toronto Web Camp, where we used it as lab, so I’ll be updating the document and tutorial to get to a 1.0 release.

    I’m working on a video series for the ASP.NET website.

    I’ve also heard that people are interested in advanced, real-world scenarios, so that’s something I’ll be looking at next. Please let me know if you have any thoughts or requests there.

    Tailspin Spyworks Web Forms Sample

    If you haven’t seen it, check out Joe Stagner’s Tailspin Spyworks sample - an update of the popular iBuySpy application for ASP.NET 4 with all the new goodies.

    More Posts