ASP.Net MVC: User must select one check box validate at client side - asp.net-mvc

I have UI where i am showing 3 checkboxes and each refer to different property of model class. i am using jquery unobtrusive validation just by mvc data annotation. i want when user submit form then user has to select one checkbox otherwise client side error message will display and form will not be submitted.
i can do it by jquery but i want to do it by mvc data annotation.
see my model class
public class Customer
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Mail to me")]
public bool SelfSend { get; set; }
[Display(Name = "3rd party")]
public bool thirdParty { get; set; }
[Display(Name = "Others")]
public bool Others { get; set; }
}
Controller
[ValidateAntiForgeryToken()]
[HttpPost]
public ActionResult Index(Customer customer)
{
if (customer.Others == false || customer.SelfSend == false || customer.thirdParty == false)
ModelState.AddModelError("Error", "Must select one option");
return View();
}
with the below code i can validate any checkboxes is selected or not from server side code and add model error which show error at client side.
but i want to do validation by client side using normal data annotation.
see my razor code
<div class="row">
<div class="col-md-8">
<section id="testform">
#using (Html.BeginForm("Index", "Customers", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Enter customer info.</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.FirstName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.FirstName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.LastName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.LastName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.LastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
#Html.CheckBoxFor(m => m.SelfSend)
#Html.LabelFor(m => m.SelfSend)
</div>
</div>
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
#Html.CheckBoxFor(m => m.thirdParty)
#Html.LabelFor(m => m.thirdParty)
</div>
</div>
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
#Html.CheckBoxFor(m => m.Others)
#Html.LabelFor(m => m.Others)
</div>
</div>
<div class="col-md-offset-2 col-md-10">
#Html.ValidationMessage("Error", "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}

You can try to write a customer model validation attribute.
add CheckBoxAuthAttribute in your one of three validation property.
There is a method protected virtual ValidationResult IsValid(object value, ValidationContext validationContext) in you can override inValidationAttribute.
public class CheckBoxAuthAttribute : ValidationAttribute
{
public CheckBoxAuthAttribute(params string[] propertyNames)
{
this.PropertyNames = propertyNames;
}
public string[] PropertyNames { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties
.Select(p => p.GetValue(validationContext.ObjectInstance, null))
.OfType<bool>();
if (values.Contains(true) || (bool)value == true)
{
return null;
}
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
public class Customer
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Mail to me")]
[CheckBoxAuth("thirdParty", "Others", ErrorMessage = "Must select one option"))]
public bool SelfSend { get; set; }
[Display(Name = "3rd party")]
public bool thirdParty { get; set; }
[Display(Name = "Others")]
public bool Others { get; set; }
}

Since you want one of 3 possible options to be selected, then use radio buttons and bind to a property with a required attribute.
Start by defining a view model
public class CustomerVM
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
public int? Mailing { get; set; } // see notes below
}
And in the view
#model CustomerVM
....
#using (Html.BeginForm())
{
....
<label>
#Html.RadioButtonFor(m => m.Mailing, 1, new { id = ""})
<span>Mail to me</span>
</label>
<label>
#Html.RadioButtonFor(m => m.Mailing, 2, new { id = ""})
<span>3rd party</span>
</label>
.... // ditto for "Others"
#Html.ValidationMessageFor(m => m.Mailing)
....
}
and the POST method will be
[HttpPost]
public ActionResult Index(CustomerVM model)
{
if(!ModelState.IsValid)
{
return View(model);
}
.... // map to instance of data model, save and redirect
}
Note that if these options are unlikely to change, it would be more appropriate to make the property an enum rather than an int, for example
public enum Mailing
{
[Display(Name = "Mail to me")]
SelfSend = 1,
[Display(Name = "3rd party")]
ThirdParty = 2,
[Display(Name = "Others")]
Others = 3
}
public class CustomerVM
{
....
[Required]
public Mailing? Mailing { get; set; }
}
and the view code would be
#Html.RadioButtonFor(m => m.Mailing, Mailing.SelfSend, new { id = ""})

Related

ViewModel Contents are Null after Posting Form to Controller

