ViewBag, ViewData, TempData, Session - how and when to use them? - c#

ViewData and ViewBag allows you to access any data in view that was passed from controller.
The main difference between those two is the way you are accessing the data.
In ViewBag you are accessing data using string as keys - ViewBag[“numbers”]
In ViewData you are accessing data using properties - ViewData.numbers.
ViewData example
CONTROLLER
var Numbers = new List<int> { 1, 2, 3 };
ViewData["numbers"] = Numbers;
VIEW
<ul>
#foreach (var number in (List<int>)ViewData["numbers"])
{
<li>#number</li>
}
</ul>
ViewBag example
CONTROLLER
var Numbers = new List<int> { 1, 2, 3 };
ViewBag.numbers = Numbers;
VIEW
<ul>
#foreach (var number in ViewBag.numbers)
{
<li>#number</li>
}
</ul>
Session is another very useful object that will hold any information.
For instance when user logged in to the system you want to hold his authorization level.
// GetUserAuthorizationLevel - some method that returns int value for user authorization level.
Session["AuthorizationLevel"] = GetUserAuthorizationLevel(userID);
This information will be stored in Session as long as user session is active.
This can be changed in Web.config file:
<system.web>
<sessionState mode="InProc" timeout="30"/>
So then in controller inside the action :
public ActionResult LevelAccess()
{
if (Session["AuthorizationLevel"].Equals(1))
{
return View("Level1");
}
if (Session["AuthorizationLevel"].Equals(2))
{
return View("Level2");
}
return View("AccessDenied");
}
TempData is very similar to ViewData and ViewBag however it will contain data only for one request.
CONTROLLER
// You created a method to add new client.
TempData["ClientAdded"] = "Client has been added";
VIEW
#if (TempData["ClientAdded"] != null)
{
<h3>#TempData["ClientAdded"] </h3>
}
TempData is useful when you want to pass some information from View to Controller. For instance you want to hold time when view was requested.
VIEW
#{
TempData["DateOfViewWasAccessed"] = DateTime.Now;
}
CONTROLLER
if (TempData["DateOfViewWasAccessed"] != null)
{
DateTime time = DateTime.Parse(TempData["DateOfViewWasAccessed"].ToString());
}

ViewBag, ViewData, TempData, Session - how and when to use them?
ViewBag
Avoid it. Use a view model when you can.
The reason is that when you use dynamic properties you will not get compilation errors. It's really easy to change a property name by accident or by purpose and then forget to update all usages.
If you use a ViewModel you won't have that problem. A view model also moves the responsibility of adapting the "M" (i.e. business entities) in MVC from the controller and the view to the ViewModel, thus you get cleaner code with clear responsibilities.
Action
public ActionResult Index()
{
ViewBag.SomeProperty = "Hello";
return View();
}
View (razor syntax)
#ViewBag.SomeProperty
ViewData
Avoit it. Use a view model when you can. Same reason as for ViewBag.
Action
public ActionResult Index()
{
ViewData["SomeProperty"] = "Hello";
return View();
}
View (razor syntax):
#ViewData["SomeProperty"]
Temp data
Everything that you store in TempData will stay in tempdata until you read it, no matter if there are one or several HTTP requests in between.
Actions
public ActionResult Index()
{
TempData["SomeName"] = "Hello";
return RedirectToAction("Details");
}
public ActionResult Details()
{
var someName = TempData["SomeName"];
}

TempData
is meant to be a very short-lived instance, and you should only use it during the current and the subsequent requests only! Since TempData works this way, you need to know for sure what the next request will be, and redirecting to another view is the only time you can guarantee this. Therefore, the only scenario where using TempData will reliably work is when you are redirecting. This is because a redirect kills the current request (and sends HTTP status code 302 Object Moved to the client), then creates a new request on the server to serve the redirected view. Looking back at the previous HomeController code sample means that the TempData object could yield results differently than expected because the next request origin can't be guaranteed. For example, the next request can originate from a completely different machine and browser instance.
ViewData
ViewData is a dictionary object that you put data into, which then becomes available to the view. ViewData is a derivative of the ViewDataDictionary class, so you can access by the familiar "key/value" syntax.
ViewBag
The ViewBag object is a wrapper around the ViewData object that allows you to create dynamic properties for the ViewBag.
public class HomeController : Controller
{
// ViewBag & ViewData sample
public ActionResult Index()
{
var featuredProduct = new Product
{
Name = "Special Cupcake Assortment!",
Description = "Delectable vanilla and chocolate cupcakes",
CreationDate = DateTime.Today,
ExpirationDate = DateTime.Today.AddDays(7),
ImageName = "cupcakes.jpg",
Price = 5.99M,
QtyOnHand = 12
};
ViewData["FeaturedProduct"] = featuredProduct;
ViewBag.Product = featuredProduct;
TempData["FeaturedProduct"] = featuredProduct;
return View();
}
}

