您的位置:首页 > 编程语言 > ASP

ASP.NET MVC: Do You Know Where Your TempData Is?

2010-08-07 20:07 686 查看

ASP.NET MVC: Do You Know Where Your TempData Is?

I recently discovered that despite the fact that I’d been using the TempData dictionary in my applications,I didn’t really have a full grasp on what it was doing behind the scenes. Of course this meant learning the lesson the hard way once something stopped working like I thought it would. I only really had myself to blame since in the end it was a simple case of RTFM,but it seemed like a good opportunity to dig in and see how TempData really works.

What Is TempData?
Let’s start by describing what TempData gives you. Basically,it’s a bucket where you can dump data that is only needed for the following request. That is,anything you put into TempData is discarded after the next request completes. This is useful for one-time messages,such as form validation errors. The important thing to take note of there is that this applies to the next request in the session,so that request can potentially happen in a different browser window or tab. If you need something more persistent,Session is likely what you’re looking for.

Where Is TempData Stored?
This is the part that came back to bite me. By default,TempData is stored in the session. Yes,the session! Most of the time this probably doesn’t matter to you,since as long as you get your objects back when you want them you have no reason to worry about where it was kept. However,let’s say you decide you want to switch away from the default Session-State Mode,and use State Server Mode or SQL Server Mode. These modes require that all objects stored in session be serializable. This is exactly what I did,and without knowing that TempData used the session,it was non-obvious why I started seeing session errors.

You might have noticed that I said that TempData is kept in the session by DEFAULT,meaning you are not tied to that if you decide you don’t like it that way. Let’s take a look at some of the source code to ASP.NET MVC 2 and see how things work.

ITempDataProvider and SessionStateTempDataProvider
In the System.Web.Mvc namespace you’ll find a very simple interface called ITempDataProvider that looks like this:

view source

print?

1

public
interface
ITempDataProvider {


2

IDictionary<
string
,
object
>LoadTempData(ControllerContext controllerContext);


3

void
SaveTempData(ControllerContext controllerContext,


4

IDictionary<
string
,
object
>values);


5

}


Not much to see there. It simply defines the methods of saving and loading the dictionary. In the same namespace is SessionStateTempDataProvider which implements ITempDataProvider. When loading the dictionary out of the session,it explicitly removes it afterwards to make sure only one request gets access to it:

view source

print?

01

public
virtual
IDictionary<
string
,
object
>LoadTempData(


02

ControllerContext controllerContext){


03



04

HttpSessionStateBase session = controllerContext.HttpContext.Session;


05



06

if
(session !=
null
){


07

Dictionary<
string
,
object
>tempDataDictionary =


08

session[TempDataSessionStateKey]
as
Dictionary<
string
,
object
>;


09



10

if
(tempDataDictionary !=
null
){


11

// If we got it from Session,remove it so that no other request gets it 


12

session.Remove(TempDataSessionStateKey);


13

return
tempDataDictionary;


14

}


15

}


16



17

return
new
Dictionary<
string
,
object
>(StringComparer.OrdinalIgnoreCase);


18

}


The controller object has a public property for the TempData provider which that uses the session provider by default:

view source

print?

01

public
ITempDataProvider TempDataProvider {


02

get
{


03

if
(_tempDataProvider ==
null
){


04

_tempDataProvider = CreateTempDataProvider();


05

}


06

return
_tempDataProvider;


07

}


08

set
{


09

_tempDataProvider = value;


10

}


11

}


12



13

protected
virtual
ITempDataProvider CreateTempDataProvider(){


14

return
new
SessionStateTempDataProvider();


15

}


How the TempData Provider Gets Used
If you want to tell your controllers to use a different provider,you set it up in your controller’s constructor and you’re good to go. You might have also noticed that the LoadTempData(ControllerContext)function in ITempDataProvider returns an IDictionary,but the TempData property on the controller (actually,ControllerBase to be specific),is of type TempDataDictionary. This is another MVC class that wraps around an IDictionary and provides all the functionality we need from TempData. This is a big class so I won’t go over everything,but there are a couple parts to highlight. First,you can see that the class is marked as being serializable,so that it can properly be put into session:

view source

print?

1

[Serializable]


2

