Scott Hanselman

Automatically Backup your Gmail account on a schedule with GMVault and Windows Task Scheduler

September 10, '12 Comments [32] Posted in Tools
Sponsored By

It's nice to have your things backed up to the cloud, but you really need to have local backups as well. I have two 1TB pocket hard drives that I rotate between my home and the bank. They are labeled Offsite Backup A and Offsite Backup B. You can encrypt them with either Bitlocker To Go or TrueCrypt, and I do.

Related Links

I've got years and years of email in my only personal email account, powered by Gmail. I've recently started backing up my WHOLE gmail account with a wonderful free tool called GMVault. Setup requires a little attention to detail but once it's done, it's done.

Once installed, you run GMVault-Shell and type "gmvault sync youremail@address.com." The first backup will take HOURS and on Windows will put thousands and thousands of files in your C:\Users\YOURNAME\gmvault-db directory. You can move this directory if you want. My email backup was over 350,000 emails so I moved it to my larger D drive by using the -d option on the command line.

After this multi-hour sync was finally done, I wanted to make sure I updated the archive every week or so with backups of new emails.

Create a Scheduled Gmail Backup with Task Scheduler

Go to your start menu and type "Task" and run the Task Scheduler. Some folks don't even know this exists!

On the right side click "Create Basic Task."

Create Basic Task Wizard - Task Name

Make it weekly or monthly or whatever makes you happy.

Create Basic Task Wizard - Setting time

Your action is Start a Program

Create Basic Task Wizard - Start a program

Make the Program like this and check your path first.

"C:\Users\YOURNAME\AppData\Local\gmvault\gmvault.bat"

Under Arguments, use sync -t quick like this. Be sure to use the -t quick or you'll get ALL your email again!

sync -t quick youremail@address.com 

optionally you can point to a specific backup directory like this. If there is a space in your path, use quotes around it.

sync -t quick youremail@address.com -d D:\gmvault-db

I also made my task start in the same directory as GmVault, so "C:\Users\YOURNAME\AppData\Local\gmvault"

Create a Basic Task - final screen with all options set

My scheduled task ended up with command line arguments like this:

sync -t quick scott@myemail.com -d D:\gmvault-db

You can test it by right clicking on it in the Task Scheduler list and clicking "Run." If you need to debug it or if it just starts and then quickly disappears, go into your gmvault.bat and add a "pause" command before the "exit" command to keep the window open long enough to see any errors.

Here's my automatic Gmail backup in action:

GMVault automatically backing up my email

Hope this helps you!


This week's sponsor: Be part of GENERATION APP. Your Idea. Your App. 30 Days. Begin your 30-day journey to create a Windows Store style app and talk 1-on-1 with a Windows 8 app development pro. Get started today.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Introducing ASP.NET FriendlyUrls - cleaner URLs, easier Routing, and Mobile Views for ASP.NET Web Forms

September 9, '12 Comments [91] Posted in ASP.NET | Javascript | Mobile | Open Source | VS2012
Sponsored By

I've said before how surprised I am that more ASP.NET Web Forms developers don't use Routing to make their URLs prettier. If you don't want "foo.aspx" in your URL, then change it with Routes.MapPageRoute(). However, managing Routing Tables is a little tedious and most WebForms folks aren't used to the concept and don't want to invest the time.

I've also heard a number of ASP.NET Web Forms Developers express a little envy at how easy it is to make a site that has both desktop and mobile views using ASP.NET MVC. They like the idea of seeing an iPhone show up and showing a different  view while reusing logic as I've shown in my mobile talks before.

Let's solve both these problems with a new ASP.NET feature just pre-released today in alpha form on NuGet. My peer Damian Edwards and developer Levi Broderick along with QA by  Pranav and Anton have come up with a pretty awesome solution based on the original "Smarty Routes" idea from Eilon Lipton and the result is FriendlyUrls.

Install-Package Microsoft.AspNet.FriendlyUrls -pre

NOTE: If you've been paying attention to ASP.NET for the last few months you'll recognize this incremental useful but appropriately sized forward motion as being all part of the One ASP.NET master plan.

It's also worth noting that this FriendlyUrls NuGet package includes BOTH an ASP.NET 4.5 and ASP.NET 4 version so .NET 4 folks get love too.

FriendlyUrls Hello World Example

First, the obvious example. Bring up Visual Studio and File | New Project | New ASP.NET Web Forms Application. Now, from the Package Manager Console or from Manage NuGet Packages, install Microsoft.AspNet.FriendlyUrls. You'll need to "Include Prerelease" packages with -pre from the command line or via the dropdown in the UI.

