天天看点

关于上下文

susan warren

microsoft corporation

编写 web 应用程序时最常见的问题之一,是要让代码知道它的执行上下文。让我们通过一个简单的例子(即个性化页面)来说明这个问题:

   请登录。

   欢迎 susan!

虽然看起来很简单,但即使是这一小段 web ui,仍然需要好几段信息,而且每次请求该页时这些信息都会发生变化。我们需要知道以下内容:

用户登录了吗?

用户的显示名是什么?

更通常的问题是,每次请求该页时,唯一的上下文是什么?以及如何编写代码以便能考虑到此信息?

事实上,由于 http 的无状态特性,web 应用程序可能需要跟踪许多不同的上下文片段。当用户与 web 应用程序交互时,浏览器将一系列独立的 http 请求发送到 web 服务器。应用程序自身必须将这些请求组织成令用户感到愉悦的体验;同时,知道请求的上下文也十分关键。

asp 引入了几个内部对象,如 request 和 application,以便帮助跟踪 http 请求的上下文。asp.net 完成下一步骤,并将这些对象以及其他几个与上下文有关的对象捆绑在一起,形成一个极为方便的内部对象 context。

context 是 system.web.httpcontext(英文)类型的对象。它作为 asp.net page 类的属性公开。也可以通过用户控件和业务对象(下文中详细介绍)获得该对象。以下是 httpcontext 形成的对象的部分列表:

对象 说明

application 值的关键字/值对集合,可由应用程序的每个用户访问。application 是 system.web.httpapplicationstate 类型。

applicationinstance 实际运行的应用程序,它公开一些请求处理事件。这些事件在 global.asax、httphandler 或 httpmodule 中处理。

cache asp.net cache 对象,它提供对缓存的编程访问。rob howard 的 asp.net caching 专栏(英文)对缓存作了详尽介绍。

error 处理页时遇到的第一个错误(如果有)。有关详细信息,请参阅 rob 撰写的 exception to the rule, part 1(英文)。

items 关键字/值对集合,可以用来在参与处理同一请求的所有组件之间传递信息。items 是 system.collections.idictionary 类型。

request 有关 http 请求的信息,包括浏览器信息、cookies 以及在窗体或查询字符串中传递的值。request 是 system.web.httprequest 类型。

response 用于创建 http 响应的设置和内容。response 是 system.web.httpresponse 类型。

server 服务器是一个实用程序类,带有一些有用的帮助器方法,包括 server.execute()、server.mappath() 和 server.htmlencode()。server 是 system.web.httpserverutility 类型的对象。

session 值的关键字/值对集合,可由应用程序的单个用户访问。session 是 system.web.httpsessionstate 类型。

trace asp.net 的 trace 对象,提供对跟踪功能的访问。有关详细信息,请参阅 rob 撰写的文章 tracing(英文)。

user 当前用户(如果已经过身份验证)的安全上下文。context.user.identity 是用户的名称。user 是 system.security.principle.iprincipal 类型的对象。

如果您是一位 asp 开发人员,那么对上面讲述的部分对象应不会感到陌生。虽然有一些改进,但大体而言,它们在 asp.net 中的作用与在 asp 中是完全一样的。

context 基础知识

context 中的部分对象也已升级为 page 中的顶级对象。例如,page.context.response 和 page.response 指的是同一个对象,因此,以下代码是等价的:

[visual basic® web 窗体]

  response.write ("您好")

  context.response.write ("你好")

