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.
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.
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.
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:
1
|
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?
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
1
|
routes.EnableFriendlyUrls();
|
in RouteConfig (this default came down with the NuGet package) my whole WebForms app loses its .ASPX extensions and gets reasonable defaults.
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.
If I click "Click Me" the URL points to /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:
1
2
3
|
<% 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.
1
|
<%@ Import Namespace="Microsoft.AspNet.FriendlyUrls" %>
|
Better yet, I can generate Friendly URLs without string concatenation!
1
|
<
a
href
=
"<%: FriendlyUrl.Href("
~/Foo", "bar", 34) %>">Click me</
a
>
|
Nice, eh? OK, let's make it mobile.
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.
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.
What I want to support switching back and forth from Desktop to Mobile? Just add a ViewSwitcher control, also included.
1
|
<
friendlyUrls:ViewSwitcher
runat
=
"server"
/>
|
Now I re-render and I get a "switch to mobile" and switch to desktop.
Now I can go back and forth between views and request a desktop site even when on mobile.
So basic mobile is nice but I might want very specific mobile views for iPhone, iPad, Opera Mobile, etc.
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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.
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.
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!
---------------------------------华丽的分割线---------------------------------