I have read some posts, but I still find it difficult to solve this. My problem is that my action only reads some of the values from the binded list. This is how I send the list to the view:
public ActionResult RegisterSurvey()
{
RegisterSurveyModel model = new RegisterSurveyModel();
var questions = new List<QuestionModel>();
var survey = EFSurvey.Survey.FirstOrDefault();
survey.QuestionSurvey
.Where(x => x.AuditingDeleted == false)
.Where(x => x.Active == true).ToList().ForEach((item) =>
{
var questionModel = new QuestionModel();
ModelCopier.CopyModel(item, questionModel);
questionModel.Answer = string.Empty;
questions.Add(questionModel);
});
model.Questions = questions;
return View(model);
}
This is my model:
public class RegisterSurveyModel
{
public List<QuestionModel> Questions { get; set; }
}
public class QuestionModel
{
public int QuestionSurveyID { get; set; }
public string Question { get; set; }
public string Answer { get; set; }
public bool Suggestion { get; set; }
}
This is my view:
<div class="SiteSurveyContainer">
#using (Html.BeginForm())
{
<div class="SurveyUp">
#for (int i = 0; i < Model.Questions.Count(); i++)
{
if (!Model.Questions[i].Suggestion)
{
<p>#Model.Questions[i].Question</p>
#Html.HiddenFor(x => Model.Questions[i].QuestionSurveyID);
#Html.TextBoxFor(x => Model.Questions[i].Answer, new { #class = "surveyBox" });
}
}
</div>
<div class="SurveyBottom">
<div class="line">
</div>
<p>
Suggestions</p>
#for (int i = 0; i < Model.Questions.Where(x => x.Suggestion == true).Count(); i++)
{
#Html.HiddenFor(x => Model.Questions[i].QuestionSurveyID);
#Html.TextAreaFor(x => Model.Questions[i].Answer, new { #class = "surveyTextArea" })
}
</div>
<div class="surveyBottomButton">
<input type="submit" value="Submit Results" />
</div>
}
So far so good. Anyway, when I answer all of the survey questions, I only get the first 4 answers... Weird. Anyone knows why is that happening?
You have multiple input controls with within the same form with the same name.
Suggested questions are a sub set of all questions, so they are being repeated twice on the same form. That will throw off the ModelBinder, so your receiving action is probably only seeing the questions that have not been repeated.
Related
I have a create view with a form. It has an input text (enviroment), input file (logo) drop down list (platforms) and list of viewmodel (days).
This is working OK. Now I want to add a "dynamic field" to insert files. Typical Add File, Remove File. I´ve seen this is done with knockout. My problem is how to deal with HttpPostedFileBase and knockout, and worst, a list of HttpPostedFileBase and knockout.
I´ve done this, but it´s not working. Any help would be very appreciated.
CreateView:
#model HPRWT.ViewModels.ReportViewModel
<script src="http://code.jquery.com/jquery-latest.js" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/knockout.namepathbinding.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/Files.js")" type="text/javascript"></script>
#using (Html.BeginForm("Create", "Service", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Report</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Enviroment)
#Html.EditorFor(model => model.Enviroment, new { data_bind = "value: enviroment" })
#Html.ValidationMessageFor(model => model.Enviroment)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Logo)
#Html.TextBoxFor(model => model.Logo, new { type = "file", data_bind = "value: logo" })
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PlatformID)
#Html.DropDownListFor(model => model.PlatformID, Model.Platforms.Select(f => new SelectListItem { Text = f.name, Value = f.ID.ToString() }))
</div>
<div class="editor-label">
#Html.LabelFor(model => model.InputFiles)
#for (int j = 0; j < Model.InputFiles.Count; j++)
{
#Html.TextBoxFor(model => model.InputFiles[j], new { type = "file", data_bind = "value: inputFile" })
}
<button data-bind="click: addFile">Add Log</button>
<button data-bind="click: removeFile">Remove Log</button>
</div>
<table id="hours">
#for (int i = 0; i < 7; i++)
{
<tr>
<td>#Model.Days[i].Label</td>
#for (int j = 0; j < 24; j++)
{
<td><div>#Html.CheckBoxFor(model => model.Days[i].Hours[j].Selected)#Html.LabelFor(model => model.Days[i].Hours[j].Selected, #" ")</div></td>
}
</tr>
}
</table>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Files.js
function createViewModel() {
var createFile = function () {
return {
inputFile: ko.observable()
};
};
var addFile = function () {
inputFiles.push(createFile());
};
var removeFile = function () {
inputFiles.pop();
};
var enviroment = ko.observable();
var logo = ko.observable();
var inputFiles = ko.observableArray();
return {
enviroment: enviroment,
logo: logo,
inputFiles: inputFiles,
addFile: addFile,
removeFile: removeFile
};
}
$(document).ready(function () {
var viewModel = createViewModel();
ko.applyBindings(viewModel);
});
ReportViewModel:
public class ReportViewModel
{
public int ID { get; set; }
public IEnumerable<Platform> Platforms { get; set; }
public int PlatformID { get; set; }
public string Enviroment { get; set; }
public HttpPostedFileBase Logo { get; set; }
public IList<HttpPostedFileBase> InputFiles { get; set; }
public IList<DayViewModel> Days { get; set; }
public ReportViewModel()
{
Days = Enumerable.Range(1, 7).Select(day => new DayViewModel { Value = day }).ToList();
InputFiles = new List<HttpPostedFileBase>();
}
}
DayViewModel:
public class DayViewModel
{
public int Value { get; set; }
public virtual IList<HourViewModel> Hours { get; set; }
public DayViewModel()
{
Hours = Enumerable.Range(0, 24).Select(hour => new HourViewModel { Value = hour }).ToList();
}
}
HourViewModel:
public class HourViewModel
{
public int Value { get; set; }
public bool Selected { get; set; }
}
Try by making InputFiles Array instead of IList
I am not sure about IList but when you take property as List, you have to keep html field name as fieldlist.propname.index (and Id will be fieldlist_propname_index) where you index will start from 0 for the default or first field and when you will add new control then its name should get increment like fieldlist.propname.0 (first one) then fieldlist.propname.1 and so on...
then only model binder will be able to bind.
Better try with Array if it works then I think you are good to go
I don't really have a good example for this mostly because I'm just winging it and so far, my code is just a jumbled up mish-mash of gobble-dee-gook. I'm at a loss and need some help or advice on this one. Here's what I need:
I am creating a mock register form meant for learning purposes only. I used a job application form as my example. One page has the applicant's personal info such as first and last name, age, gender, and level of education. The second page allows them to choose which position they wish to apply for and allows them to provide a list of skills. I have a single model set up to take the data and save it to a database. The first page will have an ajax next button that replaces the first page form with the second page form. The second page has two buttons, Back and Submit (simple enough) that are also ajax-y. My issue right now is getting the data from both forms to save to a single entry in the model. Does anyone have a simple example or a link I could look into for this kind of situation? Or maybe even another way of going about this? It would be greatly appreciated!! :)
Model
public int Id { get; set; }
[Required(ErrorMessage = "First name Req")]
[Display(Name = "First name")]
public string First { get; set; }
[Required(ErrorMessage = "Last name Req")]
[Display(Name = "Last name")]
public string Last { get; set; }
[Required(ErrorMessage = "Age Req")]
[Range(18, 75, ErrorMessage = "Age Range of {1}-{2}")]
public int Age { get; set; }
[Required(ErrorMessage = "Please Select Gender")]
public string Gender { get; set; }
[Required(ErrorMessage = "Education Level Req")]
public string Education { get; set; }
public string Position { get; set; }
public string Skills { get; set; }
Controller
[HttpGet]
public PartialViewResult Apply1()
{
var model = db.Applications.Find(id);
if (model != null)
{
return PartialView("_Apply1", model);
}
return PartialView("_Apply1");
}
[HttpPost]
public PartialViewResult Apply1(Application app)
{
if (Request.IsAjaxRequest())
{
if (db.Applications.Where(a => a.First.ToLower() == app.First.ToLower() && a.Last.ToLower() == app.Last.ToLower() && a.Age == app.Age && a.Gender == app.Gender && a.Education == app.Education).Count() == 0)
{
app.Position = "x";
db.Applications.Add(app);
db.SaveChanges();
}
else
{
app = db.Applications.Single(a => a.First.ToLower() == app.First.ToLower() && a.Last.ToLower() == app.Last.ToLower() && a.Age == app.Age && a.Gender == app.Gender && a.Education == app.Education);
}
PosList(); //This is a helper Method, get's values for a dropdown list
id = app.Id;
var model = db.Applications.Find(id);
return PartialView("_Apply2", model);
}
return PartialView("_Apply1", app);
}
[HttpGet]
public PartialViewResult Apply2()
{
var model = db.Applications.Find(id);
if (model != null)
{
return PartialView("_Apply2", model);
}
return PartialView("_Apply2");
}
[HttpPost]
public PartialViewResult Apply2(Application app)
{
if (ModelState.IsValid)
{
db.Entry(app).State = EntityState.Modified;
db.SaveChanges();
ModelState.Clear();
return PartialView("_Success");
}
PosList();
return PartialView("_Apply2", app);
}
First View
#model SiPrac.Models.Application
#using (Ajax.BeginForm("Apply1", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "appForm"
}))
{
#Html.AntiForgeryToken()
<div class="editor-label">
#Html.LabelFor(model => model.First)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.First)
#Html.ValidationMessageFor(model => model.First)
</div>
<div class="clear"></div>
<div class="editor-label">
#Html.LabelFor(model => model.Last)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Last)
#Html.ValidationMessageFor(model => model.Last)
</div>
<div class="clear">
<input class="btn" type="submit" value="Next" />
}
Second View
#using (Ajax.BeginForm("Apply2", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "appForm"
}))
{
#Html.AntiForgeryToken()
<div class="editor-label">
#Html.LabelFor(model => model.Position)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Position, (IEnumerable<SelectListItem>)ViewData["selectPos"])
</div>
<div class="clear"></div>
<input class="btn" type="submit" value="Submit" />
}
Okay I'll try to make this as short as possible. So assuming you already know how to switch between forms, as I know you do based on your question, and you have forms like these:
<form id="form1">
#Html.EditorFor(model => model.First)
// rest of the fields goes here
</form>
<form id="form2">
#Html.EditorFor(model => model.Position)
// rest of the fields goes here
<button id="submitAll">Submit</button>
</form>
That assumes that you have the buttons to switch back and forth the views. The submitAll button triggers the postback action to a controller method and pass the value like this:
function submitAllFields() {
var o = {
// list properties here similar to your model property name
// and assign values to them
First: $("#First").val(),
Position: $("#Position").val()
};
$.post('#Url.Action("apply2")', { app: o }, function(result) {
// do something here
});
}
You then need one method to accept all the inputs:
[HttpPost]
public PartialViewResult Apply2(Application app)
{
if (ModelState.IsValid)
{
db.Entry(app).State = EntityState.Modified;
db.SaveChanges();
ModelState.Clear();
return PartialView("_Success");
}
// do something here
}
Can you help me to build my "ChangePassword" View in MVC3 ?
Here what I have tried to do:
ProfileTeacherController.cs
public ViewResult ChangePassword(int id)
{
var user = User.Identity.Name;
int inter = int.Parse(user);
var teachers = from t in db.Teachers
where t.AffiliationNumber == inter
select t;
Teacher teacher = new Teacher();
foreach (var teach in teachers)
{
teacher = teach;
}
return View(teacher);
}
[HttpPost]
public ActionResult ChangePassword(Teacher teacher)
{
if (ModelState.IsValid)
{
// How can I compare the two fields password in my view ?
db.Entry(teacher).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Edit", "ProfileTeacher", new { id = teacher.TennisClubID });
}
return View(teacher);
}
Here the ChangePassword (View)
#model TennisOnline.Models.Teacher
#{
ViewBag.Title = "ChangePassword";
}
<h2>Changement du mot de passe</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend></legend>
<div class="editor-label">
#Html.Label("Enter the new password")
</div>
<div class="editor-field">
#Html.PasswordFor(model => model.Pin, new { value = Model.Pin })
</div>
<div class="editor-label">
#Html.Label("Confirm your password")
</div>
<div class="editor-field">
#Html.Password("ConfirmPassword")
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
So, How can I verify in my controller if the two passwords are the same, please ? Thanks in advance
In addition you can add the message to be shouwn when the two password are not the same in the Compare attribute.
[Compare("NewPassword", ErrorMessage = "The new password and confirm password do not match.")]
I would recommend the usage of a view model:
public class TeacherViewModel
{
...
[Compare("ConfirmPassword")]
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
now have your view take the view model and also your Post action.
In addition to that in your GET action you seem to have written some foreach loop which I don't see its usage. You could simplify:
[Authorize]
public ViewResult ChangePassword(int id)
{
var user = User.Identity.Name;
int inter = int.Parse(user);
var teacher = db.Teachers.SingleOrDefault(t => t.AffiliationNumber == inter);
return View(teacher);
}
In Model:
public class DataViewModel
{
public IList<RowsCollection> Rows { get; set; }
public PaiementMethod PaiementMethod { get; set; }
}
public class RowsCollection
{
public string ID { get; set; }
public string Question { get; set; }
}
public enum PaiementMethod
{
Cash,
CreditCard,
}
In the Controller - Index ActionResult I have return my Model and render its into view page something like :
#model WebNexpo.Controllers.DataViewModel
#using (Html.BeginForm("Save", "page", FormMethod.Post, new { id = "saves"}))
{
foreach (var item in Model.Rows)
{
#Html.Label(item.Question)
<label for="paiement_cash">
Cash</label>
#Html.RadioButtonFor(m => m.PaiementMethod, "Cash", new { name = "paiement_cash" + #item.ID + "", id = "radioID" + #item.ID + "", Group = "Group" + #item.ID + "" })
<label for="paiement_cc">
Credit card</label>
#Html.RadioButtonFor(m => m.PaiementMethod, "CreditCard", new { name = "paiement_cc" + #item.ID + "", id = "radioname" + #item.ID + "", Group = "Group" + #item.ID + "" })
}
<input id="Saveser" type="submit" value="" class="button1" />
}
in submit form Action Event :
[HttpPost]
public ActionResult Save(DataViewModel model)
{
if (ModelState.IsValid)
{
//want to read all the label which selected rediobutton.
means suppose 4 question render on page and user have selected only 2
question of the answer.so How can accomplish here?
}
return RedirectToAction("Index", new {value = "123"});
}
There's some inconsistency here. You are rendering 2 radio buttons for each row so you probably want to reorganize your view model into:
public class DataViewModel
{
public IList<RowsCollection> Rows { get; set; }
}
public class RowsCollection
{
public string ID { get; set; }
public string Question { get; set; }
public PaiementMethod PaiementMethod { get; set; }
}
and then:
#model DataViewModel
#using (Html.BeginForm("Save", "page", FormMethod.Post, new { id = "saves"}))
{
for (var i = 0; i < Model.Rows.Count; i++)
{
<div>
#Html.HiddenFor(x => x.Rows[i].ID)
#Html.LabelFor(x => x.Rows[i].Question)
#Html.EditorFor(x => x.Rows[i].Question)
#Html.Label("payment_cash" + i, "Cash")
#Html.RadioButtonFor(m => m.Rows[i].PaiementMethod, "Cash", new { id = "payment_cash" + i })
#Html.Label("payment_cc" + i, "Credit card")
#Html.RadioButtonFor(m => m.Rows[i].PaiementMethod, "CreditCard", new { id = "payment_cc" + i })
</div>
}
<input id="Saveser" type="submit" value="" class="button1" />
}
and finally:
[HttpPost]
public ActionResult Save(DataViewModel model)
{
if (ModelState.IsValid)
{
// model.Rows[0].PaiementMethod will contain the selected payment method for the first question
// model.Rows[1].PaiementMethod will contain the selected payment method for the second question
// ...
}
return RedirectToAction("Index", new { value = "123" });
}
or if you want a single payment method you could keep your view model as is but then leave the radio buttons outside of the loop in your view. Like that:
#using (Html.BeginForm("Save", "page", FormMethod.Post, new { id = "saves" }))
{
for (var i = 0; i < Model.Rows.Count; i++)
{
<div>
#Html.HiddenFor(x => x.Rows[i].ID)
#Html.LabelFor(x => x.Rows[i].Question)
#Html.EditorFor(x => x.Rows[i].Question)
</div>
}
#Html.Label("payment_cash", "Cash")
#Html.RadioButtonFor(x => x.PaiementMethod, "Cash", new { id = "payment_cash" })
#Html.Label("payment_cc", "Credit card")
#Html.RadioButtonFor(x => x.PaiementMethod, "CreditCard", new { id = "payment_cc" })
<input id="Saveser" type="submit" value="" class="button1" />
}
I'm working on a project using MVC3. I've first created a database (with the necessary constraints like PK and FK's) and added this database to my webapplicatie using the ADO.NET Entity Data Model. Everything seems to work fine but I can't make validation flexible. Here some code to clarify my question.
Edit: Added the View + correct models. This version is without importing a database but results the same.
--Models----
namespace CustomValidation.Models
{
public class Person : IValidatableObject
{
public int Id { get; set; }
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (String.IsNullOrEmpty(FirstName))
{
var field = new[] { "FirstName" };
yield return new ValidationResult("Firstname can't be null", field);
}
}
}
}
namespace CustomValidation.Models
{
public class Address : IValidatableObject
{
public int Id { get; set; }
[Required]
public String Streetname { get; set; }
[Required]
public String Zipcode { get; set; }
[Required]
public String City { get; set; }
[Required]
public String Country { get; set; }
public Person Person { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (String.IsNullOrEmpty(Streetname))
{
var field = new[] { "Streetname" };
yield return new ValidationResult("Streetname can't be null", field);
}
}
}
}
public class MasterModel
{
public List<Address> Addressess { get; set; }
public Person Person { get; set; }
}
}
namespace CustomValidation.Models
{
public class DBEntities : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Address> Addressess { get; set; }
}
}
----Controller----
namespace CustomValidation.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
MasterModel Model = new MasterModel();
Model.Person = new Person();
Model.Addressess = new List<Address>();
Model.Addressess.Add(new Address());
Model.Addressess.Add(new Address());
return View(Model);
}
[HttpPost]
public ActionResult Index(MasterModel Model)
{
DBEntities _db = new DBEntities();
if (TryValidateModel(Model.Person))
{
_db.People.Add(Model.Person);
_db.SaveChanges();
}
else
{
return View(Model);
}
for (int i = 0; i < Model.Addressess.Count; i++)
{
if (!String.IsNullOrEmpty(Model.Addressess[i].Country))
{
if (TryValidateModel(Model.Addressess[i]))
{
Model.Addressess[i].Person = Model.Person;
_db.Addressess.Add(Model.Addressess[i]);
_db.SaveChanges();
}
else
{
return View(Model);
}
}
}
return RedirectToAction("Index");
}
}
Here is my View:
#model CustomValidation.Models.MasterModel
#{
ViewBag.Title = "Index";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
<div class="editor-label">
#Html.LabelFor(m => Model.Person.FirstName)
#Html.EditorFor(m => Model.Person.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(m => Model.Person.LastName)
#Html.EditorFor(m => Model.Person.LastName)
</div>
</fieldset>
for (int i = 0; i < Model.Addressess.Count; i++)
{
<fieldset>
<legend>Address</legend>
<div class="editor-label">
#Html.LabelFor(model => Model.Addressess[i].Streetname)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Addressess[i].Streetname)
#Html.ValidationMessageFor(model => Model.Addressess[i].Streetname)
</div>
<div class="editor-label">
#Html.LabelFor(model => Model.Addressess[i].Zipcode)
</div>
<div class="editor-field">
#Html.EditorFor(model => Model.Addressess[i].Zipcode)
#Html.ValidationMessageFor(model => Model.Addressess[i].Zipcode)
</div>
<div class="editor-label">
#Html.LabelFor(model => Model.Addressess[i].City)
</div>
<div class="editor-field">
#Html.EditorFor(model => Model.Addressess[i].City)
#Html.ValidationMessageFor(model => Model.Addressess[i].City)
</div>
<div class="editor-label">
#Html.LabelFor(model => Model.Addressess[i].Country)
</div>
<div class="editor-field">
#Html.EditorFor(model => Model.Addressess[i].Country)
#Html.ValidationMessageFor(model => Model.Addressess[i].Country)
</div>
</fieldset>
}
<p>
<input type="submit" value="Create" />
</p>
}
The problem (that I've noticed while debugging something like this) is that before the request gets inside of my action MVC first runs validators for your collection. So if I've only filled out Person in my form, while validating Model.Person it just return false (i guess because of the Required fields for Address) and so it will bounce back to my View.
I need to have some workaround that lets me decided which instances of my collection I want to validate in my action.
I've not been able to find any solution after a lot of time searching on the net.
I hope that someone can help me out.
PS. I'm quite a newbie with Visual Studio and MVC 3 so please don't be to hard on me :)
In order to validate sub-models of the model returned to your action, you need to apply TryValidateModel to the specific sub-model, and use the overload which includes the prefix for the model. If you look at your generated HTML, you will see that the Person fields have Person as a prefix, the first Address fields have Addressess[0] as the prefix, and the second Address fields have Addressess[1] as the prefix.
In addition, you must clear all model errors before invoking TryValidateModel (or the existing errors will cause TryValidateModel to return false).
Do the following:
[HttpPost]
public ActionResult Index(MasterModel model)
{
var reloadView = true;
// clear any existing errors
foreach (var key in ModelState.Keys)
ModelState[key].Errors.Clear();
if (TryValidateModel(model.Person, "Person"))
{
_db.People.Add(model.Person);
_db.SaveChanges();
reloadView = false;
for (var i = 0; i < model.Addressess.Count(); i++)
{
foreach (var key in ModelState.Keys)
ModelState[key].Errors.Clear();
if (TryValidateModel(model.Addressess[i], "Addressess[" + i.ToString() + "]"))
{
**// UPDATED CODE**
// add Person to Address model
model.Addressess[i].Person = model.Person;
_db.Addressess.Add(model.Addressess[i]);
_db.SaveChanges();
}
}
}
if (reloadView)
{
// clear and re-add all errors
foreach (var key in ModelState.Keys)
ModelState[key].Errors.Clear();
TryValidateModel(model);
return View(model);
}
else
{
// go to some other view
}
}