[c# web 窗体]

  response.write ("您好");

  context.response.write ("你好");

还可以从业务对象使用 context 对象。httpcontext.current 是静态属性,可以很方便地返回当前请求的上下文。这在各种方法中都十分有用,下面仅列举一个从业务类的缓存中检索项目的简单示例:

[visual basic]

   ' 获取请求上下文

   dim _context as httpcontext = httpcontext.current

  ' 获取缓存中的数据集

  dim _data as dataset = _context.cache("mydataset")

[c#]

   // 获取请求上下文

   httpcontext _context = httpcontext.current;

  // 获取缓存中的数据集

  dataset _data = _context.cache("mydataset");

操作中的 context

context 对象为一些常见的 asp.net“如何…?”问题提供了答案。也许,说明此宝贵对象的价值的最好方法,就是在操作中将它展示出来。下面是一些我所知道的最巧妙的 context 技巧。

我如何从自己的业务类中生成 asp.net 跟踪语句?

回答:很简单!使用 httpcontext.current 获取 context 对象,然后调用 context.trace.write()。

imports system

imports system.web

namespace context

  ' 演示从业务类中生成一个 asp.net

  ' 跟踪语句。

  public class traceemit

   

   public sub somemethod()

    

     ' 获取请求上下文

     dim _context as httpcontext = httpcontext.current

     ' 使用上下文编写跟踪语句

     _context.trace.write("在 traceemit.somemethod 中")

   end sub

  end class

end namespace 

using system;

using system.web;

{

  // 演示从业务类中生成一个 asp.net

  // 跟踪语句。

  {

    public void somemethod() {

      // 获取请求上下文

      httpcontext _context = httpcontext.current;

      // 使用上下文编写跟踪语句

      _context.trace.write("在 traceemit.somemethod 中");

    }

  }

}

如何才能从业务类中访问会话状态值?

回答:很简单!使用 httpcontext.current 获取 context 对象,然后访问 context.session。

  ' 演示从业务类中访问 asp.net 内部

  ' 会话。

  public class usesession

 

     ' 访问内部会话

     dim _value as object = _context.session("thevalue")

end namespace

  // 演示从业务类中访问 asp.net 内部

  // 会话

      // 访问内部会话

      object _value = _context.session["thevalue"];

如何才能在应用程序的每页中添加标准页眉和页脚?

回答:处理应用程序的 beginrequest 和 endrequest 事件,并使用 context.response.write 生成页眉和页脚的 html。

从技术上讲,可以在 httpmodule 中或通过使用 global.asax 处理 beginrequest 这样的应用程序。httpmodules 的编写比较困难,而且正如本例所示,简单应用程序使用的功能通常不使用它。因此,我们使用应用程序范围的 global.asax 文件。

与 asp 页一样,一些固有的 asp.net 上下文已提升为 httpapplication 类的属性,其中的类表示 global.asax 继承类。我们不需要使用 httpcontext.current 获取对 context 对象的引用;它在 global.asax. 中已可用。

本例中,我将 <html> 和 <body> 标记以及一条水平线放入页眉部分,而将另一条水平线及相应的结束标记放入页脚部分。页脚还包含版权消息。运行结果应如下图所示:

图 1:浏览器中呈现的标准页眉和页脚示例

这是一个简单的示例,但您可以很容易地将它扩展,使其包含标准的页眉与导航,或者仅输出相应的 <!-- #include ---> 语句。请注意,如果希望页眉或页脚包含交互内容,应考虑使用 asp.net 用户控件。

[somepage.aspx 源代码 - 内容示例]

<font face="arial" color="#cc66cc" size="5">

常规页面内容

</font>

[visual basic global.asax]

<%@ application language="vb" %>

<script runat="server">

   sub application_beginrequest(sender as object, e as eventargs)

     ' 生成页眉

     context.response.write("<html>" + controlchars.lf + _

"<body bgcolor=#efefef>" + controlchars.lf + "<hr>" + _ controlchars.lf)

   sub application_endrequest(sender as object, e as eventargs)

     ' 生成页脚

     context.response.write("<hr>" + controlchars.lf + _

   "2002 microsoft corporation 版权所有" + _

   controlchars.lf + "</body>" + controlchars.lf + "</html>")

</script>

[c# global.asax]

<%@ application language="c#" %>

    void application_beginrequest(object sender, eventargs e) {

      // 生成页眉

      context.response.write("<html>\n<body bgcolor=#efefef>\n<hr>\n");

    void application_endrequest(object sender, eventargs e) {

      // 生成页脚

      context.response.write("<hr>\2002 microsoft corporation 版权所有\n");

      context.response.write("</body>\n</html>");

如何在用户经过身份验证后显示欢迎信息?

回答:测试 user 上下文对象以查看用户是否经过身份验证。如果是,还要从 user 对象获取用户名。当然,这是本文开头的示例。

<script language="vb" runat="server">

  sub page_load(sender as object, e as eventargs) {

    if user.identity.isauthenticated then

      welcome.text = "欢迎" + user.identity.name

    else

      ' 尚未登录,添加一个指向登录页的链接

      welcome.text = "请登录!"

      welcome.navigateurl = "signin.aspx"

    end if

  end sub

<asp:hyperlink id="welcome" runat="server" maintainstate="false">

</asp:hyperlink>

<script language="c#" runat="server">

  void page_load(object sender, eventargs e) {

    if (user.identity.isauthenticated) {

      welcome.text = "欢迎" + user.identity.name;

    else {

      // 尚未登录,添加一个指向登录页的链接

      welcome.text = "请登录!";

      welcome.navigateurl = "signin.aspx";

context.items 简介

希望以上示例可以说明,使用手头仅有的上下文信息编写 web 应用程序是多么容易。那么,如果可以用同样的方法访问您应用程序独有的一些上下文,不是很好吗?

这就是 context.items 集合的用途。它使用在参与处理请求的各部分代码中都可用的方法,保存应用程序的请求特有值。例如,同样一条信息可以用在 global.asax、aspx 页、页内的用户控件中,也可以由页调用的业务逻辑使用。

请考虑 ibuyspy portal(英文)应用程序示例。它使用一个简单的主页 desktopdefault.aspx 来显示门户内容。显示的内容取决于所选择的选项卡,以及用户(如果已经过身份验证)角色。

图 2:ibuyspy 主页

查询字符串包含正被请求的选项卡的 tabindedx 和 tabid 参数。在处理请求的整个过程中,一直使用此信息筛选要显示给用户的数据。http://www.ibuyspyportal.com/desktopdefault.aspx?tabindex=1&tabid=2(英文)

要使用查询字符串值,需要首先确保它是一个有效值,如果不是,则要进行一些错误处理。它并不是一大串代码,但是您真的要在每个使用该值的页和组件中复制它吗?当然不!在 portal 示例中,甚至更多的地方都涉及到它,因为一旦我们知道了 tabid,就可以预先加载其他信息。

portal 使用查询字符串值作为参数,以构造一个新的 portalsettings 对象,并将它添加到 global.asax 的 beginrequest 事件的 context.items 中。由于在每个请求开始处都执行了开始请求,这使得与该选项卡有关的值在应用程序的所有页和组件中都可用。请求完成后,对象将被自动丢弃 - 非常整齐!

   sub application_beginrequest(sender as [object], e as eventargs)

     dim tabindex as integer = 0

     dim tabid as integer = 0

     ' 从查询字符串获取 tabindex

     if not (request.params("tabindex") is nothing) then

      tabindex = int32.parse(request.params("tabindex"))

     end if

     ' 从查询字符串获取 tabid

     if not (request.params("tabid") is nothing) then

      tabid = int32.parse(request.params("tabid"))

     context.items.add("portalsettings", _

new portalsettings(tabindex, tabid))

void application_beginrequest(object sender, eventargs e) {

  int tabindex = 0;

  int tabid = 0;

  // 从查询字符串获取 tabindex

  if (request.params["tabindex"] != null) {       

    tabindex = int32.parse(request.params["tabindex"]);

        

  // 从查询字符串获取 tabid

  if (request.params["tabid"] != null) {       

    tabid = int32.parse(request.params["tabid"]);

  context.items.add("portalsettings",

new portalsettings(tabindex, tabid));

desktopportalbanner.ascx 用户控件从 context 请求 portalsetting 的对象,以访问 portal 的名称和安全设置。事实上,此模块是操作中的 context 的一个典型综合示例。为阐明这一点,我已将代码进行了一些简化,并用粗体标记了 http 或应用程序特定的 context 被访问过的所有地方。

[c# desktopportalbanner.ascx]

<%@ import namespace="aspnetportal" %>

<%@ import namespace="system.data.sqlclient" %>

  public int     tabindex;

  public bool     showtabs = true;

  protected string  logofflink = "";

    // 从当前上下文获取 portalsettings

 portalsettings portalsettings =

(portalsettings) context.items["portalsettings"];

    // 动态填充门户站点名称

    sitename.text = portalsettings.portalname;

    // 如果用户已登录,自定义欢迎信息

    if (request.isauthenticated == true) {

      welcomemessage.text = "欢迎" +

context.user.identity.name + "!<" +

"span class=accent" + ">|<" + "/span" + ">";

      // 如果身份验证模式为 cookie,则提供一个注销链接

      if (context.user.identity.authenticationtype == "forms") {

        logofflink = "<" + "span class=\"accent\">|</span>\n" +

"<a href=" + request.applicationpath +

"/admin/logoff.aspx class=sitelink> 注销" +

"</a>";

      }

    // 动态显示门户选项卡条

    if (showtabs == true) {

      tabindex = portalsettings.activetab.tabindex;

      // 生成要向用户显示的选项卡列表                 

      arraylist authorizedtabs = new arraylist();

      int addedtabs = 0;

      for (int i=0; i < portalsettings.desktoptabs.count; i++) {

      

        tabstripdetails tab =

(tabstripdetails)portalsettings.desktoptabs[i];

        if (portalsecurity.isinroles(tab.authorizedroles)) {

          authorizedtabs.add(tab);

        }

        if (addedtabs == tabindex) {

          tabs.selectedindex = addedtabs;

        addedtabs++;

      }     

      // 用已授权的选项卡填充页顶部的选项卡

// 列表

      tabs.datasource = authorizedtabs;

      tabs.databind();

<table width="100%" cellspacing="0" class="headbg" border="0">

  <tr valign="top">

    <td colspan="3" align="right">

      <asp:label id="welcomemessage" runat="server" />

      <a href="<%= request.applicationpath %>">portal 主页</a>

<span class="accent"> |</span>

<a href="<%= request.applicationpath %>/docs/docs.htm">

        portal 文档</a>

      <%= logofflink %>

        

    </td>

  </tr>

  <tr>

    <td width="10" rowspan="2">

       

    <td height="40">

      <asp:label id="sitename" runat="server" />

    <td align="center" rowspan="2">

    

    <td>

      <asp:datalist id="tabs" runat="server">

        <itemtemplate>

          

<a href='<%= request.applicationpath %>

/desktopdefault.aspx?tabindex=<%# container.itemindex %>&tabid=

<%# ((tabstripdetails) container.dataitem).tabid %>'>

<%# ((tabstripdetails) container.dataitem).tabname %>

</a> 

        </itemtemplate>

        <selecteditemtemplate>

         <span class="selectedtab">

</span> 

        </selecteditemtemplate>

      </asp:datalist>

</table>

您可以使用 visual basic 和 c# 在 http://www.ibuyspy.com(英文)联机浏览并运行 ibuyspy portal 的完整源文件,或者下载后再运行。

小结

context 是 asp.net 中的又一个“精益求精”的功能。它扩展了 asp 的已经很不错的上下文支持,以便将两个挂钩添加到 asp.net 的新运行时功能中。同时添加了 context.items,作为短期值的新状态机制。但对于开发人员,此功能的最大好处是使代码更紧凑,且易于维护,而且此上下文我们都能看懂。