ASP.NET MVC: Pass parameters when redirecting from one action to another
In ASP.NET MVC, it’s common to have a situation where you post to one controller action and then redirect to another controller action. Let’s say that you want to pass values from the first controller method to the other. The problem is that in out-of-the-box ASP.NET MVC, there is no way to redirect to another action and pass a parameter into the action that you are redirecting to. Here is one way you can get around this problem:
public class AccountController : Controller
{
[AcceptGet]
public ActionResult Index(IndexViewModel model)
{
model.Message = (string) TempData["Message"];
return View(model);
}
[AcceptPost]
public ActionResult Save(SaveUpdateModel model)
{
// save the information
TempData["Message"] = model.Message;
return RedirectToAction("Index");
}
}
The reason that you would do a redirect in this case is so that (a) you can reuse the code in the Index method and (b) you are redirecting to a GET operation, so if the user clicks Refresh in their browser, it won’t try to re-post the previous page.
I don’t really like how that code works, for various reasons:
- I’m using hardcoded strings (“Index” and “Message”). These might not get caught by refactoring tools if I want to change names, and I don’t get compile time checking on them.
- In the Index() method, I have to grab the value out of TempData. That works fine if you’re being redirected from the Save() action, but what if I’m being redirected from another action? What if I’m not being redirected from anywhere and the user is accessing the action directly? Ideally, when the Index() method is called, it should have everything that it needs in its parameters at the beginning of the method.
Personally, I don’t like to see TempData or ViewData anywhere in my controller methods. Don’t get me wrong, I need them and I’m going to use them, but I want it all done under the covers. I favor the “One Model In, One Model Out” pattern, where each controller action method takes in one model parameter (or none) and returns another model. Doing it this way allows me to avoid the use of magic strings and hashtables. I want my code to look like this:
[PassParametersDuringRedirect]
public class AccountController : Controller
{
[AcceptGet]
public ActionResult Index(IndexPresentationModel model)
{
return View(model);
}
[AcceptPost]
public ActionResult Save(SaveUpdateModel model)
{
// save the information
var presentationModel = new IndexPresentationModel();
presentationModel.Message = model.Message;
return this.RedirectToAction(c => c.Index(presentationModel));
}
}
This code does exactly what I want. Now…
- I don’t have any magic strings.
- Everything is strongly typed.
- The parameter passed to the Index() method will always be complete. I don’t have to load anything out of TempData to get it to work.
One thing I love about ASP.NET MVC is that it is very extensible, so you can usually make it do what you want. This is the purpose of the MVCContrib project — people extending ASP.NET MVC to make it even better.
You may notice that in the code snippet above, I decorated my controller class with the [PassParametersDuringRedirect] attribute. Now, any parameters that I pass into RedirectToAction() will get passed into the action that I redirected to.
One slight problem… this new version of RedirectToAction() is an extension method on the Controller class, which means to call it, you would have to write “this.RedirectToAction()”. If you don’t want to have to write “this.” every time, you can just create a base controller class that has this method:
protected RedirectToRouteResult RedirectToAction(Expression> action)
where T : Controller
{
return ControllerExtensions.RedirectToAction(this, action);
}
Thanks a lot !! I was wondering how to do this..
Hi Jon,
Thanks for posting this article! I am not very fond of using TempData explicitly in my controller classes and this is a really helpful solution. Everything appears to be working with this approach. TempData is being populated with the object parameter and I’m able to use it in the second controller method. However, a ModelError exists as part of the ModelStateDictionary on the object that is being passed as a parameter to the controller method. The error text is “The parameter conversion from type ‘System.String’ to type ” failed because no TypeConverter can convert between these types…’ The only way I was able to get rid of the error was to override ToString() on my object and return an empty string, but this does not seem like a very elegant approach. Have you had this issue or have any ideas how this can be resolved?
Thanks,
Brandon
@Brandon,
I haven’t run into that problem. Can you post some code snippets of what you’re trying to do?
Here is a short example.
namespace MvcApplication1.Controllers
{
[PassParametersDuringRedirect]
public class HomeController : Controller
{
public ActionResult Index()
{
//build basic DTO to pass as parameter
TestModel testModel = new TestModel();
testModel.Name = “Brandon”;
testModel.TestModelID = 5;
//redirect to another controller action, passing TestModel as parameter
return this.RedirectToAction(c => c.About(testModel));
}
public ActionResult About(TestModel testModel)
{
//TestData is properly populated with the TestModel object, but there is an error in the ModelStateDictionary
//triggering the ValidationSummary on the view to display itself
ModelStateDictionary msd = ViewData.ModelState;
foreach (var modelState in msd.Values)
{
foreach (var modelError in modelState.Errors)
Debug.WriteLine(modelError.Exception.ToString());
}
return View();
}
}
}
The Debug output displays this exception:
System.InvalidOperationException: The parameter conversion from type ‘System.String’ to type ‘MvcApplication1.Models.TestModel’ failed because no TypeConverter can convert between these types.
at System.Web.Mvc.ValueProviderResult.ConvertSimpleType(CultureInfo culture, Object value, Type destinationType)
at System.Web.Mvc.ValueProviderResult.UnwrapPossibleArrayType(CultureInfo culture, Object value, Type destinationType)
at System.Web.Mvc.ValueProviderResult.ConvertTo(Type type, CultureInfo culture)
at System.Web.Mvc.DefaultModelBinder.ConvertProviderResult(ModelStateDictionary modelState, String modelStateKey, ValueProviderResult valueProviderResult, Type destinationType)
Thanks again,
Brandon
Jon,
Here is some more information. The URL ends up looking like this:
http://localhost:26964/Home/About?testModel=MvcApplication1.Models.TestModel
Here is the TestModel object definition:
public class TestModel
{
public string Name { get; set; }
public int TestModelID { get; set; }
}
@Brandon,
Make sure that your MVCContrib is up to date. This looks like a bug that I fixed on March 25.
Jon
Hi Jon,
I was using the most recent release of the MvcContrib bits, released on Mar 25. The following update addressed the issue I’m having:
r951 by Jeremy.Skinner on Jun 14, 2009 Diff
Issue #4405 – PassParametersDuringRedirect
should not add reference types to the
RouteData.
The specific method of interest is RemoveReferenceTypesFromRouteValues.
Thanks again for your help. No more TempData references in my controllers!
Brandon
when i try to redirectaction method, i get error like below
__________________________________
return this.RedirectToAction(c => c.CreateSqlTask(custList));
__________________________________
Could not load file or assembly ‘Microsoft.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ or one of its dependencies. The system cannot find the file specified.
have any idea ?
@sefer,
Microsoft.Web.Mvc, also known as the “MVC Futures” assembly, comes as part of MVCContrib. It should be packaged with the MVCContrib DLLs that you downloaded.
I am having a similar problem with persisting model data over various views under the same controller. Is this a good solution for view to view communication of a common ViewModel – or is there a better way to do this in the context of a single view?
More info on the problem: http://stackoverflow.com/questions/1681325/asp-net-mvc-how-to-persist-model-over-various-views
Thanks,
Adam Tolley
To reply to my own post – It looks like this also supports intra-controller redirects. My question then changes slightly – Why would I use a redirect rather than just returning View(“otherView”, model); ?
Link to answer http://mvccontrib.codeplex.com/wikipage?title=RedirectToAction&referringTitle=Documentation
@Adam,
A lot of times the action that you’re redirecting to has a bunch of logic in it. So you could just return View() if you want, because in your case you don’t have any logic in the action method that you would be redirecting to if you did the redirect.
OH! Light bulb moment! Calling View(“otherView”, model) causes the current action to dump data into that view but not invoke the method – this seems obvious but I had missed it for some reason. I think that clears up a lot, Thanks!
Hi Jon,
This was exactly what i was looking for, but alas, my model parameter which im trying to pass over to the Action is not being passed during my redirect. Am i doing something wrong or missing something?
i’ve added the decoration [PassParametersDuringRedirect], and the code looks like:
if(!ModelState.IsValid)
{
return this.RedirectToAction(c => c.Index(model));
}
The Action index looks like :
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index(RegisterModel model)
Any help would be great.
ps. Curretly Using MVC2 and MVCContrib 2.0.61
Thanks Nick
Hi,
TempData is stored in session right? Won’t this give problems in a load balanced environment (if session state is in-proc)?
Thanks,
Steven
@Nick Formosa — did you get that sorted out, I have the same issue.
hi
my name is pavan
i got stucked with small problem.
1.i wanted to change the case of the url generated when return this.RedirectToAction(c => c.Login(String.Empty)); is executed .
2.is there any way to know to which view the code will return
Very nice article, really like the “One Model In, One Model Out†idea.
What about buttons? Do you prefer a parameter like “string cancelButton” on your Save method or would you add CancelButton as a property on your SaveUpdateModel (which I assume MVC would also bind to)?
This is just what I was looking for. So nice to be able to pass models between controller instead of having to use TempData. Thanks!
I’ve noticed that your example uses this. What about passing parameters to another controller class? Is that possible?
It does not work for me. The model passed to the next Action was always null. What’s missing?
using Microsoft.Web.Mvc;
using MvcContrib.Filters;
namespace MySeat.WebClient.Controllers
{
[PassParametersDuringRedirect]
public class SkedsController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MakeIt(MyChosenViewModel chosen, FormCollection fc)
{
if (ModelState.IsValid)
{
// do something
MyOtherViewModel otherModel = new MyOtherViewModel();
otherModel.Chosen = chosen;
return this.RedirectToAction(x => x.SelectIt(otherModel));
}
return View();
}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult SelectIt(MyOtherViewModel otherModel)
{
// here i got null otherModel
// do other things and display View
return View(otherModel);
}
}
}