ASP.NET三大核心对象及基础功能解析

  想当初在只使用WebForms框架并以服务端为中心的开发模式时,发现Asp.net好复杂。一大堆服务端控件,各有各的使用方法,有些控件的事件也很重要,必须在合适地时机去响应,还真有些复杂。后来逐渐发现这些复杂的根源其实就是服务器控件相关的抽象逻辑。随着Ajax越用越多,可能有些人也做过这些事情:【新建一个ashx文件,读取一些用户的输入数据,Form, QueryString,然后调用业务逻辑代码,将处理后的结果序列化成JSON字符串再发给客户端】,这样也能完成一次请求。不知大家有没有做过这类事情,反正我是做过的。慢慢地,我也嫌烦了,这些事情中除了调用业务逻辑部分,都是些体力活嘛。于是想,写点代码把这些事情交给它们去做吧,我只处理与请求有关的数据处理就好了。终于,我写了个简陋的框架,并自称为【我的Ajax服务端框架】 以及【我的MVC框架】。写完这些东西后,发现Asp.net的东西变少了,但是仍可以实现很多功能。

  其实,我们可以从另一角度来看Asp.net,它就是一个底层框架平台,它负责接收HTTP请求(从IIS传入),将请求分配给一个线程,再把请求放到它的处理管道中,由一些其它的【管道事件订阅者】来处理它们,最后将处理结果返回给客户端。而WebForms或者MVC框架,都属于Asp.net平台上的【管道事件订阅者】而已,Web Service也是哦。如果你不想受限于WebForms或者MVC框架,或者您还想用Asp.net做点其它的事情,比如:自己的服务框架,就像WebService那样。但希望用其它更简单的序列化方式来减少网络流量,或者还有加密要求。那么了解Asp.net提供了哪些功能就很有必要了。

  本文将站在Asp.net平台的角度,来看看Asp.net的一些基础功能。虽然不会涉及任何其它上层框架,但所讲述的内容其实是适合其它上层框架的。

  前面我说到:Asp.net负责接收请求,并将请求分配给一个线程来执行。最终执行什么呢?当然就是我们的处理逻辑。但我们在处理时,用户输入的数据又是从哪里来的呢?只能是HTTP请求。但它又可分为二个部分:请求头和请求体。在Asp.net中,我们并不需要去分析请求头和请求体,比如:我们可以直接访问QueryString,Form就可以得到用户传过来的数据了,然而QueryString其实是放在请求头上,在请求头上的还有Cookie,Form以及PostFile则放在请求体中。如果对这些内容不清楚的可以参考我的博客:【细说Cookie】 和【细说 Form (表单)】。在我这二篇博客中,您应该可以看出:要是让您从请求头请求体中读取这些数据,还是很麻烦的。幸好,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输出时。如果您对这个功能有兴趣可以参考我的博客:【我的Ajax服务端框架 - (4) JS直接请求ascx用户控件】     

         HttpRequest

  现在总算轮到第一个核心对象出场了。MSDN给它作了一个简短的解释:“使 ASP.NET 能够读取客户端在 Web 请求期间发送的 HTTP 值。”

  这个解释还算是到位的。HttpRequest的实例包含了所有来自客户端的所有数据,我们可以把这些数据看成是输入数据, Handler以及Module就相当于是处理过程,HttpResponse就是输出了。

  在HttpRequest包含的所有输入数据中,有我们经常使用的QueryString, Form, Cookie,它还允许我们访问一些HTTP请求头、浏览器的相关信息、请求映射的相关文件路径、URL详细信息、请求的方法、请求是否已经过身份验证,是否为SSL等等。

  HttpRequest的公开属性绝大部分都是比较重要的,这里就简单地列举一下吧。

// 获取服务器上 ASP.NET 应用程序的虚拟应用程序根路径。
publicstring ApplicationPath { get; }

// 获取应用程序根的虚拟路径,并通过对应用程序根使用波形符 (~) 表示法(例如,以“~/page.aspx”的形式)使该路径成为相对路径。
publicstring AppRelativeCurrentExecutionFilePath { get; }

// 获取或设置有关正在请求的客户端的浏览器功能的信息。
public HttpBrowserCapabilities Browser { get; set; }

// 获取客户端发送的 cookie 的集合。
public HttpCookieCollection Cookies { get; }

// 获取当前请求的虚拟路径。
publicstring 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)。
publicstring HttpMethod { get; }

// 获取传入的 HTTP 实体主体的内容。
public Stream InputStream { get; }

// 获取一个值,该值指示是否验证了请求。
public bool IsAuthenticated { get; }

// 获取当前请求的虚拟路径。
publicstring Path { get; }

// 获取 HTTP 查询字符串变量集合。
public NameValueCollection QueryString { get; }

// 获取当前请求的原始 URL。
publicstring RawUrl { get; }

// 获取有关当前请求的 URL 的信息。
public Uri Url { get; }

// 从 QueryString、Form、Cookies 或 ServerVariables 集合中获取指定的对象。
publicstring this[string key] { get; }

// 将指定的虚拟路径映射到物理路径。
// 参数:  virtualPath:  当前请求的虚拟路径(绝对路径或相对路径)。
// 返回结果:  由 virtualPath 指定的服务器物理路径。
publicstring 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}
",
            HttpUtility.HtmlEncode(key), HttpUtility.HtmlEncode(Request.QueryString[key]));

    this.labResult.Text = sb.ToString();
}

     页面最终显示结果如下(注意键值为aa的结果):

  Asp.net核心对象

  说明:

  HttpUtility.ParseQueryString(string)这个静态方法能帮助我们解析一个URL字符串,返回的结果也是NameValueCollection类型。

  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个集合的数据合并到一个新集合(集合不存在时创建),然后再查找指定的结果。

  为了更清楚地演示这们的差别,请看以下示例代码:

    
    

  Item结果:<%= this.ItemValue %>


    

  Params结果:<%= this.ParamsValue %>


    
    

    
    

        
        
    

publicpartialclass ShowItem : System.Web.UI.Page
{
    protectedstring ItemValue;
    protectedstring 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"];        
    }
}

  页面在未提交前浏览器的显示:

  Asp.net核心对象

  点击提交按钮后,浏览器的显示:

  Asp.net核心对象

  差别很明显,我也不多说了。说下我的建议吧:尽量不要使用Params,不光是上面的结果导致的判断问题,没必要多创建一个集合出来吧,而且更糟糕的是写Cookie后,也会更新集合。

  HttpRequest还有二个很【低调】的属性:InputStream, Filter ,这二位的能量很巨大,却不经常被人用到。

  HttpResponse也有这二个对应的属性,本文的后面部分将向您展示它们的强大功能。


来源:IT168 作者:

免责声明:本文仅代表作者个人观点,与世界朋友网无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

[责任编辑:世界朋友]