程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> ASP.NET >> ASP.NET基礎 >> 一個完整的ASP.NET 2.0 URL重寫方案[翻譯]

一個完整的ASP.NET 2.0 URL重寫方案[翻譯]

編輯:ASP.NET基礎

這篇文章描述了一個完整的 ASP.NET 2.0 URL 重寫方案。這個方案使用正則表達式來定義重寫規則並解決通過虛擬 URLs 訪問頁面產生回發事件的一些可能的困難。

為什麼要重寫 URL ?

將 URL 重寫方法應用到你的 ASP.Net 應用程序的兩個主要原因是:可用性和可維護性。

可用性

誰都知道,相對於難於辨認的帶參數的長的查詢路徑,用戶更喜歡一些短的、簡潔的 URL。任何時候,一個容易記住和敲入的路徑比添加到收藏夾更有用。其次,當一個浏覽器的收藏夾不可用時,記住的地址總比在搜索引擎中輸入關鍵字進行搜索,然後再查找要強的多。比較下面的兩個地址:

 

(1)

http://www.somebloghost.com/Blogs/Posts.aspx?Year=2006&Month=12&Day=10

(2)

http://www. somebloghost.com/Blogs/2006/12/10/

 

第一個 URL 包含了查詢字符串;第二個URL包含的信息可以讓用戶清楚的看到他看的東西,它還可以使用戶更容易的修改地址欄的內容,如:http://www.somehost.com/Blogs/2006/12/.

可維護性

在很多WEB應用程序中,開發人員經常會將頁面從一個目錄移到另一個目錄,讓我們假設一開始有兩個可用頁面: http://www.somebloghost.com/Info/Copyright.aspx 和 http://www.somebloghost.com/Support/Contacts.aspx,但是後來開發者將 Copyright.aspx 和 Contacts.aspx 移到了 Help 目錄,用戶收藏起來地址就需要重新定位。這個問題雖然可以簡單的用 Response.Redirect(new location) 來解決,但是如果有成百上千的頁面呢?應用程序中就會包含大量的無效鏈接。

使用 URL 重寫,允許用戶只需修改配置文件,這種方法可以讓開發者將web應用程序邏輯結構與物理結構獨立開來。

ASP.NET 2.0 中的原有的URL 映射

ASP.NET 2.0 為 web 應用程序提供了一個開箱即用的映射靜態 URL 的解決方案。這個方案不用編寫代碼就可以在 web.config 中將舊的 URLs 映射到新的地址。 要使用 URL 映射,只需在 web.config 文件的 system.web 節中創建一個新的 urlMappings 節 ,並添加要映射的地址 (“ ~/ ”指向應用程序的根目錄):

<urlMappings enabled="true">

   <add url="~/Info/Copyright.aspx" mappedUrl="~/Help/Copyright.aspx" />

   <add url="~/Support/Contacts.aspx" mappedUrl="~/Help/Contacts.aspx" />

</urlMappings>

這樣,如果用戶輸入 http://www.somebloghost.com/Support/Contacts.aspx, 它將看到 http://www.somebloghost.com/Help/Contacts.aspx , 而他並不知道那個頁已經移除。

這個方案對於只有兩個頁面被移到其它位置的情況是足夠的。但它對有一打的需要重定位的頁或者需要創建一個整潔的URL來說,它是不合適的。另一個使用Asp.Net 的原有的URL映射技術的不太好的地方是:如果 Contacts.aspx 頁包含的元素在回發到服務器時(這是非常可能的), 用戶將會驚奇的發現地址 http://www.somebloghost.com/Support/Contacts.aspx 卻變成了 http://www.somebloghost.com/Help/Contacts.aspx

  這是因為ASP.NET 引擎用頁面的實際地址修改了表單formaction 屬性 ,所以表單就變成了下面的樣子:

<form name="formTest" method="post"
action="http://www.simple-talk.com/Help/Contacts.aspx" id="formTest">

</form>

