Discuz!NT 的URL地址重写(URLRewrite)

在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  内部属性和方法#region 内部属性和方法
  7  private
static
object lockHelper =
new
object();
  8  private
static
volatile SiteUrls instance =
null;
  9
10  string SiteUrlsFile = HttpContext.Current.Server.MapPath(BaseConfigs.GetForumPath +
"config/urls.config");
11  private System.Collections.ArrayList _Urls;
12  public System.Collections.ArrayList Urls
13  {
14  get
15  {
16    return _Urls;
17  }
18  set
19  {
20    _Urls = value;
21  }
22  }
23
24  private System.Collections.Specialized.NameValueCollection _Paths;
25  public System.Collections.Specialized.NameValueCollection Paths
26  {
27  get
28  {
29    return _Paths;
30  }
31  set
32  {
33    _Paths = value;
34  }
35  }
36 
37  private SiteUrls()
38  {
39  Urls =
new System.Collections.ArrayList();
40  Paths =
new System.Collections.Specialized.NameValueCollection();
41
42  XmlDocument xml =
new XmlDocument();
43
44  xml.Load(SiteUrlsFile);
45
46  XmlNode root = xml.SelectSingleNode("urls");
47  foreach(XmlNode n in root.ChildNodes)
48  {
49    if (n.NodeType != XmlNodeType.Comment && n.Name.ToLower() ==
"rewrite")
50    {
51    XmlAttribute name = n.Attributes["name"];
52    XmlAttribute path = n.Attributes["path"];
53    XmlAttribute page = n.Attributes["page"];
54    XmlAttribute querystring = n.Attributes["querystring"];
55    XmlAttribute pattern = n.Attributes["pattern"];
56
57    if (name !=
null
&& path !=
null
&& page !=
null
&& querystring !=
null
&& pattern !=
null)
58    {
59      Paths.Add(name.Value, path.Value);
60      Urls.Add(new URLRewrite(name.Value, pattern.Value, page.Value.Replace("^", "&"), querystring.Value.Replace("^", "&")));
61    }
62    }
63  }
64  }
65  #endregion
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  成员变量#region 成员变量
102  private
string _Name;
103  public
string Name
104  {
105    get
106    {
107    return _Name;
108    }
109    set
110    {
111    _Name = value;
112    }
113  }
114
115  private
string _Pattern;
116  public
string Pattern
117  {
118    get
119    {
120    return _Pattern;
121    }
122    set
123    {
124    _Pattern = value;
125    }
126  }
127
128  private
string _Page;
129  public
string Page
130  {
131    get
132    {
133    return _Page;
134    }
135    set
136    {
137    _Page = value;
138    }
139  }
140
141  private
string _QueryString;
142  public
string QueryString
143  {
144    get
145    {
146    return _QueryString;
147    }
148    set
149    {
150    _QueryString = value;
151    }
152  }
153  #endregion

154
155  构造函数#region 构造函数
156  public URLRewrite(string name, string pattern, string page, string querystring)
157  {
158    _Name = name;
159    _Pattern = pattern;
160    _Page = page;
161    _QueryString = querystring;
162  }
163  #endregion
164  }
165
166 }
167
168

      因为代码很简单,这里就不再另行说明了。
      到目前,我们知道了要通过“urls.config”文件来进行伪URL重写,那么这个文件中的数据到底是什么
样子呢,下面就是在成功安装discuz!nt 2.0产品之后的文件内容:


<?xml version="1.0" encoding="utf-8"
?>