Two Ajax forms, one view model. Validation only works for one - asp.net-mvc

I've got two forms on my page. My viewmodel looks like this:
public class HomeViewModel
{
[Required(ErrorMessage = "We kind of need an email address!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string Email { get; set; }
public ContactForm ContactForm { get; set; }
}
ContactForm:
public class ContactForm
{
[Required(ErrorMessage = "We need your name, please!")]
public string Name { get; set; }
[Required(ErrorMessage = "We need your email, please!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Please elaborate a little!")]
public string Message { get; set; }
}
First form action:
public ActionResult FreeConsultSubmit(HomeViewModel model)
{
if (ModelState.IsValid)
{
//do stuff
}
return PartialView("_SubmitResult", false);
}
Second Action:
public ActionResult ContactSubmit(HomeViewModel model)
{
if (ModelState.IsValid)
{
//dostuff
}
return PartialView("_SubmitContactResult", false);
}
First Ajax Form
#using (Ajax.BeginForm("FreeConsultSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "FreeConsultResults"
}))
{
<div id="FreeConsultResults">
<div class="row">
<div class="small-4 columns small-centered text-center splashEmail">
#Html.TextBoxFor(m => m.Email, new { #placeholder = "Please enter your email..." })
#Html.ValidationMessageFor(m => m.Email)
</div>
</div>
<div class="row">
<div class="small-5 columns small-centered text-center">
<input type="submit" class="hvr-border-fade splashCallToAction" value="Get Started" />
</div>
</div>
</div>
}
Second Ajax Form:
#using (Ajax.BeginForm("ContactSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ContactSubmitResults"
}))
{
<div id="ContactSubmitResults">
<div class="row">
<div class="small-12 columns small-centered">
#Html.TextBoxFor(m => m.ContactForm.Name, new { #placeholder = "Your Name" })
#Html.ValidationMessageFor(m => m.ContactForm.Name)
#Html.TextBoxFor(m => m.ContactForm.EmailAddress, new { #placeholder = "Your Email Address" })
#Html.ValidationMessageFor(m => m.ContactForm.EmailAddress)
#Html.TextAreaFor(m => m.ContactForm.Message, new { #placeholder = "Your Message", #class = "contactMessage" })
#Html.ValidationMessageFor(m => m.ContactForm.Message)
</div>
</div>
<div class="row">
<div class="small-12 columns small-centered text-center">
<a href="">
<input type="submit" class="hvr-border-fade sendMessageSmall" value="Send Message" />
</a>
</div>
</div>
</div>
}
I've got everything wired up fine, and client side validation works as it should. This may be overkill, but I also wanted to set up server side validation.
The issue is, when submitting the first form (just the email string), I check if the ModelState is valid, and it is. If you drill down into the ModelState, you see that only 1 property is being looked at.
If you submit the second form though (the ContactForm), the ModelState.IsValid returns false, and if you drill down, you see that it's looking at 4 properties (the 3 ContactForm properties, plus the string email). Since the email string is required, it fails.
I'm confused as to why it works for one, but not the other. I could just remove the server side validation, but I'd at least like to know why this is the case. I could also remove the error from the ModelState, but that doesn't seem elegant at all.

