Scott Hanselman

Dick and Jane: Jedi Masters

September 15, '12 Comments [27] Posted in Musings
Sponsored By

Dick and Jane: Jedi MastersMy 6 year old started First Grade this last week. We've been reading to him and his brother, without fail, every night for their entire lives. It's been awesome to start seeing their little brains firing as they are starting to read on their own.

My wife picked up a copy of the classic 1950s Dick and Jane series since it's what her parents used and how she learned to read. We only made it a few pages in when both boys declared "this is boring!"

"Why is it boring?"

"There are no light sabers! Dick and Jane don't DOOOOOO anything."

So before dinner this afternoon the 6 year old and the 4 year old - the same little boys for whom I wrote The Nerd Parent's Guide: When and how to introduce your kids to Star Wars - and I proceeded to sit down and create Dick and Jane: Jedi Master.

The boys not only wrote all the text but also made all the decisions about the pictures. You can blame me for the poor Photoshop work but the kids get the credit for everything else. We printed it on glossy inkjet paper and stapled it and the boys are THRILLED.

Have fun. PDF at the bottom, thumbnails here.

NOTE: This is a parody, it's not for sale. Don't sue me. Just playin'.

 

Jump, Sally. Jump up. Flip, Sally. Flip up!Force push, Sally! Force push Tim. Fly, Tim, fly!

Use the Force, Dick. Use the Force, JaneThere is no try, Jane. There is only do. Do, Jane, Do.

We had so much fun doing this. I hope my kids end up feeling empowered to create and remix.

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

FREE: Visual Studio Express 2012 for Windows Desktop

September 13, '12 Comments [32] Posted in Open Source | VS2012
Sponsored By

