詳談關於Discuz!NT 的URL地址重寫(URLRewrite)

NO IMAGE

在Discuz!NT中的前臺頁面訪問(特別是aspx)是被HttpModule接管的,所以大家在Discuz.Web
專案的目錄下看到的唯一”aspx檔案”是index.aspx,而所有其它前臺頁面都有“/aspx/”資料夾下的相應
的子目錄中,而這些子目錄名稱是與後臺所“生成”的模板存在對應關係的。而這種“關係”的繫結是通
過dnt_templates(模板資料表)來進行關聯的。而有關模板機制的文章詳見:

    “Discuz!NT 模板機制分析”一文。

     今天所要說的其實是模板機制的“延續”,當然這種“延續”僅是我個人的觀點。因為地址重寫最終
要繫結的路徑,恰恰與模板機制是有著前後呼應的關係。首先請大家看一下這張圖:

        
     圖1 :[discuz!NT 的web.config檔案相關配置節]    

      相信對於那些以前做過“URL重寫”的朋友看到這一行配置的第一反映可能就是要去Discuz.Forum
.HttpModule類中一看究竟。同時考慮到在<httpModules>配置節中加入處理的資料在網上有許多,所
以這裡就不再多費脣舌了。

      另外如果大家感興趣味的話,也可以抽空看看我的這篇文章,”NET框架中的 Observer 模式”。雖
然該文是我在挖.net底層程式碼時是寫的一些個人想法,可讀性不高,但我相信會對大家有所幫助的:)

      好了,下面讓我們看一下在Discuz.Forum專案中的HttpModule類中的一些資訊,看看URL重寫到
底如何實現:)   

 1/// <summary>
 2 /// 論壇HttpModule類
 3 /// </summary>
 4 public class HttpModule : System.Web.IHttpModule
 5 {
 6         /// <summary>
 7  /// 實現介面的Init方法
 8  /// </summary>
 9  /// <param name=”context”></param>
10  public void Init(HttpApplication context)
11  {
12    OnlineUsers.ResetOnlineList();
13   context.BeginRequest  = new EventHandler(ReUrl_BeginRequest);
14  }
15
16 
17
18

 

      上面程式碼中的Init(HttpApplication context)是HttpModule類進行操作的入口,所有實現
System.Web.IHttpModule 介面的類都必須實現這個函式。
     同時大家看到的OnlineUsers.ResetOnlineList()方法主要是用於“重置(復位)線上表”,
而有關“線上使用者”的功能我會在以後專門寫文章來加以介紹,所以這裡大家只要知道它要乾的
事(程式碼如下)即可:)

 

 1  /// <summary>
 2  /// 復位線上表, 如果系統未重啟, 僅是應用程式重新啟動, 則不會重新建立
 3  /// </summary>
 4  /// <returns></returns>
 5  public static int ResetOnlineList()
 6  {
 7   try
 8   {
 9    // 取得線上表最後一條記錄的tickcount欄位 (因為本功能不要求特別精確)
10                //int tickcount = DatabaseProvider.GetInstance().GetLastTickCount();
11    // 如果距離現在系統執行時間小於10分鐘
12    if (System.Environment.TickCount < 600000)
13    {
14     return InitOnlineList();
15    }
16    return -1;
17   }
18   catch
19   {
20    try
21    {
22     return InitOnlineList();
23    }
24    catch
25    {
26     return -1;
27    }
28   }
29
30  }
31
32

      而緊隨其後的事件繫結程式碼就是一個關鍵點了,形如:

