ASP.NET URL雙向改寫的實現

iDotNetSpace發表於2009-10-22

我們在進行Web程式開發時,為了進行搜尋引擎優化(SEO),往往需要對web的訪問地址進行優化,如將http://localhost/Default.aspx?tab=performance修改為http://localhost/Default_performance.aspx,後一個地址能夠更好地被搜尋引擎搜尋到,從而達到了搜尋引擎優化的目的。微軟有一個開源類庫URLRewriter可以非常方便地實現url改寫,通過配置在web.config檔案中的對映表將使用者的請求重定向到具體的頁面中,我在“使用URLRewriter進行URL重寫失效”一文中詳細介紹瞭如何使用這個類庫,該類庫是通過asp.net的httpmodules或httphandles來執行的,但如果網站的宿主伺服器不支援asp.net httpmodules和httphandles,則該功能便失效了,這時我們可以通過global中的application_beginrequest事件來進行url重定向。本文在URLRewriter類庫的基礎上進行了改進,並給出了一個相對完整的解決方案。

  我們的改進是建立在URLRewriter的基礎之上的,所以URLRewriter原有的東西只要能用,我們都可以直接拿過來,當然,不好的東西要摒棄!

  URLRewriter的對映表是直接寫在web.config檔案中的,要讓web.config能識別對映表,必須在configSections節中新增section,告訴程式如何正確解析web.config中未被識別的內容,如原URLRewriter就需要在web.config中新增

。我覺得這個方式並不好,首先你需要單獨去編寫一個類庫來解析xml,並在web.config中進行配置,我們完全可以省去這一步。url的對映表可以單獨寫到一個xml檔案中,當程式執行時將xml載入到應用程式快取中,並設定一個快取檔案依賴項,這樣每當管理員修改完對映表後就可以馬上生效。

  另外我希望支援url的雙向改寫,即上面提到的兩個url,當使用者輸入第二個url時程式會將請求傳送到第一個url,但瀏覽器中顯示的url不變;當使用者輸入第一個url時,自動跳轉到第二個url,此時瀏覽器中顯示的是第二個url,但是請求仍然是第一個url。聽起來是不是有點繞啊?沒關係,其實也很簡單,基本的需求就是說客戶原來網站中的很多頁面在訪問時都帶了很多引數,做url改寫時都換成新的url了,這時舊的url仍然可以用,客戶想的就是當輸入原來舊的url時能自動跳轉到新的url。這個就是url的雙向改寫!這兩種方式可以分別通過Context.RewritePath()和Context.Response.Redirect()方法來實現,下面我們來看具體的實現。

  首先是對映表的實現。我在URLRewriter原有對映表的基礎上做了一點改動,就是給ReWriterRule新增了一個IsDirect屬性,該屬性可選,預設值為False,當值為真時如果使用者請求的url匹配則會進行跳轉,否則只是進行請求對映。 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--&gtxml version="1.0"?>
<ReWriterConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  
<Rules>
    
<ReWriterRule>
      
<LookFor>~/Default_(\w+)\.aspxLookFor>
      
<SendTo>~/Default.aspx?tab=$1SendTo>
    
ReWriterRule>
    
<ReWriterRule IsDirect="true">
      
<LookFor>~/Default\.aspx\?tab=(\w+)LookFor>
      
<SendTo>~/Default_$1.aspxSendTo>
    
ReWriterRule>
  
Rules>
ReWriterConfig>

  該對映表支援正規表示式,下面是對應的實體類,用來進行反序列化。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--&gtusing System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace URLRewriterTest
{
    [Serializable]
    
public class ReWriterConfig
    {
        
public ReWriterRule[] Rules;
    }

    [Serializable]
    
public class ReWriterRule
    {
        
private bool _isRedirect = false;

        [System.Xml.Serialization.XmlAttribute(
"IsDirect")]
        
public bool IsRedirect
        {
            
get { return _isRedirect; }
            
set { this._isRedirect = value; }
        }
        
public string LookFor { getset; }
        
public string SendTo { getset; }
    }
}

  下面這個類用來獲取對映表,當程式第一次執行時會將對映表反序列化的結果放到全域性應用程式快取中。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--&gtusing System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Serialization;
using System.IO;
using System.Web.Caching;

namespace URLRewriterTest
{
    
public class ReWriterConfiguration
    {
        
public static ReWriterConfig GetConfig(string filename)
        {
            
if (HttpContext.Current.Cache["RewriterConfig"== null)
            {
                ReWriterConfig config 
= null;
                
// Create an instance of the XmlSerializer specifying type and namespace.   
                XmlSerializer serializer = new XmlSerializer(typeof(ReWriterConfig));

                
// A FileStream is needed to read the XML document.   
                using (Stream reader = new FileStream(filename, FileMode.Open))
                {
                    
// Declare an object variable of the type to be deserialized.   
                    config = (ReWriterConfig)serializer.Deserialize(reader);
                }
                HttpContext.Current.Cache.Insert(
"RewriterConfig", config, new CacheDependency(filename));
            }
            
return (ReWriterConfig)HttpContext.Current.Cache["RewriterConfig"];     
        }
    }
}

  我們仍然需要原URLRewriter類庫中的ReWriterUtils類中的方法,不過對其中RewriteUrl方法進行了一點小的改動,增加了一個isRedirect引數,用來決定是執行Context.RewritePath()方法還是Context.Response.Redirect()方法,下面是原始碼。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--&gtusing System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace URLRewriterTest
{
    
public class ReWriterUtils
    {
        
/// 
        
/// Rewrite's a URL using HttpContext.RewriteUrl().
        
/// 
        
/// The HttpContext object to rewrite the URL to.
        
/// Redirect or rewrite path.
        
/// The URL to rewrite to.
        public static void RewriteUrl(HttpContext context, string sendToUrl, bool isRedirect)
        {
            
string x, y;
            RewriteUrl(context, sendToUrl, isRedirect, 
out x, out y);
        }

        
/// 
        
/// Rewrite's a URL using HttpContext.RewriteUrl().
        
/// 
        
/// The HttpContext object to rewrite the URL to.
        
/// The URL to rewrite to.
        
/// Redirect or rewrite path.
        
/// Returns the value of sendToUrl stripped of the querystring.
        
/// Returns the physical file path to the requested page.
        public static void RewriteUrl(HttpContext context, string sendToUrl, bool isRedirect, out string sendToUrlLessQString, out string filePath)
        {
            
// see if we need to add any extra querystring information
            if (context.Request.QueryString.Count > 0)
            {
                
if (sendToUrl.IndexOf('?'!= -1)
                    sendToUrl 
+= "&" + context.Request.QueryString.ToString();
                
else
                    sendToUrl 
+= "?" + context.Request.QueryString.ToString();
            }

            
// first strip the querystring, if any
            string queryString = String.Empty;
            sendToUrlLessQString 
= sendToUrl;
            
if (sendToUrl.IndexOf('?'> 0)
            {
                sendToUrlLessQString 
= sendToUrl.Substring(0, sendToUrl.IndexOf('?'));
                queryString 
= sendToUrl.Substring(sendToUrl.IndexOf('?'+ 1);
            }

            
// grab the file's physical path
            filePath = string.Empty;
            filePath 
= context.Server.MapPath(sendToUrlLessQString);

            
if (isRedirect)
            {
                
// redirect the path
                context.Response.Redirect("~/" + sendToUrlLessQString);
            }
            
else
            {
                
// rewrite the path
                context.RewritePath("~/" + sendToUrlLessQString, String.Empty, queryString);
            }

            
// NOTE!  The above RewritePath() overload is only supported in the .NET Framework 1.1
            
// If you are using .NET Framework 1.0, use the below form instead:
            
// context.RewritePath(sendToUrl);
        }

        
/// 
        
/// Converts a URL into one that is usable on the requesting client.
        
/// 
        
/// Converts ~ to the requesting application path.  Mimics the behavior of the 
        
/// Control.ResolveUrl() method, which is often used by control developers.
        
/// The application path.
        
/// The URL, which might contain ~.
        
/// A resolved URL.  If the input parameter url contains ~, it is replaced with the
        
/// value of the appPath parameter.
        public static string ResolveUrl(string appPath, string url)
        {
            
if (url.Length == 0 || url[0!= '~')
                
return url;        // there is no ~ in the first character position, just return the url
            else
            {
                
if (url.Length == 1)
                    
return appPath;  // there is just the ~ in the URL, return the appPath
                if (url[1== '/' || url[1== '\\')
                {
                    
// url looks like ~/ or ~\
                    if (appPath.Length > 1)
                        
return appPath + "/" + url.Substring(2);
                    
else
                        
return "/" + url.Substring(2);
                }
                
else
                {
                    
// url looks like ~something
                    if (appPath.Length > 1)
                        
return appPath + "/" + url.Substring(1);
                    
else
                        
return appPath + url.Substring(1);
                }
            }
        }
    }
}

  最後就是編寫Global中的Application_BeginRequest事件了,在原有URLRewriter的基礎上稍作修改。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--&gtprotected void Application_BeginRequest(object sender, EventArgs e)
{
    
string requestedPath = Request.RawUrl.ToString();

    
// get the configuration rules
    string filename = Context.Server.MapPath("."+ "//ReWriterRules.xml";
    ReWriterConfig rules 
= ReWriterConfiguration.GetConfig(filename);

    
// iterate through each rule
    for (int i = 0; i < rules.Rules.Length; i++)
    {
        
// get the pattern to look for, and Resolve the Url (convert ~ into the appropriate directory)
        string lookFor = "^" + ReWriterUtils.ResolveUrl(Context.Request.ApplicationPath, rules.Rules[i].LookFor) + "$";

        
// Create a regex (note that IgnoreCase is set)
        Regex re = new Regex(lookFor, RegexOptions.IgnoreCase);

        
// See if a match is found
        if (re.IsMatch(requestedPath))
        {
            
// match found - do any replacement needed
            string sendToUrl = ReWriterUtils.ResolveUrl(Context.Request.ApplicationPath, re.Replace(requestedPath, rules.Rules[i].SendTo));

            
// Rewrite or redirect the URL
            ReWriterUtils.RewriteUrl(Context, sendToUrl, rules.Rules[i].IsRedirect);
            
break;        // exit the for loop
        }
    }
}

  好了,大功告成!使用上面的對映表,當你輸入http://localhost/Default_performance.aspx時訪問正常,事實上Default_後面可以新增任何字元,這些字元都將作為Default.aspx頁面tab引數的值。同時,當你輸入http://localhost/Default.aspx?tab=performance時頁面會自動跳轉到前面一個url,tab引數的值將被作為url的一部分。

URLRewriterTest.rar下載

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-617171/,如需轉載,請註明出處,否則將追究法律責任。

相關文章