namespace TempData.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
TempData["hello"] = "test"; // still alive
return RedirectToAction("About");
}
public ActionResult About()
{
//ViewBag.Message = "Your application description page.";
var sonename = TempData["hello"]; // still alive (second time)
return RedirectToAction("Contact");
}
public ActionResult Contact()
{
var scondtime = TempData["hello"]; // still alive(third time)
return View();
}
public ActionResult afterpagerender()
{
var scondtime = TempData["hello"];//now temp data value becomes null
return View();
}
}
}
In above conversation, there is little confuse for everyone. if you look at my above code, tempdata is like viewdata concept but then it is able to redirect other view also. this is first point.
second point:
few of them are saying it maintains value till read and few of them are asking that so will it read only time? not so.
Actually, you can read any number of time inside your code in one postpack before page render. once page render happened, if you do again postpack (request) means, the tempdata value becomes NULL.
even you are requesting so many time , but the tempdata value is still alive -->this case your number of request should not read temp data value.

In simple terms:
ViewBag is a dynamic (dynamic: ability to assign more than one value by different programs on the same object) object which is used to send data from controller to view. It can be used when we jump from controller's action to some view. However the value which we get in the view(in the viewbag object) can not be further replicated in other view/controller/js page etc. Meaning: Controller->View (possible). Now this value can not be send to next view/controller. Meaning Controller->View->View/controller/some js file (not possible), to carry this value you need to define some other variable and store viewbag value into it and then send it as a parameter.
Tempdata is very much different than viewbag. It has nothing to do with view at all. It is used when we send data from one action(method) to other action in the same controller. That's the reason we use RedirectToAction whenever sending tempdata value from one action to another. Value of tempdata are not retained when send from controller to veiw (because it is not meant to do so).
ViewData is simillar to viewbag but is a dictionary object. ViewData may require type casting while viewbag may not. It depends on the user which one he may want to use.

Related

How to keep hidden field data not in model after validation error [MVC]