Microsoft.AspNet.FriendlyUrls -pre shown in the UI

Be sure to read the readme.txt that pops up as you'll need to ensure that the FriendlyUrls routing gets called on application startup! I added this one line to my Application_Start:

RouteConfig.RegisterRoutes(RouteTable.Routes);

Here's the cool part. If I hit one of my existing links, like Contact.aspx, look what happened. See how the GET request for /Contact.aspx turned into a 301 redirect to /Contact?

/Contact.aspx turned into a 301 redirect to /Contact

If you have a Web Form called /Foo.aspx, you automatically get a /Foo route and can call your page like that! Hence, Microsoft.AspNet.FriendlyUrls.

Just by adding the one package and calling

routes.EnableFriendlyUrls();

in RouteConfig (this default came down with the NuGet package) my whole WebForms app loses its .ASPX extensions and gets reasonable defaults.

FriendlyUrls Advanced Sample

Get it? Ok, let's dig into some of the obvious next questions and some more advanced scenarios. How do I get values out of the URL? I'm used to Request.QueryString and Request.Form, but how do I get ahold of these URL segments?

Here's a Foo.aspx that I've visited via /Foo.

A basic Foo WebForms page

If I click "Click Me" the URL points to /Foo/bar/34.

Visiting /Foo/bar/34

NOTE: Be aware of the magic. It makes sense. If there was a 34.aspx in a folder called Bar in a folder called Foo, we would have used that file. There wasn't. If there was a file called Bar.aspx in a folder called Foo we would have used that. There wasn't. So, we used Foo.aspx and passed in the rest of the URL.

I can get the segments out like this:

<% foreach (var segment in Request.GetFriendlyUrlSegments()) { %>
<li><%: segment %></li>
<% } %>

UPDATE: One thing I forgot to mention was how to get the values out of the FriendlyURL. You can use things like [Form] and [QueryString] to model bind in WebForms. Now you can add [FriendlyUrlSegments] to get data out, like the ID in this example:

public SomeItem SomeItem_GetItem([FriendlyUrlSegments]int? id)
{
SomeItem item = db.SomeItem.Find(id);
return item;
}

They're sitting on the Request option. I did have to import the Microsoft.AspNet.FriendlyUrls namespace to have this extension appear.

<%@ Import Namespace="Microsoft.AspNet.FriendlyUrls" %>

Better yet, I can generate Friendly URLs without string concatenation!

<a href="<%: FriendlyUrl.Href("~/Foo", "bar", 34) %>">Click me</a>

Nice, eh? OK, let's make it mobile.

Mobile Routes with ASP.NET FriendlyUrls

When you bring down the NuGet package you'll also get a Site.Mobile.Master. If I visit them with the Electric Plum Mobile Simulator (iPhone) I see a default mobile page, automatically.

The Default Mobile Web Forms page in an iPhone

Ah, you see where this is going. I'll copy Foo.aspx to Foo.Mobile.aspx. I'll make a small change. I'll visit /Foo/bar/34 again except now I get the mobile master and the mobile foo, automatically.

image

What I want to support switching back and forth from Desktop to Mobile? Just add a ViewSwitcher control, also included.

<friendlyUrls:ViewSwitcher runat="server" />

Now I re-render and I get a "switch to mobile" and switch to desktop.

image

Now I can go back and forth between views and request a desktop site even when on mobile.

image

So basic mobile is nice but I might want very specific mobile views for iPhone, iPad, Opera Mobile, etc.

Super Advanced Mobile Routes for Specific Devices with ASP.NET FriendlyUrls

By default FriendlyUrls uses a class called WebFormsFriendlyUrlResolver but you can derive from this class and change its behavior however you like. Here's an example of a "DeviceSpecificWebFormsFriendlyUrlResolver" or, better yet, Mobile Friendly Urls for WebForms.

This derived URL resolver does just that, it resolves URLs to physical Web Forms pages. You'd then pass it into the overload of EnableFriendlyUrls(...);

IMPORTANT NOTE: This code is just a very early sample, there will be a more complete one released later.

public class DeviceSpecificWebFormsFriendlyUrlResolver : WebFormsFriendlyUrlResolver
{
private readonly IDictionary<string, string> _deviceUserAgentMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Opera Mobi", "OperaMobile" },
{ "iPhone", "iPhone" },
{ "iPad", "iPad" }
};

