相关文章推荐
正直的凉茶  ·  ThreadPool 类 ...·  3 月前    · 
沉稳的萝卜  ·  anaconda 安装pymysql-掘金·  1 年前    · 

了解ASP.NET的开发人员都知道它有个非常强大的对象 HttpContext,而且为了方便,ASP.NET还为它提供了一个静态属性HttpContext.Current来访问它, 今天的博客打算就从HttpContext.Current说起。

由于ASP.NET提供了静态属性HttpContext.Current,因此获取HttpContext对象就非常方便了。
也正是因为这个原因,所以我们经常能见到直接访问HttpContext.Current的代码:

public class Class1
    public Class1()
        string file = HttpContext.Current.Request.MapPath("~/App_Data/xxxxxx.xml");
        string text = System.IO.File.ReadAllText(file);
        //..........其它的操作
    // 或者在一些方法中直接使用HttpContext.Current
    public void XXXXX()
        string url = HttpContext.Current.Request.RawUrl;
        string username = HttpContext.Current.Session["username"].ToString();
        string value = (string)HttpContext.Current.Items["key"];
    // 甚至还设计成静态属性
    public static string XXX
            return (string)HttpContext.Current.Items["XXX"];

这样的代码,经常能在类库项目中看到,由此可见其泛滥程度。

难道这些代码真的没有问题吗?
有人估计会说:我写的代码是给ASP.NET程序使用的,又不是给控制台程序使用,所以没有问题。

真的是这样吗?

protected void Page_Load(object sender, EventArgs e)
    HttpContext context1 = HttpContext.Current;
    HttpContext context2 = System.Runtime.Remoting.Messaging.CallContext.HostContext as HttpContext;
    bool isEqual = object.ReferenceEquals(context1, context2);
    Response.Write(isEqual);

猜猜会显示什么?

我们在一个ASP.NET程序中,为什么可以到处访问HttpContext.Current呢?
因为ASP.NET会为每个请求分配一个线程,这个线程会执行我们的代码来生成响应结果, 即使我们的代码散落在不同的地方(类库),线程仍然会执行它们, 所以,我们可以在任何地方访问HttpContext.Current获取到与【当前请求】相关的HttpContext对象, 毕竟这些代码是由同一个线程来执行的嘛,所以得到的HttpContext引用也就是我们期待的那个与请求相关的对象。

因此,将HttpContext.Current设计成与【当前线程】相关联是合适的。

显然,在1,2二种情况中,访问HttpContext.Current将会返回 null 。
因为很有可能任务在运行时根本没有任何请求发生。
了解异步的人应该能很容易理解第3种情况(就当是个结论吧)
第4种情况就更不需要解释了,因为确实不是当前线程。

既然是这样,那我们再看一下本文开头的一段代码:

public Class1()
    string file = HttpContext.Current.Request.MapPath("~/App_Data/xxxxxx.xml");
    string text = System.IO.File.ReadAllText(file);
    //..........其它的操作

想像一下:如果Class1是在定时器回调或者Cache的移除通知时被创建的,您认为它还能正常运行吗?

此刻您心里应该有答案了吧?

可能您会想:为什么我在其它任何地方又可以访问HttpContext.Current得到HttpContext引用呢?
答:那是因为ASP.NET在调用您的代码前,已经将HttpContext设置到前面所说的CallContext.HostContext属性中。
HttpApplication有个内部方法OnThreadEnter(),ASP.NET在调用外部代码前会调用这个方法来切换HttpContext, 例如:每当执行管线的事件处理器之前,或者同步上下文(AspNetSynchronizationContext)执行回调时。 切换线程的CallContext.HostContext属性之后,我们的代码就可以访问到HttpContext引用。 注意:HttpContext的引用其实是保存在HttpApplication对象中。

有时候我们会见到【ASP.NET线程】这个词,今天正好来说说我对这个词的理解: 当前线程是与一个HttpContext相关的线程,由于线程与HttpContext相关联,也就意味着它正在处理发送给ASP.NET的请求。 注意:这个线程仍然是线程池的线程。

在定时器回调或者Cache的移除通知中,有时确实需要访问文件,然而对于开发人员来说, 他们并不知道网站会被部署在哪个目录下,因此不可能写出绝对路径, 他们只知道相对于网站根目录的相对路径,为了定位文件路径,只能调用HttpContext.Current.Request.MapPath或者 HttpContext.Current.Server.MapPath来获取文件的绝对路径。 如果HttpContext.Current返回了null,那该如何如何访问文件?

其实方法并非MapPath一种,我们可以访问HttpRuntime.AppDomainAppPath获取网站的路径,然后再拼接文件的相对路径即可:

有时我们会写些通用类库给ASP.NET或者WindowsService程序来使用,例如异常记录的工具方法。 对于ASP.NET程序来说,我们肯定希望在异常发生时,能记录URL,表单值,Cookie等等数据,便于事后分析。 然而对于WindowsService这类程序来说,您肯定没想过要记录Cookie吧? 那么如何实现一个通用的功能呢?

方法其实也简单,就是要判断HttpContext.Current是否返回null,例如下面的示例代码:

public static void LogException(Exception ex)
    StringBuilder sb = new StringBuilder();
    sb.Append("异常发生时间:").AppendLine(DateTime.Now.ToString());
    sb.AppendLine(ex.ToString());
    // 如果是ASP.NET程序,还需要记录URL,FORM, COOKIE之类的数据
    HttpContext context = HttpContext.Current;
    if( context != null ) {
        // 能运行到这里,就肯定是在处理ASP.NET请求,我们可以放心地访问Request的所有数据
        sb.AppendLine("Url:" + context.Request.RawUrl);
        // 还有记录什么数据,您自己来实现吧。
    System.IO.File.AppendAllText("日志文件路径", sb.ToString());

就是一个判断,解决了所有问题,所以请忘记下面这类不安全的写法吧:

HttpContext.Current.Request.RawUrl;
HttpContext.Current.Server.MapPath("xxxxxx");

下面的方法才是安全的:

HttpContext context = HttpContext.Current;
if( context != null ) {
    // 在这里访问与请求有关的东西。
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下右下角的关注 Fish Li
因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是Fish Li 。