這樣看來,URL 映射在ASP.NET 2.0 中幾乎是無用的。我們應當能夠使用一個映射規則來指定一系列相似的 URL。最好的解決方案就是使用正則表達式 ( Wikipedia 上可以查看概覽,and 在 .NET 下的實現可以查看 MSDN), 但由於 ASP.NET 2.0 映射不支持正則表達式,所以我們需要開發一個內建到 URL 映射的不同的方案- URL 重寫模塊。 最好的方法就是創建一個可重用的、簡單的配置模塊來實現,顯然我們應創建一個 HTTP 模塊 (關於 HTTP 模塊的詳細信息請查看 MSDN 雜志) 並在獨立的程序集中實現。要使這個程序集簡單易用,我們應實現這個重寫引擎的可配置性,即能夠在 web.config 中指定規則。

 

 

在開發過程中,我們應能使這個重寫模塊打開或關閉 (比如你有一個較難捕獲的bug,而它可能是由不正確的重寫模塊引起的)這樣在 web.config 中對重寫模塊的配置節進行打開或關閉就成為一個選擇。這樣,在 web.config 中,一個配置節的示例如下:

<rewriteModule>

  <rewriteOn>true</rewriteOn>

  <rewriteRules>

      <rule source="(\d+)/(\d+)/(\d+)/"
         destination="Posts.aspx?Year=$1&Month=$2&Day=$3"/>

      <rule source="(.*)/Default.aspx"
         destination="Default.aspx?Folder=$1"/>

  </rewriteRules>

</rewriteModule>

這樣,所有像: http://localhost/Web/2006/12/10/ 這樣的請示,將會在內部將會用帶參數的請求重定向到 Posts.aspx 。

請注意: web.config 是一個結構良好的 XML 文件, 它禁止在屬性值中使用 & 符號,所以在例子中,應當使用 & 代替。

要在配置文件中使用這個重寫模塊,還需要注冊節和指定處理模塊,像下面這樣增加一個configSections配置節:

 <configSections>

    <sectionGroup name="modulesSection">

      <section name="rewriteModule" type="RewriteModule.
RewriteModuleSectionHandler, RewriteModule"/>

    </sectionGroup>

  </configSections>

這樣你就可以在 configSections 節的後面這樣使用了:

<modulesSection>

    <rewriteModule>

      <rewriteOn>true</rewriteOn>

      <rewriteRules>

              <rule source="(\d+)/(\d+)/(\d+)/" destination="Post.aspx?Year=$1&Month=$2&Day=$3"/>

              <rule source="(.*)/Default.aspx" destination="Default.aspx?Folder=$1"/>

      </rewriteRules>

    </rewriteModule>

  </modulesSection>

另一個我們在開發重寫模塊過程中要做的就是還需要允許在虛擬路徑中傳遞參數,象這樣: http://www.somebloghost.com/2006/12/10/?Sort=Desc&SortBy=Date 。所以我們還需要有一個檢測通過虛擬 URL 傳遞參數的解決方案

 

 

接下來讓我們來創建類庫。首先,我們要引用 System.Web 程序集,這樣我們可以實現一些基於 web 特殊功能。如果要使我們的模塊能夠訪問 web.config,還需要引用 System.Configuration 程序集。

 

處理配置節

要能處理 web.config 中的配置,我們必需創建一個實現了 IConfigurationSectionHandler 接口的類 (詳情查看 MSDN )。如下:

using System;

using System.Collections.Generic;

using System.Text;

using System.Configuration;

using System.Web;

using System.Xml;

 

 

namespace RewriteModule

{

    public class RewriteModuleSectionHandler : IConfigurationSectionHandler

    {

 

        private XmlNode _XmlSection;

        private string _RewriteBase;

        private bool _RewriteOn;

 

        public XmlNode XmlSection

        {

            get { return _XmlSection; }

        }

 

        public string RewriteBase

        {

            get { return _RewriteBase; }

        }

 

        public bool RewriteOn

        {

            get { return _RewriteOn; }

        }

        public object Create(object parent,
                            object configContext,
                            System.Xml.XmlNode section)