I have this neat model, which is populated on a screen and inserted to a database on successful validation. To make the web app easier to use, I make it redirect to a specific URL after it posts the data. To do that, I pass the URL as a hidden field (the URL is dynamic and depends on the Get request). Of course, on failed validation the model is returned and textboxes and other editors are repopulated, but the hidden field with the URL is not. How can I make it repopulate after validation error, without it beign the part of the model?
Here's some of my code:
-get method:
public ActionResult Create()
{
ViewBag.returnUrl = System.Web.HttpContext.Current.Request.UrlReferrer; ....
-post method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Issue_ID,Issue,Case_Status,
Issued_by,Issue_Date...,HelpDesk_Service_Request_Ticket_Number")] Case #case, string returnUrl)
.
.
.
if (ModelState.IsValid)
{
db.Cases.Add(#case);
db.SaveChanges();
if (returnUrl == null)
{
return RedirectToAction("Index");
}
else
{
return Redirect(returnUrl);
}
}
return View(#case);
Thanks in advance!
From you question I understand you want to pass the return url value from one action (GET) to another (POST). You can store the value in TempData
TempData["returnUrl"] = new Uri("<return url>");
and then try accessing it using
var returnUrl= TempData["returnUrl"];
Note that once the value is read from TempData, it is automatically removed from the collection. For retaining the value you can use keep() or peek() method. Please refer a similar question answered here
Viewbag only lives for current request. You need to use TempData instead.
Please check this thread Viewbag passing value

ViewBag value becomes null in the post method?

I have the following code in Edit get method.
public async Task<ActionResult> Edit(int? id)
{
Event e = await db.Events.FindAsync(id);
ViewBag.OldCategories = e.Categories.ToList();
// ViewBag.OldCategories is not null (tested in VS quick viewer)
And I have the following code in the post method.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,....,Categories")] EventCreateViewModel eventVm)
{
var oldCategories = ViewBag.OldCategories; // Null
However, the oldCategories is always null in the post method. Did I miss anything?
ViewBag is only accessible in View when you set it from controller, when form is posted ViewBag values are removed, if you want the values to be accessible in form post you can post them via hidden fields.
<input type="hidden" name="MyHidden" value="#ViewBag.AnyKey"/>
or other way around is to use TempData, instead of placing in viewbag, place data in TempData and remember TempData is once read, means once you read its value it will be removed from TempData, you can retain it by calling TempData.Keep()
public ActionResult MyAction()
{
TempData["myKey"] = "";
}
and View:
#{
string value = TempData["myKey"] as stirng;
TempData.Keep("myKey") // retains value in TempData
}
Once the View is rendered with Razor, the ViewBag is emptied. It's only good for one trip to the client, and then it's wiped. If you want to be able to read it back in your Controller when the client calls back to the server, you'll need to use TempData instead.
Even if you do a Redirect() inside a Controller Action, your ViewBag data will be lost, because you're moving onto a second page, and out of the scope of ViewBag.

ViewData coming up as null

I have this test page. This is the action method.
public ActionResult TestView()
{
ViewData["Test"] = "1";
return View("TestView");
}
In TestView.cshtml, I have this line of code
#ViewData["Test"]
It is coming up as null, it is not printing the "1".
Any idea what I am doing wrong?
Thanks.
MV3 have ViewBag as a new object that can hold the dynamic properties as Key/Value pair.
So you can directly assign a property using ViewBag. For e.g. -
In your action in controller you can write something like
ViewBag.Message = "1";
Now to retrieve the value you can write in your .cshtml
<h2>#ViewBag.Message</h2>

ASP.NET MVC Standard Link/Href As Save Button And Model IS NUll

Okay so, i am totally new to MVC and I'm trying to wrap my head around a few of the concepts. I've created a small application...
This application has a view for creating a new Individual record. The view is bound to a model ViewPage... And I have a associated IndividualController which has a New method...
The New method of the IndividualController looks like this...
public ActionResult New()
{
var i = new Individual();
this.Title = "Create new individual...";
i.Id = Guid.NewGuid();
this.ViewData.Model = new Individual();
return View();
}
Now, the above all seems to be working. When the view loads I am able to retrieve the data from the Individual object. The issue comes into play when I try and save the data back through the controller...
In my IndividualController I also have a Save method which accepts an incoming parameter of type Individual. The method looks like...
public ActionResult Save(IndividualService.Individual Individual)
{
return RedirectToAction("New");
}
Now, on my view I wanted to use a standard html link/href to be used as the "Save" button so I defined an ActionLink like so...
<%=Html.ActionLink("Save", "Save") %>
Also, defined in my view I have created a single textbox to hold the first name as a test like so...
<% using (Html.BeginForm()) { %>
<%=Html.TextBox("FirstName", ViewData.Model.FirstName)%>
<% } %>
So, if I put a break point in the Save method and click the "Save" link in my view the break point is hit within my controller. The issue is that the input parameter of the Save method is null; even if I type a value into the first name textbox...
Obviously I am doing something completely wrong. Can someone set me straight...
Thanks in advance...
Your New controller method doesn't need to create an individual, you probably just want it to set the title and return the view, although you may need to do some authorization processing. Here's an example from one of my projects:
[AcceptVerbs( HttpVerbs.Get )]
[Authorization( Roles = "SuperUser, EditEvent, EditMasterEvent")]
public ActionResult New()
{
ViewData["Title"] = "New Event";
if (this.IsMasterEditAllowed())
{
ViewData["ShowNewMaster"] = "true";
}
return View();
}
Your Save action should take the inputs from the form and create a new model instance and persist it. My example is a little more complex than what I'd like to post here so I'll try and simplify it. Note that I'm using a FormCollection rather than using model binding, but you should be able to get that to work, too.
[AcceptVerbs( HttpVerbs.Post )]
[Authorization( Roles = "SuperUser, EditEvent, EditMasterEvent")]
public ActionResult Save( FormCollection form )
{
using (DataContext context = ...)
{
Event evt = new Event();
if (!TryUpdateModel( evt, new [] { "EventName", "CategoryID", ... }))
{
this.ModelState.AddModelError( "Could not update model..." );
return View("New"); // back to display errors...
}
context.InsertOnSubmit( evt );
context.SubmitChanges();
return RedirectToAction( "Show", "Event", new { id = evt.EventID } );
}
}
If I don't create a new Indvidual object in the New method then when my view tries to bind the textbox to the associated model I get a NullReferenceException on the below line in my view...
`<%=Html.TextBox("FirstName", ViewData.Model.FirstName)%>`
With regards to the Save method. From what I understand since my view is strongly typed shouldn't I be able to have a method signature like...
`public ActionResult New(IndividualService.Individual ind)
{
return View();
}`
I thought that was the purpose behind model binding..?
I would strongly recommend that you take a step back from what you are doing and run through some of the Tutorials/Videos here http://www.asp.net/learn/
If you have a strongly typed View it means that when the Controller picks that view to generate the output the view has better access to the Model.
However the View is not responsible for what comes back from the client subsequently such as when a form is posted or a URL is otherwise navigated to.
ASP.NET-MVC uses information in the URL to determine which Controller to hand the request off to. After that it's the controller's responsibility to resolve the various other elements in the request into instance(s) of Model classes.
The relationship between the incoming request and the controller is clouded by the significant assistance the ASP.NET-MVC routing gives the controller. For example a route can be defined to supply parameters to the controller method and thats all the controller needs and hence you don't see any code in the method relating to the http request. However it should be understood that the contoller method is simply processing a http request.
I hope you can see from the above that it would be too early in a requests life-cycle for an instance of a class from the model to passed to a public method on a controller. Its up to the controller to determine which model classes if any need instancing.

How to RedirectToAction in ASP.NET MVC without losing request data

Using ASP.NET MVC there are situations (such as form submission) that may require a RedirectToAction.
One such situation is when you encounter validation errors after a form submission and need to redirect back to the form, but would like the URL to reflect the URL of the form, not the action page it submits to.
As I require the form to contain the originally POSTed data, for user convenience, as well as validation purposes, how can I pass the data through the RedirectToAction()? If I use the viewData parameter, my POST parameters will be changed to GET parameters.
The solution is to use the TempData property to store the desired Request components.
For instance:
public ActionResult Send()
{
TempData["form"] = Request.Form;
return this.RedirectToAction(a => a.Form());
}
Then in your "Form" action you can go:
public ActionResult Form()
{
/* Declare viewData etc. */
if (TempData["form"] != null)
{
/* Cast TempData["form"] to
System.Collections.Specialized.NameValueCollection
and use it */
}
return View("Form", viewData);
}
Keep in mind that TempData stores the form collection in session. If you don't like that behavior, you can implement the new ITempDataProvider interface and use some other mechanism for storing temp data. I wouldn't do that unless you know for a fact (via measurement and profiling) that the use of Session state is hurting you.
Take a look at MVCContrib, you can do this:
using MvcContrib.Filters;
[ModelStateToTempData]
public class MyController : Controller {
//
...
}
There is another way which avoids tempdata. The pattern I like involves creating 1 action for both the original render and re-render of the invalid form. It goes something like this:
var form = new FooForm();
if (request.UrlReferrer == request.Url)
{
// Fill form with previous request's data
}
if (Request.IsPost())
{
if (!form.IsValid)
{
ViewData["ValidationErrors"] = ...
} else {
// update model
model.something = foo.something;
// handoff to post update action
return RedirectToAction("ModelUpdated", ... etc);
}
}
// By default render 1 view until form is a valid post
ViewData["Form"] = form;
return View();
That's the pattern more or less. A little pseudoy. With this you can create 1 view to handle rendering the form, re-displaying the values (since the form will be filled with previous values), and showing error messages.
When the posting to this action, if its valid it transfers control over to another action.
I'm trying to make this pattern easy in the .net validation framework as we build out support for MVC.
If you want to pass data to the redirected action, the method you could use is:
return RedirectToAction("ModelUpdated", new {id = 1});
// The definition of the action method like public ActionResult ModelUpdated(int id);
TempData is the solution which keeps the data from action to action.
Employee employee = new Employee
{
EmpID = "121",
EmpFirstName = "Imran",
EmpLastName = "Ghani"
};
TempData["Employee"] = employee;

Categories

Resources