Why does creating a new ViewModel return the same data as the old ViewModel? - asp.net-mvc

I'm just learning MVC3 now and this is really confusing me.
I have a ViewModel that contains some child ViewModels. Each of the ChildViewModels get rendered with a different Partial View, and when submitting execute a different action on the Controller. All the ChildViewModels should perform some custom validation on their data, and if successful it should move on to the next page. If the validation fails, it should simply return to the ParentView and display the errors.
[HandleError]
public class MyController: Controller
{
public ActionResult Index()
{
var viewModel = new ParentViewModel();
return View("ParentView", viewModel);
}
[HttpPost]
public ActionResult ChildViewModelB_Action(ChildViewModelB viewModel)
{
if (ModelState.IsValid)
{
return View("ChildViewModelB_Page2", viewModel);
}
else
{
// I'm having trouble returning to the ParentView and
// simply displaying the ChildViewModel's errors, however
// discovered that creating a new copy of the VM and displaying
// the ParentView again shows the existing data and any errors
// But why??
var vm = new ParentViewModel();
return View("ParentView", vm);
}
}
}
For example,
The page loads with 3 options.
User selects option B and fills out a form.
Upon submit, the child ViewModel B gets validated and fails.
Page returns to ParentView, with ChildB all filled out, however ChildB errors are now also showing.
Why does creating a new copy of the ParentViewModel display the ParentView with the same data as the original ParentViewModel?
And is there a different way I should be returning to the ParentView after doing server-side validation?

You need to clear the modelstate if you intend to modify values in your POST action
else
{
ModelState.Clear();
var vm = new ParentViewModel();
return View("ParentView", vm);
}
The reason for that is because Html helper such as TextBoxFor will first look in the modelstate when binding their values and after that in the model. And since the modelstate already contains the POSTed values, that's what's used => the model is ignored. This is by design.
This being said the correct thing to do in your case is to simply redirect to the GET action which already blanks the model and respect the Redirect-After-Post pattern:
else
{
return RedirectToAction("Index");
}

Why does creating a new copy of the ParentViewModel display the
ParentView with the same data as the original ParentViewModel?
Because the values of the fields are retrieved from the POSTed form and not from the model. That makes sense right? We don't want the user to show a form filled with different values from what they submitted.

Related

How to add a query string from surface controller in umbraco mvc in order to persist model values

How to add a query string from surface controller in umbraco mvc . This is my current code.
Initially I wrote a code like
public ActionResult Registration(RegisterModel model)
{
//Code to insert register details
ViewBag.Success="Registered Successfully"
return CurrentUmbracoPage();
}
with this I could successful persist my ViewBag and model properties value but I could not add a query string with it.
For certain requirement I have to change the code that returns a url with querystring.
which I did as below
public ActionResult Registration(RegisterModel model)
{
//Code to insert register details
ViewBag.Success="Registered Successfully"
pageToRedirect = AppendQueryString("success");
return new RedirectResult(pageToRedirect);
}
public string AppendQueryString(string queryparam)
{
var pageToRedirect = new DynamicNode(Node.getCurrentNodeId()).Url;
pageToRedirect += "?reg=" + queryparam;
return pageToRedirect;
}
and with this my values of the properties in model could not persist and the ViewBag returned with null value.
Can any one suggest me how to add query string by persisting the values in the model and ViewBag.
Data in ViewBag will not be available on the View when it redirects. Hence you have to add message in TempData which will be available in the View after the redirect like TempData.Add("CustomMessage", "message");

Should you have separate view Models for input to and output from a controller

I am new to asp.net mvc. I have this controller that takes in a few parameters and then returns a view that gets data based on the input parameters.
I want to accept the input parameters as an object (for example instead of first name, last name and age, I want a person class that has these three parameters as its properties). Now my question is does the input parameter class (Person class) qualify to be called view model?
If yes. Do I make the return view model a part of this class?
In other words which of the bottom two approaches is preferred
Case 1: Same class for input and return
public ActionResult GetPersonDetails(Person p)
{
return View(new Person {....})
}
Case 2: Separate classes for input and return
public ActionResult GetPersonDetails(Person p)
{
return View(new PersonDetails {....})
}
Now my question is does the input parameter class (Person class)
qualify to be called view model?
Yes.
If yes. Do I make the return view model a part of this class?
Not necessarily. You could have different view model passed to the view as the one that your controller action is taking as parameter, although this is rare case scenario. It would really depend on your specific case but the general pattern is the following:
[HttpGet]
public ActionResult Index()
{
MyViewModel model = ...
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
// Some validation error occurred => redisplay the same view so
// that the user can fix his errors
return View(model);
}
// at this stage the view model has passed all validations =>
// here you could attempt to pass those values to your backend
// TODO: do something with the posted values like updating a database or something
// Finally redirect to a successful action Redirect-After-Post pattern
// http://en.wikipedia.org/wiki/Post/Redirect/Get
return RedirectToAction("Success");
}

Viewing Details from View Model

I'm trying to figure out how to use a ViewModel to view details in a view.
I have a view model set up but can't seem to use it in my views.
In my controller I have a Details method that I want to use to display all the details in the ViewModel.
So far I am passing in a username as a string and then creating a new instance of the ViewModel, I am then trying to populate the ViewModel with data but am not sure how I need to do this. I am assigning the username based on the id coming in -
public ViewResult Details(string id)
{
var viewModel = new RegisterViewModel();
viewModel.UserName = id;
return View(viewModel);
}
and then sending the viewModel back to the view, but how do I get the other related data into the viewModel??
What you need to do is query your database and pull the related information into your view model before passing it back to your view i.e.
public ViewResult Details(string id)
{
var entity = // pull record from DB by id
return View(new RegisterViewModel()
{
UserName = id,
AnotherProperty = entity.AnotherProperty
...
});
}
If your mapping view models to models in a lot of places you might want to consider using AutoMapper, it will simplify your code.