protected override IList<string> GetExtensions(HttpContextBase httpContext)
{
var extensions = base.GetExtensions(httpContext).ToList();
if (extensions.Contains(MobileAspxExtension, StringComparer.OrdinalIgnoreCase))
{
// Base has determined we should look for a mobile page, let's add device specific
// extension to the beginning.
var deviceSpecificSufffix = GetDeviceSpecificSuffix(httpContext);
if (!String.IsNullOrEmpty(deviceSpecificSufffix))
{
extensions.Insert(0, "." + deviceSpecificSufffix + AspxExtension);
}
}
return extensions;
}

protected override bool IsMobileExtension(HttpContextBase httpContext, string extension)
{
return base.IsMobileExtension(httpContext, extension) ||
_deviceUserAgentMap.Values.Any(v => extension.Contains(v, StringComparison.OrdinalIgnoreCase));
}

protected override bool TrySetMobileMasterPage(HttpContextBase httpContext, Page page, string mobileSuffix)
{
var deviceSpecificSufffix = GetDeviceSpecificSuffix(httpContext);
if (!String.IsNullOrEmpty(deviceSpecificSufffix) && base.TrySetMobileMasterPage(httpContext, page, deviceSpecificSufffix))
{
// We were able to set a device specific master page, so just return
return true;
}

// Just use the base logic
return base.TrySetMobileMasterPage(httpContext, page, mobileSuffix);
}

private string GetDeviceSpecificSuffix(HttpContextBase httpContext)
{
foreach (var item in _deviceUserAgentMap)
{
if (httpContext.Request.UserAgent.Contains(item.Key, StringComparison.OrdinalIgnoreCase))
{
return item.Value;
}
}

return String.Empty;
}
}

Now we've created a map of device specific suffixes, so we can have not Foo.Mobile.aspx, but rather Foo.iPhone.aspx and Foo.OperaMobile.aspx, etc.

Here's a little demo that loads a bunch of names into a list. Here's /async, the desktop view.

A list of names on the desktop

Now we'll add jQuery mobile to the mobile master page, and use it on the mobile version of the same page. We're still calling the same data source and reusing all that code.

The list of names now as a jQuery mobile page inside an iPhone

I'm pretty jazzed about what this means for ASP.NET and Web Forms developers. We're going to continue to push forward and improve ASP.NET even now, after Visual Studio 2012 has been released. Sometimes we'll add small features via NuGet packages, sometimes editor improvements as free VSIX Extensions like the Web Essentials playground for 2012 and larger changes via released updates to all of ASP.NET.  I hope you like the direction we're heading.

Go play with Microsoft.AspNet.FriendlyUrls now and thank Damian and friends on Twitter!


This week's sponsor: Be part of GENERATION APP. Your Idea. Your App. 30 Days. Begin your 30-day journey to create a Windows Store style app and talk 1-on-1 with a Windows 8 app development pro. Get started today.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Your Colorful Visual Studio 2012 with the Color Theme Editor (VS2010 colors, too)

September 7, '12 Comments [59] Posted in Tools | VS2012
Sponsored By

The stock Visual Studio 2012 gray color scheme is growing on me. Sue me. When you're writing code you usually focus on the code so I'm more concerned with the colors of the code than the chrome.

Here's my default, which is the VS2012 defaults with larger fonts.

VS2012 with the default color scheme

Here is Visual Studio 2012 again, except this time I've used Matthew Johnson's Visual Studio 2012 Color Theme Editor and applied the Blue theme:

VS2012 with VS2010 colors

Here it is again with the ALL CAPS registry setting turned off:

HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0\General\SuppressUppercaseConversion
REG_DWORD value: 1

Now you're pretty much back to the VS2010 look and feel. I've zoomed in here to make it clear.

Visual Studio 2012 with the ALL CAPS menu removed and the VS2010 colors restored

Go nuts! You can make and share custom themes yourself! With this add-in you can customize a lot more than the default installation allows:

A wide range of color options

Have fun! Go get Matthew Johnson's Visual Studio 2012 Color Theme Editor now.

UPDATE: I personally would not go this far, but you can also patch the icons if you feel strongly about it. http://vsip.codeplex.com

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Initial Impressions of the 3rd Generation Ivy Bridge Intel Ultrabook Reference Hardware for Developers on Windows 8

September 5, '12 Comments [29] Posted in Hardware | Reviews | Win8
Sponsored By

Some of the software folks at Intel sent me an Ivy Bridge Ultrabook to look at. It will never be production hardware - this laptop will never be made or sold. That doesn't mean it's exclusive or special or extra fancy, instead, it's meant to be a reference example for hardware makers to make Ultrabooks of their own.

