Update following initial comments
The model has an object in it called 'Account', of which there is an int propety (Account.AccountID)
ViewB has a form which collects some additional information - but also has a textbox which is populated with Model.Account.AccountID.
When I submit ViewB however, Model.Account becomes null.
Its probably easier to show a simplified version of what I have before I explain the issue:
[HttpGet]
public ActionResult ViewA()
{
return View(new BlahModel());
}
[HttpPost]
public ActionResult ViewA(BlahModel model)
{
if(there_was_a_problem)
return View("ViewA", model);
else
return View("ViewB", model);
}
// have tried both httppost, httpget and no attribute here
public ActionResult ViewB(BlahModel model)
{
return View(model);
}
I load up ViewA via a GET, fill in the strongly-typed form and submit - then the following View (either ViewA again or ViewB if the request had no problems) is fine ... it has full access to the whole model and can display the properties within it.
The problem is that if I then submit a form in ViewB (which posts to the ActionResult ViewB) - the model suddenly has null properties throughout, even though its using the same model - and prior to the post has picked up all the values sucessfully.
Any ideas?
Many thanks
Most likely - ViewB view does not render enough and model binder can't find values to bind.
Action argument is binded from form values. It does not matter if You pass model to view correctly. Rendering things out is what matters (or passing through query string/cookies).
Complementing Arnis L.'s answer, you can use a tool like Firebug to check that your model's parameters (or any parameters at all) are being sent along the request.
Related
Doesn't seem like this should work so I have no idea why it does. I LIKE the result, but worried I can't depend on it because I have no idea how it is working.
[HttpGet]
public ActionResult Modify(System.Guid id)
{
return View("Modify", LoadFromDatabase(id));
}
[HttpPost]
public ActionResult Modify(CaseModel myModel)
{
//So odd behavior. If I redirect to the actual GET Modify,
//I loose any changes on the form. however if I perform the
//same actions but here in the Post... all my unsaved changes
//stick..why?
//This one wipes any edits
return RedirectToAction("Modify", new { id = myModel.ID});
//This one actually leaves all my changes, even though
//I am re-creating the model from the database just like
//the other ActionResult
return View("Modify", LoadFromDatabase(myModel.ID));
}
As Stephen Muecke mentions in the two comments above,
You can depend on it. RedirectToAction() is redirecting to a new page which initializes a new instance of CaseModel based on ID property (i.e. reads the values from your repository. return View() is returning the current instance of CaseModel and if you using the HtmlHelper methods to generate form controls, it will use the values from ModelState which are added by the DefaultModelBinder when you submit the form. Refer this answer for more explanation of the behavior). #Html.TextBoxFor(m => m.someProperty) will display the value you posted, whereas #Model.someProperty will display the 'updated' value you set in the post method
works perfectly. The HTML helpers overwrite my Model values with the ModelState (aka ViewState).
What is the most effective method in order to make all the fields on an MVC4 Razor empty when loading it (for example first loading or after backing to the page again)? If it is possible could you please suggest me a way with the help of Razor properties instead of using Javascript/jQuery.
It's a little difficult to make out what you're trying to do, but let's see if I can help.
Firstly, if you simply wanted to clear out a form's values after it's been posted, you can do that like so:
[HttpPost]
public ActionResult Index(ViewModel model)
{
ModelState.Clear();
model = new ViewModel();
return View(model);
}
Simply creating a new ViewModel isn't enough, as the ModelState dictionary will try to repopulate the form with the old values. This does what you want, but isn't really leveraging MVC to do what you want.
The better way to do it would be to redirect back to the action you use to display your form. Something like this:
public ActionResult Create()
{
var model = new ViewModel();
return View(model);
}
This is simply passing in an empty model to your form. Once the user fills out the form, and it's posted back to the server, you can handle it like so:
[HttpPost]
public ActionResult Create(ViewModel model)
{
if (ModelState.IsValid)
{
// Form data is valid so redirect back to display the form again
return RedirectToAction("Create");
}
// If we get here, redisplay the form with the fields filled in
// and errors shown
return View(model);
}
Simply calling a ModelState.Clear() will do the trick. You shouldn't have to instantiate the view model again.
A view displays (or collect) information about a Model.
So, if you pass an Empty model (that is: properties in null or blank or default values) it will "clear all fields".
All you have to do is invoking again the Action that displays the view, now passing an empty model.
EDIT:
You can do it in javascript, but then you have to duplicate and maintain the logic of what are default values.
I have a model that has a property which is a collection. I can successfully bind from the edit actions, for example:
[HttpGet]
public ActionResult Edit(string id)
{
// code here
return this.View(complexModel);
}
[HttpPost]
public ActionResult Edit(ComplexModel complexModel)
{
// code here
return RedirectToAction("AnotherAction")
}
In the post method I can successfully receive all the object properties, including the collection one. However I have another view that can invoke the Edit action. When this happens I can see that the html rendered is the same (i.e. the nested property info is there). When I save the changes in the post Edit I receive all the correct properties with one exception - the collection propery has zero items.
Where do I have to search for the problem?
Update:
I am properly iterating through the collection and displaying all items with EditorFor; however when coming from a differnt view (the different view is in another controller and the Edit link is placed in a Display Template - if that makes any difference) with the same exact model I can see that the html is the same since all the properties of the collection are there.
Ok, false alarm. I figured it out - I am invoking the action method using #Html.ActionLink; I was passing the whole model instead of just the id. I do not know for what silly reason things got messed up, but they are ok now.
What does your Edit view look like? You need to iterate over all the items in the collection as form elements.
In the process of updating a C# MVC 2.0 application!
I have a view “Signup” and another view “ForgotPassword”.
Each view have a with a submit button.
Each form is submitted to the same Controller but to two different ActionResult:
[HttpPost]
public ActionResult Signup(SignupModel signupModel)
{…}
[HttpPost]
public ActionResult ForgotPwd(ForgotPasswordModel forgotPasswordModel)
{…}
Upon completion my goal is to redirect the user to a “thankyou” page but based on where the user is coming from (either Signup or ForgotPassword) I wish to display a particular message (or a different UI).
Inside the same Controller, I created a “Thankyou” ActionResult:
public ViewResult Thankyou()
{
return View();
}
I was thinking of adding a parameter to my Thankyou() method which would allow me to know where the user is coming from (Signup or ForgotPwd). From there, make the “thankyou” page display the appropriate UI/message.
I’m looking for a clean and simple solution.
Should I create two View User Controls and show the appropriate one based on the parameter being passed?
In addition, instead of having an “ActionResult” for my Thankyou() method couldn’t I use a “PartialViewResult” ?
EDIT:
I was actually considering something along those lines…
Where ThankyouType is an Enum.
[HttpPost]
public ActionResult Signup(SignupModel signupModel)
{
//Validation code...
return View("Thankyou", ThankyouType.SignupDone);
}
[HttpPost]
public ActionResult ForgotPassword(ForgotPasswordModel forgotPasswordModel)
{
//Validation code...
return View("Thankyou", ThankyouType.ForgotPasswordDone);
}
And then have my “Thankyou” ViewResult like this:
public ViewResult Thankyou(ThankyouType type)
{
return View(type);
}
Doesn’t seem like I can create a strongly typed view based on Enum (unless I’m wrong).
Perhaps I’ll read more on PartialViewResults and/or find examples…but then again, I could be completely wrong.
I would personally give the ThankYou view a model that has the message you want to display, and have your two controller actions render the ThankYou view directly on success rather than calling a ThankYou action.
However, if you're sure you want a redirect, you may consider using the TempData collection to store a message or a key of some kind. The ThankYou controller can then retrieve this value and pass it to the View. This situation is what TempData was made for.
Edit
There's no reason you shouldn't be able to use an enum value as your model type, but if that gives you trouble you should at least be able to create a model type that has an enum property on it.
The strategy of sending the ThankYouType as part of the redirect request would work just fine, if that's what you prefer. The only potential downside is that it would look like this in the URL:
http://domain.com/controller/ThankYou?type=ForgotPasswordDone
I have no real arguments against it. There are lots of options. Use the one that feels best to you.
I have a controller with two actions:
[AcceptVerbs("GET")]
public ActionResult Add()
{
PrepareViewDataForAddAction();
return View();
}
[AcceptVerbs("POST")]
public ActionResult Add([GigBinderAttribute]Gig gig, FormCollection formCollection)
{
if (ViewData.ModelState.IsValid)
{
GigManager.Save(gig);
return RedirectToAction("Index", gig.ID);
}
PrepareViewDataForAddAction();
return View(gig);
}
As you can see, when the form posts its data, the Add action uses a GigBinder (An implemenation of IModelBinder)
In this binder I have:
if (int.TryParse(bindingContext.HttpContext.Request.Form["StartDate.Hour"], out hour))
{
gig.StartDate.Hour = hour;
}
else
{
bindingContext.ModelState.AddModelError("Doors", "You need to tell us when the doors open");
}
The form contains a text box with id "StartDate.Hour".
As you can see above, the GigBinder tests to see that the user has typed in an integer into the textbox with id "StartDate.Hour". If not, a model error is added to the modelstate using AddModelError.
Since the gigs property gigs.StartDate.Hour is strongly typed, I cannot set its value to, for example, "TEST" if the user has typed this into the forms textbox.
Hence, I cant set the value of gigs.StartDate.Hour since the user has entered a string rather than an integer.
Since the Add Action returns the view and passes the model (return View(gig);) if the modelstate is invalid, when the form is re-displayed with validation mssages, the value "TEST" is not displayed in the textbox. Instead, it will be the default value of gig.StartDate.Hour.
How do I get round this problem? I really stuck!
I think the problem is that your ViewModel does not match closely enough with your View. It's really important in MVC that your ViewModel matches your View as closely as possible.
In your ViewModel you're assuming an integer, but in your View you're using a TextBox to render the property, which will allow any kind of text. There's a mismatch here and the difficulties you are experiencing trying to map them is a symptom of the mismatch.
I think you should either:
1. Change the type of the ViewModel property to string and then do validation in your controller to ensure the string entered is actually a number or:
2. Change the control that the View renders to a control that will only allow a number to be entered via a custom control or Javascript validation (as #Qun Wang recommends)
Personally, I'd recommend option 1. That way the ViewModel is not dependent on the View implementation.
Could you do this in your PrepareViewDataForAddAction method?..
if (!ViewData.ModelState.IsValid)
{
ViewData["StartDate.Hour"] = "Error";
}
The other fields on the form will still populate based on the properties of the Gig object.
I think you need to do some basic client side validation first.
don't allow it to post to the server.