Thanks everyone's effort of helping me out, basically I'm facing a problem in controller below, just make it simple and easy:
Controller C{
public list<model> a;
//used in action A, if it's a searched list, then don't initialize;
public bool searched = false;
public ActionResult A(){
if(searched){
ViewBag.a = a;
}else
//initial the list
a = db.model.where();
.....
return view()
}
//return a result list when people search by keywords
public ActionResult B(string str){
a = db.model.where(d=> d.id = str);
search = true;
}
}
But, it turned out that the value of both a and researched never changed after B called
Did I miss some critical knowledge in .NET MVC?
Any related articles are very welcomed
Thanks
You should use TempData to keep your value after redirect. It is exactly TempData was designed for.
In your example it will be:
Controller C{
public ActionResult A(){
TempData["str"] = "this is A";
return RedirectToActionB()
}
public ActionResult B(){
TempData["str"] += "This is B";
return View()
}
}
I'm guessing you're asking because it's not giving the result you expect, rather than because you want someone to try it for you. The easy answer (assuming you meant "On the B's View Page") is "This is B".
A RedirectToAction will send a redirection to the browser, which initiates a new request to the server. Unfortunately a ViewBag's life is only for a single request, so it will no longer be populated when action B runs.
You will need to find another way to pass the data; for example, in a cookie or within the Session object.
Instead of:
return RedirectToActionB()
Try:
return B();
You'll save a redundant Http request as well.
In asp.net mvc controller doesn't keep it's fields/properties during requests: you'll have new instance of C on each web request. This means that you cannot expect a and searched fields to keep their state after action B completion.
You may use ViewBag(ViewData) to pass values between controller and view during request.
You may use TempData to store values during 2 requests, it's usually being used in PRG pattern for example.
You may use Session to keep your values while client's session is alive.
You may pass values between requests using hidden inputs on your page, query parameters, or less obvious things like HttpHeaders and others.
If you want to pass values between actions A and B, you may use TempData or Session is collection isn't big (it'll be stored in encrypted cookie which size cannot exceed 4k as far as I remember).
There is an another option which may be useful. You may store your collection in client's localstorage if it's ok in your case.
Related
Thanks everyone's effort of helping me out, basically I'm facing a problem in controller below, just make it simple and easy:
Controller C{
public list<model> a;
//used in action A, if it's a searched list, then don't initialize;
public bool searched = false;
public ActionResult A(){
if(searched){
ViewBag.a = a;
}else
//initial the list
a = db.model.where();
.....
return view()
}
//return a result list when people search by keywords
public ActionResult B(string str){
a = db.model.where(d=> d.id = str);
search = true;
}
}
But, it turned out that the value of both a and researched never changed after B called
Did I miss some critical knowledge in .NET MVC?
Any related articles are very welcomed
Thanks
You're probably thinking the state will be preserved between two web requests. But when the web request ends, the entire controller is destroyed and the set information is lost unless it is stored in a persistent data store like Session or a database.
If I understand your code correctly, if you refactor your code slightly, you can probably achieve your searching functionality under one action, and you wouldn't need to store the data persistently.
Controller C will be recreated on each request, so even though the values are updated after B is called, the next request for A will require the creation of controller C so a an search will be reinstantiated.
You might want to make the local variables static.
This is something happening everyday
I'm looking for a better way of grabbing the values from view into the action, since the controller create and destroy based on http request, is there a good way to pass all the params thru?(By that I mean more than 5 params)
I think about session but there is a chance pepple lose their session and the important value gone, besides session, is there any other way around?
I would first consider whether your application needs to save that much information between views. MVC is a REST based architecture, and is typically designed to be stateless.
With that said, your options for passing around state with a user boil down to Session, Cookies, and Database.
I would create a static class that stores and retrieves its data from session.
public static class CustomPersistStore
{
public static CustomClass Current{
get{
var instance = HttpContext.Current.Session["key"] as CustomClass;
if(instance = null) {
instance = new CustomClass();
}
return instance;
}
}
}
If you want to pass values from the View to the Action the best practice is to use strongly typed views.
http://howmvcworks.net/OnViews/BuildingAStronglyTypedView
I was editing a project and I saw a Session[""] in one controller method and TempData[""] in another. Is there a difference between the 4 or is it just 4 ways to do the same thing.
ViewData/ViewBag - valid only for the duration of the current request. You set it in a controller action and use it in the view, then it disappears. The difference is that the first is a dictionary whereas the second is just a dynamic wrapper around this dictionary. Both point to the same data though. ViewBag was introduced in ASP.NET MVC 3.
Example:
public ActionResult Index()
{
ViewData["foo"] = "bar";
return View();
}
and inside the view you could use this value:
<div>#ViewData["foo"]</div>
Same with ViewBag but it is dynamic:
public ActionResult Index()
{
ViewBag.foo = "bar";
return View();
}
and inside the view you could use this value:
<div>#ViewBag.foo</div>
So as you can see ViewData/ViewBag are just an alternative way to pass information to a view from a controller action compared to the classic and recommended way which is using a view model:
public class MyViewModel
{
public string Foo { get; set; }
}
and then:
public ActionResult Index()
{
var model = new MyViewModel { Foo = "bar" };
return View(model);
}
and inside your strongly typed view:
#model MyViewModel
<div>#Html.DisplayFor(x => x.Foo)</div>
As you can see using view models provide a strongly typed approach in passing information to a view from a controller action.
TempData - it allows for persisting information for the duration of a single subsequent request. You store something inside TempData and then redirect. In the target controller action to which you redirected you could retrieve the value that was stored inside TempData.
Example:
public ActionResult Foo()
{
TempData["foo"] = "bar";
return RedirectToAction("bar");
}
public ActionResult Bar()
{
var value = TempData["foo"] as string;
// use the value here. If you need to pass it to the view you could
// use ViewData/ViewBag (I can't believe I said that but I will leave it for the moment)
return View();
}
ASP.NET MVC will automatically expire the value that was stored in TempData once you read it. Under the covers ASP.NET MVC persists the information into the Session.
Session - same as TempData except that it never expires - it will be valid for all requests, not a single redirect.
ASP.net MVC introduced ViewData, ViewBag, TempData, Session to pass data between controller to view.
ViewData
ViewData is implemented by using ViewDataDictionary class which stored in CurrentRequestContext. So, ViewData life-cycle will end when the current request ends.
ViewBag is also like ViewData, and only difference is it enable dynamically sharing the data using dynamics objects.
TempData is a very short-lived instance, and you should only use it during the current and the subsequent requests only.This will be handy if you want to use Redirections(RedirectToAction, RedirectToRoute, Redirect) in ASP.net MVC and pass some data among redirects. TempData stores data in Session but framework disposes the data when current and subsequent requests ends.
Session is long-lived(Never expires) data that belongs to user session.You need to be mindful when you use session variables which can be easily cause issues.
protected void Session_Start(Object sender, EventArgs e)
{
int userType = 1;
HttpContext.Current.Session.Add("_SessionUserType",userType );
}
ViewData:
Is a special dictionary inherited from ViewDataDictionary.
Used to send data from controller to view.
It's life span is the current request.
It will be destroyed if you have Redirect.
For security reasons, it's better to check it for null before usage.
The casting should be done for the operation.
ViewBag:
Is a dynamic type (this type is presented in c#4).
Like ViewData is used to send data from the controller to the view.
The duration of the validity of its values in the current request.
In redirection between pages, its value will be null.
For security reasons before use, check it for null.
The casting is not necessary, so it's more faster than ViewData.
TempData:
A special kind of dictionary derived from TempDataDictionary.
It has Short life time, and used to send information between pages (Redirect).
After rendering the View completely, its value will be null.
For security reasons before use, check it for null.
The casting should be done for the operation.
Session:
used To send information between different requests.
Its value is not null not null values; Unless after a certain time (session expire).
For security reasons before use, check it for null.
The casting should be done for the operation.
This article explains the difference between ViewData, ViewBag and TempData. I hope you can refer this article for your need.
A problem I come up against again and again is handling redirection to the previous page after a user runs some action such as clicking a 'Back To ...' link or saving the record they are editing.
Previously whenever I have needed to know what page to return to, I would provide a returnURL parameter to my current view.
http://blah.com/account/edit/1?returnURL="account/index"
This isn't a very clean way of handling this situation, as sometimes the return URL contains parameters such as search strings, etc, which have to be included in the URL.
http://blah.com/account/edit/1?returnURL="account/index?search="searchTerm""
Also, this creates an issue when a user can go another page forward before coming back to the page with the returnURL because you have to pass the returnURL through all visited pages.
Simply calling the browser's Back functionality isn't really sufficient either, because you might want the page to refresh, e.g. to show the edits you just saved in the previous page.
So my question is, has anyone found a smart way to handle this kind of situation, specifically in an MVC environment?
Note: I am using ASP .NET MVC so if possible I'd like answers to pertain to that, however any ideas are welcome.
What's wrong with setting a cookie, or using a session variable? The only reason you wouldn't is if you don't control the page that calls into you, in which case your only options are query strings, post values, or referrer.
I thought I might add my answer to the question to see if others think it's a good idea.
I'm simply passing it through to the controller using TempViewData:
#{
TempData["returnURL"] = Request.Url.AbsoluteUri;
}
and then accessing it in a similar way to this (in my real version I check that the key is in TempData and that the returnURL is a real URL):
return Redirect(TempData["returnURL"].ToString());
If it needs to continue on past the first page change (i.e. Search page -> Edit page -> Edit Section page) I'm adding it again
TempData["returnURL"] = TempData["returnURL"];
Check my blog post on it: Using cookies to control return page after login on asp.net mvc 3
Just like #Mystere Man mentioned, you can just use a cookie or session for it. I went for cookies back when I had a similar situation a while ago.
Try register a new route of which the url is /{controller}/{action}/{id}/returnurl/{*url} and then use a RedirectToAction in the action that accepts url as a parameter
Request.UrlReferrer.AbsoluteUri
though i'd still argue that you shouldn't be creating your own "back" button.
Use an interceptor or an aspect:
Intercept each request in some fashion (e.g., a #Before aspect) and save the requested URL to the session, overwriting it each time
In your view layer, access that Session object as needed, in your case for the back link.
This kind of design allows you to always have the most recent request available if you want to use it. Here's an example to write an aspect / interceptor in .NET. Additionaly, PostSharp is a .NET aspect project.
At present, a quick and dirty method has eluded me... so I'm using a practical method.
On a conceptual level, the 'back-ability' of a page should be determined by the page that you're currently on. The View can infer this (in most cases) if the parameters captured in the Controller are passed to it via the ViewModel.
Example:
Having visited Foo, I'm going to Bar to view some stuff, and the back button should return to Foo.
Controller
public ActionResult Foo(string fooId) // using a string for your Id, good idea; Encryption, even better.
{
FooModel model = new FooModel() { fooId = fooId }; // property is passed to the Model - important.
model.Fill();
return View("FooView", model);
}
public ActionResult Bar(string fooId, string barId)
{
BarModel model = new BarModel() { fooId = fooId; barId = barId };
model.Fill()
return View("BarView", model)
}
ViewModels
public class FooModel
{
public string fooId { get; set; }
public void Fill()
{
// Get info from Repository.
}
}
public class BarModel
{
public string fooId { get; set; }
public string barId { get; set; }
public void Fill()
{
// Get info from Repository.
}
}
View (Partial) // No pun intended... or maybe it was. :)
Your BarView can now interpret from its model where it needs to go back to (using fooId).
On your BarView (using MVC2 syntax):
Back
You can use Html.ActionLink as well.
Alternatively:
You can inherit your ViewModels from a BaseViewModel, which can have a protected property returnURL. Set this where necessary.
Example:
On your ViewModel:
public class BarModel : BaseViewModel
{
public string fooId { get; set; }
public string barId { get; set; }
public void Fill()
{
returnURL = string.Format("/Foo?fooId={0}", fooId)
// Get info from Repository.
}
}
On View:
Back
Would this be better handled by partial actions that display without leaving the page and using JQuery to make a dialog/wizard workflow?
Then you only need to react to the 'Finish' button on the dialog to refresh the original view.
For the part of your question regarding "saving the record they are editing" I would think the post-redirect-get (PGR) pattern would apply to you.
This might be a good place to read about it if you are not familiar with it.
http://en.wikipedia.org/wiki/Post/Redirect/Get
http://blog.andreloker.de/post/2008/06/Post-Redirect-Get.aspx
Encode the returnUrl using Url.Encode(returnUrl) for inclusion in the URL.
When ready to redirect, use Url.Decode(returnUrl) and use the value for the actual redirect.
After a pair programming session, an interesting question came up which I think I know the answer for.
Question: Is there any other desired way in ASP.NET MVC to retain 'state' other than writing to database or a text file?
I'm going to define state here to mean that we have a collection of person objects, we create a new one, and go to another page, and expect to see the newly created person. (so no Ajax)
My thoughts are we don't want any kung-fu ViewState or other mechanisms, this framework is about going back to a stateless web.
What about user session? There are plenty of valid use cases to store things in session. And what about a distributed caching system like memcached? You also seem to leave out the query string - which is an excellent state saver (?page=2). To me those seem like other desirable methods to save state across requests...?
My thoughts are we don't want any kung-fu ViewState or other mechanisms, this framework is about going back to a stateless web.
The example you provided is pretty easy to do without any sort of "view state kung fu" using capabilities that are already in MVC. "User adds a person and sees that on the next screen." Let me code up a simple PersonController that does exactly what you want:
public ActionResult Add()
{
return View(new Person());
}
[HttpPost]
public ActionResult Add(PersonViewModel myNewPersonViewModel)
{
//validate, user entered everything correctly
if(!ModelState.IsValid)
return View();
//map model to my database/entity/domain object
var myNewPerson = new Person()
{
FirstName = myNewPersonViewModel.FirstName,
LastName = myNewPersonViewModel.LastName
}
// 1. maintains person state, sends the user to the next view in the chain
// using same action
if(MyDataLayer.Save(myNewPerson))
{
var persons = MyDataLayer.GetPersons();
persons.Add(myNewPersion);
return View("PersonGrid", persons);
}
//2. pass along the unique id of person to a different action or controller
//yes, another database call, but probably not a big deal
if(MyDataLayer.Save(myNewPerson))
return RedirecToAction("PersonGrid", ...etc pass the int as route value);
return View("PersonSaveError", myNewPersonViewModel);
}
Now, what I'm sensing is that you want person on yet another page after PersonSaveSuccess or something else. In that case, you probably want to use TempData[""] which is a single serving session and only saves state from one request to another or manage the traditional Session[""] yourself somehow.
What is confusing to me is you're probably going to the db to get all your persons anyway. If you save a person it should be in your persons collection in the next call to your GetPersons(). If you're not using Ajax, than what state are you trying to persist?
ASP.NET MVC offers a cleaner way of working with session storage using model binding. You can write a custom model binder that can supply instances from session to your action methods. Look it up.