I did an unboxing and initial impressions video which I've embedded here.

Now that I've been using this Ultrabook for a while, these are my updated impressions. I've separated them into categories to make sure that we all keep things straight.

  • Hardware - This isn't a production Ultrabook. In fact, as a piece of hardware it's got problems, which I will go over. However, since Intel doesn't actual MAKE laptops, you should take all those observations with a huge grain of salt. Rather than taking my criticisms as impugning this specific laptop (that will never exist) they should instead be a warning to other Ultrabook makers. If you're going to make a tiny laptop to compete with a MacBook Air  - or surpass it - you better get the details right.
  • Software - The Ultrabook came with Windows 8 Release Preview which I promptly torched and installed the release of Windows 8. It's my personal expectation that an Ultrabook not just be a good Windows laptop, it should "just work" with Windows 8 and do all the things we expect any laptop do, like be fast, boot quickly, sleep quickly and not crash.
  • Sensors and Auxiliary Hardware - Why is sensors its own section? Because its these invisible sensors that will change how we think of laptops in the coming year. Sure they all have keyboards and screens but soon every laptop will have touchscreens and accelerometer and gyroscopes and location sensors.
  • Development and Speed - Any $399 Walmart Laptop can run Word and your blog. I am not interested in reviewing those products for those activities. I want to know if I can replace my 10 pound BEAST of a laptop - my beloved Lenovo W520 - with a 3 pound Ultrabook. Can a slim laptop run Hyper-V VMs and multiple instance of Visual Studio while running multiple monitors?

That is the underlying question that should pervade this review. Can a 3 point laptop replace a 6 to 10 pound guaranteed powerhouse desktop replacement and be my new "on the road laptop."

Hardware Impressions

Again, this isn't hardware that will ever be released, it's just a reference. And frankly, that's a good thing. It's not very good. This prototype "Intel branded" Ultrabook has a lousy keyboard and a mediocre touchpad that is a pain to use. Literally my fingertips hurt from these keys. The keys are made out of the same hard plastic that the case is made from and they have a hard clicky travel that is consistently irritating to the point of numbness. I realize I am coming from a Lenovo - the recognized king of keyboards - but this is just painful. Still, Intel is a chip company so I won't belabor the point for a non-existent product. In this case it's a good thing they aren't making laptops because this keyboard would be a deal breaker.

The only thing worse than the keyboard is the touchpad. It's an ALPS brand touchpad which means it has a kind of multi-touch. Initially this Ultrabook came with older drivers and two-finger scrolling wasn't enabled. This 24 hour period without easy scrolling was a nightmare. It's not surprising how quickly two-finger scrolling has become the expected standard with touchpads. Once you've baked it into your brain it's required equipment to the point that I truly would not purchase any laptop or Ultrabook without a two finger scroll.

Stacking the Ultrabook next to a MacBook Pro and Lenovo W520 to compare thickness.

I almost wrote this laptop off until I got all the latest drivers installed. The ALPS touchpad now includes new drivers for not only two finger scrolling (yay!) but also something called "Edge Action" that essentially takes the edge-based touchscreen gestures from Windows 8 tablets and assigns it to the touchpad. Swipe in from the right to get the charms menu, in from the left to task switch and down from the top for menus and browser tabs. This is such a clean and clear extension of the "touch" experience that if I were in charge of the Windows hardware ecosystem I would require it. Pinch to zoom works as well, just as it should. Kudos to the ALPS driver guys. Your friends at Synaptics (the makers of the Lenovo Trackpad) had better take notice. Multitouch isn't just for screens, and I will no longer buy a laptop without a multi--touch touchpad and neither should you.

From my perspective saying something is an Ultrabook is more than just saying "we can be a MacBook Air too," it's having a cohesive and completely self-consistent hardware and software mix where everything just works as it should and works seamlessly. Day one without the right drivers was a tiresome exercise that had me ready to give up. Day two of mousing was a smooth and happy experience that rivaled or surpassed the MacBook Air and the only difference was touchpad drivers. I hope that these drivers are "in the box."

TouchScreen

A touchscreen on a laptop? Why? What kind of madness is this? After using it for a while having a touchscreen is a nice to have. The mistake reviewers make is thinking that we the users are expected to be swiping around all day with tired gorilla arms. The real truth is that it's just a third input device. Keyboard, mouse, screen. Now everything can be touched - and preferably ordered like this by frequency of use, at least for a laptop.

