MVC4 Creating Register pages using one Model - ajax

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
}

Related

Adding items to list in model dynamically via ajax

I have the following code and I am trying to add the selected value to the model.SelectedPayCodes list everytime the user makes a selection from the dropdown and clicks on "Add"
CONTROLLER
[HttpPost]
public PartialViewResult AddPayCode(ReferralModel model, string SelectedPayCode)
{
var payCode = _employeeService.GetPayCodeById(Convert.ToInt32(SelectedPayCode));
model.Add(payCode);
return PartialView("_PayCodesPartial",model);
}
MODEL
public class ReferralModel
{
public Customer Customer { get; set; }
public Employee Employee { get; set; }
public List<PayCode> PayCodes { get; set; } // List of paycodes
public List<PayCode> PayCodesList { get; set; } // List of 'selected' paycodes
public SelectListItem SelectedPayCode { get; set; } // current selected paycode
public Referral Referral { get; set; }
public ReferralModel()
{
PayCodesList = new List<PayCode>();
}
// Step 3 - Add Paycode is done here.
public void Add(PayCode payCode)
{
PayCodesList.Add(payCode);
}
}
VIEW - Create.cshtml
#using (Ajax.BeginForm("AddPayCode", "Referral",
new AjaxOptions()
{
HttpMethod = "POST",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "PayCodes",
}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<!-- payCodes input-->
<div class="control-group col-lg-6">
<label class="control-label">Product</label>
<div class="controls">
<!-- Step 1 + Choose PayCode Here -->
#Html.DropDownListFor(model => model.SelectedPayCode, new SelectList(Model.PayCodes.ToList(), "Id", "Description"), "- Select -")
<input type="submit" value="Add" />
</div>
</div>
<!-- pay codes list -->
<div id="PayCodes">
</div>
}
VIEW - _PayCodesPartial.cshtml
#model Zenwire.Models.ReferralModel
#if (Model.PayCodesList != null)
{
foreach (var item in Model.PayCodesList)
{
string.Format("{0} - {1}", item.Code, item.Description);
}
}
The issue seems to be whenever a new item is added the list is empty again and does not retain it's previous selection.
Try this. I used Session State
public PartialViewResult AddPayCode(ReferralModel model, string SelectedPayCode)
{
var payCode = _employeeService.GetPayCodeById(Convert.ToInt32(SelectedPayCode));
model.PayCodesList = GetPayCodes();
model.Add(payCode);
Session["paycodes"] = model.PayCodesList;
return PartialView("_PayCodesPartial",model);
}
private List<PayCode> GetPayCodes()
{
List<PayCode> payCodes = (List<PayCode>)Session["paycodes"];
if (payCodes == null )
{
payCodes = new List<PayCode>();
Session["paycodes"] = paycode;
}
return payCodes;
}

List HttpPostedFileBase with knockout

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

Model Binding a List MVC3 not reading vaules

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.

View "ChangePassword" in MVC 3

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);
}

Custom Validation MVC 3 with Entity Data Model

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
}
}

Resources