Microsoft.AspNet.FriendlyUrls 의 webform 에서 routing 사용법


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:


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


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:

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.


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.


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.

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!


Comments 0