I find myself reaching for the screen a few times an hour. Not enough to get tired and not enough to pretend I am on a tablet, but enough to appreciate it. Sometimes clicking Start and touching a large icon is just faster than the mouse and more relaxed than the keyboard. After a while I even used the taskbar buttons by touch to open apps as those buttons are just a few inches from the top row of function keys.

I often used the touch screen to position the cursor when editing large amounts of text. When browsing long form reading on Instapaper I would scroll with the screen, usually resting my hand in the lower right corner of the screen and scrolling with my thumb. It's a very natural position.

This reference hardware includes 5 touch points and built-in apps like Maps worked great. It did tend to miss some taps though, usually just one finger doing basic stuff. I'm assuming this is a hardware driver issue, perhaps trying to prevent unwanted taps. It was noticeable enough to be irritating so I'd recommend to anyone purchasing an Ultrabook with a touch screen to spend some time with the hardware and ensure it's consistent and reliable.

Software

Let's get back to drivers. There's two kinds of drivers in the Windows World. There's what they call "inbox" drivers and there's 3rd party drivers you install later. I am a big fan of inbox drivers, myself. These mean that your computer and all it's bits and pieces will "just work" out of the box when you turn it on, even if you've just installed Windows. These drivers are super-tested and are super-stable. They may not give you all the most advanced features but they will "Just Work." If you want to get the 3rd party stuff you can install those.

Everything on this laptop worked pretty well out of the box. In my experience computers started working pretty well out of the box without hunting for drivers around the time Windows 7 came out. I am hoping that this patterns continues with Windows 8. The drivers I've been installing on this pre-release laptop have been pretty rough and very crashy so I've reverted back to the stability of the "inbox drivers" but just uninstalling the add-on ones. I assume by the time Ultrabooks in this generation come out that all the kinks will be worked out.

Sensors and Auxiliary Hardware

Sensors are getting cheap enough to include and integrated directly into Windows 8 to the point where we'll just assume that every laptop has a compass and the like. Window 8 actually formalizes a whole Sensor Platform which makes developing to them a lot easier than before and doesn't require the application developer (you and me) to think about 3rd party drivers. We just call standard APIs. The Windows Driver Kit (WDK) includes Sensor Diagnostic Tools we can use to explore our hardware's capabilities.

For example, Ultrabooks can have any or all of these sensors:

  1. GPS (recommended): Every machine should have this now since most browsers and all map applications can use it.
  2. Accelerometer (recommended): If you have a spinning hard drive the machine could shut it off if if feels it's being dropped. It's more likely that this sensor could be useful for speed and mapping.
  3. Ambient Light Sensor (ALS) (recommended): This reference Ultrabook automatically changes the brightness of the screen based on ambient lights.
  4. Compass: Again, useful for mapping.
  5. Gyroscope: Ultrabooks that have foldable screens that rotate and bend backwards will automatically adjust screen orientation for you.

This Ultrabook also has NFC (Near Field Communication) as well as Bluetooth 4.0.

Here's a screenshot of the Sensor Diagnostic Tool that comes with the WDK talking to this Ivy Bridge Ultrabook's sensors:

Windows Sensor Diagnostic Tool

I will likely poke around with these sensors from .NET and see what useful data I can get with them.

Development and Speed

As you can see half-way through the unboxing video this little Ultrabook is FAST. It can load Visual Studio 2012 in about 2 to 3 seconds and builds of average-sized projects are also just seconds.

PERFORMANCE UPDATE: I recently used this Ultrabook to turn 15gigs of video into a 10 minute 1080p memorial video for my Uncle. I had no trouble at all editing and rendering the video and was pretty pleased that was able to do it with a 3lb laptop and not my desktop machine.

While the 3D graphics speed isn't where I'd like to see it, there's a WEI of 8.1 on the hard disk and and 7.2 on the processor. Remember that WEI's are kind of like the Richter Scale. Changing a whole number is a big deal and the difference between a "7 class" and "8 class" device is not only measureable but significant.

The 8.1 WEI is for the 160gig Intel SSD in this Ultrabook and it's super fast. Visual Studio 2012 is very hard on disks and disk access and this Ultrabook is taking it like a champ. That combined with the quad (UPDATE: It's a physical Dual Core with Hyperthreading so that's 4 logical processors) processor i7 at 2.49GHz means that while there is that (as of yet) unavoidable i7 fan when it's working hard, this machine can cook.

Intel i7-3667U Ivy Bridge

Conclusion

