I've got a requirement to keep and display an incorrect entry on the view. It isn't possible to go to the next page without passing all validation.
For example, the use has to enter a date in text field in a certain format. Currently if the model binding fails it doesn't keep the invalid date text entry. I'd like to keep t and display it back to user with a vanadium failed message.
I'd like to know if this is achievable without creating a data holder type which temporarily hold the entered game and parsed value.
Yes, it is possible. Because you haven't posted any code, I'll just give you an example of how this can be achieved using server side validation.
Here is the [HttpGet] action method that serves up the form allowing the user to enter data.
[HttpGet]
public ActionResult Create()
{
// The CreateViewModel holds the properties and data annotations for the form
CreateViewModel model = new CreateViewModel();
return View(model);
}
Here is the [HttpPost] action method that receives and validates the form.
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
if (!ModelState.IsValid)
{
return Create(); // This will return the form with the invalid data
}
// Data is valid, process the form and redirect to whichever action method you want
return RedirectToAction("Index");
}
You can also use return View(model); in the [HttpPost] action method instead of return Create();.
Related
I have problem, that the text box value doesn't get updated with the new value in the model.
#Html.TextBoxFor(m => m.MvcGridModel.Rows[j].Id)
First the collection MvcGridModel.Rows get populated with some data, then when press button and submit the form it get new data successfully, but it doesn't update the textbox's value.
Do you have any ideas?
Thank u in advance
That's because HTML helpers such as TextBoxFor first look in the ModelState when binding their values and only after that in the model. So if in your POST action you attempt to modify some value that was part of the initial POST request you will have to remove it from the ModelState as well if you want those changes to take effect in the view.
For example:
[HttpPost]
public ActionResult Foo(MyViewModel model)
{
// we change the value that was initially posted
model.MvcGridModel.Rows[0].Id = 56;
// we must also remove it from the ModelState if
// we want this change to be reflected in the view
ModelState.Remove("MvcGridModel.Rows[0].Id");
return View(model);
}
This behavior is intentional and it is by design. This is what allows for example to have the following POST action:
[HttpPost]
public ActionResult Foo(MyViewModel model)
{
// Notice how we are not passing any model at all to the view
return View();
}
and yet inside the view you get the values that the user initially entered in the input fields.
There's also the ModelState.Clear(); method that you could use to remove all keys from the modelstate but be careful because this also removes any associated modelstate errors, so it is recommended to remove only values from the ModelState that you intend to modify inside your POST controller action.
All this being said, in a properly designed application you should not need this. Because you should use the PRG pattern:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
// there was some error => redisplay the view without any modifications
// so that the user can fix his errors
return View(model);
}
// at this stage we know that the model is valid.
// We could now pass it to the DAL layer for processing.
...
// after the processing completes successfully we redirect to the GET action
// which in turn will fetch the modifications from the DAL layer and render
// the corresponding view with the updated values.
return RedirectToAction("Index");
}
I have a controller with an Index method that has several optional parameters for filtering results that are returned to the view.
public ActionResult Index(string searchString, string location, string status) {
...
product = repository.GetProducts(string searchString, string location, string status);
return View(product);
}
I would like to implement the PRG Pattern like below but I'm not sure how to go about it.
[HttpPost]
public ActionResult Index(ViewModel model) {
...
if (ModelState.IsValid) {
product = repository.GetProducts(model);
return RedirectToAction(); // Not sure how to handle the redirect
}
return View(model);
}
My understanding is that you should not use this pattern if:
You do not need to use this pattern unless you have actually stored some data (I'm not)
You would not use this pattern to avoid the "Are you sure you want to resubmit" message from IE when refreshing the page (guilty)
Should I be trying to use this pattern? If so, how would I go about this?
Thanks!
PRG Stands for Post-Redirect-Get. that means when you post some data to the server back, you should redirect to a GET Action.
Why do we need to do this ?
Imagine you have Form where you enter the customer registration information and clicking on submit where it posts to an HttpPost action method. You are reading the data from the Form and Saving it to a database and you are not doing the redirect. Instead you are staying on the same page. Now if you refresh your browser ( just press F5 button) The browser will again do a similar form posting and your HttpPost Action method will again do the same thing. ie; It will save the same form data again. This is a problem. To avoid this problem, We use PRG pattern.
In PRG, You click on submit and The HttpPost Action method will save your data (or whatever it has to do) and Then do a Redirect to a Get Request. So the browser will send a Get Request to that Action
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
[HttpPost]
public ActionResult SaveCustemer(string name,string age)
{
//Save the customer here
return RedirectToAction("CustomerList");
}
The above code will save data and the redirect to the Customer List action method. So your browser url will be now http://yourdomain/yourcontroller/CustomerList. Now if you refresh the browser. IT will not save the duplicate data. it will simply load the CustomerList page.
In your search Action method, You dont need to do a Redirect to a Get Action. You have the search results in the products variable. Just Pass that to the required view to show the results. You dont need to worry about duplicate form posting . So you are good with that.
[HttpPost]
public ActionResult Index(ViewModel model) {
if (ModelState.IsValid) {
var products = repository.GetProducts(model);
return View(products)
}
return View(model);
}
A redirect is just an ActionResult that is another action. So if you had an action called SearchResults you would simply say
return RedirectToAction("SearchResults");
If the action is in another controller...
return RedirectToAction("SearchResults", "ControllerName");
With parameter...
return RedirectToAction("SearchResults", "ControllerName", new { parameterName = model.PropertyName });
Update
It occurred to me that you might also want the option to send a complex object to the next action, in which case you have limited options, TempData is the preferred method
Using your method
[HttpPost]
public ActionResult Index(ViewModel model) {
...
if (ModelState.IsValid) {
product = repository.GetProducts(model);
TempData["Product"] = product;
return RedirectToAction("NextAction");
}
return View(model);
}
public ActionResult NextAction() {
var model = new Product();
if(TempData["Product"] != null)
model = (Product)TempData["Product"];
Return View(model);
}
I'm looking to enable "saving" of form data prior to submission.
I want users to be able to save form progress, even when the form is in an invalid state, and then come back to it at a later time.
Now, my only problem is that I want to be able to use UpdateModel method to update my model. However, as the form is potentially invalid or just partially complete, this will throw an error.
Is there a way to annotate the following method such that validation is ignored in the SAVE instance?
[HttpPost]
public ActionResult Save(Model values)
{
var model = new Model();
UpdateModel(model);
}
I want to save having to write a 1-1 mapping for the elements being saved - which is my fallback option, but isn't very maintainable.
Give TryUpdateModel(model) a try, should fit your needs.
This won't throw an exception, and it will update the model and then return false if there are validation errors.
If you care about the errors, you check the ModelState in the false instance.
Hence, you can use it as so to always save changes:
[HttpPost]
public ActionResult Save(Model values)
{
var model = new Model();
TryUpdateModel(model);
model.saveChanges();
}
I am trying to pass a random string from my Controller to the View.
Here is my Controller code:
[HttpPost]
public ActionResult DisplayForm(UserView user)
{
//some data processing over here
ViewData["choice"] = "Apple";
return RedirectToAction("Next", "Account");
}
Now I want to pass that data value "Apple" to my view Next.cshtml which is created as follows:
//View: Next.cshtml
#{
ViewBag.Title = "Thanks for registering";
Layout = "~/Content/orangeflower/_layout.cshtml";
}
<p>Your favorite fruit is:</p>#ViewData["choice"]
But I am not able to see my data in the browser when the project runs.
Here is the snapshot:
1) On debug, the controller showing the value:
2) The browser view doesn't show the value "Apple"
3) On further debug to my Next.cshtml View:
Why is the value not getting passed to the View correctly. Both my controllers for Next and DisplayForm are within the same Controller AccountController.cs , still value not getting displayed.
Can someone help me solve this ?
You are not rendering a view, you are redirecting. If you wanted to pass some information tyo the view you need to return this view after adding it to ViewData:
[HttpPost]
public ActionResult DisplayForm(UserView user)
{
//some data processing over here
ViewData["choice"] = "Apple";
return View();
}
If you want to pass a message that will survive after a redirect you could use TempData instead of ViewData.
[HttpPost]
public ActionResult DisplayForm(UserView user)
{
//some data processing over here
TempData["choice"] = "Apple";
return RedirectToAction("Next", "Account");
}
then inside the Next action you could fetch the data from TempData and store it inside ViewData so that the view can read it.
You are performing a post - redirect - get. The ViewData is being set for this request, which returns a redirect, clearing the ViewData, then another request happens which does not have the data. Use TempData instead and it will be added to the ViewData automatically on the next request.
Simplified example:
[HttpGet]
public ActionResult Report(DateTime? date)
{
if (!date.HasValue)
{
date = DateTime.Now;
}
// Clear the ModelState so that the date is displayed in the correct format as specified by the DisplayFormat attribute on the model
ModelState.Clear();
// Expensive call to database to retrieve the report data
ReportModel model = DataAdapter.Convert(this.reportService.GetReport((DateTime)date));
// Store in TempData in case validation fails on HttpPost
TempData["ReportModel"] = model;
return View(model);
}
[HttpPost]
public ActionResult Report(ReportModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Report", new { date = model.Date });
}
else
{
// Load the report from TempData, and restore again in case of another validation failure
model = TempData["ReportModel"] as ReportModel;
TempData["ReportModel"] = model;
// Redisplay the view, the user inputted value for date will be loaded from ModelState
return View(model);
}
}
My question is, am I going about this the right way by storing the report data in TempData? The code seems a little strange especially reading from and then writing back to TempData in the HttpPost action method to ensure it persists the next request.
Other options I can think of are:
(1) Make another call to the service layer from the HttpPost action (I'd rather not make another database call because of a validation failure just to redisplay the form as it seems inefficient). I guess I could implement caching at the service layer to avoid the database round trip...
(2) Use hidden inputs in the form (yuk!).
(3) Store the most recently viewed report in session permanently.
How is everyone else doing this sort of thing? What's the recommended practice?
My question is, am I going about this the right way by storing the report data in TempData?
No, absolutely not. Store something into TempData if and only if you redirect immediately afterwards as TempData survivces only a single redirect. If you store something into TempData in your GET action and then render a view, an AJAX request for example from this view would kill the TempData and you won't get the values back on your POST request.
The correct pattern is the following (no TempData whatsoever):
public ActionResult Report(DateTime? date)
{
if (!date.HasValue)
{
date = DateTime.Now;
}
// Clear the ModelState so that the date is displayed in the correct format as specified by the DisplayFormat attribute on the model
ModelState.Clear();
// Expensive call to database to retrieve the report data
ReportModel model = DataAdapter.Convert(this.reportService.GetReport((DateTime)date));
return View(model);
}
[HttpPost]
public ActionResult Report(ReportModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Report", new { date = model.Date });
}
else
{
// Redisplay the view, the user inputted value for date will be loaded from ModelState
// TODO: query the database/cache to refetch any fields that weren't present
// in the form and that you need when redisplaying the view
return View(model);
}
}
(1) Make another call to the service layer from the HttpPost action
(I'd rather not make another database call because of a validation
failure just to redisplay the form as it seems inefficient). I guess I
could implement caching at the service layer to avoid the database
round trip...
That's exactly what you should do. And if you have problems with optimizing those queries or concerns of hitting your or something on each POST request cache those results. Databases are hyper optimized nowadays and are designed to do exactly this (don't abuse of course, define your indexes on proper columns and performance should be good). But of course caching is the best way to avoid hitting the database if you have a very demanding web site with lots of requests and users.
(2) Use hidden inputs in the form (yuk!).
Yuk, I agree but could work in situations where you don't have lots of them.
(3) Store the most recently viewed report in session permanently.
No, avoid Session. Session is the enemy of scalable and stateless applications.