If you simply are trying to have two separate forms within one view, you're probably better off splitting the forms into separate "sub views" and child actions, and then using #Html.Action() to render them in place.
Here's an example:
Models
I'd remove the ContactForm model from HomeViewModel, and rename HomeViewModel to ConsultingForm (to match your naming convention for the contact model):
public class ConsultingForm
{
[Required(ErrorMessage = "We kind of need an email address!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string Email { get; set; }
}
public class ContactForm
{
[Required(ErrorMessage = "We need your name, please!")]
public string Name { get; set; }
[Required(ErrorMessage = "We need your email, please!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Please elaborate a little!")]
public string Message { get; set; }
}
Controller
Add "child" actions, like those below:
public class HomeController : Controller
{
public ActionResult Home()
{
return View();
}
[ChildActionOnly, HttpGet]
public ActionResult ConsultingRequest()
{
var model = new ConsultingForm();
return View(model);
}
[ChildActionOnly, HttpGet]
public ActionResult ContactRequest()
{
var model = new ContactForm();
return View(model);
}
}
The ChildActionOnlyAttribute marks the action as a child action. From the MSDN:
A child action method renders inline HTML markup for part of a view
instead of rendering a whole view.
Views
Your first subview will be the same as you already have:
#using (Ajax.BeginForm("FreeConsultSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "FreeConsultResults"
}))
{
<div id="FreeConsultResults">
<div class="row">
<div class="small-4 columns small-centered text-center splashEmail">
#Html.TextBoxFor(m => m.Email, new { #placeholder = "Please enter your email..." })
#Html.ValidationMessageFor(m => m.Email)
</div>
</div>
<div class="row">
<div class="small-5 columns small-centered text-center">
<input type="submit" class="hvr-border-fade splashCallToAction" value="Get Started" />
</div>
</div>
</div>
}
Your second subview simply needs to remove the extra "step" in the property bindings:
#using (Ajax.BeginForm("ContactSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ContactSubmitResults"
}))
{
<div id="ContactSubmitResults">
<div class="row">
<div class="small-12 columns small-centered">
#Html.TextBoxFor(m => m.Name, new { #placeholder = "Your Name" })
#Html.ValidationMessageFor(m => m.Name)
#Html.TextBoxFor(m => m.EmailAddress, new { #placeholder = "Your Email Address" })
#Html.ValidationMessageFor(m => m.EmailAddress)
#Html.TextAreaFor(m => m.Message, new { #placeholder = "Your Message", #class = "contactMessage" })
#Html.ValidationMessageFor(m => m.Message)
</div>
</div>
<div class="row">
<div class="small-12 columns small-centered text-center">
<a href="">
<input type="submit" class="hvr-border-fade sendMessageSmall" value="Send Message" />
</a>
</div>
</div>
</div>
}
The "wrapper", or parent view, will look something like:
<div class="whatever">
#Html.Action("ConsultingRequest", "Home")
#Html.Action("ContactRequest", "Home")
</div>
If you inspect the rendered HTML, you'll see that each form is properly bound to only its model's properties, so when you post each form, only those properties are model-bound and validated.

Related

How to make your button on a form using asp.net-mvc work?