So the ViewModel has 2 sets of data.
The CurrentDetails and UpdatedDetails. Both are instances of the same class which carries strings and whatnot inside etc.
This method has worked with all other views and models I've attempted with, but for THIS one instance, when the form is posted to the controller, its contents (CurrentDetails and UpdatedDetails) are both found to be null.
I've tried changing the parameter name from model to test and to other arbitrary things, but to no avail.
The one thing that worked (but is not a solution to me) is NOT having instances of the class inside the ViewModel, and just having the data there (but I don't see why I should be forced to do things this way.
Here's the controller:
[HttpPost]
public ActionResult FloristProfile(MerchantFloristProfileViewModel test)
{
if (!ModelState.IsValid)
return View(test);
using (var db = new ApplicationDbContext())
{
Florist florist = db.Florists.Find(MerchantBase.FloristID);
if (Request.Form["editSubmit"] != null)
{
florist.Name = test.UpdatedDetails.Name;
florist.Website = test.UpdatedDetails.Website;
db.SaveChanges();
return RedirectToAction("FloristProfile");
}
else if (Request.Form["photoSubmit"] != null)
{
if (test.CurrentDetails.File.ContentLength > 0)
{
CloudBlobContainer container = FlowerStorage.GetCloudBlobContainer();
string blobName = String.Format("florist_{0}.jpg", Guid.NewGuid().ToString());
CloudBlockBlob photoBlob = container.GetBlockBlobReference(blobName);
photoBlob.UploadFromStream(test.CurrentDetails.File.InputStream);
florist.LogoPath = blobName;
florist.isRendering = true;
db.SaveChanges();
return RedirectToAction("FloristProfile");
}
}
}
return Content("Invalid Request");
}
View:
#using (Html.BeginForm("FloristProfile", "Merchant", FormMethod.Post, new { #class = "form-horizontal" }))
{
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
#Html.HiddenFor(x => x.CurrentDetails.FloristID)
#Html.HiddenFor(x => x.CurrentDetails.Name)
#Html.HiddenFor(x => x.CurrentDetails.StaffCount)
#Html.HiddenFor(x => x.CurrentDetails.StoreCount)
#Html.HiddenFor(x => x.CurrentDetails.Website)
<div class="form-group">
#Html.LabelFor(x => x.UpdatedDetails.Name, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#Html.TextBoxFor(x => x.UpdatedDetails.Name, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(x => x.UpdatedDetails.Website, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#Html.TextBoxFor(x => x.UpdatedDetails.Website, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" name="editSubmit" class="btn btn-success">Save</button>
</div>
</div>
}
ViewModel:
public class MerchantFloristProfileViewModel
{
public class FloristProfileDetails
{
public int FloristID { get; set; }
[Required(ErrorMessage = "Please Enter a Name")]
public string Name { get; set; }
[DataType(DataType.Url)]
[Required(ErrorMessage = "Please Enter a Website")]
public string Website { get; set; }
public int StoreCount { get; set; }
public int StaffCount { get; set; }
// For Picture Upload
public HttpPostedFileBase File { get; set; }
}
public FloristProfileDetails CurrentDetails;
public FloristProfileDetails UpdatedDetails;
}
Both CurrentDetails and UpdatedDetails in your MerchantFloristProfileViewModel model are fields, not properties (no getter/setter) so the DefaultModelBinder cannnot set the values. Change them to
public FloristProfileDetails CurrentDetails { get; set; }
public FloristProfileDetails UpdatedDetails { get; set; }
But you should not be sending all that extra data to the view, then sending it all back again unchanged. Apart from the extra overhead, any malicious user could alter the values in the hidden fields causing your app to fail. Just get the original from the repository again if you need it in the POST method

Better Way To Edit A View Model in MVC

I actually came up with a working example of how to display (GET) and edit (POST) a view model consisting of three models in MVC. However, my MVC skills are limited and I was looking for suggestions on "the right way" I should be doing this.
The part in question is that I'm sending the form fields back individually rather than to a view model, which is something I couldn't figure out how to do.
Here are my models
Name
public partial class Name
{
public Name()
{
this.Addresses = new HashSet<Address>();
this.Emails = new HashSet<Email>();
}
public int ID { get; set; }
public string FIRST_NAME { get; set; }
public string LAST_NAME { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Email> Emails { get; set; }
}
Address
public partial class Address
{
public int ADDRESS_ID { get; set; }
public int NameID { get; set; }
public string ADDRESS_1 { get; set; }
public string CITY { get; set; }
public string STATE { get; set; }
public string ZIP { get; set; }
public virtual Name Name { get; set; }
}
Email
public partial class Email
{
public int EMAIL_ID { get; set; }
public int NameID { get; set; }
public string EMAIL { get; set; }
public virtual Name Name { get; set; }
}
My View Model made up of all the fields from the three models.
public class ContactFormViewModel
{
public int? ID { get; set; }
public string FIRST_NAME { get; set; }
public string LAST_NAME { get; set; }
public string ADDRESS_1 { get; set; }
public string CITY { get; set; }
public string STATE { get; set; }
public string ZIP { get; set; }
public string EMAIL { get; set; }
}
The GET method of the Edit page (in the Controller)
// GET: Names/Edit/5
//The GET method takes the id from the URL and passes it into the query to return data for the specific record
public ActionResult Edit(int id)
{
//This query is an outer join of the Name, Address and Email models/tables
var query = from n in db.Names
join a in db.Addresses
on n.ID equals a.NameID into na
from a in na.DefaultIfEmpty()
join e in db.Emails
on n.ID equals e.NameID into ne
from e in ne.DefaultIfEmpty()
where n.ID == id
//Creates a new instance of the view model, populated with the query data
select new ContactFormViewModel
{
ID = id,
FIRST_NAME = n.FIRST_NAME,
LAST_NAME = n.LAST_NAME,
ADDRESS_1 = a.ADDRESS_1,
CITY = a.CITY,
STATE = a.STATE,
ZIP = a.ZIP,
EMAIL = e.EMAIL
};
//Returns the query to the view
return View(query);
}
The POST method of the Edit page (in the Controller)
// POST: Names/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
//The POST method takes the individual form field data and passes it to queries that update all three models separately
public ActionResult Edit(int id, string FIRST_NAME, string LAST_NAME, string ADDRESS_1, string CITY, string STATE, string ZIP, string EMAIL)
{
if (ModelState.IsValid)
{
//Query the database for the row to be updated.
var queryN =
from n in db.Names
where n.ID == id
select n;
var queryA =
from a in db.Addresses
where a.NameID == id
select a;
var queryE =
from e in db.Emails
where e.NameID == id
select e;
//Assign the form field data to the fields in the model
foreach (Name n in queryN)
{
n.FIRST_NAME = FIRST_NAME;
n.LAST_NAME = LAST_NAME;
}
//If there are no address records, insert
if (!queryA.Any())
{
//New instance of Address
var address = new Address
{
NameID = id,
ADDRESS_1 = ADDRESS_1,
CITY = CITY,
STATE = STATE,
ZIP = ZIP
};
db.Addresses.Add(address);
}
//Else, if there are address records, then update
else
{
foreach (Address a in queryA)
{
a.ADDRESS_1 = ADDRESS_1;
a.CITY = CITY;
a.STATE = STATE;
a.ZIP = ZIP;
}
}
//If there are no email records, insert
if (!queryE.Any())
{
//New instance of Email
var email = new Email
{
NameID = id,
EMAIL = EMAIL
};
db.Emails.Add(email);
}
//Else, if there are email records, then update
else
{
foreach (Email e in queryE)
{
e.EMAIL = EMAIL;
}
}
//// Submit the changes to the database.
try
{
db.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
// Provide for exceptions.
}
}
return RedirectToAction("Index");
}
The View
#model IQueryable<MDTestApplication.ViewModel.ContactFormViewModel>
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Name</h4>
<hr />
#*Uses foreach loop to get all field data from the view model*#
#foreach (var item in Model)
{
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(modelItem => item.ID)
<div class="form-group">
#Html.LabelFor(modelItem => item.FIRST_NAME, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#*Using razor syntax to output the value*#
#*Using form field 'Name' attribute for posting back to the controller*#
<input type="text" name="FIRST_NAME" value="#item.FIRST_NAME" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.LAST_NAME, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="LAST_NAME" value="#item.LAST_NAME" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.ADDRESS_1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="ADDRESS_1" value="#item.ADDRESS_1" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.CITY, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="CITY" value="#item.CITY" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.STATE, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="STATE" value="#item.STATE" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.ZIP, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="ZIP" value="#item.ZIP" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.EMAIL, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="EMAIL" value="#item.EMAIL" class="form-control" />
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
UPDATE
Here's additional insert and update code I added to Alex's answer below. You would use this same setup for address and email.
foreach (var address in model.Addresses)
{
var addressToUpdate = name.Addresses.FirstOrDefault(a => AddressId== address.AddressId);
if (addressToUpdate != default(Address))
{
// preform an update
addressToUpdate.AddressId = address.AddressId;
addressToUpdate.City = address.City;
addressToUpdate.State = address.State;
addressToUpdate.Zip = address.Zip;
}
else
{
//perform an insert
var newAddress = new Address
{
NameID = model.ID,
Address1 = address.Address1,
City = address.City,
State = address.State,
Zip = address.Zip
};
db.Addresses.Add(newAddress);
}
}
First of all let me start with the naming convention.
This:
public int ADDRESS_ID { get; set; }
public int NameID { get; set; }
Is BAD You have no naming convention at all, some properties are PascalCase, others capital case with underscores. I strongly advise you to install some tool that will enforce you to apply set of style and consistency rules(for example StyleCop). In general it is very common to use PascalCase for properties.
Once you apply it your models will look like:
public partial class Address
{
public int AddressId { get; set; }
public int NameId { get; set; }
public string Address1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public virtual Name Name { get; set; }
}
Second thing:
If I understand correct you are trying to edit a data for one user:
His(or hers) first and last name, list of addresses and a lit of emails. If I am right both your View and ViewModel are wrong. Your ViewModel could look as following:
public class ContactFormViewModel
{
public int NameId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IList<Address> Addresses { get; set; }
public IList<Emails> { get; set; }
}
Controller(UPDATED):
// GET: Names/Edit/5
//The GET method takes the id from the URL and passes it into the query to
//return data for the specific record
public ActionResult Edit(int id)
{
//You don't need the joins since you have navigation properies!
var name = db.Names.FirstOrDefault(n => n.ID == id);
ContactFormViewModel model;
if(name == default(Name))
{
model = new ContactFormViewModel{
Addresses = new List<Address>(),
Emails = new List<Email>()
};
}
else {
model = new ContactFormViewModel
{
NameId = name.NameId ,
FirstName = name.FirstName,
LastName = name.LastName ,
Addresses = name.Addresses.ToList(),
Emails = name.Emails.ToList(),
};
}
if(!model.Addresses.Any())
{
model.Addresses.Add(new Address());
}
if(!model.Emails.Any())
{
model.Emails.Add(new Email());
}
//Returns the query to the view
return View(model);
}
View:
#model MDTestApplication.ViewModel.ContactFormViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Name</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.NameId)
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FirstName, new { #class = "FirstName" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LastName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => item.LastName, new { #class = "FirstName" })
</div>
</div>
#for (int i = 0; i < Model.Addresses.Count; i++)
{
#Html.HiddenFor(model => model.Addresses[i].AddressId)
<div class="form-group">
#Html.LabelFor(model => model.Addresses[i].Address1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Addresses[i].Address1, new { #class = "FirstName" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Addresses[i].City, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Addresses[i].City, new { #class = "FirstName" })
</div>
</div>
/// Continue here with all the address properties
}
#for (int i = 0; i < Model.Emails.Count; i++)
{
#Html.HiddenFor(model => model.Emails[i].EmailId)
<div class="form-group">
#Html.LabelFor(model => model.Emails[i].Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Emails[i].Email, new { #class = "FirstName" })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Edit Action in Controller:
public ActionResult Edit(ContactFormViewModel model)
{
if (ModelState.IsValid)
{
//Query the database for the row to be updated.
var name = db.Names.FirstOrDefault( n => n.NameId == model.NameId);
if(name != default(Name))
{
name.FirstName = model.FirstName;
name.LastName = model.LastName;
bool hasAddresses = name.Addresses.Any();
foreach(var address in model.Addresses)
{
var addressToUpdate = name.Addresses.FirstOrDefault(a => a.AddressId == address.AddressId);
if(addressToUpdate != default(Address))
{
// preform an update
}
else
{
//perform an insert
}
}
foreach(var email in model.Emails)
{
var emailToUpdate = name.Emails.FirstOrDefault(a => a.EmailId == email.EmailId);
if(emailToUpdate != default(Email))
{
// preform an update
}
else
{
//perform an insert
}
}
db.SaveChanges();
}
}
}
What you are attempting to do is submit a variable length list, which is not something asp mvc can handle on its own. Let me explain:
You have a form, and inside the form is a foreach loop that creates editable fields for multiple records. ASP MVC can handle updating a single record just fine with its form helpers, however when you have multiple records that need updating, ASP MVC doesn't automatically create an indexing number for each record, which makes it impossible for it to keep track of which property belongs to which record. When you submit the form, MVC doesn't create this indexing for you, so you need to find alternative solutions.
I highly suggest you look at this blog and incorporate the helper that is provided to achieve your form submission:
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Asp.net MVC + Multiple PartialView + Multiple Controllers

I want to create a registering page with multiple data from user
e.g.
1 - I got a userViewModel with basic ID and Name and got a model for this View;
[Key]
public int CandidatoId { get; set; }
[Required(ErrorMessage = "Preencher o campo Nome")]
[MaxLength(150, ErrorMessage = "Máximo {1} caracteres")]
[MinLength(2, ErrorMessage = "Mínimo {1} caracteres")]
public string Nome { get; set; }
[Required(ErrorMessage = "Preencher o campo CPF")]
[MaxLength(15, ErrorMessage = "Máximo {1} caracteres")]
[MinLength(2, ErrorMessage = "Mínimo {1} caracteres")]
//Criar Datatype de CPF
public string CPF { get; set; }
2 - Got a Perssonal Data too:
public class DadosPessoaisViewModel
{
[Key]
public int CandidatoId { get; set; }
public char Sexo { get; set; }
[Required(ErrorMessage = "Preencher o campo Endereço")]
[MaxLength(500, ErrorMessage = "Máximo {0} caracteres")]
[MinLength(2, ErrorMessage = "Mínimo {0} caracteres")]
public string Endereco { get; set; }
public virtual Candidato Candidato { get; set; }
}
And I will have more about 5 or More data from user, like family info etc.
For the user interface I'm planning to make it inside a bootstrap carousel.
So I create a generic ViewModel for all data ( WithoutFisical model )
and one View model for each one kind of user Data:
//User ViewModel
public class DadosCandidatoViewModel
{
[Key]
public int CandidatoId { get; set; }
//User ViewModel
public virtual CandidatoViewModel Candidato { get; set; }
//Pessoal Data Info ViewModel
public virtual DadosPessoaisViewModel DadosPessoais { get; set; }
//will Be more data from user here
}
//User Pessoal Data VewModel ( Sample )
public class DadosPessoaisViewModel
{
[Key]
public int CandidatoId { get; set; }
public char Sexo { get; set; }
[Required(ErrorMessage = "Preencher o campo Endereço")]
[MaxLength(500, ErrorMessage = "Máximo {0} caracteres")]
[MinLength(2, ErrorMessage = "Mínimo {0} caracteres")]
public string Endereco { get; set; }
public virtual Candidato Candidato { get; set; }
}
So I created an Edit View that will have all the user data separated by partial Views :
#model Gestao_RH.MVC.ViewModels.DadosCandidatoViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<!-- Div na qual o "carousel" será aplicado. -->
<div id="div-carousel" class="carousel slide">
<div class="carousel-inner">
<!-- Divs com efeito de transição. -->
<div class="item active">
#Html.Partial("~/Views/DadosPessoais/Edit.cshtml" , Neeed Passs DadosPessoaisViewModel Here???? )
</div>
<div class="item">
Conteúdo da DIV 2.
</div>
</div>
</div>
<div class="row">
<!-- Botões de navegação -->
<div id="div-1" class="span2">
<a id="a-1" class="btn" href="#div-carousel" data-slide="prev"><i class="icon-chevron-left"></i>Voltar para DIV 1</a>
</div>
<div id="div-2" class="span2">
<a id="a-2" class="btn" href="#div-carousel" data-slide="next">Avançar para DIV 2<i class="icon-chevron-right"></i></a>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Now there's a problem...
All of my partial views are strongly typed like sample above:
#model Gestao_RH.MVC.ViewModels.DadosPessoaisViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>DadosPessoaisViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.CandidatoId)
<div class="form-group">
#Html.LabelFor(model => model.Sexo, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Sexo, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Sexo, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Endereco, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Endereco, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Endereco, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
#Html.ActionLink("Save", "Edit", "DadosPessoais")
#*<input type="submit" value="Save" class="btn btn-default" />*#
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The problem is this View expects a DadosPessoaisViewModel ( User Perssonal data) but I have only DadosCandidatoViewModel in this context to sent.
e.g for my Controller:
public class DadosCandidatoController : Controller
{
private readonly ICandidatoAppService _candidato;
public DadosCandidatoController(ICandidatoAppService candidato)
{
_candidato = candidato;
}
public ActionResult Edit(int id)
{
var cargo = _candidato.GetById(id);
var DadosCandidatoViewModel = Mapper.Map<Candidato, DadosCandidatoViewModel>(cargo);
return View(DadosCandidatoViewModel);
}
}
If i understand your question correctly you have two view models that are dependant on each other but distinct partial views. The best way to solve this would be to have the dependant property be party of the overall page view model so both views can access the properties at the same time.
The question of how to access one property of the view model from multiple partial views can be found here

MVC model not bound to collection in POST

Visual Studio 2012
MVC 5.2.0
jQuery 2.1.1
jQuery UI Combined 1.10.4
Json.Net 6.0.3
Kendo UI MVC 2014.1.528
I've read through many similar posts. I've tried to apply what I've seen to no avail. I throw myself at feet of your keyboards for your mercy and help.
I have a survey-like site, so for simplicity here, I made up a sample project to duplicate the issue. I can upload that too.
I have a model with a child object of Address - no problemo there - it binds.
The model also has a collection of questions. It never binds on the post, and this is the issue here.
Lets start with models:
The Survey itself:
public class Survey
{
[ScaffoldColumn(false)]
public int Id { get; set; }
public string Name { get; set; }
[DisplayName("Phone #")]
[StringLength(15)]
[DataType(DataType.PhoneNumber)]
public string ContactMethodPhone { get; set; }
[DisplayName("Email")]
[StringLength(120)]
[EmailAddress]
public string ContactMethodEmail { get; set; }
public virtual Address Address { get; set; }
public virtual List<Question> Questions { get; set; }
}
and the address:
public class Address
{
[ScaffoldColumn(false)]
public int AddressId { get; set; }
[DisplayName("Address line 1")]
[Required]
[StringLength(200)]
public string Street { get; set; }
[DisplayName("Address line 2")]
[StringLength(200)]
public string Street2 { get; set; }
[DisplayName(" ")]
[StringLength(50)]
public string ApartmentNum { get; set; }
[DisplayName("Type")]
[StringLength(50)]
public string Tenement { get; set; }
[DisplayName("City")]
[Required]
[StringLength(200)]
public string City { get; set; }
[DisplayName("Province/State")]
[StringLength(20)]
public string State { get; set; }
[Required]
[DisplayName("Postal/Zip Code")]
[StringLength(10)]
public string MailCode { get; set; }
[DisplayName("Country")]
[StringLength(30)]
public string Country { get; set; }
[NotMapped]
public List<SelectListItem> Cities { get; set; }
[NotMapped]
public List<SelectListItem> Provinces { get; set; }
[NotMapped]
public List<SelectListItem> Countries { get; set; }
[NotMapped]
public List<SelectListItem> MailCodes { get; set; }
[NotMapped]
public List<SelectListItem> Tenements { get; set; }
}
and the questions:
public class Question
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[ScaffoldColumn(false)]
public int ImageID { get; set; } // Question Image
[DisplayName("Question: ")]
public string InformationIntakeGroupValue { get; set; } // Question: ie Did you graguate high school
public int ImageID_Topic { get; set; } // Topic Image
[DisplayName("Topic: ")]
public string InformationIntakeTopicValue { get; set; } // Topic: ie Education
[ScaffoldColumn(false)]
public string InformationIntakeTypeCode { get; set; } // Type of question (date, bool, text)
// below not filled by select;
// the key from the EntityAffilliateIntake record insert
public int PersonId { get; set; } // Person anwering question
// this is the user response area
[DisplayName("Answer: ")]
public string InformationIntakeValue { get; set; }
[DisplayName("Choice: ")]
public string InformationIntakeValueBool { get; set; }
[DisplayName("Date: ")]
public DateTime InformationIntakeValueDate { get; set; }
[ForeignKey("Survey")]
public int SurveyId { get; set; }
public virtual Survey Survey { get; set; }
}
(Note: fyi, I've tried the models without foreign keys as well - but perhaps it's not defined correctly)
The controller :
// GET: /Inquiry/Create
public ActionResult Create()
{
var geoIpData = Strings._download_serialized_json_data<GeoData>(StringConstants.UrlForGeoService);
SurveyModel = new Survey
{
Id=1,
Address = new AddressController().GetAddressModel(geoIpData.country, geoIpData.regionName, geoIpData.city, ""),
Questions = new QuestionController().GetQuestions(1).ToList()
};
return View(SurveyModel);
}
// POST: /Inquiry/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
public ActionResult Create([Bind(Include = "Id, Name, ContactMethodPhone, ContactMethodEmail, Address, Questions")] Survey survey)
{
if (ModelState.IsValid)
{
int i = 0;
}
if (survey.Address.Cities == null)
{
survey.Address.Cities = SurveyModel.Address.Cities;
survey.Address.Countries = SurveyModel.Address.Countries;
survey.Address.MailCodes = SurveyModel.Address.MailCodes;
survey.Address.Provinces = SurveyModel.Address.Provinces;
survey.Address.Tenements = SurveyModel.Address.Tenements;
}
if (survey.Questions == null)
{
survey.Questions = SurveyModel.Questions;
}
return View(survey);
}
The view:
#model BindCollection.Models.Survey
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
// I use <div class="container">, <fieldset> and <div class="row">
// instead of <div class="form-horizontal"> and <div class="form-group">
<div class="container">
<fieldset>
<legend></legend>
<h4>Survey</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
<div class="row">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2 col-md-2 col-lg-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
Your address information:<br />
<br />
</div>
</div>
#* no problem here with address *#
#{ var vddAddress = new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "Address" } };}
#Html.Partial("_AddressPartial", Model.Address, #vddAddress)
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
How we can contact you? :<br />
<br />
</div>
</div>
<div class="row">
#Html.LabelFor(model => model.ContactMethodPhone, new { #class = "control-label col-sm-2 col-md-2 col-lg-2" })
<div class="col-sm-10 col-md-10 col-lg-10">
#Html.EditorFor(model => model.ContactMethodPhone)
#Html.ValidationMessageFor(model => model.ContactMethodPhone)
</div>
</div>
<div class="row">
#Html.LabelFor(model => model.ContactMethodEmail, new { #class = "control-label col-sm-2 col-md-2 col-lg-2" })
<div class="col-sm-10 col-md-10 col-lg-10">
#Html.EditorFor(model => model.ContactMethodEmail)
#Html.ValidationMessageFor(model => model.ContactMethodEmail)
</div>
</div>
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
Some Questions<br />
<br />
</div>
</div>
#*Here is the evil one! Beware!*#
#{ var vddQuestions = new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "Questions" } };}
#Html.Partial("_QuestionsPartial", Model.Questions, #vddQuestions)
<hr />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</fieldset>
</div>
}
The address partial view is insignificant as there's no problem there.
Here's the partial view for the questions:
#model IEnumerable<BindCollection.Models.Question>
#using BindCollection
#using Kendo.Mvc.UI
#{
string CurrentTopic = string.Empty;
bool FirstTime = true;
}
#foreach (var item in Model)
{
if (CurrentTopic != item.InformationIntakeTopicValue)
{
CurrentTopic = item.InformationIntakeTopicValue;
if (!FirstTime)
{
FirstTime = false;
item.InformationIntakeTopicValue = string.Empty;
}
}
else
{
item.InformationIntakeTopicValue = string.Empty;
}
#Html.EditorFor(m=>item, "Question")
<br />
<br />
}
and, of course, I made an EditorTemplate for a question, as you can see a few lines above...
#model BindCollection.Models.Question
#Html.HiddenFor(m=>m.Id)
#{if (!string.IsNullOrWhiteSpace(Model.InformationIntakeTopicValue))
{
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
<br />
<br />
<h4>
#Html.DisplayFor(m => m.InformationIntakeTopicValue, new { #class = "control-label col-sm-10 col-md-10 col-lg-10" })
</h4>
<br />
</div>
</div>
}}
#*type of value to ask for is denoted by item.InformationIntakeTypeCode*#
<div class="row">
<div class="control-label col-sm-9 col-md-9 col-lg-9">
#Html.DisplayFor(m => m.InformationIntakeGroupValue, null)
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
#{
var outputBool = false;
var outputDate = false;
var outputText = false;
if(Model.InformationIntakeTypeCode.ToLower().Contains("date") )
{
outputDate = true;
}
else if (Model.InformationIntakeTypeCode.ToLower().Contains("bool"))
{
outputBool = true;
}
else if (Model.InformationIntakeTypeCode.ToLower().Contains("string"))
{
outputText = true;
}
}
#if(outputBool)
{
#(Html.Kendo().DropDownListFor(m => m.InformationIntakeValueBool)
.HtmlAttributes(new { style ="width: 100px;", id="InformationIntakeValueBool"+Model.Id.ToString() })
.DataTextField("Text")
.DataValueField("Value")
.BindTo(new List<SelectListItem>() {
new SelectListItem() {
Text = "Please Select...",
Value = "0"
},
new SelectListItem() {
Text = "Yes",
Value = "true"
},
new SelectListItem() {
Text = "No",
Value = "false"
}
}).Value("0")
)
}
#if(outputDate)
{
#(Html.Kendo().DatePickerFor(m => m.InformationIntakeValueDate)
.HtmlAttributes(new { style = "width: 100px;", id="InformationIntakeValueDate"+Model.Id.ToString() })
)
}
#if (outputText)
{
#Html.Kendo().AutoCompleteFor(m => m.InformationIntakeValue).HtmlAttributes(new { style = "width: 80%;", id="InformationIntakeValue"+Model.Id.ToString()})
}
</div>
</div>
So... When I POST, an odd thing occurs. All form values are passed, but the ones for Questions look strange:
Id 1
Name sally
Address.Street 123 Avenue Steet
Address.Street2 Building C
Address.Tenement Suite
Address.ApartmentNum 111
Address.City_input Sarnia
Address.City Sarnia
Address.State_input Ontario
Address.State Ontario
Address.Country_input Canada
Address.Country Canada
Address.MailCode_input N6B 2K0
Address.MailCode N6B 2K0
ContactMethodPhone 555-555-5555
ContactMethodEmail r#r.com
Questions.item.Id 1
Questions.item.InformationIntakeValueBool true
Questions.item.Id 2
Questions.item.InformationIntakeValueDate 2/4/2014
Questions.item.Id 3
Questions.item.InformationIntakeValue Speckled
Questions.item.Id 4
Questions.item.InformationIntakeValueBool true
Questions.item.Id 5
Questions.item.InformationIntakeValue Lightly Toasted
Questions.item.Id 7
Questions.item.InformationIntakeValueBool true
Questions.item.Id 8
Questions.item.InformationIntakeValue Nothing!
Questions.item.Id 6
Questions.item.InformationIntakeValueBool true
Questions.item.Id 9
Questions.item.InformationIntakeValueDate 6/29/2014
I thought, as I've seen in other posts, that the Question items should look like:
Questions[0].Id 1
Questions[0].InformationIntakeValueBool true
Questions[1].Id 2
Questions[1].InformationIntakeValueDate 2/4/2014
Questions[2].Id 3
Questions[2].InformationIntakeValue Speckled
So I'm not sure why mine looks like this.
On the server side, the Request only shows one variable for each:
Request.Form.Results View Expanding the Results View will enumerate the IEnumerable
[0] "__RequestVerificationToken" object {string}
[1] "Id" object {string}
[2] "Name" object {string}
[3] "Address.Street" object {string}
[4] "Address.Street2" object {string}
[5] "Address.Tenement" object {string}
[6] "Address.ApartmentNum" object {string}
[7] "Address.City_input" object {string}
[8] "Address.City" object {string}
[9] "Address.State_input" object {string}
[10] "Address.State" object {string}
[11] "Address.Country_input" object {string}
[12] "Address.Country" object {string}
[13] "Address.MailCode_input" object {string}
[14] "Address.MailCode" object {string}
[15] "ContactMethodPhone" object {string}
[16] "ContactMethodEmail" object {string}
[17] "Questions.item.Id" object {string}
[18] "Questions.item.InformationIntakeValueBool" object {string}
[19] "Questions.item.InformationIntakeValueDate" object {string}
[20] "Questions.item.InformationIntakeValue" object {string}
See the last 4 items? What about the other records?
I'm guessing that there's something strange with the ViewDataDictionary that I'm sending to the Question Partial View.
Any help would be appreciated. This should be simple...
Neither Partial Views or foreach statements contain the necessary information to properly bind collections. I always use EditorTemplates for everything. Especially since EditorTemplates will automatically iterate over a collection.
However, if you're bound and determined to use a loop, then use a for loop, and then index the model. In your case:
#for(int i = 0; i < Model.Count; i++)
{
if (CurrentTopic != Model[i].InformationIntakeTopicValue)
...
#Html.EditorFor(m => Model[i]) // don't have to specify the template name
// since it's the same name as the type
}
However, i'd just do this:
In your view, do this:
#*Here is the evil one! Beware!*#
#Html.EditorFor(m => m.Questions)
Then have your Question.cshtml as usual, which will automatically get iterated over by the EditorFor
However, at the top of the Question.cshtml (after the model declaration) add the following code, it's all that's necessary to achieve what you're trying to do. You don't need the partial view at all.
#{
if (ViewBag.CurrentTopic != Model.InformationIntakeTopicValue)
{
ViewBag.CurrentTopic = Model.InformationIntakeTopicValue;
}
else
{
Model.InformationIntakeTopicValue = string.Empty;
}
}