        {

            // set base path for rewriting module to

            // application root

            _RewriteBase = HttpContext.Current.Request.ApplicationPath + "/";

 

            // process configuration section

            // from web.config

            try

            {

                _XmlSection = section;

                _RewriteOn = Convert.ToBoolean(
                            section.SelectSingleNode("rewriteOn").InnerText);

            }

            catch (Exception ex)

            {

                throw (new Exception("Error while processing RewriteModule
configuration section.", ex));

            }

            return this;

        }

    }

}

RewriteModuleSectionHandler 類將在 web.config 中的 XmlNode 通過調用 Create 方法初始化。XmlNode 類的 SelectSingleNode 方法被用來返回模塊的配置值。

使用重寫的 URL 的參數

在處理象 http://www. somebloghost.com/Blogs/gaidar/?Sort=Asc (這是一個帶參數的虛擬 URL ) 虛擬的 URLS 時,能夠清楚的辨別通過虛擬路徑傳遞的參數是非常重要的,如下:

<rule source="(.*)/Default.aspx" destination="Default.aspx?Folder=$1"/>,

你可能使用這樣的 URL:

http://www. somebloghost.com/gaidar/?Folder=Blogs

它的效果和下面的相似:

http://www. somebloghost.com/Blogs/gaidar/

要處理這個問題,我們需要對'虛擬路徑參數' 進行包裝。這可以是通過一個靜態的方法去訪問當前的參數集:

using System;

using System.Collections.Generic;

using System.Text;

using System.Collections.Specialized;

using System.Web;

 

namespace RewriteModule

{

 

    public class RewriteContext

    {

        // returns actual RewriteContext instance for

        // current request

        public static RewriteContext Current

        {

            get

            {

                // Look for RewriteContext instance in

                // current HttpContext. If there is no RewriteContextInfo

                // item then this means that rewrite module is turned off

                if(HttpContext.Current.Items.Contains("RewriteContextInfo"))

                    return (RewriteContext)
HttpContext.Current.Items["RewriteContextInfo"];

                else

                    return new RewriteContext();

            }

        }

 

        public RewriteContext()

        {

            _Params = new NameValueCollection();

            _InitialUrl = String.Empty;

        }

 

        public RewriteContext(NameValueCollection param, string url)

        {

            _InitialUrl = url;

            _Params = new NameValueCollection(param);

           

        }

 

        private NameValueCollection _Params;

 

        public NameValueCollection Params

        {

            get { return _Params; }

            set { _Params = value; }

        }

 

        private string _InitialUrl;

 

        public string InitialUrl

        {

            get { return _InitialUrl; }

            set { _InitialUrl = value; }

        }

    }

}

可以看到,這樣就可以通過RewriteContext.Current 集合來訪問 “虛擬路徑參數”了,所有的參數都被指定成了虛擬路徑或頁面,而不是像查詢字符串那樣了。

重寫 URL

接下來,讓我們嘗試重寫。首先,我們要讀取配置文件中的重寫規則。其次,我們要檢查那些在 URL 中與規則不符的部分,如果有,進行重寫並以適當的頁執行。

創建一個 HttpModule:

class RewriteModule : IHttpModule
{

public void Dispose() { }
public void Init(HttpApplication context)

{}

}

當我們添加 RewriteModule_BeginRequest 方法以處理不符合規則的 URL時,我們要檢查給定的 URL 是否包含參數,然後調用 HttpContext.Current.RewritePath 來進行控制並給出合適的 ASP.NET 頁。

using System;

using System.Collections.Generic;

using System.Text;

using System.Web;

using System.Configuration;

using System.Xml;

using System.Text.RegularExpressions;

using System.Web.UI;

using System.IO;

using System.Collections.Specialized;

 

namespace RewriteModule

{

    class RewriteModule : IHttpModule

    {

 

        public void Dispose() { }

 

        public void Init(HttpApplication context)

        {

            // it is necessary to

            context.BeginRequest += new EventHandler(
                 RewriteModule_BeginRequest);

        }

 

        void RewriteModule_BeginRequest(object sender, EventArgs e)