context.BeginRequest  = new EventHandler(ReUrl_BeginRequest);

      因為當通過瀏覽器提交請求時,IIS都會接管請求進行相應的操作(詳見DUDU的文章:” ASP.NET 2.0執行時
簡要分析”)後,最終通過Web.config中的相應配置節(上圖所示)來執行使用者預定的處理操作。而我們的程式碼就
在ReUrl_BeginRequest事件中(程式碼如下,詳情見註釋):

 

 1/// <summary>
 2        /// 重寫Url
 3        /// </summary>
 4  /// <param name=”sender”>事件的源</param>
 5  /// <param name=”e”>包含事件資料的 EventArgs</param>
 6  private void ReUrl_BeginRequest(object sender, EventArgs e)
 7  {
 8                        //獲取基本配置資訊
 9   BaseConfigInfo baseconfig = BaseConfigProvider.Instance();
10            
11   if (baseconfig == null)
12          {
13                  return;
14              }
15
16                        //得到論壇配置資訊
17              GeneralConfigInfo config = GeneralConfigs.GetConfig();
18   HttpContext context = ((HttpApplication)sender).Context;
19   string forumPath = baseconfig.Forumpath.ToLower();
20
21   string requestPath = context.Request.Path.ToLower();
22
23   if (requestPath.StartsWith(forumPath))
24   {
25    if (requestPath.Substring(forumPath.Length).IndexOf(“/”) == -1)
26    {
27     // 宣告並設定預設論壇模板
28                   string strTemplateid = config.Templateid.ToString();
29     // 判斷COOKIE中模板是否是系統當前的有效(已入庫)模板
30                      if (Utils.InArray(Utils.GetCookie(Utils.GetTemplateCookieName()), 
31      Templates.GetValidTemplateIDList()))
32     {
33                           strTemplateid = Utils.GetCookie(Utils.GetTemplateCookieName());
34     }
35
36     //當訪問的是首頁時
37                      if (requestPath.EndsWith(“/index.aspx”))
38                       {
39      //當配置檔案中未指定首頁時,則將forumindex.aspx做為首頁並重寫路徑
40                           if (config.Indexpage == 0)
41                           {
42                                
43                                context.RewritePath(forumPath   “aspx/”   strTemplateid   “/forumindex.aspx”);
44                           }
45                           else //否則使用聚合首頁來做為網站首頁並重寫路徑
46                           {
47                                
48                                context.RewritePath(forumPath   “aspx/”   strTemplateid   “/website.aspx”);
49                           }
50
51                           return;
52                       }
53
54
55                       //當使用偽aspx, 如:showforum-1.aspx等.
56                       if (config.Aspxrewrite == 1)
57                       {
58      //獲取後臺設定的可以使用的偽aspx設定.
59      //SiteUrls.GetSiteUrls()類和方法說明見下文
60                           foreach (SiteUrls.URLRewrite url in SiteUrls.GetSiteUrls().Urls)
61                           {
62       //進行正則匹配,來看訪問頁面是否是使用者定義的偽URL地址
63                                if (Regex.IsMatch(requestPath, url.Pattern, Utils.GetRegexCompiledOptions() | RegexOptions.IgnoreCase))
64                                {
65                                    string newUrl = Regex.Replace(requestPath.Substring(context.Request.Path.LastIndexOf(“/”)), url.Pattern, url.QueryString, Utils.GetRegexCompiledOptions() | RegexOptions.IgnoreCase);
66                                    
67                                    context.RewritePath(forumPath   “aspx/”   strTemplateid   url.Page, string.Empty, newUrl);
68                                    return;
69                                }
70                           }
71                       }
72
73                       
74                   
75                       context.RewritePath(forumPath   “aspx/”   strTemplateid   requestPath.Substring(context.Request.Path.LastIndexOf(“/”)), string.Empty, context.Request.QueryString.ToString());
76    }
77    //當前請求路徑是“論壇路徑/archiver(簡潔版路徑)/”下時.
78    else if (requestPath.StartsWith(forumPath   “archiver/”))
79    {
80                       
81                       
82                       return;
83    }
84     //當前請求路徑是“論壇路徑/tools/(論壇工具頁面如:rss,sitemap,help等)”請求時
85    else if (requestPath.StartsWith(forumPath   “tools/”))
86    {
87                       
88                   return;
89    }
90
91   }
92  }
93
94

       這樣就實現了偽URL的動態轉換(地址重寫)。而相應的“SiteUrls”類則是對偽URL設定資訊
進行訪問讀取的“封裝類”,目的就是要將Discuz.Web專案中config資料夾下的urls.config檔案轉換
成可訪問的資訊物件。形如:

 

  1/// <summary>
  2 /// 站點偽Url資訊類
  3 /// </summary>
  4 public class SiteUrls
  5 {
  6  內部屬性和方法
 66
 67                //獲取偽URL設定物件的例項
 68  public static SiteUrls GetSiteUrls()
 69  {
 70   if (instance == null)
 71   {
 72    lock (lockHelper)
 73    {
 74     if (instance == null)
 75     {
 76      instance = new SiteUrls();
 77     }
 78    }
 79   }
 80   return instance;
 81
 82  }
 83
 84  public static void SetInstance(SiteUrls anInstance)
 85  {
 86   if (anInstance != null)
 87    instance = anInstance;
 88  }
 89
 90  public static void SetInstance()
 91  {
 92   SetInstance(new SiteUrls());
 93  }
 94
 95                
 96  /// <summary>
 97  /// 重寫偽地址(內部類),用於宣告和封裝物件
 98  /// </summary>
 99  public class URLRewrite
100  {
101   成員變數 
154
155   建構函式
164  }
165
166 }
167
168

       因為程式碼很簡單,這裡就不再另行說明了。

       到目前,我們知道了要通過“urls.config”檔案來進行偽URL重寫,那麼這個檔案中的資料到底是什麼