I could develop on an Ultrabook. I could. I didn't think I could. It pains me to say it as I have been carrying around 10lb laptops in the name of power for over a decade. I have always said that I would carry that extra few pounds with me for 20% more power because, hey, all laptops weigh the same when they are sitting on your desktop. However, if you can get me a quad i7 in 3lbs, then we can talk. We are SO close.

The Bad or Weird

My major complaints about this Ultrabook from a developer - or even a power user - perspective are:

  • 4 gigs of RAM? Come on, son. It's 2012, just make it 8 gigs and let's move  on.
  • The fan. Yes, I admit it, I want a quad-core Intel processor in an iPad and I want it silent and cool as iced tea. Sue me.
  • The keyboard. This is likely an unfair beef as this Ultrabook will never be made, but just a reminder to us all. The thing you touch on the laptop is the most important. Get the keyboard right.
  • Just 2 USB Ports. I would have appreciated a third USB port, and a smart card slot.

The Good

  • It's small. 3 pounds is small. It's flat and it's easy to throw in a bag. I may give it a try and do an international trip with ONLY this Ultrabook. That is no small risk for me to take, especially as I need my machines to present.
  • It's fast. Compared to my wife's 3 year old Dell of similar (albeit swollen) size, this thing moves along at the speed of thought.
  • It works. It's like an appliance. It turns on in seconds, shuts off when you close the lid and is reliable. I trust it more than my other laptops.
  • You can touch it. The touchscreen is nice to use when you lean forward and scroll, or reach up to navigate, or just browse. It's not fundamental but touch DOES change the relationship with your laptop.

I'll use it full time as a developer some more and report back, especially if I go on a trip with it.

Disclosure of Material Connection: Intel sent me this Ultrabook for free in the hope that I would review it on my blog. Regardless, I only recommend products or services I I would use and think you would find useful. I am disclosing this in accordance with the Federal Trade Commission’s 16 CFR, Part 255: “Guides Concerning the Use of Endorsements and Testimonials in Advertising.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

ASP.NET MVC DisplayTemplate and EditorTemplates for Entity Framework DbGeography Spatial Types

September 4, '12 Comments [28] Posted in ASP.NET | ASP.NET MVC
Sponsored By

UPDATE: Be sure to read the whole post including the refactoring update at the end via a Pull Request from Dave Ward.

I was trying to write a blog post about something totally different and was using this small EntityFramework 5 Code First model:

public class TouristAttraction
{
public int TouristAttractionId { get; set; }
public string Name { get; set; }

public DbGeography Location { get; set; }
}

The little bit I added

You'll notice I'm using the DbGeography spatial type with Latitude and Longitude. This support is new in Entity Framework 5. For a more complex example I might want to make a custom type of my own or split things up into a double lat and double long, but for this little app I just wanted a few fields and a map.

However, when I was scaffolding things out, of course, DbGeography was left in the cold. It wasn't scaffolded or included. When I added a map, there was no model binder. When I started making more complex views, there was no EditorFor or DisplayFor template support. So, rather than finishing the thing I was trying to do (I'll finish that project later) I became slightly obsessed focused with getting some kind of basic system working for DbGeography.

First, I scaffolded out a standard Entity Framework controller for "TouristAttraction" and changed nothing. The goal was to change nothing because DbGeography should just be treated like any other complex type. My prototype should be easily changeable for any other database or map system. There shouldn't be any Controller hacks to get it to work.

There's basically a DisplayTemplate and an EditorTemplate to show and edit maps. There's a model binder to handle DbGeography, and a small helper extension to get the client id for something more easily.

There's also some supporting JavaScript that I'm sure Dave Ward will hate because I'm still learning how to write JavaScript the way the kids write it today. Refactoring is welcome.

Creating and Editing

When creating a TouristAttraction you type the name then click the map. Clicking the map puts the lat,long in a text box (that could be hidden, of course).

Creating a location with a clickable Google Map

I simply added a Google Map to the Layout:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> 

then on the Create.cshml used an EditorFor just like any other field:

<fieldset>
<legend>TouristAttraction</legend>

<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Location)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Location)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>

There is an EditorTemplate called DbGeography, the name of the type by convention. If the type is "Foo" and the file is EditorTemplates/Foo.cshtml or DisplayTemplates/Foo.cshtml you can use EditorFor() and DisplayFor() and the whole app gets the benefits.

@model System.Data.Spatial.DbGeography
@Html.TextBox("")
@if (Model != null) {
<script>
$(function () {
maps.markerToSet = new google.maps.LatLng(@Model.Latitude, @Model.Longitude);
});
</script>
}
@{ string textbox = Html.ClientIdFor(model => model).ToString(); }
<div id="map_canvas" data-textboxid="@textbox" style="width:400px; height:400px"></div>

