使用 HttpApplication 对象
ASP.NET 框架中的许多类都提供了许多很方便的属性可以直接映射到 HttpContext 类中定义的属性。这种交叠有一个很好的例子就是 HttpApplication,它就是全局应用类的基类,在下表中,你可以看到 HttpApplication 类中定义的属性和方法。许多和 HttpContext 中定义的很相似。
表 1 – HttpApplication 类中定义的成员
名称 | 描述 |
Application | 对应到 HttpContext.Application 属性,通过它可以获取应用层面的状态数据。 |
CompleteRequest() | 终止当前请求的生命周期,直接跳转到 LogRequest 事件。 |
Context | 返回当前请求的 HttpContext 对象。 |
Init() | 每个注册模块上的 Init() 方法调用之后调用。 |
Modules | 返回一个 HttpModuleCollection 对象,当中详细描述了当前应用中的模块。 |
RegisterModule(type) | 注册新模块的静态方法。 |
Request | 返回 HttpContext.Request 值, 但是如果值为 null 的时候会抛出一个 HttpException。 |
Response | 返回 HttpContext.Response 值,但是如果值为 null 的时候会抛出一个 HttpException。 |
Server | 映射到 HttpContext.Server。 |
Session | 返回 HttpContext.Session 值,但是如果值为 null 的时候会抛出一个 HttpException。 |
大多数的这些成员都是很方便的属性可以映射到 HttpContext 类中的属性,但是有三个值得注意,接下来详细说明。
处理属性异常
Request, Response, Session 和 User 属性返回的是 HttpContext 类中相对应的属性的值,但是,这些属性如果从 HttpContext 上对应的属性中获取的值是 null 就会抛出一个 HttpException。
发生这样的事是因为 HttpApplication 类会为两种不同的生命周期接收通知:应用生命周期和请求生命周期。描述单个请求的对象是不可以在全局应用类中用来处理应用相关的事件,所以如果我们在处理应用层级的通知的时候使用了与请求相关联的属性就会抛出 HttpException 异常。
抛出这样一个异常的策略是非常粗糙的,因为这使得处理未知来源的 HttpApplication 对象非常困难,我们可以看一下下面的代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 using System.Web.Routing; 7 8 namespace SimpleApp 9 { 10 public class MvcApplication : System.Web.HttpApplication 11 { 12 public MvcApplication() 13 { 14 PostAcquireRequestState += (src, args) => CreateTimeStamp(); 15 } 16 17 protected void Application_Start() 18 { 19 AreaRegistration.RegisterAllAreas(); 20 RouteConfig.RegisterRoutes(RouteTable.Routes); 21 CreateTimeStamp(); 22 } 23 24 private void CreateTimeStamp() 25 { 26 string stamp = Context.Timestamp.ToLongTimeString(); 27 if (Session != null) 28 { 29 Session["request_timestamp"] = stamp; 30 } 31 else 32 { 33 Application["app_timestamp"] = stamp; 34 } 35 } 36 } 37 }
现在,我们删除我们之前写的代码,重新定义一个新的方法用来创建时间戳并将其存放在状态数据中。现在,我们不需要对状态数据了解太多,只需要知道 Session 属性会为每一个独立的请求返回状态数据对象。
提示:注意到,我为 PostAcquireRequestState 事件创建了请求时间戳。这是因为模块直到 AcquireRequestState 事件触发之后才会提供状态数据服务。因此,即使全局应用类实例创建出来用来处理请求相关的事件,Session 属性也会返回 null。
这个新方法叫做 CreateTimeStamp,旨在减少代码冗余并为应用层级和请求层级的事件添加事件戳。但是,只要现在应用一启动就会报错,因为在这段代码中,在全局应用类创建出来处理 Application_Start 方法的时候想要尝试读取 Session 属性。
我能够使用 try…catch 代码块,但是这是一个糟糕的实践,因为异常处理不应该用来管理一个应用中可预见的流程。因此,我们应该直接使用 HttpContext 中同等的属性。代码如下:
1 private void CreateTimeStamp() 2 { 3 string stamp = Context.Timestamp.ToLongTimeString(); 4 if (Context.Session != null) 5 { 6 Session["request_timestamp"] = stamp; 7 } 8 else 9 { 10 Application["app_timestamp"] = stamp; 11 } 12 }
注意到,我只是修改了之前对 Session 属性的检查;如果它不为 null,我就可以使用 HttpApplication 类中定义的 Session 属性。Application 属性总是能够返回 HttpApplicationState 对象,状态管理相关的知识我们会在后期讲解到。
我们需要注意这种问题特别是在全局应用类中不与独立请求相关联的对象。ASP.NET 其他地方,特别是在 MVC 框架 controller 类中。我们总是可以使用 context 来表示一个请求。我们可以看一下下面的例子:
1 using SimpleApp.Models; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namespace SimpleApp.Controllers 9 { 10 public class HomeController : Controller 11 { 12 public ActionResult Index() 13 { 14 return View(GetTimeStamps()); 15 } 16 17 [HttpPost] 18 public ActionResult Index(Color color) 19 { 20 Color? oldColor = Session["color"] as Color?; 21 if (oldColor != null) 22 { 23 Votes.ChangeVote(color, (Color)oldColor); 24 } 25 else 26 { 27 Votes.RecordVote(color); 28 } 29 30 ViewBag.SelectedColor = Session["color"] = color; 31 return View(GetTimeStamps()); 32 } 33 34 private List<string> GetTimeStamps() 35 { 36 return new List<string> { String.Format("App timestamp: {0}", HttpContext.Application["app_timestamp"]), 37 String.Format("Request timestamp: {0}", Session["request_timestamp"])}; 38 } 39 } 40 }
Controller 类是 MVC 框架的基础,当中提供了一个方便的 Session 属性与 HttpContext.Session 相关联(但是我需要使用 HttpContext.Application,因为它并没有相关联的简便的属性)。
你可以在应用启动的时候看到时间戳。因为两个值在起初的时候很可能是相同的,但是如果你重新刷新浏览器窗口的话,你会发现应用时间戳一直不变,而请求时间戳会在每次更新的时候发生改变。
图 1 - 展示应用和请求时间戳
[根据 Adam Freeman – Pro ASP.NET MVC 5 Platform 选译]