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()。
[Visual Basic]
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
[C#]
using System;
using System.Web;
namespace Context
{
// 演示从业务类中生成一个 ASP.NET
// 跟踪语句。
public class TraceEmit
{
public void SomeMethod() {
// 获取请求上下文
HttpContext _context = HttpContext.Current;
// 使用上下文编写跟踪语句
_context.Trace.Write("在 TraceEmit.SomeMethod 中");
}
}
}
如何才能从业务类中访问会话状态值?
回答:很简单!使用 HttpContext.Current 获取 Context 对象,然后访问 Context.Session。
[Visual Basic]
Imports System
Imports System.Web
Namespace Context
' 演示从业务类中访问 ASP.NET 内部
' 会话。
Public Class UseSession
Public Sub SomeMethod()
' 获取请求上下文
Dim _context As HttpContext = HttpContext.Current
' 访问内部会话
Dim _value As Object = _context.Session("TheValue")
End Sub
End Class
End Namespace
[C#]
using System;
using System.Web;
namespace Context
{
// 演示从业务类中访问 ASP.NET 内部
// 会话
public class UseSession
{
public void SomeMethod() {
// 获取请求上下文
HttpContext _context = HttpContext.Current;
// 访问内部会话
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)
End Sub
Sub Application_EndRequest(sender As Object, e As EventArgs)
' 生成页脚
Context.Response.Write("<hr>" + ControlChars.Lf + _
"2002 Microsoft Corporation 版权所有" + _
ControlChars.Lf + "</body>" + ControlChars.Lf + "</html>")
End Sub
</script>
[C# Global.asax]
<%@ Application Language="C#" %>
<script runat="server">
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>");
}
</script>
如何在用户经过身份验证后显示欢迎信息?
回答:测试 User 上下文对象以查看用户是否经过身份验证。如果是,还要从 User 对象获取用户名。当然,这是本文开头的示例。
[Visual Basic]
<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
</script>
<asp:HyperLink id="welcome" runat="server" maintainstate="false">
</asp:HyperLink>
[C#]
<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";
}
}
</script>
<asp:HyperLink id="welcome" runat="server" maintainstate="false">
</asp:HyperLink>
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 中。由于在每个请求开始处都执行了开始请求,这使得与该选项卡有关的值在应用程序的所有页和组件中都可用。请求完成后,对象将被自动丢弃 - 非常整齐!
[Visual Basic Global.asax]
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"))
End If
Context.Items.Add("PortalSettings", _
New PortalSettings(tabIndex, tabId))
End Sub
[C# Global.asax]
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" %>
<script language="C#" runat="server">
public int tabIndex;
public bool ShowTabs = true;
protected String LogoffLink = "";
void Page_Load(Object sender, EventArgs e) {
// 从当前上下文获取 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();
}
}
</script>
<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>
<td height="40">
<asp:label id="siteName" runat="server" />
</td>
<td align="center" rowspan="2">
</td>
</tr>
<tr>
<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">
<%# ((TabStripDetails) Container.DataItem).TabName %>
</span>
</SelectedItemTemplate>
</asp:datalist>
</td>
</tr>
</table>
您可以使用 Visual Basic 和 C# 在 http://www.ibuyspy.com(英文)联机浏览并运行 IBuySpy Portal 的完整源文件,或者下载后再运行。
小结
Context 是 ASP.NET 中的又一个“精益求精”的功能。它扩展了 ASP 的已经很不错的上下文支持,以便将两个挂钩添加到 ASP.NET 的新运行时功能中。同时添加了 Context.Items,作为短期值的新状态机制。但对于开发人员,此功能的最大好处是使代码更紧凑,且易于维护,而且此上下文我们都能看懂。
分享到:
相关推荐
安卓开发关于上下文的一个说明可以很好了解上下文
基于形状上下文的形状匹配,关于形状匹配的资料
关于上下文感知的移动视觉识别的调查
这篇文章我们主要讲述了:上下文的理论和基于上下文(COT)的操作转换算法。COT算法是一种能够支持do和某一操作的任一形式的undo的算法,而且COT算法不要求转换函数满足可逆性、收敛性质2以及反向性质2和3。COT算法...
关于编译原理试验 中上下文无关文法求first集合以及follow集合LL(1)文法判断
词嵌入表示向量是自然语言处理的重要组成部分。最近来自牛津大学和DeepMind等撰写了关于上下文嵌入表示的综述论文,详述了当前预训练模型的代表性工作等。
关于基于上下文信息预测对象的轨迹的介绍说明.rar
关于使用上下文和深度排序的部分遮挡对象检测的说明.rar
包含 获取上下文 加载布局 dp px转换 等一些关于界面的工具方法
关于用于为位置提供上下文信息的系统和方法的介绍说明.rar
了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很多博文有关于对flask上下文管理的...
服务上下文是实现对用户透明的服务容错以及降低服务恢复代价的关键,然而现有研究缺乏关于服务恢复的上下文分类和表示方法。针对该问题,提出了面向服务恢复的上下文归类、表示和持久化方法。利用一定的应用场景对上...
关于上下文.doc 创建可分页、可排序的 DataGrid.doc 创建用于 ASP.NET 的分页程序控件.doc 创建用于ASP.NET的分页控件.txt 利用 ASP.NET 创建多页自定义报表.doc 利用属性扩展元数据.doc 在 ASP.NET 中实现...
4.2.2 关于上下文的详细说明 43 4.2.3 回顾以前的几个功能 44 4.3 对数组进行操作 45 4.3.1 遍历数组 46 4.3.2 在数组与标量之间进行转换 46 4.3.3 给数组重新排序 48 4.4 练习:做一个小游戏 49 4.5 课时小...
§8.3 关于上下文的使用 196 §8.3.1 设置上下文选项 196 §8.3.2 为上下文查询设置表 197 §8.3.3 优化文本索引 199 §8.4 关于维数(DIMENSION) 199 §8.4.1 CREATE DIMENSION语法 200 §8.4.2 创建维的例子 201 ...
4.2.2 关于上下文的详细说明 43 4.2.3 回顾以前的几个功能 44 4.3 对数组进行操作 45 4.3.1 遍历数组 46 4.3.2 在数组与标量之间进行转换 46 4.3.3 给数组重新排序 48 4.4 练习:做一个小游戏 49 4.5 课时小结 51 ...
4.2.2 关于上下文的详细说明 43 4.2.3 回顾以前的几个功能 44 4.3 对数组进行操作 45 4.3.1 遍历数组 46 4.3.2 在数组与标量之间进行转换 46 4.3.3 给数组重新排序 48 4.4 练习:做一个小游戏 49 4.5 课时小结 51 ...
4.2.2 关于上下文的详细说明 43 4.2.3 回顾以前的几个功能 44 4.3 对数组进行操作 45 4.3.1 遍历数组 46 4.3.2 在数组与标量之间进行转换 46 4.3.3 给数组重新排序 48 4.4 练习:做一个小游戏 49 4.5 课时小结 51 ...
4.2.2 关于上下文的详细说明 43 4.2.3 回顾以前的几个功能 44 4.3 对数组进行操作 45 4.3.1 遍历数组 46 4.3.2 在数组与标量之间进行转换 46 4.3.3 给数组重新排序 48 4.4 练习:做一个小游戏 49 4.5 课时小结 51 ...