ASP.NET MVC中的HttpContext.Items

39
我正在实现自己的ApplicationContext类,使用单例模式。我想将它的实例存储在HttpContext.Items中,因为它可以在请求的所有部分中访问。我一直在阅读有关在ASP.NET MVC中使用HttpContext的内容,其中一个主要问题是它引入了测试复杂性。我已经尝试研究HttpContext.Items的可测试性,但我能找到的只有关于Session的东西。我在Wrox的《Professional ASP.NET 3.5 MVC》书的示例章节中找到了一些信息(pdf链接在这里)。第15页上说:

无法使用的内容:HttpContext.Items
在本节中,我们坦白告诉您,我们对您撒谎了:HttpContext在ASP.NET MVC和ASP.NET Web Forms之间不共享。因此,您不能使用HttpContext.Items集合来存储和检索数据位。

原因是一旦重定向到控制器,您的HttpHandler就变成了System.Web.Mvc.MvcHandler,它是使用HttpContextWrapper创建的,该HttpContextWrapper将具有其自己的HttpContext.Current定义。不幸的是,在这个握手期间,像HttpContext.Items这样的东西没有被传输。

这意味着HttpContext类型,尽管看起来和听起来非常相似,但并不相同,您不能以这种方式传递数据。

现在,我已经尝试过测试这个问题,并且据我所知,如果使用RedirectToAction重定向到另一个控制器,HttpContext.Items仍然存在。 我正在使用默认的ASP.NET MVC项目进行测试。 我所做的是,在Global.asax.cs中添加了此方法:

protected void Application_BeginRequest()
{
    Context.Items["Test"] = "Hello World";
}

在HomeController.cs中,我已将Index方法更改为:

public ActionResult Index()
{
    return RedirectToAction("About");
}

并将 About 方法更改为:

public ActionResult About()
{
    Response.Write(Convert.ToString(HttpContext.Items["Test"]));
    return View();
}

当我运行应用程序时,页面会正确重定向到 /Home/About 并在 global.asax.cs 中设置的 Response.Write 正确显示 "Hello World" 字符串。因此,对我来说似乎我不理解书中所说的“像 HttpContext.Items 这样的东西不会被传输”,或者它确实传输了这些内容,并且可以使用 HttpContext.Items。
如果您建议我避免使用 HttpContext.Items,是否有另一种替代方法以按每个请求存储对象?

这是一个非常有用的资源,用于创建“应用程序上下文”类型的对象:http://jcapka.blogspot.co.uk/2013/11/creating-application-context-for-net.html - JDandChips
3个回答

48

您的问题包含几个方面,但我认为第一项是您需要的答案。

  1. 使用Context.Items来基于每个请求缓存是否可行? 是的。如果在进程内、每个请求、每台机器在Web Farm中是您的标准,那么Context.Items可以实现这一点。

  2. 使用Context.Items难以进行测试吗? 就可测试性而言,我会在某种接口后面隐藏Context.Items。这样,您就可以获得单元测试的功能,而无需直接引用Context.Items。否则,您需要测试Context.Items的什么内容?框架是否会存储和检索值?让您的代码不了解System.Web,您将会很满意。

  3. Context.Items能够在RedirectToAction之后存在吗? 不行。您的测试是无效的。它在每个Web请求上设置“Hello, world”,而且您的测试涉及两个Web请求。第一个是调用Index操作时。第二个是调用RedirectToAction操作时(这是一个HTTP 302)。为了让它失败,请在Index操作中设置一个新值,并查看它是否被保留在About操作中。


1
太好了!你是对的。当然我没有意识到这一点,因为我在BeginRequest中设置了上下文项,它会运行两次,一次是在调用Index时,另一次是在Index重定向到About后。不过,我想对于我的场景(创建一个单例,其实例存储在Request.Items中),这种行为应该没问题,因为对象将在重定向时重新构建。再次感谢你的答案! - Ryan Hoffman
Ryan - “制作一个单例,其实例存储在Request.Items中”,您能否提供示例代码来说明如何实现? - Picflight
我会将 Context.Items 封装在某个接口后面,或者使用 HttpContextBase 或 HttpContextWrapper 类,这两个类(作为抽象类)都可以进行模拟。 - Liam

4
使用TempData字典,主要用于在操作重定向之间存储对象:
public ActionResult Index()
{
    TempData.Add("Test", "Hello world");
    return RedirectToAction("About");
}

public ActionResult About()
{
    ViewData["Test"] = TempData["Test"];
    return View();
}

然后在您的视图中检索该值:
<%=ViewData["Test"] %>

4
谢谢您的回答,但我不想使用Session状态。这是因为我的应用程序可能在Web Farm下运行,而当超出进程运行时,Session状态的性能不好。(TempData将其值存储在SessionState中) - Ryan Hoffman

1

我进行了测试,确实在禁用会话状态时TempData会爆炸。我的唯一建议是不要将对象本身存储在temp data中,而是像建议的那样存储简单类型的字段。由于您没有序列化对象树,因此在进程外运行时不应该对性能产生太大影响。


1
实际上,这确实会影响性能。如果你运行一个 Web 农场,你需要在整个农场之间共享 session(因为在一个请求中,你可能会访问服务器 A,而在下一个请求中,你可能会被路由到服务器 B)。这通常是通过使用 SQL Server 会话提供程序来实现的,但与进程内相比非常慢和糟糕。它很慢,很糟糕,每个人都应该避免它。如果你有什么方便的东西,甚至可以用更长的东西。 - Ryan Hoffman
此外,即使将对象存储在TempData中,它也会跨请求带来。我不想要这个。我只想将其存储为当前请求。 - Ryan Hoffman
FYI:通过编写一个什么都不做的自定义Temp数据提供程序,完全可以禁用TempData。事实上,当会话状态被禁用时,默认提供程序会崩溃,这实际上只是ASP.NET MVC 1.0中的一个错误,我们已经在下一个版本的ASP.NET MVC中修复了它! - Eilon
@Ryan:我并没有说这不会有影响。我只是认为它不应该那么大,但我很欣赏你的想法。@Eilon:知道第一部分,不知道第二部分。很高兴听到这个消息。 - andymeadows
对于任何查看此消息的读者,这里有一篇关于如何拥有一个不存储任何内容的TempData提供程序的博客文章:http://billrob.com/archive/2009/07/15/asp-net-mvc-without-sessionstate.aspx。 - Ryan Hoffman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接