I have a button, it does not seem to create new users to my database. What it does it only inherits user validaton to my Login method and need some guidance to this please and thanks. Below is the logic what i am trying to do. What i want to do my create button must be able to create new users if not exist to the database.
Controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateModel objSubmit)
{
ViewBag.Msg = "Details submitted successfully";
return View(objSubmit);
}
// This is for login, and its hits this method each time.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(Login login)
{
if (ModelState.IsValid)
{
bool success = WebSecurity.Login(login.username, login.password, false);
var UserID = GetUserID_By_UserName(login.username);
var LoginType = GetRoleBy_UserID(Convert.ToString(UserID));
if (success == true)
{
if (string.IsNullOrEmpty(Convert.ToString(LoginType)))
{
ModelState.AddModelError("Error", "Rights to User are not Provide Contact to Admin");
return View(login);
}
else
{
Session["Name"] = login.username;
Session["UserID"] = UserID;
Session["LoginType"] = LoginType;
if (Roles.IsUserInRole(login.username, "Admin"))
{
return RedirectToAction("AdminDashboard", "Dashboard");
}
else
{
return RedirectToAction("UserDashboard", "Dashboard");
}
}
}
else
{
ModelState.AddModelError("Error", "Please enter valid Username and Password");
return View(login);
}
}
else
{
ModelState.AddModelError("Error", "Please enter Username and Password");
return View(login);
}
}
Model:
namespace eNtsaPortalWebsiteProject.Models
{
public class CreateModel
{
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string password { get; set; }
[Required]
public string username { get; set; }
}
}
// View for login
<div data-="mainContent">
<section class="container">
<div class="logo col-sm-12 text-center col-md-12"> <img alt="" src="~/Images/eNtsa.png" /></div>
<div class="clearfix"></div>
<div class="container">
<div class="row">
<div id="MyWizard" class="formArea LRmargin">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div id="divMessage" class="text-center col-md-12 col-md-offset-12 alert-success">
#Html.ValidationSummary()
</div>
<div class="col-md-12 col-md-offset-10 col-xs-12">
<div class="loginPage panel-info">
<div class="form-group"><span class=""><i class="glyphicon glyphicon-user">Username:</i></span>
#Html.TextBoxFor(model => model.username, new { #class = "form-control text-center", autocomplete = "off" })
#Html.ValidationMessageFor(model => model.username)
</div>
<div class="form-group">
<span class=""><i class="glyphicon glyphicon-lock">Password:</i></span>
#Html.PasswordFor(model => model.password, new { #class = "form-control text-center", autocomplete = "off" })
#Html.ValidationMessageFor(model => model.password)
</div>
</div>
<div class="form-group">
<input id="BtnLogin" type="submit" class="btn btn-success btn-pressure" name="BtnLogin" value="Login" />
<input type ="Submit" class="btn btn-info btn-pressure" name="BtnCreate" value="Create" />
</div>
</div>
}
<div class="clear"></div>
</div>
</div>
</div>
</section>
</div>
View for creating user:
<div class="mainContent">
<section class="container">
<div class="logo col-sm-12 text-center col-md-10">
<img alt="" src="~/Images/eNtsa.png"/>
</div>
<div class="container">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div id="divMessage" class="text-center col-md-12 col-md-offset-12 alert-success">
#Html.ValidationSummary()
</div>
<div class="col-md-12 col-md-offset-10 col-xs-12">
<div class="glyphicon-registration-mark">
<div class="form-group"><span class=""><i class="glyphicon glyphicon-user">Username:</i></span>
#Html.TextBoxFor(model=>model.username, new {#class ="form-control text-center", automplete="off" })
#Html.ValidationMessageFor(model=>model.username)
</div>
<div class="form-group">
<span class=""><i class="glyphicon glyphicon-lock">Password:</i></span>
#Html.PasswordFor(model=>model.password, new {#class = "form-control text-center", autocomplete="off" })
#Html.ValidationMessageFor(model=>model.password)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-success btn-pressure" name="BtnSubmit" value="Submit"/>
</div>
</div>
}
</div>
</section>
</div>
The button is working - that isn't the problem that you're having.
You can have multiple buttons to submit the form but they will return to the same place, either:
a) the controller/action specified in the "action" property of the form
b) if no action is specified then the default location - in your case there isn't one directly specified so it is posting back to the default location.
(see: How to link HTML5 form action to Controller ActionResult method in ASP.NET MVC 4)
The easiest way to accomplish what you're trying to do would be refactor your controller and branch the logic depending on what the value is of the submit button.
(see: MVC razor form with multiple different submit buttons?
and How to handle two submit buttons on MVC view)
This will require some refactoring of the code that you have written, but it is the most straightforward way of achieving what you're trying to do.
In very basic terms it would look something like this:
Model:
namespace eNtsaPortalWebsiteProject.Models
{
public class LoginCreateModel
{
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string password { get; set; }
[Required]
public string username { get; set; }
public string btnSubmit { get; set; } // both buttons will have the same name on your form, with different values ("Create" or "Login")
}
}
Controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginCreateModel objSubmit)
{
if (objSubmit.btnSubmit == "Create")
{
// Handle creation logic here
}
if (objSubmit.btnSubmit == "Login")
{
// Handle login logic here
}
return View(objSubmit);
}

Stay on same page if form is not valid

