Persisting model data on form post when redisplaying the view for an invalid ModelState - asp.net-mvc-3

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.

Related

Retaining failed validation data

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();.

Save data from a Complex View

I'm new to MVC and want to make sure I understand how to appreach a complex view I need to build.
I need to allow the user to add multiple Institutions on a page and each Institution can
have multiple trainings.
so the view model would be a list of institutions which contains a list of Trainings.
I will have a button that allows them to add multiple institutions and within the institution
section another button that will allow them to add multiple trainings.
On the postback method Do I simply loop through the institutions in the model
and for each institution loop through it's list of trainings to save them to the database?
You probably want to instead use a Controller like this:
public class InstitutionController : Controller
{
public ViewResult Index()
{
return View(); // Keep it simple, load data via JSON instead
}
[HttpPost]
public JsonResult Load()
{
// Go get Institutions etc
return Json(institutions);
}
[HttpPost]
public JsonResult Save(Institutions[] institutions)
{
try
{
// Save the institutions to the DB
}
catch (Exception ex)
{
return Json(new { Message = "Error." });
}
return Json(null); // Or some other way of saying it worked
}
}
That is, the Model you pass to the View is not persisted by the browser - instead it's briefly in server memory while the server generates a response.
In the example above however, you can create a View that uses for example jquery to load the Model from the server via JSON, which you can then persist in the browser for the life of the page. When the user makes a modification to an Institution or Instutions you can use a method like Save() above to send the new data or data changes to the server to be saved to the database.

How to update the textbox value #Html.TextBoxFor(m => m.MvcGridModel.Rows[j].Id)

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");
}

How to disable validation before calling UpdateModel in MVC3 Controller

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();
}

Is there a simple way to pass data into an MVC3 app?

[Edit] To try to clarify:
I have a view that needs to be launched from an external application. The application requires string data to be passed from an external application (the data is free text and too long to pass as a query parameter), So I would like to launch the MVC application with a POST request. The view that is launched also needs to post data back to itself in order to submit the data it collects for storage in a database. So I end up with a View with two HttpPost flagged methods in my controller (MVC throws an error that there are ambiguous Create methods).
So in the code below Create() would be posted to from the external application. Create(FormCollection collection) would be posted to when a displayed View is submitted.
//POST: /Application/Create
[HttpPost]
public ActionResult Create()
{
MyModel model = new MyModel();
//Parse External Data to model from Request.InputStream
return View(Model);
}
//POST: /Application/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
//Save form collection data to database
return RedirectToAction("Index");
}
So long story short, how can I post data to an MVC application to launch a view, without getting an error for an ambiguous call.
Thanks.
in the first case when the post method comes in from the outside:
return View("ConfirmCreate", model)
Then create an action method named ConfirmCreate. After ConfirmCreate is called the second time you will redirect back to Index as you have.
So I was able to do this by changing the POST call to load the application to a PUT To avoid have duplicate post endpoints), then sending the PUT from an ajax call in another application and replacing the current document with the returned html from the successful ajax call. Thanks for the suggestions.

Resources