深入理解Asp.net核心对象
在我的眼里,Asp.net有三大核心对象:HttpContext, HttpRequest, HttpResponse。
除此之外,还有二个对象虽然称不上核心,但仍然比较重要:HttpRuntime,HttpServerUtility
事实上,这些类的实例在其它的一些类型中也经常被引用到,从出现的频率也可以看出它们的重要性。
中国人喜欢把较重要的东西放在最后,做为压轴出场。今天我也将按照这个风俗习惯做为这些对象的出场顺序来分别说说它们有哪些【重要的功能】。
HttpRuntime
第一个出场的是HttpRuntime,其实这个对象算是整个Asp.net平台最核心的对象,从名字可以看出它的份量。但它包含的很多方法都不是public类型的,它在整个请求的处理过程中,做了许多默默无闻但非常重要的工作。反而公开的东西并不多,因此需要我们掌握的东西也较少。不能让它做为压轴出场就让它第一个出场吧。这就是我的想法。
HttpRuntime公开了一个静态方法 UnloadAppDomain() ,这个方法可以让我们用代码重新启动网站。通常用于用户通过程序界面修改了一个比较重要的参数,这时需要重启程序了。
HttpRuntime还公开了一个大家都熟知的静态属性 Cache 。可能有些人认为他/她在使用Page.Cache或者HttpContext.Cache,事实上后二个属性都是HttpRuntime.Cache的【快捷方式】。HttpRuntime.Cache是个非常强大的东西,主要用于缓存一些数据对象,提高程序性能。虽然缓存实现方式比较多,一个static变量也算是能起到缓存的作用,但HttpRuntime.Cache的功能绝不仅限于一个简单的缓存集合,如果说实现“缓存项的滑动过期和绝对过期”算是小儿科的话,缓存依赖的功能应该可以算是个强大的特性吧。更有意义的是:它缓存的内容还可以在操作系统内存不足时能将一些缓存项释放(可指定优先级),从而获得那些对象的内存,并能在移除这些缓项时能通知您的代码。可能有人认为当内存不足时自动释放一些缓存对象容易啊,使用WeakReference类来包装一下就可以了。但WeakReference不提供移除时的通知功能。
HttpRuntime.Cache还有个非常酷的功能是:它并非只能在Asp.net环境中使用,也能在其它编程模型中使用,比如大家熟知的WinForm编程模型。如何使用呢,直接访问HttpRuntime.Cache这个静态属性肯定是不行的。我们只要在程序初始化时创建一个HttpRuntime的实例,当然还要保证它不会被GC回收掉。然后就可以像在Asp.net中一样使用HttpRuntime.Cache了,就这么简单。是的,就是这样简单,您就可以在其它编程模型中使用Cache的强大功能:线程安全的集合,2种过期时间的选择,缓存依赖,内存不足时自动释放且有回调通知。
这里我还想说说缓存依赖。我曾经见过一个使用场景:有人从一堆文件(分为若干类别)中加载数据到Cache中,但是他为了想在这些数据文件修改时能重新加载,而采用创建线程并轮询文件的最后修改时间的方式来实现,总共开了60多个线程,那些线程每隔15去检查各自所“管辖”的文件是否已修改。如果您也是这样处理的,我今天就告诉您:真的没必要这么复杂,您只要在添加缓存项时创建一个CacheDependency的实例并调用相应的重载方法就可以了。具体CacheDependency有哪些参数,您还是参考一下MSDN吧。这里我只告诉您:它能在一个文件或者目录,或者多个文件在修改时,自动通知Cache将缓存项清除,而且还可以设置到依赖其它的缓存项,甚至能将这些依赖关系组合使用,非常强大。
可能还有人会担心往Cache里放入太多的东西会不会影响性能,因此有人还想到控制缓存数量的办法。我只想说:缓存容器决定一个对象的保存位置是使用Hash算法的,并不会因为缓存项变多而影响性能,更有趣的是Asp.net的Cache的容器还并非只有一个,它能随着CPU的数量而调整,看这个架式,应该在设计Cache时还想到了高并发访问的性能问题。如果这时你还在统计缓存数量并手工释放某些缓存项,我只能说您在写损害性能的代码。
HttpServerUtility , HttpUtility
不要觉得奇怪,这次我一下子请了二个对象出场了。由于HttpServerUtility的实例通常以Server的属性公开,但它的提供一些Encode, Decode方法其实调用的是HttpUtility类的静态方法。所以我就把它们俩一起请出来了。
HttpUtility公开了一些静态方法,如:
HtmlEncode(),应该是使用频率比较高的方法,用于防止注入攻击,它负责安全地生成一段HTML代码。
有时我们还需要生成一个URL,那么UrlEncode()方法就能派上用场了,因为URL中并不能包含所有字符,所以要做相应的编码。
HttpUtility还有一个方法HtmlAttributeEncode(),它也是用于防止注入攻击,安全地输出一个HTML属性。
在.net4中,HttpUtility还提供了另一个方法:JavaScriptStringEncode(),也是为了防止注入攻击,安全地在服务端输出一段JS代码。
HttpUtility还公开了一些静态方法,如:
HtmlDecode(), UrlDecode(),通常来说,我们并不需要使用它们。尤其是UrlDecode ,除非您要自己的框架,一般来说,在我们访问QueryString, Form时,已经做过UrlDecode了,您就不用再去调用了。
HttpServerUtility除了公开了比较常用的Encode, Decode方法外,还公开了一个非常有用的方法:Execute(),是的,它非常有用,尤其是您需要在服务端获取一个页面或者用户控件的HTML输出时。
HttpRequest
现在总算轮到第一个核心对象出场了。MSDN给它作了一个简短的解释:“使 ASP.NET 能够读取客户端在 Web 请求期间发送的 HTTP 值。”
这个解释还算是到位的。HttpRequest的实例包含了所有来自客户端的所有数据,我们可以把这些数据看成是输入数据, Handler以及Module就相当于是处理过程,HttpResponse就是输出了。
在HttpRequest包含的所有输入数据中,有我们经常使用的QueryString, Form, Cookie,它还允许我们访问一些HTTP请求头、浏览器的相关信息、请求映射的相关文件路径、URL详细信息、请求的方法、请求是否已经过身份验证,是否为SSL等等。
HttpRequest的公开属性绝大部分都是比较重要的,这里就简单地列举一下吧。
// 获取服务器上 ASP.NET 应用程序的虚拟应用程序根路径。 public string ApplicationPath { get; } // 获取应用程序根的虚拟路径,并通过对应用程序根使用波形符 (~) 表示法(例如,以“~/page.aspx”的形式)使该路径成为相对路径。 public string AppRelativeCurrentExecutionFilePath { get; } // 获取或设置有关正在请求的客户端的浏览器功能的信息。 public HttpBrowserCapabilities Browser { get; set; } // 获取客户端发送的 cookie 的集合。 public HttpCookieCollection Cookies { get; } // 获取当前请求的虚拟路径。 public string FilePath { get; } // 获取采用多部分 MIME 格式的由客户端上载的文件的集合。 public HttpFileCollection Files { get; } // 获取或设置在读取当前输入流时要使用的筛选器。 public Stream Filter { get; set; } // 获取窗体变量集合。 public NameValueCollection Form { get; } // 获取 HTTP 头集合。 public NameValueCollection Headers { get; } // 获取客户端使用的 HTTP 数据传输方法(如 GET、POST 或 HEAD)。 public string HttpMethod { get; } // 获取传入的 HTTP 实体主体的内容。 public Stream InputStream { get; } // 获取一个值,该值指示是否验证了请求。 public bool IsAuthenticated { get; } // 获取当前请求的虚拟路径。 public string Path { get; } // 获取 HTTP 查询字符串变量集合。 public NameValueCollection QueryString { get; } // 获取当前请求的原始 URL。 public string RawUrl { get; } // 获取有关当前请求的 URL 的信息。 public Uri Url { get; } // 从 QueryString、Form、Cookies 或 ServerVariables 集合中获取指定的对象。 public string this[string key] { get; } // 将指定的虚拟路径映射到物理路径。 // 参数: virtualPath: 当前请求的虚拟路径(绝对路径或相对路径)。 // 返回结果: 由 virtualPath 指定的服务器物理路径。 public string MapPath(string virtualPath);
下面我来说说一些不被人注意的细节。
HttpRequest的QueryString, Form属性的类型都是NameValueCollection,它个集合类型有一个特点:允许在一个键下存储多个字符串值。
以下代码演示了这个特殊的现象:
protected void Page_Load(object sender, EventArgs e) { string[] allkeys = Request.QueryString.AllKeys; if( allkeys.Length == 0 ) Response.Redirect( Request.RawUrl + "?aa=1&bb=2&cc=3&aa=" + HttpUtility.UrlEncode("5,6,7"), true); StringBuilder sb = new StringBuilder(); foreach( string key in allkeys ) sb.AppendFormat("{0} = {1}<br />", HttpUtility.HtmlEncode(key), HttpUtility.HtmlEncode(Request.QueryString[key])); this.labResult.Text = sb.ToString(); }
页面最终显示结果如下(注意键值为aa的结果):
说明:
1. HttpUtility.ParseQueryString(string)这个静态方法能帮助我们解析一个URL字符串,返回的结果也是NameValueCollection类型。
2. NameValueCollection是一个不区分大小写的集合。
HttpRequest有一个Cookies属性,MSDN给它的解释是:“获取客户端发送的 Cookie 的集合。”,这次MSDN的解释就不完全准确了。
请看如下代码:
protected void Page_Load(object sender, EventArgs e) { string key = "Key1"; HttpCookie c = new HttpCookie(key, DateTime.Now.ToString()); Response.Cookies.Add(c); HttpCookie cookie = Request.Cookies[key]; if( cookie != null ) this.labResult.Text = cookie.Value; Response.Cookies.Remove(key); }
这段代码的运行结果就是【能显示当前时间】,我就不贴图了。
如果写成如下形式:
protected void Page_Load(object sender, EventArgs e) { string key = "Key1"; HttpCookie cookie = Request.Cookies[key]; if( cookie != null ) this.labResult.Text = cookie.Value; HttpCookie c = new HttpCookie(key, DateTime.Now.ToString()); Response.Cookies.Add(c); Response.Cookies.Remove(key); }
此时就读不到Cookie了。这也提示我们:Cookie的读写次序可能会影响我们的某些判断。
HttpRequest还有二个用于方便获取HTTP数据的属性Params,Item ,后者是个默认的索引器。
这二个属性都可以让我们方便地根据一个KEY去【同时搜索】QueryString、Form、Cookies 或 ServerVariables这4个集合。通常如果请求是用GET方法发出的,那我们一般是访问QueryString去获取用户的数据,如果请求是用POST方法提交的,我们一般使用Form去访问油桶泵用户提交的表单数据。而使用Params,Item可以让我们在写代码时不必区分是GET还是POST。这二个属性唯一不同的是:Item是依次访问这4个集合,找到就返回结果,而Params是在访问时,先将4个集合的数据合并到一个新集合(集合不存在时创建),然后再查找指定的结果。
为了更清楚地演示这们的差别,请看以下示例代码:
<body> <p>Item结果:<%= this.ItemValue %></p> <p>Params结果:<%= this.ParamsValue %></p> <hr /> <form action="<%= Request.RawUrl %>" method="post"> <input type="text" name="name" value="123" /> <input type="submit" value="提交" /> </form> </body>
public partial class ShowItem : System.Web.UI.Page { protected string ItemValue; protected string ParamsValue; protected void Page_Load(object sender, EventArgs e) { string[] allkeys = Request.QueryString.AllKeys; if( allkeys.Length == 0 ) Response.Redirect("ShowItem.aspx?name=abc", true); ItemValue = Request["name"]; ParamsValue = Request.Params["name"]; } }
页面在未提交前浏览器的显示:
点击提交按钮后,浏览器的显示:
差别很明显,我也不多说了。说下我的建议吧:尽量不要使用Params,不光是上面的结果导致的判断问题,没必要多创建一个集合出来吧,而且更糟糕的是写Cookie后,也会更新集合。
HttpRequest还有二个很【低调】的属性:InputStream, Filter ,这二位的能量很巨大,却不经常被人用到。
HttpResponse也有这二个对应的属性,本文的后面部分将向您展示它们的强大功能。