Using Data Annotations with similar models and the same view to have different validation

I have two separate classes derived from the same interface, but have different validation/data annotations assigned. The requirement is that the same data needs to be collected, but on one screen nothing is required (a save screen), but on the other there are some required fields (a submit/finalize screen.)
I've made a PartialView that is to be used in two separate View, one for save, one for final submit.
I've tried using the parent Interface as the View's model, however my validators don't fire (as I expect, I'm guessing that because the Interface itself doesn't have any annotations, nothing will fire.) Is there a way to have the page dynamically choose one class or the other depending on which page I'm using instead of the Interface?
As a side-note, this is being done in ASP.net MVC 3 with Razor.
You can achieve what you want with one class, and a little lateral thinking.
First, create your class, with the validation baked in. Next, create a custom ModelValidatorProvider inheriting from DataAnnotationsModelValidatorProvider, like so:
public class MyMetadataValidatorProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var vals = base.GetValidators(metadata, context, attributes);
// check to see if any keys have been inserted
if (context.Controller.ViewData.Keys.Count > 0)
{
// check if we have a key named "NoValidate" with a value of true
// do not return the validtors if we do
if ((bool)context.Controller.ViewData.FirstOrDefault(k => k.Key == "NoValidate").Value)
{
// we do not want to return our validators, return an empty list
return new List<ModelValidator>();
}
}
else
{
// check if the form has a key named "NoValidate" with a value of true
// do not return the validtors if we do
if (context.HttpContext.Request.Form["NoValidate"].ToLowerInvariant() == "true")
{
// we do not want to return our validators, return an empty list
return new List<ModelValidator>();
}
}
// we want to return our validators
return vals;
}
}
Next, register the custom ModelValidatorProvider in Application_Start in Global.asax.cs, like so:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MyMetadataValidatorProvider());
Then, add the following to your view (this will govern whether the validators are returned when the form is POSTed):
#Html.Hidden("NoValidate", ViewData.FirstOrDefault(k => k.Key == "NoValidate").Value)
Finally, add actions like the following:
public ActionResult Index()
{
var model = new MyModel();
// this will set validation to appear
ViewData.Add("NoValidate", false);
// this will suppress validation
ViewData.Add("NoValidate", true);
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
// we DO want validation, so let's test for it in addition
// to testing if the ModelState is valid
if (Request.Form["NoValidate"].ToLowerInvariant() != "true" && ModelState.IsValid)
{
ModelState.Clear();
var newmodel = new MyModel();
ViewData.Add("NoValidate", true);
return View(newmodel);
}
ViewData.Add("NoValidate", false);
return View(model);
}
Note that you can control whether the validation appears in your GET action by setting the NoValidate key in ViewData as you want. On the POST, the validation is governed by the form value for NoValidate.
IMPORTANT NOTE: In your action which requires validation, you need to add a test to confirm that the Form does not have the key NoValidate, or its value is not True, in order to enforce that a user cannot avoid the validation.
UPDATE
At first, I had validation only appearing when certain conditions were true. Idecided this was a BAD IDEA, so now validation will only be suppressed if the conditions are true.
Each view should be strongly typed to a separate view model. Each viewmodel then has the validation logic on it (annotations) or inherits from a base that has the required validation on it.
Any logic that cannot be inherited is simply set on your ViewModel itself. If its a small moderl I would consider just copy/paste and two separate viewmodels with their own set of attributes.
You can use AutoMapper to easily map between some concrete object that implements your interface and your ViewModels.
Could you use one class? You can create a filter that allows you to manage the validation errors for an action. In your case you can add an attribute to the Save action and ignore the required errors, but the validations will run for the submit/finalize action. This example will discard all the errors.
public class DontValidateEmailAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var modelState = filterContext.Controller.ViewData.ModelState;
var incomingValues = filterContext.Controller.ValueProvider;
foreach (var key in modelState.Keys)
modelState[key].Errors.Clear();
}
}
I learnt this technique from Steve Sanderson's Pro ASP NET MVC 3. He uses the technique to validate a model that has required fields but the data entry is a multistep wizard. If the value has not been returned in the form post, he removes the errors for that property.

Can't pass viewmodel to new action by RedirectToAction

I have a form, when user submit the form, I want to direct the user the new view to display the submitted result(transfer viewmode data to display view).
public class HomeController : Controller
{
private MyViewModel _vm;
.....
// POST: /Home/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(MyViewModel vm){
//.....
//set up vm to temp data _vm
_vm = vm;
return RedirectToAction("DisplayData");
}
// GET: /Home/DisplayData
public ActionResult DisplayData()
{
//get temp data for display
return View(_vm);
}
}
When I posted the form, I can create vm and put it to temp data place _vm. But this _vm can be sent to another action DisplayData, it's null in action DisplayData(). It seems that when redirect action even in same controller, _vm is lost although it is Controller var, not action method var.
How to resolve this problem?
It creates a new instance of the controller as it is a new request therefore as you have found it will be null.
You could use TempData to store the vm, TempData persists the data for 1 request only
Good explanation here
One good way is to call
return DisplayData(_vm)
instead of
RedirectToAction("DisplayData")
DisplayData should accept a model anyway.

Resources