public ActionResult Add(Models.ContactModel contact)
{
if (ModelState.IsValid)
{
DAL.Contact mappedContact = Mapper.Map<Models.ContactModel, DAL.Contact>(contact);
repository.AddContact(mappedContact);
return RedirectToAction("Index");
}
else
/* What to return here */
}
This is the controller for adding contact to the database. I am validating the form using data annotations if the form is valid i am redirecting it to the index page. If it is not valid it should stay on the same page showing error message. what to write in else part. can any one suggest me.There is no view for Add Controller.
<div>
<label>Name</label>
#Html.ValidationMessageFor(model => model.Name, null, new { #class = "error-message"})
#Html.TextBoxFor(model => model.Name, new { #class = "long-box" })
</div>
<div>
<label>Email</label>
#Html.ValidationMessageFor(model => model.Email, null, new { #class = "error-message" })
#Html.TextBoxFor(model => model.Email, new { #class = "long-box" })
</div>
<div class="mob-land-container">
<label>Mobile</label>
#Html.ValidationMessageFor(model => model.MobileNumber, null, new { #class = "error-message" }) <br>
#Html.TextBoxFor(model => model.MobileNumber, new { #class = "short-box" })
</div>
<div class="mob-land-container" id="landline-container">
<label>Landline</label>
#Html.ValidationMessageFor(model => model.LandlineNumber, null, new { #class = "error-message" })<br>
#Html.TextBoxFor(model => model.LandlineNumber, new { #class = "short-box" })
</div>
<div>
<label>Website</label>
#Html.ValidationMessageFor(model => model.Website, null, new { #class = "error-message" })
#Html.TextBoxFor(model => model.Website, new { #class = "long-box" })
</div>
<div>
<label>Address</label>
#Html.ValidationMessageFor(model => model.Address, null, new { #class = "error-message" })
#Html.TextAreaFor(model => model.Address, new { #class = "address-box" })
</div>
</div>
<div class="button-container">
<input type="button" id="cancel" value="Cancel" onclick="location.href='#Url.Action("Index", "Contact")'" />
<input type="submit" id="add" value="Add" onclick="location.href='#Url.Action("Add", "Contact")'" />
</div>
This is the form where i am getting data to controller.
public class ContactModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
[Required(ErrorMessage = "Email is required")]
public string Email { get; set; }
[Required(ErrorMessage = "Mobile Number is required")]
public string MobileNumber { get; set; }
[Required(ErrorMessage = "Landline Number is required")]
public string LandlineNumber { get; set; }
[Required(ErrorMessage = "Website is required")]
public string Website { get; set; }
[Required(ErrorMessage = "Address is required")]
public string Address { get; set; }
}
This is the model class.
Thanks in advance.
I like to flip the login in situations like this. If the model isn't valid just return it back to the view. the model binder on the POST will take care of the validations and once you send the model back to the view, you will see the individual validations on the screen.
If you have any dropdown, you will need to re-populate them before sending sending the model back.
public ContactController : Controller
{
[HttpGet]
public ActionResult Add()
{
return View(new Models.ContactModel());
}
[HttpPost]
public ActionResult Add(Models.ContactModel contact)
{
if (!ModelState.IsValid)
{
return View(contact);
}
DAL.Contact mappedContact = Mapper.Map<Models.ContactModel, DAL.Contact>(contact);
repository.AddContact(mappedContact);
return RedirectToAction("Index");
}
}
The GET action returns the empty form.
The POST action posts the model to the server.
Your view model should be named Add.cshtml to that mvc can automatically pick it up.
And change your view buttons
<div class="button-container">
#Html.ActionLink("Cancel", "Index", "Contact")
<input type="submit" value="Save" />
</div>
Style the Cancel link to look like a button
Your submit will automatically submit to the Add POST method.
The model state check returns the model back to the view with the validation information in it so that you can correct the form.

ASP MVC Validation

I need to perform validation on a textbox and Dropdown which triggers only when both the values are empty and does nothing when one of the value is empty. How would i implement it? Do i need to create a custom validator? Below is my Model and View
Model
public class CustomValidators
{
[Required]
[Required(ErrorMessage = "State Required")]
public string drpStateId { set; get; }
public System.Web.Mvc.SelectList drpState { set; get; }
[Required(ErrorMessage ="Region Required")]
public string txtRegion { set; get; }
}
View
#model InterviewTest.Models.CustomValidators
#{
ViewBag.Title = "Custom Validator";
Layout = "~/Views/_Layout.cshtml";
}
<p>#Html.ActionLink("< Back", "Index")</p>
#using (Html.BeginForm("CustomValidatorPost"))
{
#Html.ValidationSummary()
<div class="container-fluid">
<div class="row">
<div class="col-sm-3">
<div class="form-group">
#Html.DropDownListFor(c => c.drpStateId, Model.drpState, "", new { #class = "form-control" })
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
#Html.TextBoxFor(x => Model.txtRegion, new { #class = "form-control" })
#*<input type="text" id="txtRegion" name="txtRegion" class="form-control" />*#
</div>
</div>
<div class="col-sm-3">
<button type="submit" name="btnSubmit" id="btnSubmit" class="btn btn-default">Submit</button>
</div>
</div>
</div>
}
There is no out of the box validation that works on 2 fields except for the compare validator, so in your case you have to create a custom validation.
You can create a JavaScript function and fire it on onchange on both the two text boxes and within it check the values and if both are empty, show an error message and prevent the form from being submitted, you can achieve that using JQuery validation by adding a custom validator, see this link for more details https://jqueryvalidation.org/jQuery.validator.addMethod/
On Server side, you can do a simple if statement in the controller action to validate that both the values are not empty and if both are empty, then add an error to the ModelState

