How to pass class via RedirectToAction - asp.net-mvc-3

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.

Related

Return raw objects from Action methods and convert them to JsonResult before rendering

The website that I'm working on is heavily depending on ajax/json and knockout.js.
I would like to have a lot of my Controllers return view-tailored 'json objects', without wrapping them in a JsonResult when returning the method.
This would mean I could easily composite multiple calls into one parent object, but still be able to call the Actions separately too.
Simplified example:
public object Main(int groupId)
{
var viewModel = new
{
Persons = Employees(groupId),
Messages = AllMessages()
};
return viewModel;
}
public object Employees(int groupId)
{
return DatabaseContext.Employees.Where(e => e.GroupId == groupId).ToList();
}
public object AllMessages()
{
return DatabaseContext.Messages.ToList();
}
I was hoping I could capture the returned object in OnActionExecuted and at that point wrap the whole result up in a final JsonResult.
The result is already converted to a string and captured in a ContentResult though.
Any ideas? :) Thanks,
A good approach on this is to create helper methods for your entity calls. Or if you have those methods already somewhere, they can actually serve as the helper methods. In that manner you can return a list of strongly-typed Messages and Employees as well as returning your desired parent object. You can then have individual controller methods that returns json objects. In addition, you can extend the parent viewmodel to return additional fields.
The Parent ViewModel
public class ParentModel {
public Employee Persons {get;set;}
public Message Messages {get;set;}
}
The Helper Methods
The beauty of using helper methods similar to what is defined here is that you can apply a few more logic to your query, and more, and you don't have to change anything in your controller methods.
public ParentModel GetMain(int groupId)
{
var viewModel = new ParentModel
{
Persons = Employees(groupId),
Messages = AllMessages()
};
return viewModel;
}
public IEnumerable<Employee> Employees(int groupId)
{
return DatabaseContext.Employees.Where(e => e.GroupId == groupId).ToList();
}
public IEnumerable<Message> AllMessages()
{
return DatabaseContext.Messages.ToList();
}
The Controller Methods
public ActionResult GetParent(int groupId){
return Json(helperinstance.GetMain());
}
public ActionResult GetEmployees(int groupId){
return Json(helperinstance.Employees());
}
public ActionResult GetMessages(int groupId){
return Json(helperinstance.AllMessages());
}
Thanks for the answer. I'm not going for the solution of von v. because I like to keep the boilerplate as small as possible.
In the end I am trying out the following approach. It seems to work pretty well for now, but I still have to test it in real production.
If anyone has some (security) concerns with this, I'm happy to hear them in the comments.
// BaseController
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var returnType = ((ReflectedActionDescriptor)filterContext.ActionDescriptor).MethodInfo.ReturnType;
// is the returnType not deriving from ActionResult? Automatically wrap it in a JsonResult
if ( !typeof(ActionResult).IsAssignableFrom(returnType) )
{
var result = filterContext.ActionDescriptor.Execute(filterContext, filterContext.ActionParameters);
filterContext.Result = Json( result );
}
}

Mocking Request.QueryString for Unit Tests and asserting against views

I have the following controller:
public class ResetController : Controller
{
//
// GET: /Reset/
private Models.ResetModel rm = new Models.ResetModel();
public ActionResult Index()
{
//Check that this has a query string that is containing in the database
//and has been done in the last 30 mins.
string qString = Request.QueryString["v"].ToString();
//if this is a good querystring
if (rm.CheckQString(qString))
return View();
else
return View("Index", "Home");
}
I now need to create a unit test to ensure that if the Request.QueryString value is found in the database then the appropriate view is returned but I am unable to do so. Here is my attempt at a test to check this:
[TestMethod()]
public void IndexTest()
{
ResetController target = new ResetController();
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.QueryString).Returns(HttpUtility.ParseQueryString("?v=0ocIqhOQkrBaCXRO96E4B5HcOCYgMfJYOpRdNU/yIEUmH2szuXXKU51Td6NzRxlk"));
var result = target.Index() as ActionResult;
Assert.IsNotNull(result);
}
Can someone please help me with suggestions to ensure that this controller is fully tested?
Thanks
This is a late answer, but in the event that someone comes along this post in the future... Refer to this post how would I mock a querystring
The goal is to isolate the test so that it does not depend on the QueryString result from the database, but rather a provided value. To do this in Moq use the SetupGet method after creating a Mock Context. Hope this helps someone!
I would suggest you pass the model as a dependency to the controller. Then you can mock it as well in the unit test to isolate your controller logic from the model's CheckQString implementation logic.
I'm not sure though if I understand your problem correctly.
The good case might then look like this. Of course you would need to check if the correct view was returned.
[TestMethod()]
public void IndexTest()
{
const string query = "some query";
Models.ResetModel rm = new Mock<Models.ResetModel>();
rm.Setup(m => m.CheckQString(query)).Returns(true);
ResetController target = new ResetController(rm.Object);
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.QueryString).Returns(HttpUtility.ParseQueryString("?v=" + query));
var result = target.Index() as ActionResult;
Assert.IsNotNull(result);
}

How to use different type of parameter without creating new view?

I would like to create a list with a string and an int value at the same time like follows:
#Html.ActionLink("Back to List", "IndexEvent", new { location = "location" })
and
#Html.ActionLink("Back to List", "IndexEvent", new { locationID = 1 })
It didn't work. I guess MVC controller didn't get the type difference of parameter. So, I had to make a new Action as "IndexEvenyByID" but it requires to have a new view. Since I wanted to keep it simple, is there any way to use same view with respect to different parameters?
Try adding two optional parameters to the IndexEvent action like this:
public ActionResult IndexEvent(string location = "", int? locationID = null)
This should not require a new view or view model. You should have two actions as you have described, but the code could be as follows:
Controller
public ActionResult GetEvents(string location){
var model = service.GetEventsByLocation(location);
return View("Events", model);
}
public ActionResult GetEventsById(int id){
var model = service.GetEventsById(id);
return View("Events", model);
}
Service
public MyViewModel GetEventsByLocation(string location){
//do stuff to populate a view model of type MyViewModel using a string
}
public MyViewModel GetEventsById(int id){
//do stuff to populate a view model of type MyViewModel using an id
}
Basically, if your View is going to use the same view model and the only thing that is changing is how you get that data, you can completely reuse the View.
If you really want to stick to a single action and multiple type, you could use a object parameter.
public ActionResult GetEvents(object location)
{
int locationID;
if(int.TryParse(location, out locationID))
var model = service.GetEventsByID(locationID);
else
var model = service.GetEventsByLocation(location as string);
return View("Events", model);
}
Something like that (Not completly right but it gives you an idea). This, however, wouldn't really be a "clean" way to do it IMO.
(Edit)
But the 2 actions method is still by far preferable (eg. What happens if we're able to parse a location name into a int?)

Storing object in Session

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)

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.

Resources