        {

 

            RewriteModuleSectionHandler cfg =
(RewriteModuleSectionHandler)
ConfigurationManager.GetSection
("modulesSection/rewriteModule");

 

            // module is turned off in web.config

            if (!cfg.RewriteOn) return;

 

            string path = HttpContext.Current.Request.Path;

 

            // there us nothing to process

            if (path.Length == 0) return;

 

            // load rewriting rules from web.config

            // and loop through rules collection until first match

            XmlNode rules = cfg.XmlSection.SelectSingleNode("rewriteRules");

            foreach (XmlNode xml in rules.SelectNodes("rule"))

            {

                try

                {

                    Regex re = new Regex(
                     cfg.RewriteBase + xml.Attributes["source"].InnerText,
                     RegexOptions.IgnoreCase);

                    Match match = re.Match(path);

                    if (match.Success)

                    {

                        path = re.Replace(
                             path,
                             xml.Attributes["destination"].InnerText);

                        if (path.Length != 0)

                        {

                            // check for QueryString parameters

                       if(HttpContext.Current.Request.QueryString.Count != 0)

                       {

                       // if there are Query String papameters

                       // then append them to current path

                       string sign = (path.IndexOf('?') == -1) ? "?" : "&";

                       path = path + sign +
                          HttpContext.Current.Request.QueryString.ToString();

                       }

                       // new path to rewrite to

                       string rew = cfg.RewriteBase + path;

                       // save original path to HttpContext for further use

                       HttpContext.Current.Items.Add(

                         "OriginalUrl",

                         HttpContext.Current.Request.RawUrl);

                       // rewrite

                       HttpContext.Current.RewritePath(rew);

                       }

                       return;

                    }

                }

                catch (Exception ex)

                {

                    throw (new Exception("Incorrect rule.", ex));

                }

            }

            return;

        }

 

    }

}

 

這個方法必須注冊:

public void Init(HttpApplication context)

{

 context.BeginRequest += new EventHandler(RewriteModule_BeginRequest);

}

但這些僅僅完成了一半,因為重寫模塊還要處理表單的回發和虛擬路徑參數集合,而這段代碼中你會發現並沒處理這些。讓我們先把虛擬路徑參數放到一邊,先來正確地處理最主要的回發。

如果我們運行上面的代碼,並通過查看 ASP.NET 的 HTML 源代碼 的 action 會發現,它竟然包含了一個 ASP.NET 的實際路徑頁。例如,我們使用頁 ~/Posts.aspx 來處理像 http://www. somebloghost.com/Blogs/2006/12/10/Default.aspx 的請求, 發現 action="/Posts.aspx"。這意味著用戶並沒有使用虛擬路徑進行回發,而是使用了實際的 http://www. somebloghost.com/Blog.aspx. 這個並不是我們需要的。所以,需要加一段代碼來處理這些不希望的結果。

首先,我們要在 HttpModule 注冊和實現一個另外的方法:

        public void Init(HttpApplication context)

        {

            // it is necessary to

            context.BeginRequest += new EventHandler(
                 RewriteModule_BeginRequest);

            context.PreRequestHandlerExecute += new EventHandler(
                 RewriteModule_PreRequestHandlerExecute);

        }

 

      void RewriteModule_PreRequestHandlerExecute(object sender, EventArgs e)

        {

            HttpApplication app = (HttpApplication)sender;

            if ((app.Context.CurrentHandler is Page) &&
                 app.Context.CurrentHandler != null)

            {

                Page pg = (Page)app.Context.CurrentHandler;

                pg.PreInit += new EventHandler(Page_PreInit);

            }

        }

這個方法檢查用戶是否請求了一個正常的 ASP.NET 頁,然後為該頁的 PreInit 事件增加處理過程。這兒 RewriteContext 將處理實際參數,然後二次重寫URL。二次重寫是必需的,以使 ASP.NET 能夠在它的表單的action屬性中使用一個虛擬路徑。

void Page_PreInit(object sender, EventArgs e)