Visual Studio Express 2012 for Windows DesktopWhen the Visual Studio 2012 free versions were originally announced the first thing I noticed was that they had switched from a language-specific model (C# Express, VB Express, etc) to a target-specific model (Windows 8, Web). However, I was very surprised (and disappointed) that there was no free way to make Console Apps, or Windows Forms apps, or anything for the Windows Desktop. I wasn't the only one who thought this was a problem. Shortly thereafter (with a lot of people pushing) Soma announced there would be a "Windows Desktop" Express version for free. He said:

...we heard from our community that developers want to have for Windows desktop development the same great experience and access to the latest Visual Studio 2012 features at the Express level.

Today, I’m happy to announce that we will add Visual Studio Express 2012 for Windows Desktop to the Visual Studio 2012 family. This will bring to the Visual Studio Express family significant new capabilities that we’ve made available in Visual Studio 2012 for building great desktop applications.

I'm glad folks realized that no-cost desktop software development is important. Open Source projects need free tools like the Express SKUs. Even better that the the Express Desktop SKU gets the new 2012 features as well.

Today Visual Studio has made Visual Studio Express 2012 for Windows Desktop available and you can go download it now free. The best part is that this one SKU supports C++, C#, and Visual Basic together. With this one free version you can make WinForms, WPF, Console or Class Libraries with any or all of Visual Basic, C#, as well as Win32 projects, class libraries, and CLR apps using C++. You can also, of course, combine projects over multiple languages into a single solution. You can target both .NET 4.0 and 4.5.

NOTE: You might wonder, what about a free F#? Why isn't F# included? We've got a free download to add F# support to the free Visual Studio Express 2012 for Web!

Related Links

While Express SKUs don't allow arbitrary add-ins (you need Pro for that) the free SKU does include Unit Testing, Code Analysis, as well as the NuGet package manager. It's a bit of a nice coup for my little group that NuGet is now included in ALL Visual Studio 2012 SKUs, even Express ones. Package management is finally happening in .NET.

In the screenshot below I've added a C++ Console app, a Window Forms C# app and a C# Console to a single solution in VS2012 using Express for Windows Desktop.

image

Just to illustrate one of the reasons I think a free "Desktop" SKU is so important, I wanted to share a cool open source project I found recently called Topshelf. It's a library for making Windows Services easier to write using .NET. It's up on GitHub under an Apache license. The easiest way to get Topshelf is with NuGet with simply "install-package Topshelf". Topshelf has some impressive documentation as well, especially for an open source project!

Here's a simple Windows Service with a basic heartbeat timer using Topshelf:

public class TownCrier
{
readonly Timer _timer;
public TownCrier()
{
_timer = new Timer(1000) {AutoReset = true};
_timer.Elapsed += (sender, eventArgs) => Console.WriteLine("It is {0} an all is well", DateTime.Now);
}
public void Start() { _timer.Start(); }
public void Stop() { _timer.Stop(); }
}

public class Program
{
public static void Main()
{
HostFactory.Run(x => //1
{
x.Service<TownCrier>(s => //2
{
s.ConstructUsing(name=> new TownCrier()); //3
s.WhenStarted(tc => tc.Start()); //4
s.WhenStopped(tc => tc.Stop()); //5
});
x.RunAsLocalSystem(); //6

x.SetDescription("Sample Topshelf Host"); //7
x.SetDisplayName("Stuff"); //8
x.SetServiceName("stuff"); //9
}); //10
}
}

Topshelf even has nice Log4net and NLog integration. Anyway, this is just the kind of powerful, useful, and interesting open source library that could be helped by a free Express SKU for Desktop. I'm not involved directly (yet ;) ) in making decisions this high up, but I (and many, many others) inside and out continue to advocate for balance. In this case I'm very glad that the decision was made to ship this SKU and I hope you all find it useful whether you work in open source or in education.

There's more details over at the Visual Studio blog and the team is watching the comments.


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

ASP.NET Web Forms DynamicData FieldTemplates for DbGeography Spatial Types (plus Model Binders and Friendly URLs)

September 11, '12 Comments [16] Posted in ASP.NET | ASP.NET MVC | Javascript | Open Source | VS2012
Sponsored By

Did you enjoy my recent post on ASP.NET MVC DisplayTemplate and EditorTemplates for Entity Framework DbGeography Spatial Types and it's associated GIANT URL?

Modeling Binding and EditorTemplates...for ASP.NET Web Forms?

DisplayTemplates and EditorTemplates are a great way in ASP.NET MVC to keep things DRY (Don't Repeat Yourself.) That means I can just write EditorFor() calls like this:

@Html.EditorFor(model => model.Location)   

See how I didn't say "TextBoxFor" or "MapFor"? You say EditorFor and it makes the right choice. If the type is called DbGeography then it will look for a Editor Template at ~/Shared/EditorTemplates/DbGeography.cshtml. It's a nice feature of ASP.NET MVC that folks don't use enough.

Now, remember ASP.NET Dynamic Data? You might think that idea "died" or was "retired" when actually the concepts are built into ASP.NET itself. That means that ASP.NET Web Forms developers can have "Editor Templates" as well. They are called FieldTemplates in ASP.NET Web Forms parlance, and making sure we have feature parity like this is part of the larger move towards One ASP.NET. We'll take the ASP.NET MVC sample using DbGeography and make it work for Web Forms in a very similar way.

<%--  Let's not do this: <asp:TextBox ID="location" runat="server" Text="<%# BindItem.Location %>"></asp:TextBox>--%>
<asp:DynamicControl runat="server" ID="Location" DataField="Location" Mode="Insert" />

When we do a POST, ModelBinders handle the boring work of digging types out of the HTTP POST. These work in not just MVC but also Web Forms and Web API now. Rather that Request["this"] and Request["that"] a model binder can be registered to do the work of populating a type from the Request. Even better, we can populate objects not only from the POST but also anywhere that provides values including Cookies, QueryStrings and more.

Let's walk through this one by one and at the end we'll have a complete sample that has:

  • ASP.NET Web Forms and AS.NET MVC in one application, living together.
  • FriendlyURLs for Web Forms and Routing for MVC
  • 90% Shared Model Binding Code between Web Forms and MVC
    • Spatial types custom DbGeography Model Binder
  • Simple CRUD (Create, Read, Update, Delete) to the same database in both Web Forms and MVC using the same model.

The goal is to continue to move towards a cleaner, more unified platform...One ASP.NET. This is an example. Thanks to Pranav for his help!

Related Links

DbGeography FieldTemplates for ASP.NET Web Forms

Here's a FormView in ASP.NET Web Forms. Notice the ItemType is set, rather than using Eval(). We're also using SelectMethod rather than an ObjectDataSource.

<asp:FormView runat="server" ItemType="TouristAttraction" ID="attractionDetails"
SelectMethod="attractionDetails_GetItem">
<ItemTemplate>
Name:
<asp:DynamicControl DataField="name" runat="server" ClientIDMode="Static" /><br />
Location:
<asp:DynamicControl DataField="location" runat="server" /><br />
<a id="A1" href='<%# FriendlyUrl.Href("~/WebForms/Edit", Item.TouristAttractionId ) %>'>Edit</a> |
<a id="A1" href='<%# FriendlyUrl.Href("~/WebForms") %>'>Back To List</a>
</ItemTemplate>
</asp:FormView>

The FormView doesn't specify what a location or name should look like, but since we know the model...

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

...it will dynamically figure out the controls (hence, DynamicControl) and find FieldTemplates in the DynamicData folder:

Dynamic Data Field Templates called DbGeography.ascx

Those templates are simple. Here's the Edit example.

<%@ Control Language="C#" CodeBehind="DbGeography_Edit.ascx.cs" Inherits="MvcApplication2.DynamicData.FieldTemplates.DbGeography_EditField" %>

<asp:TextBox runat="server" ID="location" CssClass="editor-for-dbgeography" />

You might say, hang on, this is just a text box! I thought we weren't using TextBoxes? The point is that we have control in a single place over what a DbGeography - or any type - looks like when it's being edited, or when it's read-only. In this example, I AM using a Textbox BUT I've added a CssClass that I will use to create a Google Map using obtrusive JavaScript thanks to my recent refactoring from Dave Ward. If I wanted I could change this FieldTemplate to be a 3rd party control or whatever custom markup I want.

If you have an object called Foo, then make a Foo.ascx and Foo_Edit.ascx and put them in ~/DynamicData/FieldTemplates and they'll be used by DynamicControl.

Model Binding for ASP.NET Web Forms

I did 13 short videos recently on new features in ASP.NET 4.5 including one on Model Binding for ASP.NET Web Forms. Here's the Model Binding one.

Let me first say that Model Binding between ASP.NET Web Forms, MVC and Web API isn't unified. I want more unification and I am continuing to push the One ASP.NET vision internally and many people share that goal.

In the previous blog post on ASP.NET MVC, Model Binding and DbGeography I already had a good Model Binder that I want to reuse between MVC and Web Forms. I can do it, although the ModelBinderProvider stuff isn't very well unified.

First, here's the unified Model Binder for DbGeography that is used for both MVC and Web Forms. We implement two interfaces and use one implementation. Not ideal, but it works.

public class DbGeographyModelBinder : IMvcModelBinder, IWebFormsModelBinder
{
public object BindModel(ControllerContext controllerContext, MvcModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return BindModelImpl(valueProviderResult != null ? valueProviderResult.AttemptedValue : null);
}

public bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, WebFormsModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.Model = BindModelImpl(valueProviderResult != null ? valueProviderResult.AttemptedValue : null);
return bindingContext.Model != null;
}

private DbGeography BindModelImpl(string value)
{
if (value == null)
{
return (DbGeography)null;
}
string[] latLongStr = value.Split(',');
// TODO: More error handling here, what if there is more than 2 pieces or less than 2?
// Are we supposed to populate ModelState with errors here if we can't conver the value to a point?
string point = string.Format("POINT ({0} {1})", latLongStr[1], latLongStr[0]);
//4326 format puts LONGITUDE first then LATITUDE
DbGeography result = DbGeography.FromText(point, 4326);
return result;
}
}

Part of the "trick" are these namespace aliases:

using IMvcModelBinder = System.Web.Mvc.IModelBinder;
using IWebFormsModelBinder = System.Web.ModelBinding.IModelBinder;

using MvcModelBindingContext = System.Web.Mvc.ModelBindingContext;
using WebFormsModelBindingContext = System.Web.ModelBinding.ModelBindingContext;

I'd love to see unification of the Model Binding stack at some point.

In Web Forms we could register a single model binder for a single type like this:

ModelBinderProviders.Providers.RegisterBinderForType(typeof(DbGeography), new DbGeographyModelBinder());

Or collect a collection of like types into a Provider of Binders and add them like this:

ModelBinderProviders.Providers.Insert(0,new EFModelBinderProviderWebForms());

I only have one Model Binder but here's how I'd register a provider for both Web Forms and MVC and have them use my same binder if I wanted:

public class EFModelBinderProviderMvc : System.Web.Mvc.IModelBinderProvider
{
public IMvcModelBinder GetBinder(Type modelType)
{
if (modelType == typeof(DbGeography))
return new DbGeographyModelBinder();
return null;
}
}

public class EFModelBinderProviderWebForms : System.Web.ModelBinding.ModelBinderProvider
{
public override IWebFormsModelBinder GetBinder(ModelBindingExecutionContext modelBindingExecutionContext, WebFormsModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(DbGeography))
return new DbGeographyModelBinder();
return null;
}
}

