ViewData, ViewBag And TempData
While working with ASP.NET MVC applications we often need to pass data from controller action methods to the view. And there are various techniques to accomplish that goal - ViewData, ViewBag and TempData.
In an ASP.NET MVC application a controller can easily pass a model to a view using an overloaded View() method as shown below:
public ActionResult Index()
{
List<Customer> model = GetCustomers();
return View(model);
}
As we can see the Index() method gets a List of Customer objects from a hypothetical GetCustomers() helper method and then passes it to the Index view by calling the View() method. The Index view can then access this model using the Model property.
Although passing data to a view through a model or view model class is a recommended practice, at times we need to pass some arbitrary data to the view.
For example, we may need to pass a success or error message from a controller to a view so that it can be rendered in the browser. Or we may want to pass an auxiliary object holding some calculated results to the view. Since such data is not a part of the model we need some mechanism to pass it to the view. That is where ViewData, ViewBag and TempData objects come into picture. ViewData, ViewBag and TempData are inbuilt objects available to controllers and views. Each of these objects have their own specialties and features. So, let's examine each of them in bit more detail.
If we need to pass reasonable amount of data from a controller to a view that's not a part of model itself, we should give a thought to creating a view model. If something can't go in view model for some reason we can use the techniques discussed here to facilitate the data transfer.
ViewData
The primary purpose of ViewData is to carry data from the controller to the view. ViewData is a dictionary object and is of type ViewDataDictionary. Just like any other dictionary object in .NET, ViewData allows we to store key-value pairs. Data stored in ViewData object exists only during the current request. In other words, as soon as the view is rendered in the browser the ViewData object is emptied.
The following code shows an action method that sets a ViewData key named message with a developer defined message.
public ActionResult Index()
{
Customer model = new Customer()
{
CustomerID = "CCC1",
CompanyName = "Company 1",
ContactName = "Contact 1",
Country = "Country 1"
};
ViewData["message"] = "This is a test message";
return View(model);
}
The Index() action method creates an instance of Customer class. The Customer object thus created is supposed to act as the model for the Index view. Then the code stores message key in the ViewData dictionary with its value set to This is a test message. Finally, the action method returns Index view by calling View() method and passing Customer model to it.
The following code from the Index view shows how ViewData dictionary can be accessed inside a view.
@model ViewDataViewBagTempData.Models.Customer
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" /> <title>Index</title>
</head>
<body>
<h1>@Model.CustomerID</h1>
<h3>@ViewData["message"]</h3>
<h2>@ViewData.Model.CompanyName</h2>
</body>
</html>
The @model sets the data model for the view to ViewDataViewBagTempData.Models.Customer. This data model can be accessed using Model property as shown by @Model.CustomerID line. Next, the code outputs the value of message ViewData key using the dictionary syntax. The last line marked in bold letters may surprise us. It uses Model property of ViewData object to access the model associated with the view. So, ViewData provides a pointer to the model although we will use it rarely in this fashion because the model is directly available to a view through Model property.
As soon as the request-response cycle completes the ViewData dictionary is emptied.
In the preceding example, we stored a string value in ViewData. We could have stored any other type of data such as integer, Boolean or object. The following code stores CustomerInfo object into ViewData:
public ActionResult Index()
{
Customer model = new Customer()
{
...
};
CustomerInfo info = new CustomerInfo()
{
LastOrderID = 100,
LastOrderDate = new DateTime(2013, 01, 20),
LastOrderAmount = 12345.6M
};
ViewData["extrainfo"] = info;
return View(model);
}
The above code creates Customer model as before. Additionally, it creates an instance of CustomerInfo class. The CustomerInfo class has three properties - LastOrderID, LastOrderDate and LastOrderAmount. It then sets a ViewData key named extrainfo to this CustomerInfo object.
Whatever we store inside ViewData gets stored as a generic object and we must typecast it while retrieving it inside a view. The following code shows how:
<h3>@{
CustomerInfo info = (CustomerInfo)ViewData["extrainfo"];
@info.LastOrderID @: | @info.LastOrderDate @: | @info.LastOrderAmount
}</h3>
As we can see the above code residing in the Index view retrieves extrainfo from ViewData and typecasts it to CustomerInfo class. We can then use properties of CustomerInfo class to display their values in the view. Remember that we also need to check for null values in our view in case we expect ViewData entries to be null.
ViewBag
ViewBag is a wrapper over ViewData and allows we to store and retrieve values using object-property syntax rather than key-value syntax used by dictionary objects. It does so using the dynamic data type feature of .NET. In addition to providing object-properties syntax ViewBag also saves we from the job of typecasting as we did with ViewData object. Let's see how ViewBag can be used:
public ActionResult Index()
{
Customer model = new Customer()
{ ... };
CustomerInfo info = new CustomerInfo()
{ ... };
ViewBag.ExtraInfo = info; return View(model);
}
As we can see, this time the code uses ViewBag object to store CustomerInfo object. It does so by declaring a developer defined property ExtraInfo and setting it to info object. If we observe ViewData object in Quick Watch window you will see this:
The ExtraInfo dynamic property is actually stored as ExtraInfo ViewData key ! Of course, we should stick to a uniform syntax while accessing this object (stick to ViewBag syntax in this case).
To retrieve CustomerInfo passed to the view you will write the following code:
<h3>@{
@ViewBag.ExtraInfo.LastOrderID @: |@ViewBag.ExtraInfo.LastOrderDate @: | @ViewBag.ExtraInfo.LastOrderAmount}
</h3>
This time we don't need to typecast ExtraInfo to CustomerInfo and we can access its properties directly (Remember, however, that these properties won't be displayed in VS IntelliSense as such).
While deciding whether to use ViewData or ViewBag we need to consider developer choice and need for typecasting. Since ViewBag is just a wrapper over ViewData, whatever we store inside ViewBag is accessible only during the current request.
TempData
TempData offers a dictionary storage like ViewData. However, values stored in TempData exists unless they are read in some view. Most commonly TempData is used to pass a value between the current request and the subsequent request. Consider an example where we are deleting a record from the database. We have created two action methods - Index() and Delete(). The Index() action method fetches a set of record and passes then to Index view to display in a table. The Index view consists of a table showing all the records and each table row has a Delete link that points to the Delete() action method. The Delete() action method does the job of deleting a record and once a record is deleted it takes the control to Index() action method so that the table can be re-displayed. Now suppose that when the table is re-displayed after deleting a record we wish to display a message to the user informing that so-and-so record has been deleted. We can't pass such a message using ViewData or ViewBag because here one request (the one that causes deletion) wants to pass data to another request (the one that renders a table of records).
The following code makes this clear:
public ActionResult Index()
{
Customer model = new Customer()
{ ... };
return View(model);
}
public ActionResult Delete(int id)
{
//delete record here TempData["message"] = "Record deleted successfully!"; //transfer control to Index().
This will be another request. return RedirectToAction("Index");
}
In this case request that invokes Delete() is the first request. During this request we store a TempData key named message and assign it some value. We then call RedirectToAction() to take the control to Index() method. Remember that RedirectToAction() returns HTTP 302 response to the browser so that the browser makes a GET request to the specified action method. At this point of time the second request is made. We wish to access data set during the first request in the second request and hence we use TempData.
The value set in the TempData can be accessed in the Index view like this:
<body>
<h1>Index View</h1>
<h3>@TempData["message"]</h3>
</body>
So far so good. While the above example works as expected, we need to be aware of a tricky thing. If we don't read the TempData values in the second request, they are persisted and can be read in yet subsequent request. They exist unless we read them in some view at which point of time they are removed. Consider the following code that makes this behavior clear:
public ActionResult Index1()
{
TempData["message"] = "Test message!";
return View();
}
public ActionResult Index2()
{
return View();
}
public ActionResult Index3()
{
return View();
}
Assume that Index1 view has a link pointing to Index2 action method and Index2 view has a link pointing to Index3 action method. Now, if Index1 and Index2 views do not use TempData at all then it is still available to Index3 view. However, if Index1 or Index2 views use TempData then it is removed as soon as the respective request completes. Index3 view won't be able to read TempData value in that case.
There is one more twist in the TempData story that we should be aware of. The TempData object has Keep() method that can preserve the value(s) even after they are read. To do so, once we finish reading the values call TempData.Keep() or TempData.Keep(<key>). Calling Keep() marks the tempdata key(s) for retention till next read operation.It would be interesting to know that TempData values are actually stored in ASP.NET Session and are automatically removed once they are read. To prove that they are indeed stored in Session just disable session state altogether by adding this line in web.config:
<sessionState mode="Off"></sessionState>
If you try to run the same example now, you will get this error.
Just like ViewData, TempData also requires typecasting and null value checking while accessing its values.
To summarize,
ViewData, ViewBag and TempData allow we to pass values from a controller to a view.
ViewData and TempData objects allow we to store values as key-value pairs.
ViewBag object allows we to store values using object-properties syntax.
ViewBag is a wrapper over ViewData.
ViewData and ViewBag values are available only during current request.
TempData values are accessible in the current request and the subsequent requests. They are removed when a view reads them.
TempData internally uses Session to store its values. The values are removed once they are read.
ViewData and TempData require typecasting and null checking whereas ViewBag doesn't need such checking.
No comments:
Post a Comment