        {

            // restore internal path to original

            // this is required to handle postbacks

            if (HttpContext.Current.Items.Contains("OriginalUrl"))

            {

              string path = (string)HttpContext.Current.Items["OriginalUrl"];

 

              // save query string parameters to context

              RewriteContext con = new RewriteContext(
                HttpContext.Current.Request.QueryString, path);

 

              HttpContext.Current.Items["RewriteContextInfo"] =  con;

 

              if (path.IndexOf("?") == -1)

                  path += "?";

              HttpContext.Current.RewritePath(path);

            }

        }

最後,我們來看一下在我們的重寫模塊程序集中的三個類:

 

在 web.config 中注冊重寫模塊

要使用重寫模塊,需要在配置文件中的 httpModules 節注冊重寫模塊,如下:

<httpModules>

<add name="RewriteModule" type="RewriteModule.RewriteModule, RewriteModule"/>

</httpModules>

使用重寫模塊

在使用重寫模塊時,需要注意:

  • 在 web.config 中來使用一些特殊字符是不可能的,因為它是一個結構良好的 XML 文件,因此,你只能用 HTML 編碼的字符代替,如:使用 & 代替 &。
  • 要在你的 ASPX 中使用相對路徑,需要在HTML標簽調用 ResolveUrl 方法,如: <img src="<%=ResolveUrl("~/Images/Test.jpg")%>" />。
  • Bear in mind the greediness of regular expressions and put rewriting rules to web.config in order of their greediness, for instance:
<rule source="Directory/(.*)/(.*)/(.*)/(.*).aspx"
destination="Directory/Item.aspx?
Source=$1&Year=$2&ValidTill=$3&Sales=$4"/>
<rule source="Directory/(.*)/(.*)/(.*).aspx"
destination="Directory/Items.aspx?
Source=$1&Year=$2&ValidTill=$3"/>
<rule source="Directory/(.*)/(.*).aspx"
destination="Directory/SourceYear.aspx?
Source=$1&Year=$2&"/>
<rule source="Directory/(.*).aspx"
destination="Directory/Source.aspx?Source=$1"/>
  • 如果你要在頁面中使用 RewriteModule 而不使用 .aspx,就必須在 IIS 中進行配置以使用期望的擴展映射到請求頁,如下節所述:

IIS 配置: 使用帶擴展的重寫模塊代替 .aspx

要使用帶擴展的重寫模塊代替 .aspx (如 .html or .xml), 必須配置 IIS ,以使這些擴展映射到 ASP.NET 引擎 (ASP.NET ISAPI 擴展)。要進行這些設置,需要以管理員身份登錄。

打開 IIS 管理控制台,並選擇你要配置的站點的虛擬路徑:

Windows XP (IIS 5)
Virtual Directory "RW"                                

 

Windows 2003 Server (IIS 6)
Default Web Site

然後在虛擬路徑標簽上點擊 Configuration… 按鈕  (或如果要使用整個站點都做映射就選擇主目錄標簽)。

Windows XP (IIS 5)                     

Windows 2003 Server (IIS 6)

接下來,點擊添加按鈕,並輸入一個擴展,你還需要指定一個 ASP.NET ISAPI 擴展,注意去掉選項的對勾以檢查文件是否存在。

如果你要把所有的擴展都映射到 ASP.NET,對Windows XP上的 IIS 5 來說只需要設置 .* 到 ASP.NET ISAPI ,但對 IIS 6 就不一樣了,點擊“添加”然後指定 ASP.NET ISAPI 擴展。

總結

現在,我們已經創建了一個簡單的但非常強大的 ASP.NET 重寫模塊,它支持可基於正則表達式的 URLs 和頁面回發,這個解決方案是容易實現的,並且提供給用戶的例子也是可用的,它可以用簡短的、整潔的URL來替代查詢字符串參數。  要使用這個模塊,只需簡單在你的應用程序中對 RewriteModule 進行引用,然後在 web.config 文件中添加幾行代碼以使你不想顯示的 URL 通過正則表達式代替。這個重寫模塊是很容易部署的,因為只需要在web.config中修改任何“虛擬”的URL即可,如果你需要進行測試,還可以對重寫模塊進行關閉。

要想對重寫模塊有一個深入的了解,你可以查看本文提供的原代碼。我相信你會發現這是一個比ASP.NET提供的原始映射更好的體驗。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved