hejdig.
In Aspnetmvc2 I have a model object that I send to a View. A control in the view isn't updated with the value. Why? What obvious trap have I fallen in?
The View:
<%:Html.TextBox(
"MyNumber",
null == Model ? "1111" : Model.MyNumber ) %>
<%:Model.MyNumber%>
is first fetched trough a Get. The "1111" value in the textbox is manually updated to "2222". We post the form to the controller which appends "2222" to the Model object and sends it to the view again.
The Controller:
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index( MyModel myModel)
{
myModel.MyNumber += " 2222";
return View(myModel);
}
Alltogether we get an output like:
<input id="MyNumber" type="text" value="1111">
1111 2222
As you can see the control doesn't use the Model's attribute but instead falls back to thew viewstate that doesn't exist in Aspnetmvc.
(The same happens with Razor.)
That's normal and it is how HTML helpers work : they look first in the model state and then in the model when binding a value. So if you intend to modify some property in the POST action you need to remove it from the model state first or you will always get the old value:
[HttpPost]
public ActionResult Index(MyModel myModel)
{
ModelState.Remove("MyNumber");
myModel.MyNumber += " 2222";
return View(myModel);
}
Related
I have a HomeController with an Index.cshtml Razor view that uses an InitialChoicesViewModel with validation attributes. The Index view contains the following form:
#using (Html.BeginForm("CreateCharacter", "DistributePoints", FormMethod.Get))
This goes to a different controller (which is what I want):
public class DistributePointsController : Controller
{
public ActionResult CreateCharacter(/* my form parameters */)
// ...
}
How do I perform server-side validation on the form (such as checking ModelState.IsValid), returning my original Index view with a correct ValidationSummary on error? (On success I want to return the CreateCharacter view of the other controller.)
Based on John H's answer, I resolved this as follows:
#using (Html.BeginForm("CreateCharacter", "Home"))
HomeController:
[HttpPost]
// Only some of the model fields are posted, along with an additional name field.
public ActionResult CreateCharacter(InitialChoicesViewModel model, string name)
{
if (ModelState.IsValid)
{
return RedirectToAction("CreateCharacter", "DistributePoints",
new {name, model.Level, model.UseAdvancedPointSystem});
}
// Unsure how to post a collection - easier to reload from repository.
model.ListOfStuff = _repository.GetAll().ToList();
return View("Index", model);
}
I had to add a parameterless constructor to my view model, too.
[HttpPost]
public ActionResult CreateCharacter(InitialChoicesViewModel model)
{
if (ModelState.IsValid)
return RedirectToAction("SomeSuccessfulaction");
return View("~/Views/Home/Index.cshtml", model);
}
The ~/ denotes the relative root of your site.
The code above complies with the Post-Redirect-Get pattern, in order to prevent some types of duplicate form submission problems. It does that by redirecting to a separate action when the form submission is successful, and by returning the current view, complete with ModelState information, on error.
By default, ASP.NET MVC checks first in \Views\[Controller_Dir]\, but after that, if it doesn't find the view, it checks in \Views\Shared.
If you do return View("~/Views/Wherever/SomeDir/MyView.aspx") You can return any View you'd like.
But for now in your case, try the following
public ActionResult CreateCharacter(SomeModel model)
{
if(!ModelState.IsValid){
return View("~/Views/Home/Index.cshtml", model )
}
return View();
}
To check your ModelState just use an if statement in Controller:
if(ModelState.IsValid)
{
...
}
If there is any error add you can add an error message to the ModelState Dictionary like this:
ModelState.AddModelError("Somethings failed", ErrorCodeToString(e.StatusCode));
After that return your same View and pass it to your model
return View(model);
If you add "#Html.ValidationSummary()" in your View, it will get the errors from the ModelState Dictionary and display them.But if you show values yourself maybe with different styles you can do it manually, take a look at this question
And if there is no error you can return your CreateCharacter View like this, just redirect user to the appropriate action:
return RedirectToAction("CreateCharacter","DistributePoints");
I'm attempting a multiple page form where I use a single controller action and returning a view depending on a value on my model.
My model has a property that I put in an input field on my views, using Html.HiddenFor().
Here's my simplified model:
public class MyModel
{
public virtual int Step { get; set; }
}
And in my views, I have:
#model MyModel
...
#Html.HiddenFor(model => model.Step)
Then in my controller I have:
public ActionResult Create()
{
...
myModel.Step = 1;
return View("View1", myModel);
}
[HttpPost]
public ActionResult Create(MyModel myModel)
{
...
if (myModel.Step == 1)
{
myModel.Step = 2;
return View("View2", myModel);
}
else if (myModel.Step == 2)
{
...
}
...
}
My problem is, my controller always sees mymodel.Step as having the value of 1. Why is that?
What's weird is that I tried to display it on the form with these:
#Html.DisplayFor(model => model.Step)
#Html.EditorFor(model => model.Step)
The second time the page was displayed, the first line showed the text "2". The second showed an input field with "1". I'm confused.
ADDITIONAL INFO:
My model also has a Guid property which is passed onto the View in a hidden field. I tried to change it also on postback, and check its value the second time around. The new value did not register. The model returned the original value before the first post. So it is consistent with the other field.
I may have to use different controller actions if I couldn't find why it is behaving the way it does at the moment.
SOLUTION:
As Reda suggested below, I fixed it by doing this in my post action method:
Before displaying "View2" and to effect changes my controller makes to a value in my model, I run ModelState.Clear()
Here is a blog post which confirms the need to clear ModelState for this scenario.
Usually, when you return to view from your post action, it means that something failed during validation process and the form should be displayed again with the submitted values. That's why the ModelState remembers your inputs when you return to View, and your inputs will be filled from the ModelState, not your view model.
In my opinion you have two solutions :
ModelState.Clear, which will erase your old value, before setting new ones
redirecting to a GET action, with new values
Second solution is a better one, because you're not displaying the old form with validation errors, you're just showing a new view with different values.
Here's an example (of course you adapt it to your needs) :
public ActionResult Create(int? step)
{
...
myModel.Step = step.HasValue ? step.Value : 1; // or whatever logic you need to apply
return View("View1", myModel);
}
[HttpPost]
public ActionResult Create(MyModel myModel)
{
...
if (myModel.Step == 1)
{
return RedirectToAction("Create", new { step = 2 });
}
else if (myModel.Step == 2)
{
...
}
...
}
I have a registration form in the side bar of my web application. When the user submits the entered data, the user should be redirected to another page with a more complete registration form when he can fill the rest of the data. The data that was entered by the user in the first form should be already there in the second form, but that's not happening... I checked to see the value of the view model I'm passing to the second action method and it was null and in the browser's address bar I get:
http://localhost:2732/User/RegisterPage?model=Sharwe.MVC.ViewModels.RegisterPageViewModel
Here's the code:
public ActionResult Register()
{
return PartialView(new RegisterViewModel());
}
[HttpPost]
public ActionResult Register(RegisterViewModel dto)
{
var model = Mapper.Map<RegisterViewModel, RegisterPageViewModel>(dto);
return RedirectToAction("RegisterPage", "User", new { viewModel = model });
}
public ActionResult RegisterPage(RegisterPageViewModel viewModel)
{
return View(viewModel);
}
Isn't that the way to do this? Or am I missing something here...?
The Dictionary passed to RedirectToAction() is the Route Value not the View Model. And RedirectToAction() is basically telling the browser to go to a certain URL. Browser by default makes the GET request and obviously you lose your data.
For this, you need to use TempData dictionary. You can store view model in TempData and then RedirectToAction() to RegisterPage. TempData saves the data for only 1 request span and would delete it automatically. It's ideal for this scenario.
See this for more details > The value for a object inside a viewmodel lost on redirect to action in asp.net mvc 2.0?
In this particular case you don't need to use RedirectToAction, you can simply call the RegisterPage action directly:
[HttpPost]
public ActionResult Register(RegisterViewModel dto)
{
var model = Mapper.Map<RegisterViewModel, RegisterPageViewModel>(dto);
return RegisterPage(model);
}
public ActionResult RegisterPage(RegisterPageViewModel viewModel)
{
return View(viewModel);
}
I have a website that uses one Action method which passes a pagename to the get action method. In the action method it finds the model item by the pagename and returns the relevant stuff to the view.
I have now created a POST action method for this because I need it in my contact page. I still need to find the model by page name and return it to the view however when the user submits the contact information I do a TryUpdateModel on my Enquiry model item and if not valid it returns the errors into the modelstate and the validation summary shows the errors but none of the information they submitted is re-rendered.
Is there anyway I can return the page model and get the textboxes to re-render what they had previously typed when the model fails?
Here's what we do (with stuff not essential to this question removed):
private ModelType UpdateModel(Guid id)
{
var dbData = (from m in Repository.SelectAll()
where m.Id == id
select new ModelType
{
Id = m.Id,
Data = m.Data
}).First();
return UpdateModel(dbData);
}
private ModelType UpdateModel(ModelType model)
{
//add other data for view:
model.SelectStuff = new SelectList( //...
// etc.
return model;
}
[HttpGet]
public ActionResult Update(Guid id)
{
return View(UpdateModel(id));
}
[HttpPost]
public ActionResult Update(ModelType model)
{
if (!ModelState.IsValid)
{
return View(UpdateModel(model));
}
// else post to repository
}
If you add a property to your view model for what should be bound to the textbox (in my example Thing) you could use something like:
<%=Html.TextBox("Thing", Model.Thing != null ? Model.Thing : string.Empty)
Kindness,
Dan
I worked out I could use the following approach:
<input name="ENQ.Name" class="inputText" type="text" maxlength="150" title="Please enter your name" value="<%= ViewData.ModelState["ENQ.Name"] != null ? ViewData.ModelState["ENQ.Name"].Value.AttemptedValue : "" %>" />
i'm having a textbox inside a form.
[View]
<%=html.textbox("name") %>
[Controller]
Index(string name)
{
name = "something";
return View();
}
On Form Submit
In this case without sending any ViewData the textbox value is maintained.But the value "something" is not setting up.
But whn i change the Action to
[Controller]
Index()
{
string name="something";
return view();
}
the value is not maintained.
Really wat happening on that parameter.
If you want to set data for html.textbox("name") in the Controller use ViewData["name"] = "something"
Your question is not very clear and your code example is not actually adding anything to ViewData or the view Model - here's a shot at what i think your trying to do...
Assuming you want to re-populate the form and your View is Strongly Typed, You would do something like this:
public ActionResult Index(String name)
{
MyModel model = new MyModel;
model.Name = name;
ViewData.Model = model;
return View();
}
A textbox in your view with the same name would then have the value auto populated from the Model
<%= html.textbox("Name") %>
Posting the form would then post the model object to your controller like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(MyModel model)
{
// do something with the model
ViewData.Model = model;
return View();
}
and then re-populate the form with the model data.
string name in your Index action in the controller, is mapped to the FormValue, if you change this, MVC understands that it needs to add the value from the FormValueCollection to the textbox, and you have changed that in your Index action. If you declare a variable by yourself this doesn't work because there is no binding to the formvalues.