This EditorTemplate needs to support Create as well as Edit so if Model isn't null it will set a variable that will be used later when the map is initialized in an Edit scenario.

Next steps would be to make a generated map id for the div so that I could have multiple maps on one page. I'm close here as I'm sticking the "friend" textbox id in a data- attribute so I don't have to have any JavaScript on this page. All the JavaScript should figure out names unobtrusively. It's not there yet, but the base is there. I should also put the width and height elsewhere.

You'll notice the call to ClientIdFor. At this point I want the name of the textbox's client id but I don't know it. I don't want to make an EditorTemplate that only works hard-coded for one type so I need to get the generated value. I have an HtmlHelper extension method:

public static partial class HtmlExtensions
{
public static MvcHtmlString ClientIdFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
return MvcHtmlString.Create(
htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression)));
}
}

This won't work unless you are sure to add your HtmlExtensions full namespace to the web.config in the Views folder under namespaces. That will let Razor see your HtmlHelper Extension method.

NOTE: Make sure you add a reference to System.Data.Entity to your MVC View's web.config in order to have a @model like I have above.

<compilation ... >
<assemblies>
<add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</assemblies>
</compilation>

When you click the map it fills out the text box. I've also got some initial values in here as well as the addListenerOnce which is used later in DisplayFor to load a read-only" map when viewing a record's details.

function maps() { }
maps.mapInstance = null;
maps.marker= null;
maps.mapInstanceId = "map_canvas";
maps.markerToSet = null;

function initialize() {
var latlng = new google.maps.LatLng(40.716948, -74.003563); //a nice default
var options = {
zoom: 14, center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP,
maxZoom: 14 //so extents zoom doesn't go nuts
};
maps.mapInstance = new google.maps.Map(document.getElementById(maps.mapInstanceId), options);

google.maps.event.addListener(maps.mapInstance, 'click', function (event) {
placeMarker(event.latLng);
});

google.maps.event.addListenerOnce(maps.mapInstance, 'idle', function (event) {
if (maps.markerToSet) {
placeMarker(maps.markerToSet);
var bound = new google.maps.LatLngBounds();
bound.extend(maps.markerToSet);
maps.mapInstance.fitBounds(bound);
}
});
}

function placeMarker(location) {
if (maps.marker) {
maps.marker.setPosition(location);
} else {
maps.marker = new google.maps.Marker({
position: location,
map: maps.mapInstance
});
}

if (maps.marker) { //What's a better way than this dance?
var textboxid = $("#" + maps.mapInstanceId).data("textboxid");
$("#" + textboxid).val(maps.marker.getPosition().toUrlValue(13));
}
}

$(function () {
initialize();
});

When I've filled out a new location and hit SAVE the lat,long is POSTed to the controller which I want to "just work" so I made a Model Binder to handle the DbGeography type.

I add it (actually its provider in case I add to it with other Entity Framework types) to the ModelBinderProviders collection in Global.asax:

ModelBinderProviders.BinderProviders.Add(new EFModelBinderProvider());

The ModelBinder itself tries to be generic as well. I must say I see FAR too many CustomModelBinders out there with folks calling into Request.Form digging for custom strings. That's not reusable and it's just wrong.

public class DbGeographyModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
string[] latLongStr = valueProviderResult.AttemptedValue.Split(',');
string point = string.Format("POINT ({0} {1})",latLongStr[1], latLongStr[0]);
//4326 format puts LONGITUDE first then LATITUDE
DbGeography result = valueProviderResult == null ? null :
DbGeography.FromText(point,4326);
return result;
}
}

public class EFModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
if (modelType == typeof(DbGeography))
{
return new DbGeographyModelBinder();
}
return null;
}
}

At this point, DbGeography is model bound and I don't have to change the controller. Now, again, I am NOT using a ViewModel here but I would on a larger application so be aware. I'd likely make a custom model binder for that hypothetical ViewModel and it would to be married to a database technology like this one is.

You can see in the database that I've got the points stored as "geography" types and they are round tripping just fine.

Entity Framework v5 Code First and VS2012 support for SQL Server's geography types

I threw this ASP.NET MVC 4 sample up on GitHub in https://github.com/shanselman/ASP.NET-MVC-and-DbGeography. You'll need VS2012 to play.

Again, please forgive my 3am hacking and poor JavaScript but I hope you get the idea and find it useful. I think this has the potential to be packaged up into a NuGet or perhaps useful as an EntityFramework and ASP.NET MVC sample.

