I´m making a form for registring personal data andjob experiences of a user. MVC5.
I have a Model that consists of a Clients and JobExperiences entities, created with EF code first
Over this model I created viewmodels, like this.
A view Model for the jobExperience
public class JobExperienceViewModel
{
[Required]
[Display(Name = "Nombre Compañia")]
public string CompanyName { get; set; }
[Required]
[Display(Name = "Fecha de Ingreso")]
[DataType(DataType.Date)]
public DateTime EntryDate { get; set; }
[Required]
[Display(Name = "Fecha de Retiro")]
[DataType(DataType.Date)]
public DateTime RetirementDate { get; set; }
[Required]
[Display(Name = "Cargo")]
public string Role { get; set; }
[Required]
[Display(Name = "Funciones")]
public string Functions { get; set; }
[Required]
[Display(Name = "Motivo de Retiro")]
public string RetirementCause { get; set; }
}
A Resume view model for saving the personal data and job experiences, like this
public class ResumeViewModel
{
public ResumeViewModel(Client client, ApplicationUser user)
{
...
}
public List<JobExperienceViewModel> JobExperienceViewModels { get; set; }
[Display(Name = "Primer nombre")]
public string FirstName { get; set; }
[Display(Name = "Segundo nombre")]
public string MiddleName { get; set; }
[Display(Name = "Primer apellido")]
public string LastName { get; set; }
}
I created a Resume Controller for orchesting all this data
public class ResumeController : Controller
{
#region Global Actions
//
// GET: /Resume/
//This method loads the information for a given userName and loads the viewmodel and pass it to the view
[Authorize(Roles = "Admin, Recepcionista, Orientador")]
public ActionResult Index(string userName)
{
var context = new ApplicationDbContext();
var user = (from _user in context.Users
where _user.UserName == userName
select _user).FirstOrDefault();
Client client = new Client();
try
{
client = (from _client in context.Client
where _client.ApplicationUserId == user.Id
select _client).FirstOrDefault();
}
catch (System.Reflection.TargetException)
{
client = new Client();
}
ResumeViewModel model;
model = new ResumeViewModel(client, user);
TempData["ResumeViewModel"] = model;
return View(model);
}
//This method is inttended to save all resume data to persistence
[HttpPost]
public ActionResult Index(ResumeViewModel model)
{
ApplicationDbContext context = new ApplicationDbContext();
bool exists = false;
var client = (from _client in context.Client
where _client.ApplicationUserId == model.ApplicationUserId
select _client).FirstOrDefault();
if (!String.IsNullOrEmpty(client.ApplicationUserId))
{
exists = true;
}
client.Address = model.Address;
client.ArmyCard = model.ArmyCard;
client.ArmyCardClass = model.ArmyCardClass;
client.BasicKnowledgeCoreId = model.SelectedBasicKnowledgeCoreId;
client.BirthCityId = model.SelectedBirthCityId;
client.BirthCountryId = model.SelectedBirthCountryId;
client.BirthDate = model.BirthDate;
client.BirthDepartmentId = model.SelectedBirthDepartmentId;
client.CellPhoneNumber = model.CellPhoneNumber;
client.Comments = model.Comments;
client.DataVisibilityLevelId = model.SelectedDataVisibilityLevelId;
client.DocumentNumber = model.DocumentNumber;
client.DocumentTypeId = model.SelectedDocumentTypeId;
client.DrivingLicenceExpirationDate = model.DrivingLicenceExpirationDate;
client.DrivingLicenseCategoryId = model.SelectedDrivingLicenseCategoryId;
client.EducationCountryId = model.SelectedEducationCountryId;
client.FirstNationalityId = model.SelectedFirstNationalityId;
client.GenreID = model.SelectedGenreId;
client.GraduationYear = model.GraduationYear;
client.HandicapTypeID = model.SelectedHandicapTypeID;
client.HasWorkExperience = model.HasWorkExperience;
client.HireTypeId = model.SelectedHireTypeId;
client.HomeCityId = model.SelectedHomeCityId;
client.HomeCountryId = model.SelectedHomeCountryId;
client.HomeDepartmentId = model.SelectedHomeDepartmentId;
client.Institution = model.Institution;
client.IsGraduated = model.IsGraduated;
client.IsHomeOfficeInterested = model.IsHomeOfficeInterested;
client.IsHouseHoldHead = model.IsHouseHoldHead;
client.LanguageId = model.SelectedLanguageId;
client.LanguageSkillLevelID = model.SelectedLanguageSkillLevelID;
client.MarriageStatusId = model.SelectedMarriageStatusId;
client.Neighborhood = model.Neighborhood;
client.PhoneNumber = model.PhoneNumber;
client.ProCardExpeditionDate = model.ProCardExpeditionDate;
client.ProCardNumber = model.ProCardNumber;
client.ProfessionalCardCareer = model.ProfessionalCardCareer;
client.ProfessionalProfile = model.ProfessionalProfile;
client.SalaryRangeId = model.SelectedSalaryRangeId;
//Este campo está comentadoporque el proceso de registro debe hacerse por detalles del nivel, donde esta elid y sus detalles.
//client.ScholarLevelId = model.SelectedScholarLevelId;
client.SecondNationalityId = model.SelectedSecondNationalityId;
client.Title = model.Title;
client.WorkStatusID = model.SelectedWorkStatusID;
if (!exists)
{ context.Client.Add(client); }
try
{
if (context.SaveChanges() > 0)
RedirectToAction("Index", "SearchClient");
else { return View(model); }
}
catch (Exception)
{
return View(model);
throw;
}
return View(model);
}
#endregion
#region JobExperience
public ActionResult ShowJobExperience(ICollection<Models.JobExperienceViewModel> JobExperienceViewModels)
{
return View(JobExperienceViewModels);
}
public ActionResult CreateJobExperience(ICollection<Models.JobExperienceViewModel> JobExperienceViewModels)
{
return View(JobExperienceViewModels);
}
[HttpGet]
public ActionResult CreateJobExperience(Models.JobExperienceViewModel JobExperienceViewModel)
{
#region Using TempData
ResumeViewModel model = (ResumeViewModel)TempData["ResumeViewModel"];
model.JobExperienceViewModels.Add(JobExperienceViewModel);
TempData["ResumeViewModel"] = model;
#endregion
return View("Index", model);
}
#endregion
}
And finally the views are organized this way. A parent view which contains all the personal data, and the jobExperiences list and create view as partial views within the big form.
SOme relevant portions ofthe view are
@model CAEWebSite.Models.ResumeViewModel
@{
ViewBag.Title = "Hoja de Vida";
}
@using (Ajax.BeginForm(null))
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>
@{var fullName = Model.FirstName +
(!String.IsNullOrWhiteSpace(Model.MiddleName) ? " " + Model.MiddleName : String.Empty) +
(!String.IsNullOrWhiteSpace(Model.LastName) ? " " + Model.LastName : String.Empty) +
(!String.IsNullOrWhiteSpace(Model.SecondLastName) ? " " + Model.SecondLastName : String.Empty);
}
@fullName
</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
<div class="col-md-12">
@Model.Email
</div>
</div>
<div class="form-group">
<div class="col-md-12">
@Model.UserName
</div>
</div>
<hr />
<h3>Datos personales</h3>
<div class="form-group">
@Html.LabelFor(model => model.BirthDate, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(model => model.BirthDate, new { @type = "date" })
@Html.ValidationMessageFor(model => model.BirthDate)
</div>
</div>
<h3>Experiencia laboral</h3>
<div class="form-group">
@Html.LabelFor(model => model.HasWorkExperience, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.HasWorkExperience)
@Html.ValidationMessageFor(model => model.HasWorkExperience)
</div>
</div>
<h3>Trabajos anteriores</h3>
<div class="col-md-12">
<div id="divListJobExperience">
@Html.Partial("_ListJobExperience", Model.JobExperienceViewModels)
</div>
<p>
@*@Html.Action("ShowJobExperience", Model.JobExperienceViewModels)*@
<a href="#" onclick="$('#newJobExperience').bPopup({}); return false;">Nueva experiencia</a>
</p>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div id="newJobExperience" style="background-color:#fff; border-radius:5px;">
<div class="col-md-12">
@Html.Partial("_CreateJobExperience", new CAEWebSite.Models.JobExperienceViewModel())
</div>
</div>
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script>
$("#newJobExperience").hide();
$("#newRelative").hide();
$("#newCapacitationCourse").hide();
$("#newScholarLevelDetail").hide();
</script>
}
The
@Html.Partial("_CreateJobExperience", new CAEWebSite.Models.JobExperienceViewModel())
partial view is loaded with bpopup plugin as a modal. The partial views are like this
_ListJobExperience view
@model IEnumerable<CAEWebSite.Models.JobExperienceViewModel>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.CompanyName)
</th>
<th>
@Html.DisplayNameFor(model => model.EntryDate)
</th>
<th>
@Html.DisplayNameFor(model => model.RetirementDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Role)
</th>
<th>
@Html.DisplayNameFor(model => model.Functions)
</th>
<th>
@Html.DisplayNameFor(model => model.RetirementCause)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CompanyName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EntryDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.RetirementDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Role)
</td>
<td>
@Html.DisplayFor(modelItem => item.Functions)
</td>
<td>
@Html.DisplayFor(modelItem => item.RetirementCause)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
@Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
@Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
}
</table>
_CreateJobExperience View
public class ResumeController : Controller
{
#region Global Actions
//
// GET: /Resume/
[Authorize(Roles = "Admin, Recepcionista, Orientador")]
public ActionResult Index(string userName)
{
var context = new ApplicationDbContext();
var user = (from _user in context.Users
where _user.UserName == userName
select _user).FirstOrDefault();
Client client = new Client();
try
{
client = (from _client in context.Client
where _client.ApplicationUserId == user.Id
select _client).FirstOrDefault();
}
catch (System.Reflection.TargetException)
{
client = new Client();
}
ResumeViewModel model;
model = new ResumeViewModel(client, user);
TempData["ResumeViewModel"] = model;
return View(model);
}
[HttpPost]
public ActionResult Index(ResumeViewModel model)
{
ApplicationDbContext context = new ApplicationDbContext();
bool exists = false;
var client = (from _client in context.Client
where _client.ApplicationUserId == model.ApplicationUserId
select _client).FirstOrDefault();
if (!String.IsNullOrEmpty(client.ApplicationUserId))
{
exists = true;
}
client.Address = model.Address;
client.ArmyCard = model.ArmyCard;
client.ArmyCardClass = model.ArmyCardClass;
client.BasicKnowledgeCoreId = model.SelectedBasicKnowledgeCoreId;
client.BirthCityId = model.SelectedBirthCityId;
client.BirthCountryId = model.SelectedBirthCountryId;
client.BirthDate = model.BirthDate;
client.BirthDepartmentId = model.SelectedBirthDepartmentId;
client.CellPhoneNumber = model.CellPhoneNumber;
client.Comments = model.Comments;
client.DataVisibilityLevelId = model.SelectedDataVisibilityLevelId;
client.DocumentNumber = model.DocumentNumber;
client.DocumentTypeId = model.SelectedDocumentTypeId;
client.DrivingLicenceExpirationDate = model.DrivingLicenceExpirationDate;
client.DrivingLicenseCategoryId = model.SelectedDrivingLicenseCategoryId;
client.EducationCountryId = model.SelectedEducationCountryId;
client.FirstNationalityId = model.SelectedFirstNationalityId;
client.GenreID = model.SelectedGenreId;
client.GraduationYear = model.GraduationYear;
client.HandicapTypeID = model.SelectedHandicapTypeID;
client.HasWorkExperience = model.HasWorkExperience;
client.HireTypeId = model.SelectedHireTypeId;
client.HomeCityId = model.SelectedHomeCityId;
client.HomeCountryId = model.SelectedHomeCountryId;
client.HomeDepartmentId = model.SelectedHomeDepartmentId;
client.Institution = model.Institution;
client.IsGraduated = model.IsGraduated;
client.IsHomeOfficeInterested = model.IsHomeOfficeInterested;
client.IsHouseHoldHead = model.IsHouseHoldHead;
client.LanguageId = model.SelectedLanguageId;
client.LanguageSkillLevelID = model.SelectedLanguageSkillLevelID;
client.MarriageStatusId = model.SelectedMarriageStatusId;
client.Neighborhood = model.Neighborhood;
client.PhoneNumber = model.PhoneNumber;
client.ProCardExpeditionDate = model.ProCardExpeditionDate;
client.ProCardNumber = model.ProCardNumber;
client.ProfessionalCardCareer = model.ProfessionalCardCareer;
client.ProfessionalProfile = model.ProfessionalProfile;
client.SalaryRangeId = model.SelectedSalaryRangeId;
//Este campo está comentadoporque el proceso de registro debe hacerse por detalles del nivel, donde esta elid y sus detalles.
//client.ScholarLevelId = model.SelectedScholarLevelId;
client.SecondNationalityId = model.SelectedSecondNationalityId;
client.Title = model.Title;
client.WorkStatusID = model.SelectedWorkStatusID;
if (!exists)
{ context.Client.Add(client); }
try
{
if (context.SaveChanges() > 0)
RedirectToAction("Index", "SearchClient");
else { return View(model); }
}
catch (Exception)
{
return View(model);
throw;
}
return View(model);
}
#endregion
#region JobExperience
public ActionResult ShowJobExperience(ICollection<Models.JobExperienceViewModel> JobExperienceViewModels)
{
return View(JobExperienceViewModels);
}
public ActionResult CreateJobExperience(ICollection<Models.JobExperienceViewModel> JobExperienceViewModels)
{
return View(JobExperienceViewModels);
}
[HttpPost]
public ActionResult CreateJobExperience(Models.JobExperienceViewModel JobExperienceViewModel)
{
#region Using TempData
ResumeViewModel model = (ResumeViewModel)TempData["ResumeViewModel"];
model.JobExperienceViewModels.Add(JobExperienceViewModel);
TempData["ResumeViewModel"] = model;
#endregion
return View("Index", model);
}
#endregion
}
}
The problem
Whenever I create a new job experience Iupdate the viemodel adding a jobExperienceViewModel to the list of experiences, and send it back to the view to be returned.
And it works, The list of experiences appears filled. however, all the data in the form got lost, cleaned.
I´m using the approach of keeping viewmodel state during all user interaction until he or she clicks the forms submit when I´ll send all the model to persistence.
I think what I need is a way to update the experiences list like it were inside an old fashioned Updatepanel.
Or a workaround that alows me to cover the functionality.