public
class
TempDataDictionary : IDictionary<
string
,
object
>,ISerializable {


Second,there is the Load(ControllerContext,ITempDataProvider)function that uses the specified provider to hydrate the dictionary:

view source

print?

01

public
void
Load(ControllerContext controllerContext,


02

ITempDataProvider tempDataProvider){


03

IDictionary<
string
,
object
>providerDictionary =


04

tempDataProvider.LoadTempData(controllerContext);


05



06

_data= (providerDictionary !=
null
)


07

?
new
Dictionary<
string
,
object
>(


08

providerDictionary,StringComparer.OrdinalIgnoreCase): 


09

new
Dictionary<
string
,
object
>(StringComparer.OrdinalIgnoreCase);


10



11

_initialKeys=
new
HashSet<
string
>(


12

_data.Keys,StringComparer.OrdinalIgnoreCase);


13



14

_retainedKeys.Clear();


15

}


Similarly,it also has a Save(ControllerContext,ITempDataProvider)function that uses the provider to save out the new dictionary:

view source

print?

01

public
void
Save(ControllerContext controllerContext,


02

ITempDataProvider tempDataProvider){


03



04

string
[] keysToKeep =


05

_initialKeys


06

.Union(_retainedKeys,StringComparer.OrdinalIgnoreCase)


07

.ToArray();


08



09

string
[] keysToRemove =


10

_data


11

.Keys


12

.Except(keysToKeep,StringComparer.OrdinalIgnoreCase)


13

.ToArray();


14



15

foreach
(
string
key
in
keysToRemove){


16

_data.Remove(key);


17

}


18



19

tempDataProvider.SaveTempData(controllerContext,_data);


20

}


The only part left is figuring when in the pipeline TempData gets load,and then when it gets saved out again. The answers to both are found in the ExecuteCore()method in Controller. This method gets called by MVC to invoke the current action. In order,it loads TempData,then executes the current action,then saves out the new dictionary:

view source

print?

01

protected
override
void
ExecuteCore(){


02

PossiblyLoadTempData();


03



04

try
{


05

string
actionName = RouteData.GetRequiredString(
"action"
);


06



07

if
(!ActionInvoker.InvokeAction(ControllerContext,actionName)){


08

HandleUnknownAction(actionName);


09

}


10

}


11

finally
{


12

PossiblySaveTempData();


13

}


14

}


ASP.NET MVC 2 introduced the concept of RenderAction,where you could call out from a view to another controller action and render it within that view. If you do this,the child action actually shares TempData with the parent request. I have found this useful in passing something down to a child request to avoid having to redo a database call for something needed by both the parent and the child. This logic is part of the PossiblyLoadTempData()and PossiblySaveTempData()functions referenced in ExecureCore():

view source

print?

01

internal
void
PossiblyLoadTempData(){


02

if
(!ControllerContext.IsChildAction){


03

TempData.Load(ControllerContext,TempDataProvider);


04

}


05

}


06



07

internal
void
PossiblySaveTempData(){


08

if
(!ControllerContext.IsChildAction){


09

TempData.Save(ControllerContext,TempDataProvider);


10

}


11

}


Those are the highlights of how TempData works under the hood,and how you can plug in a different provider to tailor the behavior to what you want. You can also find references to it outside of the controller in other places where you have access to TempData,such as ViewContext,ViewPage,etc. As of right now the session provider is the only one in the main assembly. The MvcFutures assembly includes the CookieTempDataProvider class which provides cookie-based TempData storage.

A MongoDB TempData Provider
I think we’ve looked at enough ASP.NET MVC code now,so let’s try writing a custom provider. For this example I’m going to store the data in a MongoDB collection. Now,keep in mind that this example is meant to show how to implement a custom provider,so I’m not trying to make a case that you should store your TempData in MongoDB. For this example I’m going to use the MongoDB-CSharp driver.

First,let’s define the model object that we’re going to store in MongoDB.

view source

print?

1

public
class
MongoTempData


2

{


3

public
string
SessionIdentifier {
get
;
set
;}


4



5

public
string
Key {
get
;
set
;}


6



7

public
object
Value {
get
;
set
;}


8

}


Now we can define our TempData provider to store that model. To avoid sending a user someone else’s TempData,we’ll need a way of uniquely identifying users. Since this is a crude demo,I’ll just use the user’s host address. Obviously,don’t do this in production code.

view source

print?

01

public
class
MongoTempDataProvider : ITempDataProvider


02

{


03

private
string
_collectionName;


04

private
string
_databaseName;


05



06

public
MongoTempDataProvider(
string
databaseName,
string
collectionName)


07

{


08

_collectionName = collectionName;


09

_databaseName = databaseName;


10

}


11

}


Now for the function for loading data from the collection. Since I’m not specifying the hostname or port for the database,it assumes a default of localhost and 27017. Different values can be provided via the contructors to the Mongo object.

view source

print?

01

public
IDictionary<
string
,
object
>LoadTempData(ControllerContext controllerContext)


02

{


03

var tempDataDictionary =
new
Dictionary<
string
,
object
>();


04



05

using
(Mongo mongo=
new
Mongo())


06

{


07

mongo.Connect();


08



09

IMongoCollection<MongoTempData>collection = 


10

mongo


11

.GetDatabase(_databaseName)


12

.GetCollection<MongoTempData>(_collectionName);


13



14

IEnumerable<MongoTempData>tempData = collection.Find(item =>


15

item.SessionIdentifier ==


16

controllerContext.HttpContext.Request.UserHostAddress


17

).Documents;


18



19

foreach
(var tempDataItem
in
tempData)


20

{


21

tempDataDictionary.Add(tempDataItem.Key,tempDataItem.Value);


22



23

collection.Remove(tempDataItem);


24

}


25

}


26



27

return
tempDataDictionary;


28

}


That will connect to the database and read in any TempData that is queued up for the current user. Once an item is read into the dictionary it gets removed from the collection,to make sure no other requests can get it.

Now,the save method:

view source

print?

01

public
void
SaveTempData(ControllerContext controllerContext,


02

IDictionary<
string
,
object
>values)


03

{


04

using
(Mongo mongo=
new
Mongo())


05

{


06

mongo.Connect();


07



08

IMongoCollection<MongoTempData>collection = 


09

mongo


10

.GetDatabase(_databaseName)


11

.GetCollection<MongoTempData>(_collectionName);


12



13

IEnumerable<MongoTempData>oldItems = 


14

collection.Find(item =>


15

item.SessionIdentifier ==


16

controllerContext.HttpContext.Request.UserHostAddress


17

).Documents;


18



19

foreach
(var tempDataItem
in
oldItems)


20

{


21

collection.Remove(tempDataItem);


22

}


23



24

if
(values !=
null
&& values.Count >0)


25

{


26

collection.Insert(


27

values.Select(tempDataValue =>


28

new
MongoTempData


29

{


30

SessionIdentifier =


31

controllerContext.HttpContext.Request.UserHostAddress,


32

Key = tempDataValue.Key,


33

Value = tempDataValue.Value


34

}


35

)


36

);


37

}


38

}


39

}


That will clear out any old data in the dictionary and replace it with the new values. That’s all the code required for the provider,so now we just need to plug it into the controller. We’ll create a base controller class that our controllers will inherit from,to avoid having to specify the TempDataProvider in every controller.

view source

print?

1

public
abstract
class
MongoControllerBase : Controller


2

{


3

public
MongoControllerBase()


4

{


5

TempDataProvider =
new
MongoTempDataProvider(
"test"
,
"MongoTempData"
);


6

}


7

}


Next we’ll define a test controller with two actions. The first action will insert some data into TempData,and the second will print out the contents of TempData. Remember that the controller needs to inherit from our base controller in order to use our custom provider. If you don’t,it will default to using the session provider.

view source

print?

01

public
class
HomeController : MongoControllerBase


02

{


03

public
ActionResult Index()


04

{


05

TempData[
"CurrentDateTime"
] = DateTime.Now;


06

TempData[
"MeaningOfLife"
] = 42;


07



08

return
Content(
"TempData Updated"
);


09

}


10



11

public
ActionResult ReadTempData()


12

{


13

return
View();


14

}


15

}


Here’s our view for ReadTempData:

view source

print?

01

Contents of TempData:


02



03

<
br
/>


04

<
br
/>


05



06

<% foreach (var tempDataItemin TempData)


07

{%>


08



09

Key: <%: tempDataItem.Key %>;Value: <%: tempDataItem.Value %>


10

<
br
/>


11



12

<% }%>


If you fire up the application,you should see text that says “TempData Updated”. Now if you hit /Home/ReadTempData,you should see something that looks like:

view source

print?

1

Contents of TempData:


2



3

Key: CurrentDateTime;Value: 7/14/2010 10:52:45 PM 


4

Key: MeaningOfLife;Value: 42


If you refresh the page,you will see that the contents of TempData are now empty.

For me,this was one big reminder that you should know exactly how something works before making assumptions about it. Since ASP.NET MVC is open source,it was very easy to load up the solution and poke around under the hood and see precisely how it behaves.

You can download the sample project containing the MongoDB provider code here.

转载:http://www.gregshackles.com/2010/07/asp-net-mvc-do-you-know-where-your-tempdata-is/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: