MVC - Passing Data with RedirectToAction() - c#

I'd like to take data entered in an MVC user form and display it in a different view.
The class has the following private variable:
IList<string> _pagecontent = new List<string>();
The following action accepts a FormCollection object, validates it, and passes it on to the "Preview" view as a List:
[Authorize(Roles = "Admins")]
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateContent(FormCollection collection)
{
if (ModelState.IsValid)
{
string PageToInsert = collection["PageToInsert"];
string PageHeader = collection["PageHeader"];
string PageBody = collection["PageBody"];
//validate, excluded...
_pagecontent.Add(PageToInsert);
_pagecontent.Add(PageHeader);
_pagecontent.Add(PageBody);
}
return RedirectToAction("Preview", _pagecontent);
}
The Preview view has the following Page Directive for passing a strongly typed object List:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Template.Master" Inherits="System.Web.Mvc.ViewPage<List<string>>" %>
I would expect to be able to use the Model object to get my data, but alas I cannot. At the following line, I get an error index out of bounds exception, stating the index must be non-negative and less than the size of the collection:
<% if (Model[0].ToString() == "0") { %>
And some strange parameters have been added to the URL, as it resolves to
http://localhost:1894/Admin/Preview?Capacity=4&Count=3
So I have two questions:
When I call RedirectToAction and pass it my List, why is it inaccessible in the view's Model object?
What is the proper way to go about doing what I'm trying to do, namely pass a collection of strings to a view for display there?

Try using TempData. It is like a single-shot session object. You put values you want into TempData, immediately redirect and get them out. There is a good writeup here: http://blogs.teamb.com/craigstuntz/2009/01/23/37947/

Be careful when using TempData. It works great in a single server environment but in a cloud environment it may not work as expected since you cannot guarantee that the request will hit the same machine. This happens because TempData rely on the asp.net session. But if you are using other session manager like SQL or AppFabric Cache it will work fine.

The second parameter to RedirectAction is routeValues, not model.
protected internal RedirectToRouteResult RedirectToAction(string actionName, object routeValues);
Try using TempData for the model. Its for persisting data between redirects.

The problem with RedirectToAction is it's returning a HTTP 302 and the browser is then on it's own going and doing a brand new HTTP request. You may want to consider using a cookie and/or session object to persist the data between requests.

This is not working because RedirectToAction is actually sending back a Http 302 to the browser. When the browser receives this 302, it does a new request to the server asking for the new page. New request, new temp variables.
You will also face this problem when you try to save/edit/delete something and for some reason you deny it and you have to return the old form again.
So, instead of:
return RedirectToAction("Preview", _pagecontent);
Put the Preview logic in a separate method and just call it:
return PreviewLogic(_pagecontent);
You can also use the TempData[] dic to persist data for the next request like others have said, but then you will not avoid the 302 additional round trip to the server.

It sounds like you're trying to do:
public ActionResult UpdateContent(FormCollection form) {
...
return View("Preview", _pagecontent);
}
Note that a redirection is supposed to be a "clean slate" for the browser (except for things like the auth cookie). You don't get to tell the browser to pass information along to the next request, since the next request should be able to stand on its own. All you get to do is tell the browser what URL to request next. In ASP.NET MVC, when you pass an arguments-object to RedirectToAction, the public properties of that object are appended as query-string parameters to the generated URL.

Can't you just make 2 action results with the same name and mark 1 of them with HttpPost?
public ActionResult UpdateContent(FormCollection preview = null)
{
return View(preview);
}
[HttpPost]
public ActionResult UpdateContent(FormCollection collection = null, bool preview = false)
{
if (preview)
return UpdateContent(collection);
else
return UpdateContent(null);
}

It looks like you are looking for the UpdateModel command:
Check out ScottGu's blog post on the topic:
Improved UpdateModel and TryUpdateModel methods

Related

What's the correct code to use View like a redirect?

One of the View methods on the Controller class (in System.Web.Mvc) allows you pass a model to a different view. However, it does not update the url to the name of the new view because this method works more like Server.Tranfer than Response.Redirect. The point is that this behaviour can be confusing to anyone picking-up MVC. So, after the View method has been called, I would like the url path to be rewritten to reflect the name of the new view. I have tried the following, which does not work, on a class that implements the Controller class:
public ViewResult ViewAsRedirect(string viewName, object model)
{
var baseView = base.View(viewName, model);
ControllerContext.HttpContext.RewritePath(baseView.ViewName);
return baseView;
}
What's the correct code to implement what I have described?
EDIT
Any RedirectToAction method does not allow you to send you model to another action. There is a dirty workaround where you store it in TempData before you exit one action and retrieve it at the beginning of another action. I don't want to use this pattern. This is why I am using View(...) instead.
You simply cannot "update the URL" (i.e. redirect) and return content.
If you want the new URL to show the same content as you anticipated, then you'll need to (temporarily) store the results and include an identifier for the resource that you wish to display on the redirected URL.
There you can pull the resource in from the controller for the redirected URL again, and display it on the appropriate view.
So if you POST your object model to /Foo/Create, you can for example store model in a database, which yields an ID: 42. Then you can redirect to /Foo/View/42, and display it.
If you can explain what you are actually trying to do, a more concrete answer can be given.
RedirectToAction is very intuitive in my opinion... you should use one of the redirecting methods of MVC controller: Redirect(url), RedirectToAction(acion), RedirectToAction(acion, controller), and so on.
example
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
public ActionResult Redirect()
{
return this.RedirectToAction("Index");
}
}
EDIT
If your action needs to collect tons of data to pass to the view, you could redirect very early, by detecting the condition of redirection, and loading all the data inside the other action, based on simple route data such as database IDs, or simple strigs or numbers.
== OR ==
You can render javascript code to change the URL in the client side, when the page loads: Modify the URL without reloading the page
This works only in very recent browsers (current date nov/2013).

Asp.net Membership Provider shows login information in url

I'm not sure what I did, but now my site is showing this as the url:
http://localhost:53187/Customer/AccountScreen?UserName=testx&Password=test12345&RememberMe=False&AccountId=5b89d595-ef19-4feb-b95d-bf39672c9ac4
I am calling the customer's account screen like this:
return RedirectToAction("AccountScreen", "Customer", model);
So, I don't see why it's now displaying this in the url now. That really seems like a bad practice to me.
Is there a way to prevent this?
It's not clear why are you passing the model to the RedirectToAction method. The third parameter is for routeValues.
Whatever you pass to the routeValues parameter will expose its properties in the url. Just remove the third parameter and it will be ok. If you need to pass anything to the AccountScreen use something like
return RedirectToAction("AccountScreen", "Customer", new { id = model.Id });
You could include only the values you are interested in:
return RedirectToAction(
"AccountScreen",
"Customer",
new { AccountId = mode.AccountId }
);
will redirect to http://localhost:53187/Customer/AccountScreen?AccountId=5b89d595-ef19-4feb-b95d-bf39672c9ac4
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. So, what you see is a get request with model as route values. HTTP doesn't support redirection using POST, so you can't change that.
What you can do - call method from your controller without returning to browser (if this is a same controller):
return AccountScreen(model);
You can use TempData to store your model (this also will be GET request, but model will not be passed in route values - it will be stored in session). In your controller:
TempData["model"] = model;
return RedirectToAction("AccountScreen", "Customer");
In Customer controller:
public ActionResult AccountScreen()
{
YourModel model = TempData["model"] as YourModel;
//...
}

Passing object from one action method to another in ASP.NET MVC 3

I am using ASP.NET and MVC 3 in visual studio and have a question about passing an object from one action method(called Search) to another(called SearchResult). I tried using ViewData but it didnt persist down to the View of SearchResult. Below is a snippet of my code, from within a single controller. 'Search' gets called when data has been collected from a form and submit has been called(hence the [HttpPost] declaration):
[HttpPost]
public ActionResult Search(Search_q Q){
// do some work here, come up with an object of type 'Search_a'
// called 'search_answer'.
ViewData["search_answer"] = search_answer;
return RedirectToAction("SearchResults");
}
public ActionResult SearchResult(Search_a answer)
{
return View(answer);
}
I also tried using RedirectToAction("SearchResults", new {answer = search_answer}); instead of the above call to RedirectToAction but my 'search_answer' still didnt persist to the View. What is the best way to send this Search_a object to the SearchResult View?
You can use TempData to pass the object.
[HttpPost]
public ActionResult Search(Search_q Q){
// do some work here, come up with an object of type 'Search_a'
// called 'search_answer'.
TempData["search_answer"] = search_answer;
return RedirectToAction("SearchResult");
}
public ActionResult SearchResult()
{
var answer = (Search_a)TempData["search_answer"];
return View(answer);
}
If TempData does not suffice (what Eranga suggested), because SearchResult is not somewhat dependent on a Redirect (though you can have checks to get around it), you might want to look at the Request object. You can likely store the data as one of the query parameters via Request.Params. This can take advantage of the ModelBinding filter chain that Asp MVC has.

c# pass a string from an action to another

Imagine a page where the user can update, delete, edit or even vote the input. Since each action will be handled by a different ActionResult, the url of the page will have to change in case there are errors. My question is simple: Say your page is "http://localhost/Input/" and when you there are some errors, I want to redirect to "http://localhost/Input/Error" and display an error message. I don't want to use sessions for this therefore please don't show me this link. Basically, want I want to do this is something similar:
public ActionResult Create(FormCollection form) {
try {
// some code here
return RedirectToAction("Index");
}
catch () {
string errorMessage = "You have done something bad!";
// and I want to pass this string to my Error Action.
return RedirectToAction("Error");
}
}
The Solution I've Used
I know that I said I don't want to use TempData, but apparantly that is the best option and I've used that for my issue. As for the answer I've chosen DigBySwift's answer because that's the most logical thing you can do if you don't want to use Session for this type of operation.
As you say, if you don't want to use sessions then TempData is not an option. Ideally, you should try not to pass the error message to the next page. Instead store the error message(s) in a resource file.
You could pass an id/enum to you next page and then retrieve the error based on the passed parameter:
return RedirectToAction("Error", new { ErrorId = 4 });
public ActionResult Error(int errorId){ ... }
Edit: An alternative (since a db write-read would be expensive considering) would be to write the message to a cookie and then retrieve it after the redirect, and then delete the cookie. This is not great and I personally would not use this. But if you need to customise the message, it is an option.
Is there an option of saving the error message in database from the sender page and getting it back on the other page ?
I know its similar to the one posted above but it will be more dynamic as you dont need to put the message in resource file and you can save the error message in db and get that on other page
I'd like to suggest an alternative way. I use AJAX to do these types of operations, thus instead of redirecting user to another view, I can return a view, or mostly a partial view from my controller. Thus I can use that string message right inside my view using ViewBag.
public ActionResult Create(FormCollection form) {
try {
// some code here
return View('CreationSuccess');
}
catch () {
ViewBag.errorMessage = "You have done something bad!";
// and I want to pass this string to my Error Action.
return View('CrudError');
}
}

Pass complex object with redirect in ASP.NET MVC?

Hi,
I have a action that looks like this :
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Register(AdRegister adRegister, IEnumerable<HttpPostedFileBase> files)
The AdRegister is a complex class and I need to pass this in to a redirect method further down in the Register action, like this :
return this.RedirectToAction("Validate", adRegister);
The Validate action looks like this :
public ActionResult Validate(AdRegister adRegister)
I do know that I can pass simple parameters but in this case itĀ“s a complex object. This example do not work, the adRegisterĀ“s properties will be null.
Is this posible and if so, how?
BestRegards
More Information : Register action will take the adRegister and do som magic on it, then It will be sent to the Validate action. The Validate action will return a validation page to the user. When the user hit the grant button the adRgister will be filled from the form and then sent to the vValidate post where it will be saved. I have looked in to place the adRegister in cache or database temporarily but it will be better if I could simple pass it to the next action.
One possibility would be to pass the simple properties in the query string:
return RedirectToAction(
"Validate",
new {
foo = adRegister.Foo,
bar = adRegister.Bar,
... and so on for all the properties you want to send
}
);
Another possibility is to store it in TempData (for the lifetime of the redirect) or Session (for the lifetime of the ASP.NET session):
TempData["adRegister"] = adRegister;
return RedirectToAction("Validate");
and then retrieve it from TempData:
public ActionResult Validate()
{
adRegister = TempData["adRegister"] as AdRegister;
...
}
Yet another possibility (and the one I would recommend you) is to persist this object in the POST method in your datastore:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Register(AdRegister adRegister, IEnumerable<HttpPostedFileBase> files)
{
...
string id = Repository.Save(adRegister);
return RedirectToAction("Validate", new { id = adRegister.Id });
}
and then fetch it from the data store after you redirect:
public ActionResult Validate(string id)
{
AdRegister adRegister = Repository.Get(id);
...
}
an idea would probably create a session variable and pass around a Key that references that session variable if the object is required acorss a few views?
ASP.NET MVC's tempdata should be perfect for this.
That said, TempData or Session is one option, but has some downsides like being quite violate and oftentimes murky or difficult to debug. What might be preferable is to "stash" the temporary value in a persistent store, such as the user's profile or your own database, then pass a key through the validate method which can then load the data from said store. This also opens up the possibility of recovering abandoned carts and such.

Categories

Resources