Storing object in Session - asp.net-mvc-3

I know this subject has been treated in numerous posts but I just cannot work it out.
Within an Controller Inside an ActionResult I would like to store an object in the Session and retrieve it in another ActionResult. Like that :
public ActionResult Step1()
{
return View();
}
[HttpPost]
public ActionResult Step1(Step1VM step1)
{
if (ModelState.IsValid)
{
WizardProductVM wiz = new WizardProductVM();
wiz.Step1 = step1;
//Store the wizard in session
// .....
return View("Step2");
}
return View(step1);
}
[HttpPost]
public ActionResult Step2(Step2VM step2)
{
if (ModelState.IsValid)
{
//Pull the wizard from the session
// .....
wiz.Step2 = step2;
//Store the wizard in session again
// .....
return View("Step3");
}
}

Storing the wizard:
Session["object"] = wiz;
Getting the wizard:
WizardProductVM wiz = (WizardProductVM)Session["object"];

If you only need it on the very next action and you plan to store it again you can use TempData. TempData is basically the same as Session except that it is "removed" upon next access thus the need to store it again as you have indicated you are doing.
http://msdn.microsoft.com/en-us/library/dd394711(v=vs.100).aspx
If possible though, it may be better to determine a way to use posted parameters to pass in the necessary data rather than relying on session (tempdata or otherwise)

Related

send data between actions with redirectAction and prg pattern

how can i send data between actions with redirectAction??
I am using PRG pattern. And I want to make something like that
[HttpGet]
[ActionName("Success")]
public ActionResult Success(PersonalDataViewModel model)
{
//model ko
if (model == null)
return RedirectToAction("Index", "Account");
//model OK
return View(model);
}
[HttpPost]
[ExportModelStateToTempData]
[ActionName("Success")]
public ActionResult SuccessProcess(PersonalDataViewModel model)
{
if (!ModelState.IsValid)
{
ModelState.AddModelError("", "Error");
return RedirectToAction("Index", "Account");
}
//model OK
return RedirectToAction("Success", new PersonalDataViewModel() { BadgeData = this.GetBadgeData });
}
When redirect you can only pass query string values. Not entire complex objects:
return RedirectToAction("Success", new {
prop1 = model.Prop1,
prop2 = model.Prop2,
...
});
This works only with scalar values. So you need to ensure that you include every property that you need in the query string, otherwise it will be lost in the redirect.
Another possibility is to persist your model somewhere on the server (like a database or something) and when redirecting only pass the id which will allow to retrieve the model back:
int id = StoreModel(model);
return RedirectToAction("Success", new { id = id });
and inside the Success action retrieve the model back:
public ActionResult Success(int id)
{
var model = GetModel(id);
...
}
Yet another possibility is to use TempData although personally I don't recommend it:
TempData["model"] = model;
return RedirectToAction("Success");
and inside the Success action fetch it from TempData:
var model = TempData["model"] as PersonalDataViewModel;
You cannot pass data between actions using objects, as Darin mentioned, you can only pass scalar values.
If your data is too large, or does not consist only of scalar values, you should do something like this
[HttpGet]
public ActionResult Success(int? id)
{
if (!(id.HasValue))
return RedirectToAction("Index", "Account");
//id OK
model = LoadModelById(id.Value);
return View(model);
}
And pass that id from RedirectToAction
return RedirectToAction("Success", { id = Model.Id });
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. So you can not pass complex objects like you calling other methods with complex objects.
Your possible solution is to pass an id using with the GET action can build the object again. Some thing like this
[HttpPost]
public ActionResult SuccessProcess(PersonViewModel model)
{
//Some thing is Posted (P)
if(ModelState.IsValid)
{
//Save the data and Redirect (R)
return RedirectToAction("Index",new { id=model.ID});
}
return View(model)
}
public ActionResult Index(int id)
{
//Lets do a GET (G) request like browser requesting for any time with ID
PersonViewModel model=GetPersonFromID(id);
return View(id);
}
}
You can keep data (The complex object) between This Post and GET request using Session also (TempData is internally using session even). But i believe that Takes away the purity of PRG Pattern.

How to pass class via RedirectToAction

I have the following code:
public ActionResult Index()
{
AdminPreRegUploadModel model = new AdminPreRegUploadModel()
{
SuccessCount = successAddedCount,
FailureCount = failedAddedCount,
AddedFailure = addedFailure,
AddedSuccess = addedSuccess
};
return RedirectToAction("PreRegExceUpload", new { model = model });
}
public ActionResult PreRegExceUpload(AdminPreRegUploadModel model)
{
return View(model);
}
but model is null when I breakpoint on PreRegExcelUpload. Why?
Instead of using the Session object in Evgeny Levin's answer I would suggest to use TempData. See http://rachelappel.com/when-to-use-viewbag-viewdata-or-tempdata-in-asp.net-mvc-3-applications about TempData.
You could also fix this by calling return PreRegExceUpload(model); instead of return RedirectToAction(..) in you Index function.
TempData is just a "smart" wrapper for the Session, under the hood it still acts the same way.
Since it's only 4 fields, i would pass them via querystring.
Always try and avoid session/tempdata where possible, for which in this scenario it certainly is.
Are you sure that's your full code? As it doesn't make sense.
If your POST'ing some data and saving it to the database (for example), usually you redirect to another action passing the unique identifier (which is usually generated after the save), fetch it back from the DB and return the view.
That is much better practice.
If you explain your scenario a bit more, and show the proper code your using, i can help further.
Use session to pass model to method:
public ActionResult Index()
{
AdminPreRegUploadModel model = new AdminPreRegUploadModel()
{
SuccessCount = successAddedCount,
FailureCount = failedAddedCount,
AddedFailure = addedFailure,
AddedSuccess = addedSuccess
};
Session["someKey"] = model;
return RedirectToAction("PreRegExceUpload");
}
public ActionResult PreRegExceUpload()
{
var model = (AdminPreRegUploadModel) Session["someKey"];
Session["someKey"] = null;
return View(model);
}
Method RedirectToAction() can't take non primitive types as parameters, because url parameters is string.

RedirectToAction not working as expected

I have a simple MVC3 application that I want to retrieve some configuration details from a service, allow the user to edit and save the configuration.
If any errors are detected during the saving process, these are to be returned and reported back to the user.
The problem is that the configuration containing the errors is failing to be called and the currently saved values are just being redisplayed.
Stepping through the code, when errors are detected, it should redirect to itself using the passed config object but it doesn't and uses the method with no parameter.
Can anyone see where I'm going wrong?
Below are the two controller methods that are being called:
//
// GET: /Settings/Edit/
public ActionResult Edit()
{
SettingsViewModel config = null;
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel =
new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
// Retrieve the current configuration from the service for editing
IChangeService channel = serviceChannel.CreateChannel();
config = channel.GetSysConfig();
}
ViewBag.Message = "Service Configuration";
return View(config);
}
//
// POST: /Settings/Edit/
[HttpPost]
public ActionResult Edit( SettingsViewModel config)
{
try
{
if (ModelState.IsValid)
{
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel = new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
IChangeService channel = serviceChannel.CreateChannel();
config = channel.SetSysConfig(config);
// Check for any errors returned by the service
if (config.ConfigErrors != null && config.ConfigErrors.Count > 0)
{
// Force the redisplay of the page displaying the errors at the top
return RedirectToAction("Edit", config);
}
}
}
return RedirectToAction("Index", config);
}
catch
{
return View();
}
}
return RedirectToAction("Index", config);
You cannot pass complex objects like this when redirecting. You will need to pass query string parameters one by one:
return RedirectToAction("Index", new {
Prop1 = config.Prop1,
Prop2 = config.Prop2,
...
});
Also I couldn't see an Index action in your controller. Maybe it's a typo. Another thing I notice is that you have an Edit GET action to which you are probably trying to redirect but this Edit action doesn't take any parameters so it just seems weird. If you are trying to redirect to the POST Edit action, well, that's obviously impossible since a redirect is always on GET by its very nature.

Is this the correct way to save form values in MVC3?

Here's my code:
[HttpGet]
public ActionResult Register()
{
RegisterViewModel model = new RegisterViewModel();
using (CityRepository city = new CityRepository())
{
model.SelectCityList = new SelectList(city.FindAllCities().ToList(), "CityID", "CityName");
}
using (CountryRepository country = new CountryRepository())
{
model.SelectCountryList = new SelectList(country.FindAllCountries().ToList(), "CountryID", "CountryName");
}
return View(model);
}
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
//Actually register the user here.
RedirectToAction("Index", "Home");
}
//Something went wrong, redisplay the form for correction.
return View(model);
}
Is this the best approach or is there another better tested way? Keep in mind that my database tables/field names are nothing like what I declared in my models. I have to scrape the values from the ViewModel and put them into an entity framework generated class to persist the information.
Anything here that screams out at you as wrong?
I use that pattern and another pattern which looks like this (important part is the AutoMapper part):
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
{
// repopulate any input or other items set in GET
// prefer to do at top due to ^^^ is easy to overlook
return View(model);
}
// if it's an edit, pull to new instance
// from the database and use automapper to
// map over the submitted values from model to instance
// then update instance in database
//
// VALUE: useful if form only shows
// some of the properties/fields of model
// (otherwise, those not shown would be null/default)
// if it's new, insert
RedirectToAction("Index", "Home");
}
That's the pattern I generally use.
I prefer this pattern:
Controller:
[HttpGet]
public ActionResult Index()
{
var cities= (from m in db.cities select m);
ViewBag.Cities= cities;
var states = (from m in db.States select m);
ViewBag.States = states;
return View();
}
[HttpPost]
public ActionResult Index(RegisterViewModel model)
{
if (ModelState.IsValid)
{
// Saving the data
return View("ActionName", model);
}
return View();
}
View:
#Html.DropDownList("DDLCities",new SelectList(ViewBag.Cities, "CityId" , "CityName" ), new { #class = "className" })
#Html.DropDownList("DDLStates",new SelectList(ViewBag.States, "StateId" , "StateName" ), new { #class = "className" })
Advised changes to [HttpGet]:
[HttpGet]
public ActionResult Register()
{
// Get
var cities = new List<City>();
var countries = new List<Country>();
using (CityRepository city = new CityRepository())
{
cities = city.FindAllCities().ToList();
}
using (CountryRepository country = new CountryRepository())
{
counties = country.FindAllCountries().ToList();
}
// Map.
var aggregatedObjects = new SomePOCO(cities, countries);
var model = Mapper.Map<SomePOCO,RegisterViewModel>(aggregatedObjects );
// Return
return View(model);
}
Summary of changes:
Layout your logic in such a way the controller's job makes sense. Get - Map - Return. Exactly the tasks (in order) for which a Controller is designed for.
Use AutoMapper to do the heavy lifting of ViewModel creation for you.
Advised changes to your [HttpPost]:
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
return View(model);
try
{
var dbObj = Mapper.Map<RegisterViewModel,SomeDomainObj>(model);
_repository.Save(dbObj);
return RedirectToAction("Index");
}
catch (Exception exc)
{
if (exc is BusinessError)
ModelState.AddModelError("SomeKey", ((BusinessError)exc).FriendlyError);
else
ModelState.AddModelError("SomeKey", Resources.Global.GenericErrorMessage);
}
return View(model);
}
Summary of changes:
Try/catch. Always need to capture exceptions, whether they are domain exceptions or lower-level (database ones)
Check ModelState validity first. As #Cymen says - do it first so you don't forget later
Add exceptions to ModelState. Use custom exception classes for business errors with descriptive, resource-based messages. If the error is too low-level for the user (foreign key constraint, etc), show a generic message

Partial ViewModels, Controllers, in Razor

Trying to get my ducks in a row with MVC3 + Razor!
I finally understand the concept of a 'View-Model' to wrap my entity classes and tailor them to a View.
Now I'm assembling a page with partial views representing different elements necessary to the page (such as drop down lists, forms, etc.) each of these will be represented by a 'View-Model' that maps to an entity class and back to my database.
First I am trying to create a partial view representing a component that is a drop-down list of elements in the database, that when selected will render another partial view, etc.
I just can't put together why I can't generate this drop-down list, and once I do how the main 'controller' maps all this together?
In short I'm curious - does each partial view need a controller even if it's based on a strongly typed model?
Breaking it down:
My Entity Model-View Wrapper (getting all the elements available from the database
*Updated* - to a working example now, note I don't think I was asking the right question before, but this will give you an idea of what I was trying to accomplish! Next step is to move all these operations 'off' the controller (and populate them in the models default constructor, for ease of use later).
CharactersListViewModel.cs
Going to move avoid the 'View Model' for now until I get a little more comfortable
Creating a partial view that displays a drop down list with the Characters' ID as a value, and name as the text, create strongly-typed view, partial view
controller for main-page in section:
HistoryController.cs
public class HistoryController : Controller
{
public ActionResult Index()
{
var list = new List<SelectListItem>();
using (var _database = new fff_newEntities())
{
foreach(Character c in (from c in _database.Characters select c)){
list.Add(new SelectListItem(){Text = c.CharacterName, Value = c.Id.ToString()});
}
}
ViewBag.Clients = list;
}
}
//
// GET: /History/Details/5
public ActionResult Details(int id)
{
return View();
}
//
// GET: /History/Create
public ActionResult Create()
{
return View();
}
//
// POST: /History/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
//
// GET: /History/Edit/5
public ActionResult Edit(int id)
{
return View();
}
//
// POST: /History/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
try
{
// TODO: Add update logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
public ActionResult Delete(int id)
{
return View();
}
//
// POST: /History/Delete/5
[HttpPost]
public ActionResult Delete(int id, FormCollection collection)
{
try
{
// TODO: Add delete logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The index to display the whole page including the partial component (my drop down list)
index.cshtml:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.DropDownListFor(x => x.CurrentCharacterId, (IEnumerable<SelectListItem>)ViewBag.Clients);
On the last line here, #Html.Action(...) where do I actually create the drop-down list?
Sorry if this seems trivial but I can't wrap my head around it and I really want to learn MVC3 + Razor correctly!
A partial view is meant to abstract out some HTML/View Logic so that it can be re-used either in multiple places or for repeating (looping).
Though you can have an action that maps to the partial and if the partial in question does some explicit data access this might be the way to go but if you're just passing down all the data it needs from the controller itself then - no, you don't need a Controller/Action for it.
Since you're doing some explicit data access I would probably make an action for it...
[ChildActionOnly]
public ActionResult Characters()
{
using (var _database = new entities())
{
CharactersViewModel viewModel = new CharactersViewModel();
viewModel.Characters = _database.Characters.ToDictionary(c => c.Id, c => c.CharacterName);
return PartialView(viewModel);
}
}
In your view...
#Html.Action("Characters")
Of course there's nothing wrong with the way you're doing it but I find having it map to an action can make things easier down the road if you ever wanted to retrieve the HTML from this rendered partial view via an ajax request or something of the sort.
Notes:
Try to wrap your entity context object in a using so it can dispose of the connection.
You can use ToDictionary to select your dictionary directly from the query scope.

Resources