Now, to finish the CRUD.

FriendlyUrls

The team released an alpha build of ASP.NET FriendlyUrls that includes cleaner URLs, easier Routing, and Mobile Views for ASP.NET Web Forms yesterday. I wanted to use them in this project as well, and have WebForms and MVC together in the same app.

I could certainly register a bunch of Web Forms routes manually like this:

RouteTable.Routes.MapPageRoute("Attraction", "WF/Attraction", "~/WebForms/Default.aspx");
RouteTable.Routes.MapPageRoute("AttractionNew", "WF/Attraction/Create", "~/WebForms/Create.aspx");
RouteTable.Routes.MapPageRoute("AttractionEdit", "WF/Attraction/Edit/{id}", "~/WebForms/Edit.aspx");
...and more...

Or I could enable FriendlyUrls after my MVC routes like this:

//MVC will be for MVC, while WebForms is under /WebForms/ using Friendly URLs
routes.MapRoute(
name: "Default",
url: "MVC/{controller}/{action}/{id}",
defaults: new { controller = "Attraction", action = "Index", id = UrlParameter.Optional }
);

routes.EnableFriendlyUrls();

Here's what my site looks like now. Notice the /MVC and /WebForms URLs. I can call /WebForms/Create or /MVC/Create..

MVC and Web Forms together in one app

