Scott Hanselman

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

Pinning Useful and Obscure Stuff to the Windows 8 Start Menu

August 29, '12 Comments [83] Posted in Tools | Win8
Sponsored By

imageThere were a lot of good comments on my post Windows 8 productivity: Who moved my cheese? Oh, there it is. I've continued to explore ways to remove clicks from my life and shorten keystroke chains. A number of folks said they didn't like this thing or that thing because they felt something used to be 2 clicks and now it's 3 or it used to be 1 and now it's 8. It's good to be reminded that you can customize almost everything in Windows. You can make scripts and batch files to your heart's content, and most importantly, any shortcut to nearly anything can be pinned to both the Taskbar and the Start Menu. Here's some examples just to make the point.

Folders

Let's say that you want to quickly get to the folder where you download stuff. You can make the Downloads folder available on the Taskbar as well as the new Start menu.

Pin to a Taskbar icon

You can drag any folder to your Explorer Taskbar icon and pin it. Here the menu is visible when right-clicking on the Explorer icon. You can also pin (promote) links from the Frequent section as well.

Downloads is Pinned to the Explorer Taskbar

Pin to the Windows 8 Start Menu Screen

You can also take any icon or folder from the desktop and right-click to Pin to Start.

Pin to Start

Now my Downloads folder is on my Start and can be opened in one click/touch after pressing the Windows key.

Download is pinned to Start

Pinning other Shortcuts of all Kinds

Once you know you can pin stuff, you can make custom shortcuts and pin other stuff. Right click anywhere and make a new Shortcut. This is a generic pointer to whatever you like.

New Shortcut

You can change the properties of a shortcut like I have here. I've got a shortcut to shutdown with -t 0 added to the parameters so the system will shutdown immediately.

NOTE: You CAN make these shortcuts if it will make you happy but in the case of Shutdown, I just press the Power Button.

shutdown.exe -s -t 0

Now just right click the new shortcut and pin it to either the Taskbar or Start. If you're tidy, and I am sure you are, why not customize the icon?

Shutdown Icon customized

And now my new Shutdown icon looks like this. There you go, one-click shutdown. Happy? ;)

A magical Shutdown Icon on my Start Menu

Pinning Custom Actions like Sleep and Restart

You can of course make more complex shortcuts to do things like Sleep your computer with other commands.

Shutdown Computer

Shutdown.exe -s -t 00

Restart Computer

Shutdown.exe -r -t 00

Lock Workstation

Rundll32.exe User32.dll,LockWorkStation

Hibernate Computer

rundll32.exe PowrProf.dll,SetSuspendState

Sleep Computer

rundll32.exe powrprof.dll,SetSuspendState 0,1,0

NOTE: You CAN make these shortcuts if it will make you happy but in the case of Sleep, I just close the lid on my laptop.

Pin Internet Explorer 10 Desktop in the Start Menu

Now that you have figured out how to make crazy shortcuts then Pin them. Hopefully this is as empowering to you as it is to me.

As a Web Developer I have a bunch of Web Browsers pinned to both Start and the Taskbar. However, the Internet Explorer icon in the Start Menu goes to the full screen IE10 and sometimes I want the Desktop IE10.

Desktop IE10

If you refer to this old old old Windows XP Knowledge Base Article you'll see that they show a number of ways you can make Internet Explorer shortcuts. Of course, the easiest one is the one you should do. ;)

NOTE: You can also make a more "real" Internet Explorer link with this "%windir%\explorer.exe shell:::{871C5380-42A0-1069-A2EA-08002B30309D}" as a new Shortcut if you like.

Go to c:\program files\internet explorer and right click on iexplore.exe and pin it. Alternatively you can Copy, then Paste Shortcut, then Rename the shortcut. I named mine "Internet Explorer (Desktop)."

Get iexplore.exe and pin it, then rename

Now I've got two choices! Just like I like it.

Both IE10 metro and IE10 Desktop are both pinned to my Start Menu

Pinning Web Sites and Customizing IE10 Pinned Site Icons

You can pin Web Sites from within IE10 using the menu in the lower right corner. That's also the same context menu that includes Jump Lists. Jump Lists can be added easily with just <meta> keys in your HTML.

Context Menu in Full Screen IE10

If your favicon.ico has a 32x32 version that is the resolution of Icon that will be used for your pinned site. The background color for the icon comes from the main color of the icon.

My giant forehead is now pinned to your Start Menu

If you want a different icon or color, you can set those with Meta Tags as well. Hm, my giant head isn't big enough. I'll add a 144x144 PNG.

<meta name="msapplication-TileImage" content="/blog/images/hanselman-144.png" />

There we go. Much clearer. I can also control the background color with msapplication-TileColor if I want using a #rgb color.

You can have a high-res 144x144 PNG if you like

I've pinned all sorts of stuff. Apps, Links, Folders, Browsers, Custom Tasks, Libraries and more. No, I didn't actually pin my face. Oh, and that days app is "Save The Date."

All sorts of pinning

Have fun customizing!

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.