
mikecat
迈克老猫
-
个人空间
- 组别:管理员
- 性别:
- 来自:中国-石家庄
- 积分:2461
- 帖子:2122
- 注册:
2007-12-31
|
Tip/Trick: Url Rewriting with ASP.NET
IIS7 中的URLREWRITER失效的问题也可以解决了 Tip/Trick: Url Rewriting with ASP.NET People often ask me for guidance on howthey can dynamically "re-write" URLs and/or have the ability to publishcleaner URL end-points within their ASP.NET web applications. Thisblog post summarizes a few approaches you can take to cleanly map orrewrite URLs with ASP.NET, and have the option to structure the URLs ofyour application however you want. Why does URL mapping and rewriting matter? The most common scenarios where developers want greater flexibility with URLs are: 1)Handling cases where you want to restructure the pages within your webapplication, and you want to ensure that people who have bookmarked oldURLs don't break when you move pages around. Url-rewriting enables youto transparently forward requests to the new page location withoutbreaking browsers. 2)Improving the search relevancy of pages on your site with searchengines like Google, Yahoo and Live. Specifically, URL Rewriting canoften make it easier to embed common keywords into the URLs of thepages on your sites, which can often increase the chance of someoneclicking your link. Moving from using querystring arguments to insteaduse fully qualified URL's can also in some cases increase your priorityin search engine results. Using techniques that force referring linksto use the same case and URL entrypoint (for example:weblogs.asp.net/scottgu instead ofweblogs.asp.net/scottgu/default.aspx) can also avoid diluting yourpagerank across multiple URLs, and increase your search results. Ina world where search engines increasingly drive traffic to sites,extracting any little improvement in your page ranking can yield verygood ROI to your business. Increasingly this is driving developers touse URL-Rewriting and other SEO (search engine optimization) techniquesto optimize sites (note that SEO is a fast moving space, and therecommendations for increasing your search relevancy evolve monthly). For a list of some good search engine optimization suggestions, I'drecommend reading the SSW Rules to Better Google Rankings, as well as MarketPosition's article on how URLs can affect top search engine ranking. Sample URL Rewriting Scenario Forthe purpose of this blog post, I'm going to assume we are building aset of e-commerce catalog pages within an application, and that theproducts are organized by categories (for example: books, videos, CDs,DVDs, etc). Let's assumethat we initially have a page called "Products.aspx" that takes acategory name as a querystring argument, and filters the productsaccordingly. The corresponding URLs to this Products.aspx page looklike this: http://www.store.com/products.aspx?category=books http://www.store.com/products.aspx?category=DVDs http://www.store.com/products.aspx?category=CDs Ratherthan use a querystring to expose each category, we want to modify theapplication so that each product category looks like a unique URL to asearch engine, and has the category keyword embedded in the actual URL(and not as a querystring argument). We'll spend the rest of this blogpost going over 4 different approaches that we could take to achievethis. Approach 1: Use Request.PathInfo Parameters Instead of QueryStrings Thefirst approach I'm going to demonstrate doesn't use Url-Rewriting atall, and instead uses a little-known feature of ASP.NET -the Request.PathInfo property. To help explain the usefulness of thisproperty, consider the below URL scenario for our e-commerce store: http://www.store.com/products.aspx/Books http://www.store.com/products.aspx/DVDs http://www.store.com/products.aspx/CDs Onething you'll notice with the above URLs is that they no longer haveQuerystring values - instead the category parameter value is appendedon to the URL as a trailing /param value after the Products.aspx pagehandler name. An automated search engine crawler will then interpretthese URLs as three different URLs, and not as one URL with threedifferent input values (search engines ignore the filename extensionand just treat it as another character within the URL). Youmight wonder how you handle this appended parameter scenario withinASP.NET. The good news is that it is pretty simple. Simply use theRequest.PathInfo property, which will return the content immediatelyfollowing the products.aspx portion of the URL. So for the above URLs,Request.PathInfo would return "/Books", "/DVDs", and "/CDs" (in caseyou are wondering, the Request.Path property would return"/products.aspx"). Youcould then easily write a function to retrieve the category like so(the below function strips out the leading slash and returning just"Books", "DVDs" or "CDs"): Function GetCategory() As String
If (Request.PathInfo.Length = 0) Then Return "" Else Return Request.PathInfo.Substring(1) End If
End Function Sample Download: A sample application that I've built that shows using this technique can be downloaded here. What is nice about this sample and technique is that no serverconfiguration changes are required in order to deploy an ASP.NETapplication using this approach. It will also work fine in a sharedhosting environment. Approach 2: Using an HttpModule to Perform URL Rewriting Analternative approach to the above Request.PathInfo technique would beto take advantage of the HttpContext.RewritePath() method that ASP.NETprovides. This method allows a developer to dynamically rewrite theprocessing path of an incoming URL, and for ASP.NET to then continueexecuting the request using the newly re-written path. For example, we could choose to expose the following URLs to the public: http://www.store.com/products/Books.aspx http://www.store.com/products/DVDs.aspx http://www.store.com/products/CDs.aspx Thislooks to the outside world like there are three separate pages on thesite (and will look great to a search crawler). By using theHttpContext.RewritePath() method we can dynamically re-write theincoming URLs when they first reach the server to instead call a singleProducts.aspx page that takes the category name as a Querystring orPathInfo parameter instead. For example, we could use an anApplication_BeginRequest event in Global.asax like so to do this: void Application_BeginRequest(object sender, EventArgs e) {
string fullOrigionalpath = Request.Url.ToString(); if (fullOrigionalpath.Contains("/Products/Books.aspx")) { Context.RewritePath("/Products.aspx?Category=Books"); } else if (fullOrigionalpath.Contains("/Products/DVDs.aspx")) { Context.RewritePath("/Products.aspx?Category=DVDs"); } } Thedownside of manually writing code like above is that it can be tediousand error prone. Rather than do it yourself, I'd recommend using oneof the already built HttpModules available on the web for free toperform this work for you. Here a few free ones that you can downloadand use today: Thesemodules allow you to declaratively express matching rules within yourapplication's web.config file. For example, to use the UrlRewriter.Net modulewithin your application's web.config file to map the above URLs to asingle Products.aspx page, we could simply add this web.config file toour application (no code is required): <?xml version="1.0"?>
<configuration>
<configSections> <section name="rewriter" requirePermission="false" type="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" /> </configSections> <system.web> <httpModules> <add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter"/> </httpModules> </system.web>
<rewriter> <rewrite url="~/products/books.aspx" to="~/products.aspx?category=books" /> <rewrite url="~/products/CDs.aspx" to="~/products.aspx?category=CDs" /> <rewrite url="~/products/DVDs.aspx" to="~/products.aspx?category=DVDs" /> </rewriter> </configuration> TheHttpModule URL rewriters above also add support for regular expressionand URL pattern matching (to avoid you having to hard-code every URL inyour web.config file). So instead of hard-coding the category list,you could re-write the rules like below to dynamically pull thecategory from the URL for any "/products/[category].aspx" combination: <rewriter> <rewrite url="~/products/(.+).aspx" to="~/products.aspx?category=$1" /> </rewriter> This makes your code much cleaner and super extensible. Sample Download: A sample application that I've built that shows using this technique with the UrlRewriter.Net module can be downloaded here. Whatis nice about this sample and technique is that no server configurationchanges are required in order to deploy an ASP.NET application usingthis approach. It will also work fine in a medium trust shared hostingenvironment (just ftp/xcopy to the remote server and you are good to go- no installation required). Approach 3: Using an HttpModule to Perform Extension-Less URL Rewriting with IIS7 Theabove HttpModule approach works great for scenarios where the URL youare re-writing has a .aspx extension, or another file extension that isconfigured to be processed by ASP.NET. When you do this no customserver configuration is required - you can just copy your webapplication up to a remote server and it will work fine. Thereare times, though, when you want the URL to re-write to either have anon-ASP.NET file extension (for example: .jpg, .gif, or .htm) or nofile-extension at all. For example, we might want to expose these URLsas our public catalog pages (note they have no .aspx extension): http://www.store.com/products/Books http://www.store.com/products/DVDs http://www.store.com/products/CDs WithIIS5 and IIS6, processing the above URLs using ASP.NET is not supereasy. IIS 5/6 makes it hard to perform URL rewriting on these types ofURLs within ISAPI Extensions (which is how ASP.NET isimplemented). Instead you need to perform the rewriting earlier in theIIS request pipeline using an ISAPI Filter. I'll show how to-do thison IIS5/6 in the Approach 4 section below. The good news, though, is that IIS 7.0makes handling these types of scenarios super easy. You can now havean HttpModule execute anywhere within the IIS request pipeline - whichmeans you can use the URLRewriter moduleabove to process and rewrite extension-less URLs (or even URLs with a.asp, .php, or .jsp extension). Below is how you would configure thiswith IIS7: <?xml version="1.0" encoding="UTF-8"?>
<configuration>
<configSections> <section name="rewriter" requirePermission="false" type="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" /> </configSections> <system.web> <httpModules> <add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter" /> </httpModules> </system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"> <add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule" /> </modules>
<validation validateIntegratedModeConfiguration="false" />
</system.webServer>
<rewriter> <rewrite url="~/products/(.+)" to="~/products.aspx?category=$1" /> </rewriter> </configuration> Notethe "runAllManagedModulesForAllRequests" attribute that is set to trueon the <modules> section within <system.webServer>. Thiswill ensure that the UrlRewriter.Net module from Intelligencia, whichwas written before IIS7 shipped, will be called and have a chance tore-write all URL requests to the server (including for folders). Whatis really cool about the above web.config file is that: 1)It will work on any IIS 7.0 machine. You don't need an administratorto enable anything on the remote host. It will also work in mediumtrust shared hosting scenarios. 2)Because I've configured the UrlRewriter in both the <httpModules>and IIS7 <modules> section, I can use the same URL Rewritingrules for both the built-in VS web-server (aka Cassini) as well as onIIS7. Both fully support extension-less URLRewriting. This makestesting and development really easy. IIS7.0 server will ship later this year as part of Windows LonghornServer, and will support a go-live license with the Beta3 release in afew weeks. Because of all the new hosting features that have beenadded to IIS7, we expect hosters to start aggressively offering IIS7accounts relatively quickly - which means you should be able to startto take advantage of the above extension-less rewriting support soon. We'll also be shipping a Microsoft supported URL-Rewriting module inthe IIS7 RTM timeframe that will be available for free as well thatyou'll be able to use on IIS7, and which will provide nice support foradvanced re-writing scenarios for all content on your web-server. Sample Download:A sample application that I've built that shows using thisextension-less URL technique with IIS7 and the UrlRewriter.Net modulecan be downloaded here. Approach 4: ISAPIRewrite to enable Extension-less URL Rewriting for IIS5 and IIS6 Ifyou don't want to wait for IIS 7.0 in order to take advantage ofextension-less URL Rewriting, then your best best is to use an ISAPIFilter in order to re-write URLs. There are two ISAPI Filter solutionsthat I'm aware of that you might want to check-out: - Helicon Tech's ISAPI Rewrite:They provide an ISAPI Rewrite full product version for $99 (with 30 dayfree trial), as well as a ISAPI Rewrite lite edition that is free.
- Ionic's ISAPI Rewrite: This is a free download (both source and binary available)
Iactually don't have any first-hand experience using either of the abovesolutions - although I've heard good things about them. Scott Hanselman and Jeff Atwoodrecently both wrote up great blog posts about their experiences usingthem, and also provided some samples of how to configure the rules forthem. The rules for Helicon Tech's ISAPI Rewrite use the same syntaxas Apache's mod_rewrite. For example (taken from Jeff's blog post): [ISAPI_Rewrite] # fix missing slash on folders # note, this assumes we have no folders with periods! RewriteCond Host: (.*) RewriteRule ([^.?]+[^.?/]) http\://$1$2/ [RP]
# remove index pages from URLs RewriteRule (.*)/default.htm$ $1/ [I,RP] RewriteRule (.*)/default.aspx$ $1/ [I,RP] RewriteRule (.*)/index.htm$ $1/ [I,RP] RewriteRule (.*)/index.html$ $1/ [I,RP]
# force proper www. prefix on all requests RewriteCond %HTTP_HOST ^test\.com RewriteRule ^/(.*) http://www.test.com/$1 [RP]
# only allow whitelisted referers to hotlink images RewriteCond Referer: (?!http://(?:www\.good\.com|www\.better\.com)).+ RewriteRule .*\.(?:gif|jpg|jpeg|png) /images/block.jpg [I,O] Definitely check out Scott's post and Jeff's post to learn more about these ISAPI modules, and what you can do with them. Note:One downside to using an ISAPI filter is that shared hostingenvironments typically won't allow you to install this component, andso you'll need either a virtual dedicated hosting server or a dedicatedhosting server to use them. But, if you do have a hosting plan thatallows you to install the ISAPI, it will provide maximum flexibility onIIS5/6 - and tide you over until IIS7 ships. Handling ASP.NET PostBacks with URL Rewriting Onegotcha that people often run into when using ASP.NET and Url-Rewritinghas to-do with handling postback scenarios. Specifically, when youplace a <form runat="server"> control on a page, ASP.NET willautomatically by default output the "action" attribute of the markup topoint back to the page it is on. The problem when using URL-Rewritingis that the URL that the <form> control renders is not theoriginal URL of the request (for example: /products/books), but ratherthe re-written one (for example: /products.aspx?category=books). Thismeans that when you do a postback to the server, the URL will not beyour nice clean one. WithASP.NET 1.0 and 1.1, people often resorted to sub-classing the<form> control and created their own control that correctlyoutput the action to use. While this works, it ends up being a littlemessy - since it means you have to update all of your pages to use thisalternate form control, and it can sometimes have problems with theVisual Studio WYSIWYG designer. Thegood news is that with ASP.NET 2.0, there is a cleaner trick that youcan use to rewrite the "action" attribute on the <form> control. Specifically, you can take advantage of the new ASP.NET 2.0 Control Adapterextensibility architecture to customize the rendering of the<form> control, and override its "action" attribute value with avalue you provide. This doesn't require you to change any code in your .aspx pages. Instead, just add a .browser file to your /app_browsers folder thatregisters a Control Adapter class to use to output the new "action"attribute: You can see a sample implementation I created that shows how to implement a Form Control Adapter that works with URLRewriting in my sample here. It works for both the Request.PathInfo and UrlRewriter.Net moduleapproaches I used in Approach 1 and 2 above, and uses theRequest.RawUrl property to retrieve the original, un-rewritten, URL torender. With the ISAPIRewrite filter in Approach 4 you can retrievethe Request.ServerVariables["HTTP_X_REWRITE_URL"] value that the ISAPIfilter uses to save the original URL instead. MyFormRewriter class implementation above should work for both standardASP.NET and ASP.NET AJAX 1.0 pages (let me know if you run into anyissues). Handling CSS and Image Reference Correctly Onegotcha that people sometime run into when using Url Rewriting for thevery first time is that they find that their image and CSS stylesheetreferences sometimes seem to stop working. This is because they haverelative references to these files within their HTML pages - and whenyou start to re-write URLs within an application you need to be awarethat the browser will often be requesting files in different logicalhierarchy levels than what is really stored on the server. Forexample, if our /products.aspx page above had a relative reference to"logo.jpg" in the .aspx page, but was requested via the/products/books.aspx url, then the browser will send a request for/products/logo.jpg instead of /logo.jpg when it renders the page. Toreference this file correctly, make sure you root qualify CSS and Imagereferences ("/style.css" instead of "style.css"). For ASP.NETcontrols, you can also use the ~ syntax to reference files from theroot of the application (for example: <asp:imageimageurl="~/images/logo.jpg" runat="server"/> Hope this helps, Scott P.S. For more ASP.NET 2.0 Tips and Tricks, please check out my ASP.NET 2.0 Tips, Tricks and Tutorials Page. P.P.S. Special thanks to Scott Hanselman and Michael K. Campbell for testing my Form Control Adapter with their sites.
金鳞岂是池中物,一遇风云便化龙

|