IMPORTANT NOTE: In order to save lots of space space with samples I didn't check in binaries or include the packages folder with dependencies. Make sure that you have given NuGet permission to download missing packages during a build if you want to build this sample.

Be sure to click "Allow NuGet to download missing packages during build"

I've enabled NuGet Package Restore in the project by right clicking on the Solution node and clicking "Enable NuGet Package Restore." This will cause NuGet to download the missing packages on build, so be aware as there will be a pause on the first build when NuGet updates the package.

UPDATE #1

My buddy Dave Ward took a look at the code and submitted a pull request that improves some of the JavaScript and makes it more the way "the kids are writing JavaScript these days."

Dave has finished up a number of things for me. First, he's made things generic enough so one can have multiple maps on a page. He has also tidied up my JavaScript.

He starts by removing any inline script from the DisplayTemplats and EditorTemplates and instead uses the TextBox itself to hold the data. This, of course, is just one of several "duh" moments for me and a reminder that using HTML elements to hold data is kind of what they're for. It also brings up an interesting debate about views and models. I did think about using KnockoutJS for this but thought it overkill.

@model System.Data.Spatial.DbGeography
@if (Model != null) {
@Html.TextBox("", Model.Latitude + "," + Model.Longitude, new { @class = "editor-for-dbgeography" })
} else {
@Html.TextBox("", "", new { @class = "editor-for-dbgeography" })
}
Dave also introduces CSS classes as markers for the text boxes to identify them as editors for a DbGeography. That's editors, plural, since the primary limitation of my implementation was that there could be only one.

Dave now keys off the text boxes rather than a div. He initializes any of these text boxes (for display or editor) and dynamically adds the Google Map after each.

$('.editor-for-dbgeography, .display-for-dbgeography').each(initialize);

He pulls the lat,long out of the text box and creates the map. When the text box changes he set a marker and pans the map to the new location.

It's surprisingly readable.

(function() {
// Method signature matching $.fn.each()'s, for easy use in the .each loop later.
var initialize = function(i, el) {
// el is the input element that we need to initialize a map for, jQuery-ize it,
// and cache that since we'll be using it a few times.
var $input = $(el);

// Create the map div and insert it into the page.
var $map = $('<div>', {
css: {
width: '400px',
height: '400px'
}
}).insertAfter($input);

// Attempt to parse the lat/long coordinates out of this input element.
var latLong = parseLatLong(this.value);

// If there was a problem attaining a lat/long from the input element's value,
// set it to a sensible default that isn't in the middle of the ocean.
if (!latLong || !latLong.latitude || !latLong.longitude) {
latLong = {
latitude: 40.716948,
longitude: -74.003563
};
}

// Create a "Google(r)(tm)" LatLong object representing our DBGeometry's lat/long.
var position = new google.maps.LatLng(latLong.latitude, latLong.longitude);

// Initialize the map widget.
var map = new google.maps.Map($map[0], {
zoom: 14,
center: position,
mapTypeId: google.maps.MapTypeId.ROADMAP,
maxZoom: 14
});

// Place a marker on it, representing the DBGeometry object's position.
var marker = new google.maps.Marker({
position: position,
map: map
});

var updateMarker = function(updateEvent) {
marker.setPosition(updateEvent.latLng);

// This new location might be outside the current viewport, especially
// if it was manually entered. Pan to center on the new marker location.
map.panTo(updateEvent.latLng);

// Black magic, courtesy of Hanselman's original version.
$input.val(marker.getPosition().toUrlValue(13));
};

// If the input came from an EditorFor, initialize editing-related events.
if ($input.hasClass('editor-for-dbgeography')) {
google.maps.event.addListener(map, 'click', updateMarker);

// Attempt to react to user edits in the input field.
$input.on('change', function() {
var latLong = parseLatLong(this.value);

latLong = new google.maps.LatLng(latLong.latitude, latLong.longitude);

updateMarker({ latLng: latLong });
});
}
};

var parseLatLong = function(value) {
if (!value) { return undefined; }

var latLong = value.match(/-?\d+\.\d+/g);

return {
latitude: latLong[0],
longitude: latLong[1]
};
};

// Find all DBGeography inputs and initialize maps for them.
$('.editor-for-dbgeography, .display-for-dbgeography').each(initialize);
})();

The next step will be to bundle this all up in a NuGet. Any ideas on a clear name? AspNet.Net.Mvc.SpatialTemplates?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.