I generate the FriendlyUrls like this in Web Forms:

<a href='<%# FriendlyUrl.Href("~/WebForms/Edit", Item.TouristAttractionId ) %>'>Edit</a>
| <a href='<%# FriendlyUrl.Href("~/WebForms/Delete", Item.TouristAttractionId ) %>'>Delete</a>
| <a href='<%# FriendlyUrl.Href("~/WebForms/Details", Item.TouristAttractionId ) %>'>Details</a>

and like this in MVC

@Html.RouteLink("Edit", "Default",new {Controller="Attraction", action="Edit",id=item.TouristAttractionId}) |
@Html.RouteLink("Details", "Default",new {Controller="Attraction", action="Details",id=item.TouristAttractionId})|
@Html.RouteLink("Delete", "Default",new {Controller="Attraction", action="Delete",id=item.TouristAttractionId})

If this was a larger app I would write better helper methods for both, perhaps using an open source helper library.

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 TouristAttraction attractionsForm_GetItem([FriendlyUrlSegments]int? id)
{
TouristAttraction touristattraction = db.TouristAttractions.Find(id);
return touristattraction;
}

Both sections talk to the same database and use the same shared Google Maps JavaScript.

MVC and Web Forms together in one app

I chose not to try to share the _Layout.cshtml and Site.Master, although I could share Razor views and Web Forms.

I've updated the my playground repository with a single project that contains all this. Hope it helps.

https://github.com/shanselman/ASP.NET-MVC-and-DbGeography

Related Links

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

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

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