HttpContext.Items with ASP.NET MVC
Asked Answered
O

3

39

I'm implimenting my own ApplicationContext class that uses the singleton pattern. I want to store my instance of it in HttpContext.Items, since it is accessible in all parts of the request. I've been reading about using HttpContext with ASP.NET MVC and one of the major pains is that it introduces testing complexity. I've tried doing research on the testability of HttpContext.Items, but all I can find is stuff on Session. One of the only things I've found is out of a sample chapter in the Professional ASP.NET 3.5 MVC book on Wrox (pdf link here). On page 15 it says this:

Something You Can’t Use: HttpContext.Items
Above in this section, we came clean and told you that we lied to you: HttpContext is not shared between ASP.NET MVC and ASP.NET Web Forms. As a result of this, you cannot use the HttpContext.Items collection to store and retrieve bits of data.

The reason for this is because once you redirect to a Controller, your HttpHandler becomes the System.Web.Mvc.MvcHandler, which is created using HttpContextWrapper, which will have its own definition of HttpContext.Current. Unfortunately, during this handshake, things like HttpContext.Items are not transferred.

What this boils down to is that the HttpContext types, despite looking and sounding very much the same, are not the same, and you cannot pass data in this way.

Now, I've tried testing this out, and as far as I can tell, if you redirect to another controller using RedirectToAction, HttpContext.Items does remain. I'm using the default ASP.NET MVC project to test this. What I've done is, add this method to Global.asax.cs:

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

And in HomeController.cs, I've changed the Index method to:

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

And changed the About method to:

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

When I run the application, the page properly redirects to /Home/About and Response.Writes the correct "Hello World" string set in the global.asax.cs.

So, it seems to me as if I'm either not understanding what the book is meaning when they say "things like HttpContext.Items are not transferred" OR it does transfer this stuff and it's okay to use HttpContext.Items.

If you guys recommend that I avoid HttpContext.Items, is there another alternative way to store an object across a request on a per-request basis?

Orthochromatic answered 15/7, 2009 at 22:19 Comment(1)
This is quite a useful resource for creating an "Application context" type object: jcapka.blogspot.co.uk/2013/11/…Mojave
I
49

Your question is asking a few things but I think item #1 is the answer you're looking for.

  1. Is it fine to use Context.Items for caching on a per request basis? Yes. If in process, per request, per machine in the web farm is your criteria then Context.Items gives you that.

  2. Is Context.Items difficult to test with? As far as testability, I would hide Context.Items behind an interface of some sort. This way you get unit testing capabilities without having to reference Context.Items directly. Otherwise, what do you need to test about Context.Items? That the framework will store and retrieve values? Keep your code ignorant of System.Web and you'll be a happy camper.

  3. Will Context.Items survive RedirectToAction? No. Your test is invalid. It's setting "Hello, world" on every web request and your test spans two web requests. The first is when the Index action is called. The second is when RedirectToAction action is called (it's an HTTP 302). To make it fail, set a new value in the Index action and see if it's retained in the About action.

Indecorum answered 16/7, 2009 at 6:14 Comment(3)
Bingo! You are right. Of course I didn't realize it, because I was setting the context item in BeginRequest, which is run twice, once when Index is called and then again after Index redirects to About. I suppose though that for my scenario (making a singleton which instance is stored in Request.Items), I will be fine with that behavior, as the object will just get rebuilt when I redirect. Thanks again for your answer!Orthochromatic
Ryan - "making a singleton which instance is stored in Request.Items," can you please elaborate with sample code how you do that?Canoewood
"I would hide Context.Items behind an interface of some sort" or you could use the HttpContextBase or the HttpContextWrapper classes, both of which (being abstract) are mockable.Derosier
M
4

Use the TempData Dictionary, it is mainly for storing objects between Actions redirects:

public ActionResult Index()
{
    TempData.Add("Test", "Hello world");
    return RedirectToAction("About");
}

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

Then retrieve the value in your view:

<%=ViewData["Test"] %>
Mauser answered 15/7, 2009 at 23:0 Comment(1)
Thanks for the answer, but I don't want to use Session state. This is because my app may be running under a web farm, and session state's performance is not good when run out of process. (TempData stores it's values in SessionState)Orthochromatic
E
1

I did a test and TempData does, indeed, explode with session state disabled. My only advice would be to not store the object itself in temp data but store the simple typed fields as has been suggested. Since you're not serializing object trees it shouldn't be that big of a performance impact running out-of-process.

Ectoderm answered 16/7, 2009 at 1:51 Comment(5)
Well it actually is a performance impact. If you run a web farm, you need to share session across the whole farm (because on one request you might hit server A, and then in the next request you could be routed to server B). This is normally accomplished using the SQL Server session provider which is totally slow and crappy compared to in process. It's so slow and crappy that everyone should avoid it with a 10 foot pole. Or longer if you've got something handy ;).Orthochromatic
Also too, storing the object even in TempData will bring it across requests. I don't want that. I want to store it only for the current request.Orthochromatic
FYI: It is possible to disable TempData entirely by writing a custom temp data provider that does nothing at all. That the default provider blows up when session state is disabled is in fact just a bug in ASP.NET MVC 1.0 and we have already fixed it for the next release of ASP.NET MVC!Eri
@Ryan: I didn't say it wasn't an impact. I said it shouldn't be that big of one, but I appreciate where you're coming from. @Eilon: Knew the first part, didn't know the second. Good to hear.Ectoderm
For any readers that check out this message, here is a blog post on how to have a tempdata provider that doesn't store anything: billrob.com/archive/2009/07/15/….Orthochromatic

© 2022 - 2024 — McMap. All rights reserved.