樣子呢,下面就是在成功安裝discuz!nt 2.0產品之後的檔案內容:

 

<?xml version=”1.0″ encoding=”utf-8″ ?> 
<urls>
     <rewrite name=”showforum”
          path=”/showforum-{0}-{1}.aspx”
          pattern = “/showforum-(/d )(-(/d ))?.aspx”
          page=”/showforum.aspx”
          querystring=”forumid=$1^page=$3″ />
    <rewrite name=”showtopic”
          path=”/showtopic-{0}-{1}.aspx”
          pattern = “/showtopic-(/d )(-(/d ))?.aspx”
          page=”/showtopic.aspx”
          querystring=”topicid=$1^page=$3″ />
    <rewrite name=”userinfo”
          path=”/userinfo-{0}.aspx”
          pattern = “/userinfo-(/d )*.aspx”
          page=”/userinfo.aspx”
          querystring=”userid=$1″ />
     <rewrite name=”rss”
          path=”/rss-{0}.aspx”
          pattern = “/rss-(/d )?.aspx”
          page=”/rss.aspx”
          querystring=”forumid=$1″ />
     <rewrite name=”spacerss”
          path=”/spacerss-{0}.aspx”
          pattern=”/spacerss(-(/d ))?.aspx(/?.*?)?”
          page=”/rss.aspx”
          querystring=”uid=$2&amp;type=space” />
     <rewrite name=”photorss”
          path=”/photorss-{0}.aspx”
          pattern=”/photorss(-(/d ))?.aspx(/?.*?)?”
          page=”/rss.aspx”
          querystring=”uid=$2&amp;type=photo” />
</urls>

     大家看到了吧。就目前而言偽URL重寫的頁面(page節點)包括如下幾個:

     showforum.aspx(版塊列表),showtopic.aspx(主題列表),userinfo.aspx(使用者資訊),rss.aspx,
雖然不多,但已基本滿足了設定需要。

     而設定(配置)管理的頁面在後臺的“全域性”–>“常規選項”–>“基本設定”–>“編輯偽靜態url替換
規則”,如果所示:

     相信聊到這裡大家對我們產品的這項功能應該有個大概認識了,但這不併是全部的設定,因為如果要接管
形如”showforum-1-2.htm”這樣的頁面,是必須要到IIS裡去“搞一把”的。因為預設的IIS中對htm(html)
的處理是無法實現將上面的連結轉成“showforum.aspx?forumid=1&page=2”的形式的,所以這裡要參考
一下官方文件(使用偽Url地址)中的方法,相應圖示如下:

         


    

      好的,下面再接著聊一下關於與第三方產品“整和”的問題。因為目前大多數採用 .net技術開發的主流產品
(如部落格園等)都會或多或少使用與本文類似或相同的做法來實現地址重寫功能。比如:繼承並實現IHttpHandler
介面。
 
      當然我們的產品所採用實現IHttpModule的方式會比IHttpHandler更早一步被 IIS進行處理。所以如果出現
了這種情況,我個人建議是改動我們產品中的程式碼,通過在上述 ReUrl_BeginRequest事件中加入條件分支(第
三方程式的偽地址規則)來實現“地址重寫整合”;當然如果第三方的 (重寫)規則太複雜的話,也可以在我們產
品的相應程式碼中加入條件分支(只要出現第三方請求的連結頁面或指定路徑時),但不作任何處理(直接綠燈放行
),這樣就會轉入到第三方的偽URL重寫規則程式碼的“勢力範圍”內了。

     當然如果第三方也使用類似IHttpModule的實現方式,則要看是誰的配置先出現在web.config中的相應配置
節中了。因為“先入為主”嘛,如果先後順序已明確的話,那麼先被載入的HttpModule 模組就要做一下變動了,
以便不會干擾後面的HttpModule模組的正常執行了。因為我手上這方面的例項也不多,所以只能在這裡聊聊我本
人的一些看法,相信隨著官方“整合案例” 的增加,這方面的資料會得到更大的補充。