Image the following controller method:
public ActionResult ShipmentDetails(Order order)
{
return View(new OrderViewModel { Order = order });
}
The incoming order parameter is filled from a custom model binder, that either creates a new order for this session and stores it in the session, or reuses an existing order from the current session. This order instace is now used to fill a shipment details form, where users can enter their address and so on.
When using #using(Html.BeginForm()) in the view. I cannot use the same signature for the post method again (because this would result in ambigious method names) and I found me adding a dummy parameter just to make this work.
[HttpPost]
public ActionResult ShipmentDetails(Order order, object dummy)
{
if (!ModelState.IsValid)
return RedirectToAction("ShipmentDetails");
return RedirectToAction("Initialize", order.PaymentProcessorTyped + "Checkout");
}
What are the best practices for this? Would you simply rename the method to something like PostShipmentDetails() and use one of the overloads of BeginForm? Or does the problem originate from the point, that the first method has the order parameter?
You could use the ActionName attribuite:
[HttpPost]
[ActionName("ShipmentDetails")]
public ActionResult UpdateShipmentDetails(Order order) { ... }
or a more classic pattern:
public ActionResult ShipmentDetails(int orderId)
{
var order = Repository.GetOrder(orderId);
return View(new OrderViewModel { Order = order });
}
[HttpPost]
public ActionResult ShipmentDetails(Order order)
{
if (!ModelState.IsValid)
return RedirectToAction("ShipmentDetails");
return RedirectToAction("Initialize", order.PaymentProcessorTyped + "Checkout");
}
Related
I understand ActionResult is the base class for RedirectResult class so essentially functionality for the code below is the same.
Is there an advantage to explicitly specify the appropriate result class as a return type as opposed to the base class?
public ActionResult Index()
{
return Redirect("Home/Contact");
}
public RedirectResult Index()
{
return Redirect("Home/Contact");
}
if you need redirect quickly to another action without lost any data and this action is in the same controller , call the action as a method:
public IActionResult Index()
{
return Contact();
}
public IActionResult Contact()
{
....
}
which one from another redirections methods is better is an opinion-based question and is not allowed by SO policy
I am trying to pass a IEnumerable model parameter from one method to another however when I debug the code the parameter is not being passed even though the method signature is the same.
I've tried with and without the Bind Prefix
Method 1:
[HttpPost]
public ActionResult Create_Filter([DataSourceRequest] DataSourceRequest request, [Bind(Prefix = "models")]IEnumerable<CourseFilterViewModel> courseFilterVM)
{
return RedirectToAction("Course", "Filter", courseFilterVM);
}
Method 2:
public ActionResult Course([Bind(Prefix = "models")]IEnumerable<CourseFilterViewModel> courseFilterVM)
{
return View(courseFilterVM);
}
I am expecting the entire model to be passed but instead I am getting a null value.
You can simply do as follows:
[HttpPost]
public ActionResult Create_Filter([DataSourceRequest] DataSourceRequest request, [Bind(Prefix = "models")]IEnumerable<CourseFilterViewModel> courseFilterVM)
{
// Others stuffs here
return Course(courseFilterVM);
}
If you want to pass a model to the View
public ActionResult Create_Filter()
{
return View();
OR
return View(CourseFilterVM); // Pass as a parameter
}
VIEW - Create_Filter.cshtml
#model IEnumerable<CourseFilterViewModel>
I'm new to MVC and trying to create a wizard-style series of views, passing the same model instance from one view to the next, where the user completes a little more information on each form. The controller looks something like this:-
[HttpGet]
public ActionResult Step1()
{
return View();
}
[HttpPost]
public ActionResult Step1(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
return View("Step2", model);
}
[HttpPost]
public ActionResult Step2(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
return View("Step3", model);
}
// etc..
Questions:-
When I submit the form from the Step1 view, it calls the Step1 POST method and results in the Step2 view being displayed in the browser. When I submit the form on this view, it calls the Step1 POST method again! I got it to work by specifying the action and controller name in Html.BeginForm(), so I'm guessing that the parameterless overload just POSTs back to the action that rendered the view?
I've noticed that the browser's address bar is out of sync with the current view - when I'm on the Step2 view it still shows the Step1 URL, and when on Step3 it shows the Step2 URL. What's going on?
Another approach I've seen for passing a model between views is to put the model in TempData then use RedirectToAction(). What are the pros and cons of this method versus what I'm currently doing?
I won't be providing any "back" buttons of my own in the wizard. Are there any pitfalls to be aware of regarding the browser's back button, and do either of the above two approaches help (or hinder)?
Edit
Prompted by #StephenMuecke's comment I've now rewritten this to use a single view. I tried this once before but had difficulties round-tripping a "step number" to keep track of where I was in the wizard. I was originally using a hidden field created with #Html.HiddenFor', but this wasn't updating as the underlying model property changed. This appears to be "by design", and the workaround is to create the hidden field using vanilla HTML (
Anyway the one-view wizard is now working. The only problem is the old chestnut of the user being able to click the back button after they have completed the wizard, make a change, and resubmit a second time (resulting in a second DB record).
I've tried adding [OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")] to my POST method, but all this does is display (in my case) a Chrome error page suggesting that the user clicks refresh to resubmit the form. This isn't user friendly and doesn't prevent a second submit.
you can use RedirectToAction() in this case without worrying about TempData. Just add your model as a parameter to each action and use RedirectToAction("Step2", model);
[HttpGet]
public ActionResult Step1()
{
return View();
}
[HttpPost]
public ActionResult Step1(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Step2", model);
}
[HttpGet]
public ActionResult Step2(MyModel model)
{
return View(model);
}
[HttpPost]
public ActionResult Step2(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Step3", model);
}
// etc..
The answer to #1 is found in #2.. if you dont specify the Action in you Html.BeginForm() it posts to the current url.
Using TempData to avoid model displaying in url.
[HttpGet]
public ActionResult Step1()
{
return View();
}
[HttpPost]
public ActionResult Step1(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
TempData["myModel"] = model;
return RedirectToAction("Step2");
}
[HttpGet]
public ActionResult Step2()
{
var model = TempData["myModel"] as MyModel;
return View(model);
}
[HttpPost]
public ActionResult Step2(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
TempData["myModel"] = model;
return RedirectToAction("Step3");
}
// etc..
Another option would be to add the name of the next action to ViewBag and set your actionName in each BeginForm()
[HttpGet]
public ActionResult Step1()
{
ViewBag.NextStep = "Step1";
return View();
}
[HttpPost]
public ActionResult Step1(MyModel model)
{
if (!ModelState.IsValid)
{
ViewBag.NextStep = "Step1";
return View(model);
}
ViewBag.NextStep = "Step2";
return View("Step2", model);
}
[HttpPost]
public ActionResult Step2(MyModel model)
{
if (!ModelState.IsValid)
{
ViewBag.NextStep = "Step2";
return View(model);
}
ViewBag.NextStep = "Step3";
return View("Step3", model);
}
//View
#using (Html.BeginForm((string)ViewBag.NextStep, "ControllerName"))
{
}
I'd prefer to add NextStep as a property to MyModel and using that instead of using ViewBag though.
I understand the thought behind your approach and don't have any issues with it. Unfortunately, I don't believe that ASP.NET MVC is geared very well for passing the the same view model (with data!) between different actions.
Typically, the scaffolded actions in the controller will either create a model item or find it by identifier in the database.
I don't know if this would help, but you could try to save it to the database on every step, and then retrieve it by identifier, or you could also save it to a session and grab it that way.
One issue I do see with your approach is you have Step2 set as a get, yet you probably want to post data to it from Step1 instead of using a query string. You may need to reconcile that issue.
I'm passed a list of parameters. Such as "Name", "Id", "Type". There will be an many of these in url, like so:
"Name=blah1,Id=231,Type=blah1;Name=blah2,Id=2221,Type=blah1;Name=blah3,Id=45411,Type=blah3;"
I wonder if there is a way to map these query parameters to a List of objects. So, I can create an object:
MyTestObject {Name;Id;Type} and can say in my controller
Index(IList<MyTestObject> params)
params will be filled in with data from query string.
Something that is similar to http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
I actually followed advice in the article by Mr. Haack I created a class with all of the parameters as public properties. Then I had a view take a list of objects of that type. If the query parameter names follow a certain pattern (prepended by index) then I get a list of my object automatically populated and I don't have to do any manual parsing at all. This is the simplest solution for me.
Example:
query param object:
public class QueryParams
{
public string Id,
public string Name,
public string Type
}
in controller method:
public ActionResult Index(IList<QueryParams> queryData)
then I make sure that query string is formated in the following way(prepended by index):
http://localhost/myapp/?[0].id=123&[0].Name=blah&[0].Type=Person&[1].Id=345&[1].Name=example&[1].Type=Stuff
In my controller, queryData list parameter will contain two objects populated with correct data.
You can you a values provider, and it will populate values from the querystring into a single object. This is what you would do if you're not going to create a View Model.
Transform the QueryString into a FormCollection via:
var GetCollection = new FormCollection( Request.QueryString );
You could create a custom model binder, that works off the Request.QueryString collection, rather than the regular FormCollection.
E.g:
public class MyTestObjectModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var qs = controllerContext.HttpContext.Request.QueryString;
return new MyTestObject
{
Name = qs["Name"],
Id = qs["Id"],
// etc, etc
};
}
}
Then setup your [HttpGet] action accordingly:
[HttpGet]
public ActionResult Index([ModelBinder(typeof(MyTestObjectModelBinder))]MyTestObject m) {
}
You could also register it globally if you like, e.g on Application_Start() :
ModelBinders.Binders.Add(typeof(MyTestObject), new MyTestObjectModelBinder());
Then you just need the model on your action:
[HttpGet]
public ActionResult Index(MyTestObject m) {
}
Having said all of this, if you've got this many parameters, one must ask where do these parameters come from? Most likely a form on another page.
In which case, this should be a [HttpPost] action, with the parameters in the form collection, then the regular MVC model binding will take care of the above code for you.
Yes, ASP.NET MVC could automatically bind collections to action params, but you need to pass your params as a from values, moreover, it is looks like to many params you going pass in query string. Have look at this one http://weblogs.asp.net/nmarun/archive/2010/03/13/asp-net-mvc-2-model-binding-for-a-collection.aspx
Basically what you need to do:
1) Create your class which would contain your params
public class MyParam
{
public int Id {get; set;}
public string Name {get; set;}
//do all the rest
}
2) Create model which you would pass to your view
public class MyViewModel
{
IList<MyParam> MyParams {get; set;}
}
3) Create your collection in your [HttpGet] action and pass that to your view:
[HttpGet]
public virtual ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.MyParams = CreateMyParamsCollection();
return View(model);
}
4) Iterate your collection in the view
#model MyViewModel
#{int index = 0;}
#foreach (MyParam detail in Model.MyParams)
{
#Html.TextBox("MyParams[" + index.ToString() + "].Id", detail.Id)
#Html.TextBox("MyParams[" + index.ToString() + "].Name", detail.Name)
index++;
}
5) Than on your [HttpPost] action you may catch your params in collection
[HttpPost]
public virtual ActionResult Index(MyViewModel model)
or
[HttpPost]
public virtual ActionResult Index(IList<MyParam> model)
P.S
Moreover, if you want to get all your form params in controller you may simple go like that:
[HttpPost]
public virtual ActionResult Index(FormCollection form)
On a related note, I was looking for a way to enumerate through the QueryString name-value collection, and this is what I came up with:
var qry =HttpContext.Request.QueryString;
if (qry.HasKeys())
{
foreach (var key in qry)
{
if(key != null)
var str= String.Format("Key:{0}, value:{1} ", key, qry.Get(key.ToString()));
}
}
This code will give you all the names and their values in the QueryString.
In Scott Hanselman's book (chapter 1), he provides us with two options to implement [HttpPost] for Create action method.
The first one relies on the TryUpdateModel to update a model object based on the incoming form fields. When the incoming form fields contains invalid input, the ModelState.IsValid will be set to false.
[HttpPost]
public ActionResult Create(FormCollection collection)
{
Dinner dinner = new Dinner();
if (TryUpdateModel(dinner))
{
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerId });
}
else
return View(dinner);
}
The second method is to utilize a model passed as Create action method arg as follows:
[HttpPost]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerId });
}
else
return View(dinner);
}
Which one is more recommended to use in production ?
If all your required data is either in Request.Form, route data, or the URL query string, then you can use model binding like in your second example.
The model binder creates your dinner object and populates it with data from the request by matching up property names.
You can customize the binding process with “white lists”, “black lists”, prefixes, and marker interfaces.
Just make sure you don't unintentionally bind values – see this link.