MVC parent child kind of model form submit doesn't send child collection to controller

I have a company model and it has employees list model as shown below
public class Company
{
[Required]
[Display(Name = "Company Name")]
public string Name { get; set; }
public List<EmployeeModel> Managers { get; set; }
}
and the Employee model as below
public class EmployeeModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
and my parent view is as shown below
#using (Html.BeginForm("CompanySignupSuccess", "Home", FormMethod.Post, new { #class = "horizontal-form", role = "form", enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary("", new { #class = "text-danger" })
<div>
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "control-label" })<span class="required">*</span>
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
</div>
<div class="form-group">
<label for="file">Logo:</label>
<input type="file" name="logo" id="logo" accept=".png,.jpg,.jpeg" />
</div>
<div id="managerList">
<div id="editorRowsManagers">
#foreach (var item in Model.Managers)
{
#Html.Partial("DetailsView", item)
}
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-default pull-right" value="Send" />
</div>
</div>
}
and the partial view shown below
#model yourAssembly.EmployeeModel
<div style="border:1px solid;margin:20px; padding:10px;">
Manager Details:
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "control-label" })
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "control-label" }) <span class="required">*</span>
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Phone, new { #class = "control-label" }) <span class="required">*</span>
#Html.TextBoxFor(m => m.Phone, new { #class = "form-control phoneno" })
</div>
</div>
When I click on submit button, the model that goes to controller does have only Name and Logo properties filled in and the list object(Managers) is null, so I am not sure what is that I am missing here. BTW, I used the list of employees , because I would like add more employees by having a 'Add' button, and the Add button will just render another partial view.
public ActionResult CompanySignupSuccess(Company model)
{
if (ModelState.IsValid)
{
//do some process
}
else
{
ModelState.AddModelError("", "Invalid Data entered.");
}
// If we got this far, something failed, redisplay form
return View("CompanySignup", Model);
}
Can anyone please help me on how to send the child list object along with some properties on parent class when the Submit button is hit.
You cannot use a partial to generate controls for a collection unless you pass the HtmlFieldPrefix (refer this answer for an example). However the correct way to do this is to use an EditorTemplate. Rename your partial to EmployeeModel.cshtml (i.e. to match the name of the class) and move it to the /Views/Shared/EditorTemplates folder (or /Views/YourControllerName/EditorTemplates folder).
Then replace your loop in the view with
#Html.EditorFor(m => m.Managers)
which will correctly generate the necessary name attributes for binding, i.e
<input ... name="Managers[0].Name" />
<input ... name="Managers[1].Name" />
etc (currently all your generating is duplicate name attributes (and duplicate id attributes which is invalid html)

Weird Razor field caching while posting back same page

The situation would seem straight-forward, but I am getting a very strange bug. Perhaps someone can explain why? I have provided full code below of the relevant parts.
The basic idea is:
Enter a company number
Use the Companies House Gateway API to retrieve results
Display the company name & address on the same page, to allow review
The problem is, that entering a different company number results in the previous results always being displayed (just the variable number of address lines change).
I have break-pointed in the controller and in the view and the correct company details are being returned, but they are not displayed. I have never seen a simple page like this go wrong.
I replaced the Gateway service with hard-wired companies to elimiate that service and it still goes wrong.
Account controller (Step1 action)
[AllowAnonymous]
public ActionResult Step1()
{
RegisterCompanyViewModel vm = new RegisterCompanyViewModel();
return View(vm);
}
[AllowAnonymous]
[HttpPost]
public ActionResult Step1(RegisterCompanyViewModel model)
{
if (ModelState.IsValid)
{
if (model.CompanyNumber.EndsWith("1"))
{
model.Address = new List<string>() { "Line 1", "line 2", "Line 3"};
model.CompanyName = "Company name 1";
}else if (model.CompanyNumber.EndsWith("2"))
{
model.Address = new List<string>() { "Line 4", "line 5", "Line 6", "Line 7" };
model.CompanyName = "Company name 2";
}
else
{
ModelState.AddModelError("CompanyNumber", "Company number not found");
}
}
return View(model);
}
RegisterCompanyViewModel class
public class RegisterCompanyViewModel
{
[Display(Name="Company Number")]
public string CompanyNumber { get; set; }
[Display(Name = "Company Name")]
public string CompanyName { get; set; }
[Display(Name = "Address")]
public List<string> Address { get; set; }
public RegisterCompanyViewModel()
{
this.Address = new List<string>();
}
}
Step1.cshtml
#model MvcApplication.Models.RegisterCompanyViewModel
#{
ViewBag.Title = "Step1";
}
<h2>Enter your Company Number</h2>
#using (#Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CompanyNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => Model.CompanyNumber)
#Html.ValidationMessageFor(x => Model.CompanyNumber)
</div>
#Html.ValidationMessageFor(model => model.CompanyNumber, "", new { #class = "text-danger" })
</div>
#if (!string.IsNullOrEmpty(Model.CompanyName))
{
<div class="form-group">
#Html.LabelFor(model => model.CompanyName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => Model.CompanyName)
#Html.ValidationMessageFor(x => Model.CompanyName)
</div>
</div>
}
#if (Model.Address.Any())
{
for (int i = 0; i < Model.Address.Count(); i++)
{
<div class="form-group">
#Html.LabelFor(model => model.Address, "Address lines " + i.ToString(), htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => Model.Address[i])
#Html.ValidationMessageFor(x => Model.Address[i])
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save Details" class="btn btn-default" />
</div>
</div>
}
else
{
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Lookup" class="btn btn-default" />
</div>
</div>
}
</div>
}
Notes:
I am using the latest MVC "everything" as of yesterday.
I even added a timer display to the main layout, so I could confirm the page was fully refreshing (it is).
Screen shots after entering "1" then "2" as the company numbers:
Note the new "Line 7" is visible, but all other fields have their previous values:
Any ideas on why this form page is showing the previous field values when re-submitting?
Solution:
My revised code, based on the very useful explanation by #Chris Pratt:
[AllowAnonymous]
public ActionResult Step1([Bind(Prefix="id")]string companyNumber)
{
RegisterCompanyViewModel vm = new RegisterCompanyViewModel();
vm.CompanyNumber = companyNumber;
if (!string.IsNullOrEmpty(companyNumber))
{
Gateway gateway = new Gateway(senderId, password);
var company = gateway.GetCompanyDetails(companyNumber);
if (company != null)
{
vm.CompanyName = company.CompanyName;
vm.Address = company.AddressLines;
}
else
{
ModelState.AddModelError("CompanyNumber", "Company number not found");
}
}
return View(vm);
}
[AllowAnonymous]
[HttpPost]
public ActionResult Step1(RegisterCompanyViewModel model, string action)
{
if (ModelState.IsValid)
{
if (action == "Search for company")
{
return RedirectToAction("Step1", new { id = model.CompanyNumber });
}
else if (action == "Save Details")
{
// Save changes now
}
}
return View(model);
}
The page now looks like this:
This is a common confusion. The posted data goes into the ModelState object, and anything in ModelState overrides anything on your model. This is by design. For example consider the scenario where you're editing an existing entity, but in the edit, the user had an error. If Razor used the model's data, then when the view was returned for the user to fix whatever problem there was, all their edits would be gone, replaced with the original model data. By have ModelState take precedence, it ensures that the posted data is preserved when returning the form again.
What you need to do is follow the proper PRG pattern (Post-Redirect-Get). If the initial submission is good, you redirect back to the same action to display it again, instead of just returning the view. That way, the ModelState is clean.

Resources