How can you pass a list of objects in a model back to a controller? [duplicate]

This question already has answers here:
Model Binding to a List MVC 4
(3 answers)
Closed 9 years ago.
UPDATE: The solution was to use an EditorTemplate. See solution below:
I want to pass a model to/from a controller which let's me set name, and set the value on an undetermined roles (as checkboxes). When I examine the postback, I get a value for Name in model, but Roles is null. How can I tell which checkboxes were checked?
Model:
public class MyModel
{
public string Name { get; set; }
public IEnumerable<RoleItem> Roles { get; set; }
}
public class RoleItem
{
public String Name { get; set; }
public String Id { get; set; }
public bool Selected { get; set; }
public RoleItem(String id, String name, bool selected = false)
{
this.Name = name;
this.Id = id;
this.Selected = selected;
}
}
Razor:
#model WebApplication1.Controllers.MyModel
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#Html.TextBoxFor(m=>m.Name)
foreach (var m in Model.Roles)
{
<div>
#Html.Label(m.Id, m.Name)
#Html.CheckBox(m.Id, m.Selected, new { id = #m.Id })
</div>
}
<input type="submit"/>
}
GOAL: To allow any Administrator to add new users to the Asp identity tables and assign them roles that are defined in a list using checkboxes.
Model:
public class RegisterViewModel
{
[Display(Name = "Name")]
public string FullName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public List<RoleItem> Roles { get; set; }
}
public class RoleItem
{
public String Name { get; set; }
public String Id { get; set; }
public bool IsMember { get; set; }
}
Controller (GET): This reads all of the roles in the database and transforms them to a list of RoleItems. This will prepend the character "r" onto the id field as some browsers have a problem with an id starting with a number. We want to make sure the "Users" group is checked by default, so we find this value in the list and set the IsMember property to true. We check the request to see if the page was redirected here from a successful POST (see below)
// GET: /Account/AddUser
[Authorize(Roles = "Administrators")]
public ActionResult AddUser()
{
var rolesDb = new ApplicationDbContext(); //Users, Administrators, Developers, etc
ViewBag.AddSuccess = Request["added"]=="1" ? true : false;
var roleItems = rolesDb.Roles.Select(r => new RoleItem() { Id = "r" + r.Id, Name = r.Name, IsMember = false }).ToList(); //add an r to get around a browser bug
var users = roleItems.FirstOrDefault(r => r.Name == "Users"); //Get the row that has the Users value and set IsMember=true
if (users != null)
users.IsMember = true;
var m = new RegisterViewModel() {Roles = roleItems};
return View(m);
}
View: Pretty standard stuff. Note #Html.EditorFor(x => x.Roles) at the bottom, which uses an editor template (follows)
#model cherry.Models.RegisterViewModel
#{
ViewBag.Title = "AddUser";
}
<h2>#ViewBag.Title.</h2>
#if (Convert.ToBoolean(ViewBag.AddSuccess))
{
<text>User added!</text>
}
#using (Html.BeginForm("AddUser", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Create a new account.</h4>
<hr />
#Html.ValidationSummary()
<div class="form-group">
#Html.LabelFor(m => m.EmailAddress, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.EmailAddress, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.FullName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.FullName, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Password, new { value = Model.Password, #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new {#class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.ConfirmPassword, new {value = Model.ConfirmPassword, #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
#Html.EditorFor(x => x.Roles)
}
EditorTemplate:
You MUST give the template the same name as the object you are creating the template for. You must also put this object in a folder called EditorTemplates below the view you are designing this for, or you can put the folder inside the shared folder.
Views\Account\EditorTemplates\RoleItem.cshtml
#model cherry.Models.RoleItem
<div>
#Html.CheckBoxFor(x => x.IsMember)
#Html.LabelFor(x => x.IsMember, Model.Name)
#Html.HiddenFor(x => x.Name)
</div>

Resources